/*------------------------------------------------------------------------- * 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 Color clear case. *//*--------------------------------------------------------------------*/ #include "teglColorClearCase.hpp" #include "tcuTestLog.hpp" #include "eglwLibrary.hpp" #include "eglwEnums.hpp" #include "egluUtil.hpp" #include "deRandom.hpp" #include "deString.h" #include "tcuImageCompare.hpp" #include "tcuVector.hpp" #include "tcuTextureUtil.hpp" #include "tcuPixelFormat.hpp" #include "glwFunctions.hpp" #include "deThread.hpp" #include "deSemaphore.hpp" #include "deSharedPtr.hpp" #include "teglGLES1RenderUtil.hpp" #include "teglGLES2RenderUtil.hpp" #include "teglVGRenderUtil.hpp" #include <memory> #include <iterator> namespace deqp { namespace egl { using tcu::TestLog; using tcu::RGBA; using std::vector; using namespace eglw; // Utilities. struct ClearOp { ClearOp (int x_, int y_, int width_, int height_, const tcu::RGBA& color_) : x (x_) , y (y_) , width (width_) , height (height_) , color (color_) { } ClearOp (void) : x (0) , y (0) , width (0) , height (0) , color (0) { } int x; int y; int width; int height; tcu::RGBA color; }; struct ApiFunctions { glw::Functions gl; }; static ClearOp computeRandomClear (de::Random& rnd, int width, int height) { int w = rnd.getInt(1, width); int h = rnd.getInt(1, height); int x = rnd.getInt(0, width-w); int y = rnd.getInt(0, height-h); tcu::RGBA col (rnd.getUint32()); return ClearOp(x, y, w, h, col); } static void renderReference (tcu::Surface& dst, const vector<ClearOp>& clears, const tcu::PixelFormat& pixelFormat) { for (vector<ClearOp>::const_iterator clearIter = clears.begin(); clearIter != clears.end(); clearIter++) { tcu::PixelBufferAccess access = tcu::getSubregion(dst.getAccess(), clearIter->x, clearIter->y, 0, clearIter->width, clearIter->height, 1); tcu::clear(access, pixelFormat.convertColor(clearIter->color).toIVec()); } } static void renderClear (EGLint api, const ApiFunctions& func, const ClearOp& clear) { switch (api) { case EGL_OPENGL_ES_BIT: gles1::clear(clear.x, clear.y, clear.width, clear.height, clear.color.toVec()); break; case EGL_OPENGL_ES2_BIT: gles2::clear(func.gl, clear.x, clear.y, clear.width, clear.height, clear.color.toVec()); break; case EGL_OPENGL_ES3_BIT_KHR: gles2::clear(func.gl, clear.x, clear.y, clear.width, clear.height, clear.color.toVec()); break; case EGL_OPENVG_BIT: vg::clear (clear.x, clear.y, clear.width, clear.height, clear.color.toVec()); break; default: DE_ASSERT(DE_FALSE); } } static void finish (EGLint api, const ApiFunctions& func) { switch (api) { case EGL_OPENGL_ES_BIT: gles1::finish(); break; case EGL_OPENGL_ES2_BIT: gles2::finish(func.gl); break; case EGL_OPENGL_ES3_BIT_KHR: gles2::finish(func.gl); break; case EGL_OPENVG_BIT: vg::finish(); break; default: DE_ASSERT(DE_FALSE); } } static void readPixels (EGLint api, const ApiFunctions& func, tcu::Surface& dst) { switch (api) { case EGL_OPENGL_ES_BIT: gles1::readPixels (dst, 0, 0, dst.getWidth(), dst.getHeight()); break; case EGL_OPENGL_ES2_BIT: gles2::readPixels (func.gl, dst, 0, 0, dst.getWidth(), dst.getHeight()); break; case EGL_OPENGL_ES3_BIT_KHR: gles2::readPixels (func.gl, dst, 0, 0, dst.getWidth(), dst.getHeight()); break; case EGL_OPENVG_BIT: vg::readPixels (dst, 0, 0, dst.getWidth(), dst.getHeight()); break; default: DE_ASSERT(DE_FALSE); } } static tcu::PixelFormat getPixelFormat (const Library& egl, EGLDisplay display, EGLConfig config) { tcu::PixelFormat pixelFmt; egl.getConfigAttrib(display, config, EGL_RED_SIZE, &pixelFmt.redBits); egl.getConfigAttrib(display, config, EGL_GREEN_SIZE, &pixelFmt.greenBits); egl.getConfigAttrib(display, config, EGL_BLUE_SIZE, &pixelFmt.blueBits); egl.getConfigAttrib(display, config, EGL_ALPHA_SIZE, &pixelFmt.alphaBits); return pixelFmt; } // SingleThreadColorClearCase SingleThreadColorClearCase::SingleThreadColorClearCase (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 SingleThreadColorClearCase::executeForContexts (EGLDisplay display, EGLSurface surface, const Config& config, const std::vector<std::pair<EGLint, EGLContext> >& contexts) { const Library& egl = m_eglTestCtx.getLibrary(); const tcu::IVec2 surfaceSize = eglu::getSurfaceSize(egl, display, surface); const int width = surfaceSize.x(); const int height = surfaceSize.y(); TestLog& log = m_testCtx.getLog(); tcu::Surface refFrame (width, height); tcu::Surface frame (width, height); tcu::PixelFormat pixelFmt = getPixelFormat(egl, display, config.config); de::Random rnd (deStringHash(getName())); vector<ClearOp> clears; const int ctxClears = 2; const int numIters = 3; ApiFunctions funcs; m_eglTestCtx.initGLFunctions(&funcs.gl, glu::ApiType::es(2,0)); // Clear to black using first context. { EGLint api = contexts[0].first; EGLContext context = contexts[0].second; ClearOp clear (0, 0, width, height, RGBA::black()); egl.makeCurrent(display, surface, surface, context); EGLU_CHECK_MSG(egl, "eglMakeCurrent"); renderClear(api, funcs, clear); finish(api, funcs); clears.push_back(clear); } // Render. for (int iterNdx = 0; iterNdx < numIters; iterNdx++) { for (vector<std::pair<EGLint, EGLContext> >::const_iterator ctxIter = contexts.begin(); ctxIter != contexts.end(); ctxIter++) { EGLint api = ctxIter->first; EGLContext context = ctxIter->second; egl.makeCurrent(display, surface, surface, context); EGLU_CHECK_MSG(egl, "eglMakeCurrent"); for (int clearNdx = 0; clearNdx < ctxClears; clearNdx++) { ClearOp clear = computeRandomClear(rnd, width, height); renderClear(api, funcs, clear); clears.push_back(clear); } finish(api, funcs); } } // Read pixels using first context. \todo [pyry] Randomize? { EGLint api = contexts[0].first; EGLContext context = contexts[0].second; egl.makeCurrent(display, surface, surface, context); EGLU_CHECK_MSG(egl, "eglMakeCurrent"); readPixels(api, funcs, frame); } egl.makeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); EGLU_CHECK_MSG(egl, "eglMakeCurrent"); // Render reference. renderReference(refFrame, clears, pixelFmt); // Compare images { tcu::RGBA eps = pixelFmt.alphaBits == 1 ? RGBA(1,1,1,127) : RGBA(1,1,1,1); bool imagesOk = tcu::pixelThresholdCompare(log, "ComparisonResult", "Image comparison result", refFrame, frame, eps + pixelFmt.getColorThreshold(), tcu::COMPARE_LOG_RESULT); if (!imagesOk) m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed"); } } // MultiThreadColorClearCase enum { NUM_CLEARS_PER_PACKET = 2 //!< Number of clears performed in one context activation in one thread. }; class ColorClearThread; typedef de::SharedPtr<ColorClearThread> ColorClearThreadSp; typedef de::SharedPtr<de::Semaphore> SemaphoreSp; struct ClearPacket { ClearPacket (void) { } ClearOp clears[NUM_CLEARS_PER_PACKET]; SemaphoreSp wait; SemaphoreSp signal; }; class ColorClearThread : public de::Thread { public: ColorClearThread (const Library& egl, EGLDisplay display, EGLSurface surface, EGLContext context, EGLint api, const ApiFunctions& funcs, const std::vector<ClearPacket>& packets) : m_egl (egl) , m_display (display) , m_surface (surface) , m_context (context) , m_api (api) , m_funcs (funcs) , m_packets (packets) { } void run (void) { for (std::vector<ClearPacket>::const_iterator packetIter = m_packets.begin(); packetIter != m_packets.end(); packetIter++) { // Wait until it is our turn. packetIter->wait->decrement(); // Acquire context. m_egl.makeCurrent(m_display, m_surface, m_surface, m_context); // Execute clears. for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(packetIter->clears); ndx++) renderClear(m_api, m_funcs, packetIter->clears[ndx]); finish(m_api, m_funcs); // Release context. 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 ApiFunctions& m_funcs; const std::vector<ClearPacket>& m_packets; }; MultiThreadColorClearCase::MultiThreadColorClearCase (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 MultiThreadColorClearCase::executeForContexts (EGLDisplay display, EGLSurface surface, const Config& config, const std::vector<std::pair<EGLint, EGLContext> >& contexts) { const Library& egl = m_eglTestCtx.getLibrary(); const tcu::IVec2 surfaceSize = eglu::getSurfaceSize(egl, display, surface); const int width = surfaceSize.x(); const int height = surfaceSize.y(); TestLog& log = m_testCtx.getLog(); tcu::Surface refFrame (width, height); tcu::Surface frame (width, height); tcu::PixelFormat pixelFmt = getPixelFormat(egl, display, config.config); de::Random rnd (deStringHash(getName())); ApiFunctions funcs; m_eglTestCtx.initGLFunctions(&funcs.gl, glu::ApiType::es(2,0)); // Create clear packets. const int numPacketsPerThread = 2; int numThreads = (int)contexts.size(); int numPackets = numThreads * numPacketsPerThread; vector<SemaphoreSp> semaphores (numPackets+1); vector<vector<ClearPacket> > packets (numThreads); vector<ColorClearThreadSp> threads (numThreads); // Initialize semaphores. for (vector<SemaphoreSp>::iterator sem = semaphores.begin(); sem != semaphores.end(); ++sem) *sem = SemaphoreSp(new de::Semaphore(0)); // Create packets. for (int threadNdx = 0; threadNdx < numThreads; threadNdx++) { packets[threadNdx].resize(numPacketsPerThread); for (int packetNdx = 0; packetNdx < numPacketsPerThread; packetNdx++) { ClearPacket& packet = packets[threadNdx][packetNdx]; // Threads take turns with packets. packet.wait = semaphores[packetNdx*numThreads + threadNdx]; packet.signal = semaphores[packetNdx*numThreads + threadNdx + 1]; for (int clearNdx = 0; clearNdx < DE_LENGTH_OF_ARRAY(packet.clears); clearNdx++) { // First clear is always full-screen black. if (threadNdx == 0 && packetNdx == 0 && clearNdx == 0) packet.clears[clearNdx] = ClearOp(0, 0, width, height, RGBA::black()); else packet.clears[clearNdx] = computeRandomClear(rnd, width, height); } } } // Create and launch threads (actual rendering starts once first semaphore is signaled). for (int threadNdx = 0; threadNdx < numThreads; threadNdx++) { threads[threadNdx] = ColorClearThreadSp(new ColorClearThread(egl, display, surface, contexts[threadNdx].second, contexts[threadNdx].first, funcs, 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; egl.makeCurrent(display, surface, surface, context); EGLU_CHECK_MSG(egl, "eglMakeCurrent"); readPixels(api, funcs, frame); } egl.makeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); EGLU_CHECK_MSG(egl, "eglMakeCurrent"); // Join threads. for (int threadNdx = 0; threadNdx < numThreads; threadNdx++) threads[threadNdx]->join(); // Render reference. for (int packetNdx = 0; packetNdx < numPacketsPerThread; packetNdx++) { for (int threadNdx = 0; threadNdx < numThreads; threadNdx++) { const ClearPacket& packet = packets[threadNdx][packetNdx]; for (int clearNdx = 0; clearNdx < DE_LENGTH_OF_ARRAY(packet.clears); clearNdx++) { tcu::PixelBufferAccess access = tcu::getSubregion(refFrame.getAccess(), packet.clears[clearNdx].x, packet.clears[clearNdx].y, 0, packet.clears[clearNdx].width, packet.clears[clearNdx].height, 1); tcu::clear(access, pixelFmt.convertColor(packet.clears[clearNdx].color).toIVec()); } } } // Compare images { tcu::RGBA eps = pixelFmt.alphaBits == 1 ? RGBA(1,1,1,127) : RGBA(1,1,1,1); bool imagesOk = tcu::pixelThresholdCompare(log, "ComparisonResult", "Image comparison result", refFrame, frame, eps + pixelFmt.getColorThreshold(), tcu::COMPARE_LOG_RESULT); if (!imagesOk) m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed"); } } } // egl } // deqp