/*-------------------------------------------------------------------------
* 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