/*------------------------------------------------------------------------- * 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 <algorithm> #include <iterator> #include <memory> #include <set> 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<Vec4> positions; vector<Vec4> 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 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); 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); } // avoid generating narrow triangles { const int maxAttempts = 40; 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<float>(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 } 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<DrawPrimitiveOp>& drawOps, const tcu::PixelFormat& colorBits, const int depthBits, const int stencilBits, const int numSamples) { 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<DrawPrimitiveOp>::const_iterator drawOp = drawOps.begin(); drawOp != drawOps.end(); drawOp++) { // Translate state rr::RenderState renderState((rr::ViewportState)(rr::MultisamplePixelBufferAccess::fromMultisampleAccess(colorBuffer.getAccess()))); 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<const rr::VertexShader*>(&shader), static_cast<const rr::FragmentShader*>(&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<Program> 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<const GLES2Program&>(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<std::pair<EGLint, EGLContext> >& 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<std::pair<EGLint, EGLContext> >& 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 float threshold = 0.02f; const tcu::PixelFormat pixelFmt = getPixelFormat(egl, display, config.config); 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<ProgramSp> programs (contexts.size()); vector<DrawPrimitiveOp> 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<DrawPrimitiveOp>::iterator drawOp = drawOps.begin(); drawOp != drawOps.end(); ++drawOp) randomizeDrawOp(rnd, *drawOp); // 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); } 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); // 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<std::pair<EGLint, EGLContext> >& contexts); glw::Functions m_gl; }; class RenderTestThread; typedef de::SharedPtr<RenderTestThread> RenderTestThreadSp; typedef de::SharedPtr<de::Semaphore> 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<DrawOpPacket>& 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<DrawOpPacket>::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(); } } 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<DrawOpPacket>& 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<std::pair<EGLint, EGLContext> >& 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 float threshold = 0.02f; const tcu::PixelFormat pixelFmt = getPixelFormat(egl, display, config.config); 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<ProgramSp> programs (numContexts); vector<SemaphoreSp> semaphores (numPackets+1); vector<DrawPrimitiveOp> drawOps (numPackets*opsPerPacket); vector<vector<DrawOpPacket> > packets (numThreads); vector<RenderTestThreadSp> 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<SemaphoreSp>::iterator sem = semaphores.begin(); sem != semaphores.end(); ++sem) *sem = SemaphoreSp(new de::Semaphore(0)); // Create draw ops. for (vector<DrawPrimitiveOp>::iterator drawOp = drawOps.begin(); drawOp != drawOps.end(); ++drawOp) randomizeDrawOp(rnd, *drawOp); // 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); } 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); // 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 <deUint32 Bits> static bool renderable (const eglu::CandidateConfig& c) { return (c.renderableType() & Bits) == Bits; } template <class RenderClass> 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<RenderFilterList> filterLists; eglu::FilterList baseFilters; baseFilters << groupIter->baseFilter; getDefaultRenderFilterLists(filterLists, baseFilters); for (vector<RenderFilterList>::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<EGL_OPENGL_ES2_BIT>, 1 }, { "gles3", "Primitive rendering using GLES3", EGL_OPENGL_ES3_BIT, renderable<EGL_OPENGL_ES3_BIT>, 1 }, }; static const RenderGroupSpec multiContextCases[] = { { "gles2", "Primitive rendering using multiple GLES2 contexts to shared surface", EGL_OPENGL_ES2_BIT, renderable<EGL_OPENGL_ES2_BIT>, 3 }, { "gles3", "Primitive rendering using multiple GLES3 contexts to shared surface", EGL_OPENGL_ES3_BIT, renderable<EGL_OPENGL_ES3_BIT>, 3 }, { "gles2_gles3", "Primitive rendering using multiple APIs to shared surface", EGL_OPENGL_ES2_BIT|EGL_OPENGL_ES3_BIT, renderable<EGL_OPENGL_ES2_BIT|EGL_OPENGL_ES3_BIT>, 1 }, }; tcu::TestCaseGroup* singleContextGroup = new tcu::TestCaseGroup(m_testCtx, "single_context", "Single-context rendering"); addChild(singleContextGroup); createRenderGroups<SingleThreadRenderCase>(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<SingleThreadRenderCase>(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<MultiThreadRenderCase>(m_eglTestCtx, multiThreadGroup, &multiContextCases[0], &multiContextCases[DE_LENGTH_OF_ARRAY(multiContextCases)]); } } // egl } // deqp