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