/*------------------------------------------------------------------------- * drawElements Quality Program EGL Module * --------------------------------------- * * Copyright 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * *//*! * \file * \brief Rendering tests for different config and api combinations. * \todo [2013-03-19 pyry] GLES1 and VG support. *//*--------------------------------------------------------------------*/ #include "teglRenderTests.hpp" #include "teglRenderCase.hpp" #include "tcuRenderTarget.hpp" #include "tcuTestLog.hpp" #include "tcuImageCompare.hpp" #include "tcuTextureUtil.hpp" #include "tcuSurface.hpp" #include "egluDefs.hpp" #include "egluUtil.hpp" #include "eglwLibrary.hpp" #include "eglwEnums.hpp" #include "gluShaderProgram.hpp" #include "glwFunctions.hpp" #include "glwEnums.hpp" #include "deRandom.hpp" #include "deSharedPtr.hpp" #include "deSemaphore.hpp" #include "deThread.hpp" #include "deString.h" #include "rrRenderer.hpp" #include "rrFragmentOperations.hpp" #include #include #include #include namespace deqp { namespace egl { using std::string; using std::vector; using std::set; using tcu::Vec4; using tcu::TestLog; using namespace glw; using namespace eglw; static const tcu::Vec4 CLEAR_COLOR = tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f); static const float CLEAR_DEPTH = 1.0f; static const int CLEAR_STENCIL = 0; namespace { enum PrimitiveType { PRIMITIVETYPE_TRIANGLE = 0, //!< Triangles, requires 3 coordinates per primitive // PRIMITIVETYPE_POINT, //!< Points, requires 1 coordinate per primitive (w is used as size) // PRIMITIVETYPE_LINE, //!< Lines, requires 2 coordinates per primitive PRIMITIVETYPE_LAST }; enum BlendMode { BLENDMODE_NONE = 0, //!< No blending BLENDMODE_ADDITIVE, //!< Blending with ONE, ONE BLENDMODE_SRC_OVER, //!< Blending with SRC_ALPHA, ONE_MINUS_SRC_ALPHA BLENDMODE_LAST }; enum DepthMode { DEPTHMODE_NONE = 0, //!< No depth test or depth writes DEPTHMODE_LESS, //!< Depth test with less & depth write DEPTHMODE_LAST }; enum StencilMode { STENCILMODE_NONE = 0, //!< No stencil test or write STENCILMODE_LEQUAL_INC, //!< Stencil test with LEQUAL, increment on pass STENCILMODE_LAST }; struct DrawPrimitiveOp { PrimitiveType type; int count; vector positions; vector colors; BlendMode blend; DepthMode depth; StencilMode stencil; int stencilRef; }; static bool isANarrowScreenSpaceTriangle (const tcu::Vec4& p0, const tcu::Vec4& p1, const tcu::Vec4& p2) { // to clip space const tcu::Vec2 csp0 = p0.swizzle(0, 1) / p0.w(); const tcu::Vec2 csp1 = p1.swizzle(0, 1) / p1.w(); const tcu::Vec2 csp2 = p2.swizzle(0, 1) / p2.w(); const tcu::Vec2 e01 = (csp1 - csp0); const tcu::Vec2 e02 = (csp2 - csp0); const float minimumVisibleArea = 0.4f; // must cover at least 10% of the surface const float visibleArea = de::abs(e01.x() * e02.y() - e02.x() * e01.y()) * 0.5f; return visibleArea < minimumVisibleArea; } void randomizeDrawOp (de::Random& rnd, DrawPrimitiveOp& drawOp, const bool alphaZeroOrOne) { const int minStencilRef = 0; const int maxStencilRef = 8; const int minPrimitives = 2; const int maxPrimitives = 4; const float maxTriOffset = 1.0f; const float minDepth = -1.0f; // \todo [pyry] Reference doesn't support Z clipping yet const float maxDepth = 1.0f; const float minRGB = 0.2f; const float maxRGB = 0.9f; const float minAlpha = 0.3f; const float maxAlpha = 1.0f; drawOp.type = (PrimitiveType)rnd.getInt(0, PRIMITIVETYPE_LAST-1); drawOp.count = rnd.getInt(minPrimitives, maxPrimitives); drawOp.blend = (BlendMode)rnd.getInt(0, BLENDMODE_LAST-1); drawOp.depth = (DepthMode)rnd.getInt(0, DEPTHMODE_LAST-1); drawOp.stencil = (StencilMode)rnd.getInt(0, STENCILMODE_LAST-1); drawOp.stencilRef = rnd.getInt(minStencilRef, maxStencilRef); if (drawOp.type == PRIMITIVETYPE_TRIANGLE) { drawOp.positions.resize(drawOp.count*3); drawOp.colors.resize(drawOp.count*3); for (int triNdx = 0; triNdx < drawOp.count; triNdx++) { const float cx = rnd.getFloat(-1.0f, 1.0f); const float cy = rnd.getFloat(-1.0f, 1.0f); const float flatAlpha = (rnd.getFloat(minAlpha, maxAlpha) > 0.5f) ? 1.0f : 0.0f; for (int coordNdx = 0; coordNdx < 3; coordNdx++) { tcu::Vec4& position = drawOp.positions[triNdx*3 + coordNdx]; tcu::Vec4& color = drawOp.colors[triNdx*3 + coordNdx]; position.x() = cx + rnd.getFloat(-maxTriOffset, maxTriOffset); position.y() = cy + rnd.getFloat(-maxTriOffset, maxTriOffset); position.z() = rnd.getFloat(minDepth, maxDepth); position.w() = 1.0f; color.x() = rnd.getFloat(minRGB, maxRGB); color.y() = rnd.getFloat(minRGB, maxRGB); color.z() = rnd.getFloat(minRGB, maxRGB); color.w() = rnd.getFloat(minAlpha, maxAlpha); if (alphaZeroOrOne) { color.w() = flatAlpha; } } // avoid generating narrow triangles { const int maxAttempts = 100; int numAttempts = 0; tcu::Vec4& p0 = drawOp.positions[triNdx*3 + 0]; tcu::Vec4& p1 = drawOp.positions[triNdx*3 + 1]; tcu::Vec4& p2 = drawOp.positions[triNdx*3 + 2]; while (isANarrowScreenSpaceTriangle(p0, p1, p2)) { p1.x() = cx + rnd.getFloat(-maxTriOffset, maxTriOffset); p1.y() = cy + rnd.getFloat(-maxTriOffset, maxTriOffset); p1.z() = rnd.getFloat(minDepth, maxDepth); p1.w() = 1.0f; p2.x() = cx + rnd.getFloat(-maxTriOffset, maxTriOffset); p2.y() = cy + rnd.getFloat(-maxTriOffset, maxTriOffset); p2.z() = rnd.getFloat(minDepth, maxDepth); p2.w() = 1.0f; if (++numAttempts > maxAttempts) { DE_ASSERT(false); break; } } } } } else DE_ASSERT(false); } // Reference rendering code class ReferenceShader : public rr::VertexShader, public rr::FragmentShader { public: enum { VaryingLoc_Color = 0 }; ReferenceShader () : rr::VertexShader (2, 1) // color and pos in => color out , rr::FragmentShader(1, 1) // color in => color out { this->rr::VertexShader::m_inputs[0].type = rr::GENERICVECTYPE_FLOAT; this->rr::VertexShader::m_inputs[1].type = rr::GENERICVECTYPE_FLOAT; this->rr::VertexShader::m_outputs[0].type = rr::GENERICVECTYPE_FLOAT; this->rr::VertexShader::m_outputs[0].flatshade = false; this->rr::FragmentShader::m_inputs[0].type = rr::GENERICVECTYPE_FLOAT; this->rr::FragmentShader::m_inputs[0].flatshade = false; this->rr::FragmentShader::m_outputs[0].type = rr::GENERICVECTYPE_FLOAT; } void shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const { for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx) { const int positionAttrLoc = 0; const int colorAttrLoc = 1; rr::VertexPacket& packet = *packets[packetNdx]; // Transform to position packet.position = rr::readVertexAttribFloat(inputs[positionAttrLoc], packet.instanceNdx, packet.vertexNdx); // Pass color to FS packet.outputs[VaryingLoc_Color] = rr::readVertexAttribFloat(inputs[colorAttrLoc], packet.instanceNdx, packet.vertexNdx); } } void shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const { for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx) { rr::FragmentPacket& packet = packets[packetNdx]; for (int fragNdx = 0; fragNdx < 4; ++fragNdx) rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, rr::readVarying(packet, context, VaryingLoc_Color, fragNdx)); } } }; void toReferenceRenderState (rr::RenderState& state, const DrawPrimitiveOp& drawOp) { state.cullMode = rr::CULLMODE_NONE; if (drawOp.blend != BLENDMODE_NONE) { state.fragOps.blendMode = rr::BLENDMODE_STANDARD; switch (drawOp.blend) { case BLENDMODE_ADDITIVE: state.fragOps.blendRGBState.srcFunc = rr::BLENDFUNC_ONE; state.fragOps.blendRGBState.dstFunc = rr::BLENDFUNC_ONE; state.fragOps.blendRGBState.equation = rr::BLENDEQUATION_ADD; state.fragOps.blendAState = state.fragOps.blendRGBState; break; case BLENDMODE_SRC_OVER: state.fragOps.blendRGBState.srcFunc = rr::BLENDFUNC_SRC_ALPHA; state.fragOps.blendRGBState.dstFunc = rr::BLENDFUNC_ONE_MINUS_SRC_ALPHA; state.fragOps.blendRGBState.equation = rr::BLENDEQUATION_ADD; state.fragOps.blendAState = state.fragOps.blendRGBState; break; default: DE_ASSERT(false); } } if (drawOp.depth != DEPTHMODE_NONE) { state.fragOps.depthTestEnabled = true; DE_ASSERT(drawOp.depth == DEPTHMODE_LESS); state.fragOps.depthFunc = rr::TESTFUNC_LESS; } if (drawOp.stencil != STENCILMODE_NONE) { state.fragOps.stencilTestEnabled = true; DE_ASSERT(drawOp.stencil == STENCILMODE_LEQUAL_INC); state.fragOps.stencilStates[0].func = rr::TESTFUNC_LEQUAL; state.fragOps.stencilStates[0].sFail = rr::STENCILOP_KEEP; state.fragOps.stencilStates[0].dpFail = rr::STENCILOP_INCR; state.fragOps.stencilStates[0].dpPass = rr::STENCILOP_INCR; state.fragOps.stencilStates[0].ref = drawOp.stencilRef; state.fragOps.stencilStates[1] = state.fragOps.stencilStates[0]; } } tcu::TextureFormat getColorFormat (const tcu::PixelFormat& colorBits) { using tcu::TextureFormat; DE_ASSERT(de::inBounds(colorBits.redBits, 0, 0xff) && de::inBounds(colorBits.greenBits, 0, 0xff) && de::inBounds(colorBits.blueBits, 0, 0xff) && de::inBounds(colorBits.alphaBits, 0, 0xff)); #define PACK_FMT(R, G, B, A) (((R) << 24) | ((G) << 16) | ((B) << 8) | (A)) // \note [pyry] This may not hold true on some implementations - best effort guess only. switch (PACK_FMT(colorBits.redBits, colorBits.greenBits, colorBits.blueBits, colorBits.alphaBits)) { case PACK_FMT(8,8,8,8): return TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_INT8); case PACK_FMT(8,8,8,0): return TextureFormat(TextureFormat::RGB, TextureFormat::UNORM_INT8); case PACK_FMT(4,4,4,4): return TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_SHORT_4444); case PACK_FMT(5,5,5,1): return TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_SHORT_5551); case PACK_FMT(5,6,5,0): return TextureFormat(TextureFormat::RGB, TextureFormat::UNORM_SHORT_565); // \note Defaults to RGBA8 default: return TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_INT8); } #undef PACK_FMT } /* The getColorThreshold function is used to obtain a threshold usable for the fuzzyCompare function. For 8bit color depths a value of 0.02 should provide a good metric for rejecting images above this level. For other bit depths other thresholds should be selected. Ideally this function would take advantage of the getColorThreshold function provided by the PixelFormat class as this would also allow setting per channel thresholds. However using the PixelFormat provided function can result in too strict thresholds for 8bit bit depths (compared to the current default of 0.02) or too relaxed for lower bit depths if scaled proportionally to the 8bit default. */ float getColorThreshold (const tcu::PixelFormat& colorBits) { if ((colorBits.redBits > 0 && colorBits.redBits < 8) || (colorBits.greenBits > 0 && colorBits.greenBits < 8) || (colorBits.blueBits > 0 && colorBits.blueBits < 8) || (colorBits.alphaBits > 0 && colorBits.alphaBits < 8)) { return 0.05f; } else { return 0.02f; } } tcu::TextureFormat getDepthFormat (const int depthBits) { switch (depthBits) { case 0: return tcu::TextureFormat(); case 8: return tcu::TextureFormat(tcu::TextureFormat::D, tcu::TextureFormat::UNORM_INT8); case 16: return tcu::TextureFormat(tcu::TextureFormat::D, tcu::TextureFormat::UNORM_INT16); case 24: return tcu::TextureFormat(tcu::TextureFormat::D, tcu::TextureFormat::UNORM_INT24); case 32: default: return tcu::TextureFormat(tcu::TextureFormat::D, tcu::TextureFormat::FLOAT); } } tcu::TextureFormat getStencilFormat (int stencilBits) { switch (stencilBits) { case 0: return tcu::TextureFormat(); case 8: default: return tcu::TextureFormat(tcu::TextureFormat::S, tcu::TextureFormat::UNSIGNED_INT8); } } void renderReference (const tcu::PixelBufferAccess& dst, const vector& drawOps, const tcu::PixelFormat& colorBits, const int depthBits, const int stencilBits, const int numSamples, const int subpixelBits) { const int width = dst.getWidth(); const int height = dst.getHeight(); tcu::TextureLevel colorBuffer; tcu::TextureLevel depthBuffer; tcu::TextureLevel stencilBuffer; rr::Renderer referenceRenderer; rr::VertexAttrib attributes[2]; const ReferenceShader shader; attributes[0].type = rr::VERTEXATTRIBTYPE_FLOAT; attributes[0].size = 4; attributes[0].stride = 0; attributes[0].instanceDivisor = 0; attributes[1].type = rr::VERTEXATTRIBTYPE_FLOAT; attributes[1].size = 4; attributes[1].stride = 0; attributes[1].instanceDivisor = 0; // Initialize buffers. colorBuffer.setStorage(getColorFormat(colorBits), numSamples, width, height); rr::clearMultisampleColorBuffer(colorBuffer, CLEAR_COLOR, rr::WindowRectangle(0, 0, width, height)); if (depthBits > 0) { depthBuffer.setStorage(getDepthFormat(depthBits), numSamples, width, height); rr::clearMultisampleDepthBuffer(depthBuffer, CLEAR_DEPTH, rr::WindowRectangle(0, 0, width, height)); } if (stencilBits > 0) { stencilBuffer.setStorage(getStencilFormat(stencilBits), numSamples, width, height); rr::clearMultisampleStencilBuffer(stencilBuffer, CLEAR_STENCIL, rr::WindowRectangle(0, 0, width, height)); } const rr::RenderTarget renderTarget(rr::MultisamplePixelBufferAccess::fromMultisampleAccess(colorBuffer.getAccess()), rr::MultisamplePixelBufferAccess::fromMultisampleAccess(depthBuffer.getAccess()), rr::MultisamplePixelBufferAccess::fromMultisampleAccess(stencilBuffer.getAccess())); for (vector::const_iterator drawOp = drawOps.begin(); drawOp != drawOps.end(); drawOp++) { // Translate state rr::RenderState renderState((rr::ViewportState)(rr::MultisamplePixelBufferAccess::fromMultisampleAccess(colorBuffer.getAccess())), subpixelBits); toReferenceRenderState(renderState, *drawOp); DE_ASSERT(drawOp->type == PRIMITIVETYPE_TRIANGLE); attributes[0].pointer = &drawOp->positions[0]; attributes[1].pointer = &drawOp->colors[0]; referenceRenderer.draw( rr::DrawCommand( renderState, renderTarget, rr::Program(static_cast(&shader), static_cast(&shader)), 2, attributes, rr::PrimitiveList(rr::PRIMITIVETYPE_TRIANGLES, drawOp->count * 3, 0))); } rr::resolveMultisampleColorBuffer(dst, rr::MultisamplePixelBufferAccess::fromMultisampleAccess(colorBuffer.getAccess())); } // API rendering code class Program { public: Program (void) {} virtual ~Program (void) {} virtual void setup (void) const = DE_NULL; }; typedef de::SharedPtr ProgramSp; static glu::ProgramSources getProgramSourcesES2 (void) { static const char* s_vertexSrc = "attribute highp vec4 a_position;\n" "attribute mediump vec4 a_color;\n" "varying mediump vec4 v_color;\n" "void main (void)\n" "{\n" " gl_Position = a_position;\n" " v_color = a_color;\n" "}\n"; static const char* s_fragmentSrc = "varying mediump vec4 v_color;\n" "void main (void)\n" "{\n" " gl_FragColor = v_color;\n" "}\n"; return glu::ProgramSources() << glu::VertexSource(s_vertexSrc) << glu::FragmentSource(s_fragmentSrc); } class GLES2Program : public Program { public: GLES2Program (const glw::Functions& gl) : m_gl (gl) , m_program (gl, getProgramSourcesES2()) , m_positionLoc (0) , m_colorLoc (0) { m_positionLoc = m_gl.getAttribLocation(m_program.getProgram(), "a_position"); m_colorLoc = m_gl.getAttribLocation(m_program.getProgram(), "a_color"); } ~GLES2Program (void) { } void setup (void) const { m_gl.useProgram(m_program.getProgram()); m_gl.enableVertexAttribArray(m_positionLoc); m_gl.enableVertexAttribArray(m_colorLoc); GLU_CHECK_GLW_MSG(m_gl, "Program setup failed"); } int getPositionLoc (void) const { return m_positionLoc; } int getColorLoc (void) const { return m_colorLoc; } private: const glw::Functions& m_gl; glu::ShaderProgram m_program; int m_positionLoc; int m_colorLoc; }; void clearGLES2 (const glw::Functions& gl, const tcu::Vec4& color, const float depth, const int stencil) { gl.clearColor(color.x(), color.y(), color.z(), color.w()); gl.clearDepthf(depth); gl.clearStencil(stencil); gl.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT); } void drawGLES2 (const glw::Functions& gl, const Program& program, const DrawPrimitiveOp& drawOp) { const GLES2Program& gles2Program = dynamic_cast(program); switch (drawOp.blend) { case BLENDMODE_NONE: gl.disable(GL_BLEND); break; case BLENDMODE_ADDITIVE: gl.enable(GL_BLEND); gl.blendFunc(GL_ONE, GL_ONE); break; case BLENDMODE_SRC_OVER: gl.enable(GL_BLEND); gl.blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); break; default: DE_ASSERT(false); } switch (drawOp.depth) { case DEPTHMODE_NONE: gl.disable(GL_DEPTH_TEST); break; case DEPTHMODE_LESS: gl.enable(GL_DEPTH_TEST); break; default: DE_ASSERT(false); } switch (drawOp.stencil) { case STENCILMODE_NONE: gl.disable(GL_STENCIL_TEST); break; case STENCILMODE_LEQUAL_INC: gl.enable(GL_STENCIL_TEST); gl.stencilFunc(GL_LEQUAL, drawOp.stencilRef, ~0u); gl.stencilOp(GL_KEEP, GL_INCR, GL_INCR); break; default: DE_ASSERT(false); } gl.disable(GL_DITHER); gl.vertexAttribPointer(gles2Program.getPositionLoc(), 4, GL_FLOAT, GL_FALSE, 0, &drawOp.positions[0]); gl.vertexAttribPointer(gles2Program.getColorLoc(), 4, GL_FLOAT, GL_FALSE, 0, &drawOp.colors[0]); DE_ASSERT(drawOp.type == PRIMITIVETYPE_TRIANGLE); gl.drawArrays(GL_TRIANGLES, 0, drawOp.count*3); } static void readPixelsGLES2 (const glw::Functions& gl, tcu::Surface& dst) { gl.readPixels(0, 0, dst.getWidth(), dst.getHeight(), GL_RGBA, GL_UNSIGNED_BYTE, dst.getAccess().getDataPtr()); } Program* createProgram (const glw::Functions& gl, EGLint api) { switch (api) { case EGL_OPENGL_ES2_BIT: return new GLES2Program(gl); case EGL_OPENGL_ES3_BIT_KHR: return new GLES2Program(gl); default: throw tcu::NotSupportedError("Unsupported API"); } } void draw (const glw::Functions& gl, EGLint api, const Program& program, const DrawPrimitiveOp& drawOp) { switch (api) { case EGL_OPENGL_ES2_BIT: drawGLES2(gl, program, drawOp); break; case EGL_OPENGL_ES3_BIT_KHR: drawGLES2(gl, program, drawOp); break; default: throw tcu::NotSupportedError("Unsupported API"); } } void clear (const glw::Functions& gl, EGLint api, const tcu::Vec4& color, const float depth, const int stencil) { switch (api) { case EGL_OPENGL_ES2_BIT: clearGLES2(gl, color, depth, stencil); break; case EGL_OPENGL_ES3_BIT_KHR: clearGLES2(gl, color, depth, stencil); break; default: throw tcu::NotSupportedError("Unsupported API"); } } static void readPixels (const glw::Functions& gl, EGLint api, tcu::Surface& dst) { switch (api) { case EGL_OPENGL_ES2_BIT: readPixelsGLES2(gl, dst); break; case EGL_OPENGL_ES3_BIT_KHR: readPixelsGLES2(gl, dst); break; default: throw tcu::NotSupportedError("Unsupported API"); } } static void finish (const glw::Functions& gl, EGLint api) { switch (api) { case EGL_OPENGL_ES2_BIT: case EGL_OPENGL_ES3_BIT_KHR: gl.finish(); break; default: throw tcu::NotSupportedError("Unsupported API"); } } tcu::PixelFormat getPixelFormat (const Library& egl, EGLDisplay display, EGLConfig config) { tcu::PixelFormat fmt; fmt.redBits = eglu::getConfigAttribInt(egl, display, config, EGL_RED_SIZE); fmt.greenBits = eglu::getConfigAttribInt(egl, display, config, EGL_GREEN_SIZE); fmt.blueBits = eglu::getConfigAttribInt(egl, display, config, EGL_BLUE_SIZE); fmt.alphaBits = eglu::getConfigAttribInt(egl, display, config, EGL_ALPHA_SIZE); return fmt; } } // anonymous // SingleThreadRenderCase class SingleThreadRenderCase : public MultiContextRenderCase { public: SingleThreadRenderCase (EglTestContext& eglTestCtx, const char* name, const char* description, EGLint api, EGLint surfaceType, const eglu::FilterList& filters, int numContextsPerApi); void init (void); private: virtual void executeForContexts (EGLDisplay display, EGLSurface surface, const Config& config, const std::vector >& contexts); glw::Functions m_gl; }; // SingleThreadColorClearCase SingleThreadRenderCase::SingleThreadRenderCase (EglTestContext& eglTestCtx, const char* name, const char* description, EGLint api, EGLint surfaceType, const eglu::FilterList& filters, int numContextsPerApi) : MultiContextRenderCase(eglTestCtx, name, description, api, surfaceType, filters, numContextsPerApi) { } void SingleThreadRenderCase::init (void) { MultiContextRenderCase::init(); m_eglTestCtx.initGLFunctions(&m_gl, glu::ApiType::es(2,0)); } void SingleThreadRenderCase::executeForContexts (EGLDisplay display, EGLSurface surface, const Config& config, const std::vector >& contexts) { const Library& egl = m_eglTestCtx.getLibrary(); const int width = eglu::querySurfaceInt(egl, display, surface, EGL_WIDTH); const int height = eglu::querySurfaceInt(egl, display, surface, EGL_HEIGHT); const int numContexts = (int)contexts.size(); const int drawsPerCtx = 2; const int numIters = 2; const tcu::PixelFormat pixelFmt = getPixelFormat(egl, display, config.config); const float threshold = getColorThreshold(pixelFmt); const int depthBits = eglu::getConfigAttribInt(egl, display, config.config, EGL_DEPTH_SIZE); const int stencilBits = eglu::getConfigAttribInt(egl, display, config.config, EGL_STENCIL_SIZE); const int numSamples = eglu::getConfigAttribInt(egl, display, config.config, EGL_SAMPLES); TestLog& log = m_testCtx.getLog(); tcu::Surface refFrame (width, height); tcu::Surface frame (width, height); de::Random rnd (deStringHash(getName()) ^ deInt32Hash(numContexts)); vector programs (contexts.size()); vector drawOps; // Log basic information about config. log << TestLog::Message << "EGL_RED_SIZE = " << pixelFmt.redBits << TestLog::EndMessage; log << TestLog::Message << "EGL_GREEN_SIZE = " << pixelFmt.greenBits << TestLog::EndMessage; log << TestLog::Message << "EGL_BLUE_SIZE = " << pixelFmt.blueBits << TestLog::EndMessage; log << TestLog::Message << "EGL_ALPHA_SIZE = " << pixelFmt.alphaBits << TestLog::EndMessage; log << TestLog::Message << "EGL_DEPTH_SIZE = " << depthBits << TestLog::EndMessage; log << TestLog::Message << "EGL_STENCIL_SIZE = " << stencilBits << TestLog::EndMessage; log << TestLog::Message << "EGL_SAMPLES = " << numSamples << TestLog::EndMessage; // Generate draw ops. drawOps.resize(numContexts*drawsPerCtx*numIters); for (vector::iterator drawOp = drawOps.begin(); drawOp != drawOps.end(); ++drawOp) randomizeDrawOp(rnd, *drawOp, (pixelFmt.alphaBits == 1)); // Create and setup programs per context for (int ctxNdx = 0; ctxNdx < numContexts; ctxNdx++) { EGLint api = contexts[ctxNdx].first; EGLContext context = contexts[ctxNdx].second; EGLU_CHECK_CALL(egl, makeCurrent(display, surface, surface, context)); programs[ctxNdx] = ProgramSp(createProgram(m_gl, api)); programs[ctxNdx]->setup(); } // Clear to black using first context. { EGLint api = contexts[0].first; EGLContext context = contexts[0].second; EGLU_CHECK_CALL(egl, makeCurrent(display, surface, surface, context)); clear(m_gl, api, CLEAR_COLOR, CLEAR_DEPTH, CLEAR_STENCIL); finish(m_gl, api); } // Render. for (int iterNdx = 0; iterNdx < numIters; iterNdx++) { for (int ctxNdx = 0; ctxNdx < numContexts; ctxNdx++) { EGLint api = contexts[ctxNdx].first; EGLContext context = contexts[ctxNdx].second; EGLU_CHECK_CALL(egl, makeCurrent(display, surface, surface, context)); for (int drawNdx = 0; drawNdx < drawsPerCtx; drawNdx++) { const DrawPrimitiveOp& drawOp = drawOps[iterNdx*numContexts*drawsPerCtx + ctxNdx*drawsPerCtx + drawNdx]; draw(m_gl, api, *programs[ctxNdx], drawOp); } finish(m_gl, api); } } // Read pixels using first context. \todo [pyry] Randomize? { EGLint api = contexts[0].first; EGLContext context = contexts[0].second; EGLU_CHECK_CALL(egl, makeCurrent(display, surface, surface, context)); readPixels(m_gl, api, frame); } int subpixelBits = 0; m_gl.getIntegerv(GL_SUBPIXEL_BITS, &subpixelBits); EGLU_CHECK_CALL(egl, makeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); // Render reference. // \note Reference image is always generated using single-sampling. renderReference(refFrame.getAccess(), drawOps, pixelFmt, depthBits, stencilBits, 1, subpixelBits); // Compare images { bool imagesOk = tcu::fuzzyCompare(log, "ComparisonResult", "Image comparison result", refFrame, frame, threshold, tcu::COMPARE_LOG_RESULT); if (!imagesOk) m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed"); } } // MultiThreadRenderCase class MultiThreadRenderCase : public MultiContextRenderCase { public: MultiThreadRenderCase (EglTestContext& eglTestCtx, const char* name, const char* description, EGLint api, EGLint surfaceType, const eglu::FilterList& filters, int numContextsPerApi); void init (void); private: virtual void executeForContexts (EGLDisplay display, EGLSurface surface, const Config& config, const std::vector >& contexts); glw::Functions m_gl; }; class RenderTestThread; typedef de::SharedPtr RenderTestThreadSp; typedef de::SharedPtr SemaphoreSp; struct DrawOpPacket { DrawOpPacket (void) : drawOps (DE_NULL) , numOps (0) { } const DrawPrimitiveOp* drawOps; int numOps; SemaphoreSp wait; SemaphoreSp signal; }; class RenderTestThread : public de::Thread { public: RenderTestThread (const Library& egl, EGLDisplay display, EGLSurface surface, EGLContext context, EGLint api, const glw::Functions& gl, const Program& program, const std::vector& packets) : m_egl (egl) , m_display (display) , m_surface (surface) , m_context (context) , m_api (api) , m_gl (gl) , m_program (program) , m_packets (packets) { } void run (void) { for (std::vector::const_iterator packetIter = m_packets.begin(); packetIter != m_packets.end(); packetIter++) { // Wait until it is our turn. packetIter->wait->decrement(); // Acquire context. EGLU_CHECK_CALL(m_egl, makeCurrent(m_display, m_surface, m_surface, m_context)); // Execute rendering. for (int ndx = 0; ndx < packetIter->numOps; ndx++) draw(m_gl, m_api, m_program, packetIter->drawOps[ndx]); finish(m_gl, m_api); // Release context. EGLU_CHECK_CALL(m_egl, makeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); // Signal completion. packetIter->signal->increment(); } m_egl.releaseThread(); } private: const Library& m_egl; EGLDisplay m_display; EGLSurface m_surface; EGLContext m_context; EGLint m_api; const glw::Functions& m_gl; const Program& m_program; const std::vector& m_packets; }; MultiThreadRenderCase::MultiThreadRenderCase (EglTestContext& eglTestCtx, const char* name, const char* description, EGLint api, EGLint surfaceType, const eglu::FilterList& filters, int numContextsPerApi) : MultiContextRenderCase(eglTestCtx, name, description, api, surfaceType, filters, numContextsPerApi) { } void MultiThreadRenderCase::init (void) { MultiContextRenderCase::init(); m_eglTestCtx.initGLFunctions(&m_gl, glu::ApiType::es(2,0)); } void MultiThreadRenderCase::executeForContexts (EGLDisplay display, EGLSurface surface, const Config& config, const std::vector >& contexts) { const Library& egl = m_eglTestCtx.getLibrary(); const int width = eglu::querySurfaceInt(egl, display, surface, EGL_WIDTH); const int height = eglu::querySurfaceInt(egl, display, surface, EGL_HEIGHT); const int numContexts = (int)contexts.size(); const int opsPerPacket = 2; const int packetsPerThread = 2; const int numThreads = numContexts; const int numPackets = numThreads * packetsPerThread; const tcu::PixelFormat pixelFmt = getPixelFormat(egl, display, config.config); const float threshold = getColorThreshold(pixelFmt); const int depthBits = eglu::getConfigAttribInt(egl, display, config.config, EGL_DEPTH_SIZE); const int stencilBits = eglu::getConfigAttribInt(egl, display, config.config, EGL_STENCIL_SIZE); const int numSamples = eglu::getConfigAttribInt(egl, display, config.config, EGL_SAMPLES); TestLog& log = m_testCtx.getLog(); tcu::Surface refFrame (width, height); tcu::Surface frame (width, height); de::Random rnd (deStringHash(getName()) ^ deInt32Hash(numContexts)); // Resources that need cleanup vector programs (numContexts); vector semaphores (numPackets+1); vector drawOps (numPackets*opsPerPacket); vector > packets (numThreads); vector threads (numThreads); // Log basic information about config. log << TestLog::Message << "EGL_RED_SIZE = " << pixelFmt.redBits << TestLog::EndMessage; log << TestLog::Message << "EGL_GREEN_SIZE = " << pixelFmt.greenBits << TestLog::EndMessage; log << TestLog::Message << "EGL_BLUE_SIZE = " << pixelFmt.blueBits << TestLog::EndMessage; log << TestLog::Message << "EGL_ALPHA_SIZE = " << pixelFmt.alphaBits << TestLog::EndMessage; log << TestLog::Message << "EGL_DEPTH_SIZE = " << depthBits << TestLog::EndMessage; log << TestLog::Message << "EGL_STENCIL_SIZE = " << stencilBits << TestLog::EndMessage; log << TestLog::Message << "EGL_SAMPLES = " << numSamples << TestLog::EndMessage; // Initialize semaphores. for (vector::iterator sem = semaphores.begin(); sem != semaphores.end(); ++sem) *sem = SemaphoreSp(new de::Semaphore(0)); // Create draw ops. for (vector::iterator drawOp = drawOps.begin(); drawOp != drawOps.end(); ++drawOp) randomizeDrawOp(rnd, *drawOp, (pixelFmt.alphaBits == 1)); // Create packets. for (int threadNdx = 0; threadNdx < numThreads; threadNdx++) { packets[threadNdx].resize(packetsPerThread); for (int packetNdx = 0; packetNdx < packetsPerThread; packetNdx++) { DrawOpPacket& packet = packets[threadNdx][packetNdx]; // Threads take turns with packets. packet.wait = semaphores[packetNdx*numThreads + threadNdx]; packet.signal = semaphores[packetNdx*numThreads + threadNdx + 1]; packet.numOps = opsPerPacket; packet.drawOps = &drawOps[(packetNdx*numThreads + threadNdx)*opsPerPacket]; } } // Create and setup programs per context for (int ctxNdx = 0; ctxNdx < numContexts; ctxNdx++) { EGLint api = contexts[ctxNdx].first; EGLContext context = contexts[ctxNdx].second; EGLU_CHECK_CALL(egl, makeCurrent(display, surface, surface, context)); programs[ctxNdx] = ProgramSp(createProgram(m_gl, api)); programs[ctxNdx]->setup(); // Release context EGLU_CHECK_CALL(egl, makeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); } // Clear to black using first context. { EGLint api = contexts[0].first; EGLContext context = contexts[0].second; EGLU_CHECK_CALL(egl, makeCurrent(display, surface, surface, context)); clear(m_gl, api, CLEAR_COLOR, CLEAR_DEPTH, CLEAR_STENCIL); finish(m_gl, api); // Release context EGLU_CHECK_CALL(egl, makeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); } // Create and launch threads (actual rendering starts once first semaphore is signaled). for (int threadNdx = 0; threadNdx < numThreads; threadNdx++) { threads[threadNdx] = RenderTestThreadSp(new RenderTestThread(egl, display, surface, contexts[threadNdx].second, contexts[threadNdx].first, m_gl, *programs[threadNdx], packets[threadNdx])); threads[threadNdx]->start(); } // Signal start and wait until complete. semaphores.front()->increment(); semaphores.back()->decrement(); // Read pixels using first context. \todo [pyry] Randomize? { EGLint api = contexts[0].first; EGLContext context = contexts[0].second; EGLU_CHECK_CALL(egl, makeCurrent(display, surface, surface, context)); readPixels(m_gl, api, frame); } int subpixelBits = 0; m_gl.getIntegerv(GL_SUBPIXEL_BITS, &subpixelBits); EGLU_CHECK_CALL(egl, makeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); // Join threads. for (int threadNdx = 0; threadNdx < numThreads; threadNdx++) threads[threadNdx]->join(); // Render reference. renderReference(refFrame.getAccess(), drawOps, pixelFmt, depthBits, stencilBits, 1, subpixelBits); // Compare images { bool imagesOk = tcu::fuzzyCompare(log, "ComparisonResult", "Image comparison result", refFrame, frame, threshold, tcu::COMPARE_LOG_RESULT); if (!imagesOk) m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed"); } } RenderTests::RenderTests (EglTestContext& eglTestCtx) : TestCaseGroup(eglTestCtx, "render", "Basic rendering with different client APIs") { } RenderTests::~RenderTests (void) { } struct RenderGroupSpec { const char* name; const char* desc; EGLint apiBits; eglu::ConfigFilter baseFilter; int numContextsPerApi; }; template static bool renderable (const eglu::CandidateConfig& c) { return (c.renderableType() & Bits) == Bits; } template static void createRenderGroups (EglTestContext& eglTestCtx, tcu::TestCaseGroup* group, const RenderGroupSpec* first, const RenderGroupSpec* last) { for (const RenderGroupSpec* groupIter = first; groupIter != last; groupIter++) { tcu::TestCaseGroup* configGroup = new tcu::TestCaseGroup(eglTestCtx.getTestContext(), groupIter->name, groupIter->desc); group->addChild(configGroup); vector filterLists; eglu::FilterList baseFilters; baseFilters << groupIter->baseFilter; getDefaultRenderFilterLists(filterLists, baseFilters); for (vector::const_iterator listIter = filterLists.begin(); listIter != filterLists.end(); listIter++) configGroup->addChild(new RenderClass(eglTestCtx, listIter->getName(), "", groupIter->apiBits, listIter->getSurfaceTypeMask(), *listIter, groupIter->numContextsPerApi)); } } void RenderTests::init (void) { static const RenderGroupSpec singleContextCases[] = { { "gles2", "Primitive rendering using GLES2", EGL_OPENGL_ES2_BIT, renderable, 1 }, { "gles3", "Primitive rendering using GLES3", EGL_OPENGL_ES3_BIT, renderable, 1 }, }; static const RenderGroupSpec multiContextCases[] = { { "gles2", "Primitive rendering using multiple GLES2 contexts to shared surface", EGL_OPENGL_ES2_BIT, renderable, 3 }, { "gles3", "Primitive rendering using multiple GLES3 contexts to shared surface", EGL_OPENGL_ES3_BIT, renderable, 3 }, { "gles2_gles3", "Primitive rendering using multiple APIs to shared surface", EGL_OPENGL_ES2_BIT|EGL_OPENGL_ES3_BIT, renderable, 1 }, }; tcu::TestCaseGroup* singleContextGroup = new tcu::TestCaseGroup(m_testCtx, "single_context", "Single-context rendering"); addChild(singleContextGroup); createRenderGroups(m_eglTestCtx, singleContextGroup, &singleContextCases[0], &singleContextCases[DE_LENGTH_OF_ARRAY(singleContextCases)]); tcu::TestCaseGroup* multiContextGroup = new tcu::TestCaseGroup(m_testCtx, "multi_context", "Multi-context rendering with shared surface"); addChild(multiContextGroup); createRenderGroups(m_eglTestCtx, multiContextGroup, &multiContextCases[0], &multiContextCases[DE_LENGTH_OF_ARRAY(multiContextCases)]); tcu::TestCaseGroup* multiThreadGroup = new tcu::TestCaseGroup(m_testCtx, "multi_thread", "Multi-thread rendering with shared surface"); addChild(multiThreadGroup); createRenderGroups(m_eglTestCtx, multiThreadGroup, &multiContextCases[0], &multiContextCases[DE_LENGTH_OF_ARRAY(multiContextCases)]); } } // egl } // deqp