/*------------------------------------------------------------------------- * drawElements Quality Program OpenGL (ES) 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 Shader - render state interaction case. *//*--------------------------------------------------------------------*/ #include "glsFragOpInteractionCase.hpp" #include "glsRandomShaderProgram.hpp" #include "glsFragmentOpUtil.hpp" #include "glsInteractionTestUtil.hpp" #include "gluRenderContext.hpp" #include "gluContextInfo.hpp" #include "rsgShader.hpp" #include "rsgProgramGenerator.hpp" #include "rsgUtils.hpp" #include "sglrContext.hpp" #include "sglrReferenceContext.hpp" #include "sglrGLContext.hpp" #include "sglrContextUtil.hpp" #include "tcuRenderTarget.hpp" #include "tcuImageCompare.hpp" #include "deRandom.hpp" #include "deString.h" #include "deStringUtil.hpp" #include "glwEnums.hpp" #include "gluDrawUtil.hpp" namespace deqp { namespace gls { using std::vector; using std::string; using tcu::Vec2; using tcu::Vec4; using tcu::IVec2; using tcu::IVec4; using gls::InteractionTestUtil::RenderState; using gls::InteractionTestUtil::StencilState; enum { NUM_ITERATIONS = 5, NUM_COMMANDS_PER_ITERATION = 5, VIEWPORT_WIDTH = 64, VIEWPORT_HEIGHT = 64 }; namespace { static void computeVertexLayout (const vector& attributes, int numVertices, vector* layout, int* stride) { DE_ASSERT(layout->empty()); int curOffset = 0; for (vector::const_iterator iter = attributes.begin(); iter != attributes.end(); ++iter) { const rsg::ShaderInput* attrib = *iter; const rsg::Variable* var = attrib->getVariable(); const rsg::VariableType& type = var->getType(); const int numComps = type.getNumElements(); TCU_CHECK_INTERNAL(type.getBaseType() == rsg::VariableType::TYPE_FLOAT && de::inRange(type.getNumElements(), 1, 4)); layout->push_back(glu::va::Float(var->getName(), numComps, numVertices, 0 /* computed later */, (const float*)(deUintptr)curOffset)); curOffset += numComps * (int)sizeof(float); } for (vector::iterator vaIter = layout->begin(); vaIter != layout->end(); ++vaIter) vaIter->pointer.stride = curOffset; *stride = curOffset; } class VertexDataStorage { public: VertexDataStorage (const vector& attributes, int numVertices); int getDataSize (void) const { return (int)m_data.size(); } void* getBasePtr (void) { return m_data.empty() ? DE_NULL : &m_data[0]; } const void* getBasePtr (void) const { return m_data.empty() ? DE_NULL : &m_data[0]; } const std::vector& getLayout (void) const { return m_layout; } int getNumEntries (void) const { return (int)m_layout.size(); } const glu::VertexArrayBinding& getLayoutEntry (int ndx) const { return m_layout[ndx]; } private: std::vector m_data; std::vector m_layout; }; VertexDataStorage::VertexDataStorage (const vector& attributes, int numVertices) { int stride = 0; computeVertexLayout(attributes, numVertices, &m_layout, &stride); m_data.resize(stride * numVertices); } static inline glu::VertexArrayBinding getEntryWithPointer (const VertexDataStorage& data, int ndx) { const glu::VertexArrayBinding& entry = data.getLayoutEntry(ndx); return glu::VertexArrayBinding(entry.binding, glu::VertexArrayPointer(entry.pointer.componentType, entry.pointer.convert, entry.pointer.numComponents, entry.pointer.numElements, entry.pointer.stride, (const void*)((deUintptr)entry.pointer.data+(deUintptr)data.getBasePtr()))); } template static void setVertex (const glu::VertexArrayPointer& pointer, int vertexNdx, const tcu::Vector& value) { // \todo [2013-12-14 pyry] Implement other modes. DE_ASSERT(pointer.componentType == glu::VTX_COMP_FLOAT && pointer.convert == glu::VTX_COMP_CONVERT_NONE); DE_ASSERT(pointer.numComponents == Size); DE_ASSERT(de::inBounds(vertexNdx, 0, pointer.numElements)); float* dst = (float*)((deUint8*)pointer.data + pointer.stride*vertexNdx); for (int ndx = 0; ndx < Size; ndx++) dst[ndx] = value[ndx]; } template static tcu::Vector interpolateRange (const rsg::ConstValueRangeAccess& range, const tcu::Vector& t) { tcu::Vector result; for (int ndx = 0; ndx < Size; ndx++) result[ndx] = range.getMin().component(ndx).asFloat()*(1.0f - t[ndx]) + range.getMax().component(ndx).asFloat()*t[ndx]; return result; } struct Quad { tcu::IVec2 posA; tcu::IVec2 posB; }; struct RenderCommand { Quad quad; float depth; RenderState state; RenderCommand (void) : depth(0.0f) {} }; static Quad getRandomQuad (de::Random& rnd, int targetW, int targetH) { // \note In viewport coordinates. // \todo [2012-12-18 pyry] Out-of-bounds values. const int maxOutOfBounds = 0; const float minSize = 0.5f; const int minW = deCeilFloatToInt32(minSize * (float)targetW); const int minH = deCeilFloatToInt32(minSize * (float)targetH); const int maxW = targetW + 2*maxOutOfBounds; const int maxH = targetH + 2*maxOutOfBounds; const int width = rnd.getInt(minW, maxW); const int height = rnd.getInt(minH, maxH); const int x = rnd.getInt(-maxOutOfBounds, targetW+maxOutOfBounds-width); const int y = rnd.getInt(-maxOutOfBounds, targetH+maxOutOfBounds-height); const bool flipX = rnd.getBool(); const bool flipY = rnd.getBool(); Quad quad; quad.posA = tcu::IVec2(flipX ? (x+width-1) : x, flipY ? (y+height-1) : y); quad.posB = tcu::IVec2(flipX ? x : (x+width-1), flipY ? y : (y+height-1)); return quad; } static float getRandomDepth (de::Random& rnd) { // \note Not using depth 1.0 since clearing with 1.0 and rendering with 1.0 may not be same value. static const float depthValues[] = { 0.0f, 0.2f, 0.4f, 0.5f, 0.51f, 0.6f, 0.8f, 0.95f }; return rnd.choose(DE_ARRAY_BEGIN(depthValues), DE_ARRAY_END(depthValues)); } static void computeRandomRenderCommand (de::Random& rnd, RenderCommand& command, glu::ApiType apiType, int targetW, int targetH) { command.quad = getRandomQuad(rnd, targetW, targetH); command.depth = getRandomDepth(rnd); gls::InteractionTestUtil::computeRandomRenderState(rnd, command.state, apiType, targetW, targetH); } static void setRenderState (sglr::Context& ctx, const RenderState& state) { if (state.scissorTestEnabled) { ctx.enable(GL_SCISSOR_TEST); ctx.scissor(state.scissorRectangle.left, state.scissorRectangle.bottom, state.scissorRectangle.width, state.scissorRectangle.height); } else ctx.disable(GL_SCISSOR_TEST); if (state.stencilTestEnabled) { ctx.enable(GL_STENCIL_TEST); for (int face = 0; face < rr::FACETYPE_LAST; face++) { deUint32 glFace = face == rr::FACETYPE_BACK ? GL_BACK : GL_FRONT; const StencilState& sParams = state.stencil[face]; ctx.stencilFuncSeparate(glFace, sParams.function, sParams.reference, sParams.compareMask); ctx.stencilOpSeparate(glFace, sParams.stencilFailOp, sParams.depthFailOp, sParams.depthPassOp); ctx.stencilMaskSeparate(glFace, sParams.writeMask); } } else ctx.disable(GL_STENCIL_TEST); if (state.depthTestEnabled) { ctx.enable(GL_DEPTH_TEST); ctx.depthFunc(state.depthFunc); ctx.depthMask(state.depthWriteMask ? GL_TRUE : GL_FALSE); } else ctx.disable(GL_DEPTH_TEST); if (state.blendEnabled) { ctx.enable(GL_BLEND); ctx.blendEquationSeparate(state.blendRGBState.equation, state.blendAState.equation); ctx.blendFuncSeparate(state.blendRGBState.srcFunc, state.blendRGBState.dstFunc, state.blendAState.srcFunc, state.blendAState.dstFunc); ctx.blendColor(state.blendColor.x(), state.blendColor.y(), state.blendColor.z(), state.blendColor.w()); } else ctx.disable(GL_BLEND); if (state.ditherEnabled) ctx.enable(GL_DITHER); else ctx.disable(GL_DITHER); ctx.colorMask(state.colorMask[0] ? GL_TRUE : GL_FALSE, state.colorMask[1] ? GL_TRUE : GL_FALSE, state.colorMask[2] ? GL_TRUE : GL_FALSE, state.colorMask[3] ? GL_TRUE : GL_FALSE); } static void renderQuad (sglr::Context& ctx, const glu::VertexArrayPointer& posPtr, const Quad& quad, const float depth) { const deUint16 indices[] = { 0, 1, 2, 2, 1, 3 }; const bool flipX = quad.posB.x() < quad.posA.x(); const bool flipY = quad.posB.y() < quad.posA.y(); const int viewportX = de::min(quad.posA.x(), quad.posB.x()); const int viewportY = de::min(quad.posA.y(), quad.posB.y()); const int viewportW = de::abs(quad.posA.x()-quad.posB.x())+1; const int viewportH = de::abs(quad.posA.y()-quad.posB.y())+1; const Vec2 pA (flipX ? 1.0f : -1.0f, flipY ? 1.0f : -1.0f); const Vec2 pB (flipX ? -1.0f : 1.0f, flipY ? -1.0f : 1.0f); setVertex(posPtr, 0, Vec4(pA.x(), pA.y(), depth, 1.0f)); setVertex(posPtr, 1, Vec4(pB.x(), pA.y(), depth, 1.0f)); setVertex(posPtr, 2, Vec4(pA.x(), pB.y(), depth, 1.0f)); setVertex(posPtr, 3, Vec4(pB.x(), pB.y(), depth, 1.0f)); ctx.viewport(viewportX, viewportY, viewportW, viewportH); ctx.drawElements(GL_TRIANGLES, DE_LENGTH_OF_ARRAY(indices), GL_UNSIGNED_SHORT, &indices[0]); } static void render (sglr::Context& ctx, const glu::VertexArrayPointer& posPtr, const RenderCommand& cmd) { setRenderState(ctx, cmd.state); renderQuad(ctx, posPtr, cmd.quad, cmd.depth); } static void setupAttributes (sglr::Context& ctx, const VertexDataStorage& vertexData, deUint32 program) { for (int attribNdx = 0; attribNdx < vertexData.getNumEntries(); ++attribNdx) { const glu::VertexArrayBinding bindingPtr = getEntryWithPointer(vertexData, attribNdx); const int attribLoc = bindingPtr.binding.type == glu::BindingPoint::BPTYPE_NAME ? ctx.getAttribLocation(program, bindingPtr.binding.name.c_str()) : bindingPtr.binding.location; DE_ASSERT(bindingPtr.pointer.componentType == glu::VTX_COMP_FLOAT); if (attribLoc >= 0) { ctx.enableVertexAttribArray(attribLoc); ctx.vertexAttribPointer(attribLoc, bindingPtr.pointer.numComponents, GL_FLOAT, GL_FALSE, bindingPtr.pointer.stride, bindingPtr.pointer.data); } } } void setUniformValue (sglr::Context& ctx, int location, rsg::ConstValueAccess value) { DE_STATIC_ASSERT(sizeof(rsg::Scalar) == sizeof(float)); DE_STATIC_ASSERT(sizeof(rsg::Scalar) == sizeof(int)); switch (value.getType().getBaseType()) { case rsg::VariableType::TYPE_FLOAT: switch (value.getType().getNumElements()) { case 1: ctx.uniform1fv(location, 1, (float*)value.value().getValuePtr()); break; case 2: ctx.uniform2fv(location, 1, (float*)value.value().getValuePtr()); break; case 3: ctx.uniform3fv(location, 1, (float*)value.value().getValuePtr()); break; case 4: ctx.uniform4fv(location, 1, (float*)value.value().getValuePtr()); break; default: TCU_FAIL("Unsupported type"); } break; case rsg::VariableType::TYPE_INT: case rsg::VariableType::TYPE_BOOL: case rsg::VariableType::TYPE_SAMPLER_2D: case rsg::VariableType::TYPE_SAMPLER_CUBE: switch (value.getType().getNumElements()) { case 1: ctx.uniform1iv(location, 1, (int*)value.value().getValuePtr()); break; case 2: ctx.uniform2iv(location, 1, (int*)value.value().getValuePtr()); break; case 3: ctx.uniform3iv(location, 1, (int*)value.value().getValuePtr()); break; case 4: ctx.uniform4iv(location, 1, (int*)value.value().getValuePtr()); break; default: TCU_FAIL("Unsupported type"); } break; default: throw tcu::InternalError("Unsupported type", "", __FILE__, __LINE__); } } static int findShaderInputIndex (const vector& vars, const char* name) { for (int ndx = 0; ndx < (int)vars.size(); ++ndx) { if (deStringEqual(vars[ndx]->getVariable()->getName(), name)) return ndx; } throw tcu::InternalError(string(name) + " not found in shader inputs"); } static float getWellBehavingChannelColor (float v, int numBits) { DE_ASSERT(de::inRange(numBits, 0, 32)); // clear color may not be accurately representable in the target format. If the clear color is // on a representable value mapping range border, it could be rounded differently by the GL and in // SGLR adding an unexpected error source. However, selecting an accurately representable background // color would effectively disable dithering. To allow dithering and to prevent undefined rounding // direction from affecting results, round accurate color to target color format with 8 sub-units // (3 bits). If the selected sub-unit value is 3 or 4 (bordering 0.5), replace it with 2 and 5, // respectively. if (numBits == 0 || v <= 0.0f || v >= 1.0f) { // already accurately representable return v; } else { const deUint64 numSubBits = 3; const deUint64 subUnitBorderLo = (1u << (numSubBits - 1u)) - 1u; const deUint64 subUnitBorderHi = 1u << (numSubBits - 1u); const deUint64 maxFixedValue = (1u << (numBits + numSubBits)) - 1u; const deUint64 fixedValue = deRoundFloatToInt64(v * (float)maxFixedValue); const deUint64 units = fixedValue >> numSubBits; const deUint64 subUnits = fixedValue & ((1u << numSubBits) - 1u); const deUint64 tweakedSubUnits = (subUnits == subUnitBorderLo) ? (subUnitBorderLo - 1) : (subUnits == subUnitBorderHi) ? (subUnitBorderHi + 1) : (subUnits); const deUint64 tweakedValue = (units << numSubBits) | (tweakedSubUnits); return float(tweakedValue) / float(maxFixedValue); } } static tcu::Vec4 getWellBehavingColor (const tcu::Vec4& accurateColor, const tcu::PixelFormat& format) { return tcu::Vec4(getWellBehavingChannelColor(accurateColor[0], format.redBits), getWellBehavingChannelColor(accurateColor[1], format.greenBits), getWellBehavingChannelColor(accurateColor[2], format.blueBits), getWellBehavingChannelColor(accurateColor[3], format.alphaBits)); } } // anonymous struct FragOpInteractionCase::ReferenceContext { const sglr::ReferenceContextLimits limits; sglr::ReferenceContextBuffers buffers; sglr::ReferenceContext context; ReferenceContext (glu::RenderContext& renderCtx, int width, int height) : limits (renderCtx) , buffers (renderCtx.getRenderTarget().getPixelFormat(), renderCtx.getRenderTarget().getDepthBits(), renderCtx.getRenderTarget().getStencilBits(), width, height) , context (limits, buffers.getColorbuffer(), buffers.getDepthbuffer(), buffers.getStencilbuffer()) { } }; FragOpInteractionCase::FragOpInteractionCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo, const char* name, const rsg::ProgramParameters& params) : TestCase (testCtx, name, "") , m_renderCtx (renderCtx) , m_ctxInfo (ctxInfo) , m_params (params) , m_vertexShader (rsg::Shader::TYPE_VERTEX) , m_fragmentShader (rsg::Shader::TYPE_FRAGMENT) , m_program (DE_NULL) , m_glCtx (DE_NULL) , m_referenceCtx (DE_NULL) , m_glProgram (0) , m_refProgram (0) , m_iterNdx (0) { } FragOpInteractionCase::~FragOpInteractionCase (void) { FragOpInteractionCase::deinit(); } void FragOpInteractionCase::init (void) { de::Random rnd (m_params.seed ^ 0x232faac); const int viewportW = de::min(m_renderCtx.getRenderTarget().getWidth(), VIEWPORT_WIDTH); const int viewportH = de::min(m_renderCtx.getRenderTarget().getHeight(), VIEWPORT_HEIGHT); const int viewportX = rnd.getInt(0, m_renderCtx.getRenderTarget().getWidth() - viewportW); const int viewportY = rnd.getInt(0, m_renderCtx.getRenderTarget().getHeight() - viewportH); rsg::ProgramGenerator generator; generator.generate(m_params, m_vertexShader, m_fragmentShader); rsg::computeUnifiedUniforms(m_vertexShader, m_fragmentShader, m_unifiedUniforms); try { DE_ASSERT(!m_program); m_program = new gls::RandomShaderProgram(m_vertexShader, m_fragmentShader, (int)m_unifiedUniforms.size(), m_unifiedUniforms.empty() ? DE_NULL : &m_unifiedUniforms[0]); DE_ASSERT(!m_referenceCtx); m_referenceCtx = new ReferenceContext(m_renderCtx, viewportW, viewportH); DE_ASSERT(!m_glCtx); m_glCtx = new sglr::GLContext(m_renderCtx, m_testCtx.getLog(), sglr::GLCONTEXT_LOG_CALLS|sglr::GLCONTEXT_LOG_PROGRAMS, IVec4(viewportX, viewportY, viewportW, viewportH)); m_refProgram = m_referenceCtx->context.createProgram(m_program); m_glProgram = m_glCtx->createProgram(m_program); m_viewportSize = tcu::IVec2(viewportW, viewportH); m_iterNdx = 0; m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); } catch (...) { // Save some memory by cleaning up stuff. FragOpInteractionCase::deinit(); throw; } } void FragOpInteractionCase::deinit (void) { delete m_referenceCtx; m_referenceCtx = DE_NULL; delete m_glCtx; m_glCtx = DE_NULL; delete m_program; m_program = DE_NULL; } FragOpInteractionCase::IterateResult FragOpInteractionCase::iterate (void) { de::Random rnd (m_params.seed ^ deInt32Hash(m_iterNdx)); const tcu::ScopedLogSection section (m_testCtx.getLog(), string("Iter") + de::toString(m_iterNdx), string("Iteration ") + de::toString(m_iterNdx)); const int positionNdx = findShaderInputIndex(m_vertexShader.getInputs(), "dEQP_Position"); const int numVertices = 4; VertexDataStorage vertexData (m_vertexShader.getInputs(), numVertices); std::vector uniformValues; std::vector renderCmds (NUM_COMMANDS_PER_ITERATION); tcu::Surface rendered (m_viewportSize.x(), m_viewportSize.y()); tcu::Surface reference (m_viewportSize.x(), m_viewportSize.y()); const tcu::Vec4 vtxInterpFactors[] = { tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f), tcu::Vec4(1.0f, 0.0f, 0.5f, 0.5f), tcu::Vec4(0.0f, 1.0f, 0.5f, 0.5f), tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f) }; rsg::computeUniformValues(rnd, uniformValues, m_unifiedUniforms); for (int attribNdx = 0; attribNdx < (int)m_vertexShader.getInputs().size(); ++attribNdx) { if (attribNdx == positionNdx) continue; const rsg::ShaderInput* shaderIn = m_vertexShader.getInputs()[attribNdx]; const rsg::VariableType& varType = shaderIn->getVariable()->getType(); const rsg::ConstValueRangeAccess valueRange = shaderIn->getValueRange(); const int numComponents = varType.getNumElements(); const glu::VertexArrayBinding layoutEntry = getEntryWithPointer(vertexData, attribNdx); DE_ASSERT(varType.getBaseType() == rsg::VariableType::TYPE_FLOAT); for (int vtxNdx = 0; vtxNdx < 4; vtxNdx++) { const int fNdx = (attribNdx+vtxNdx+m_iterNdx)%DE_LENGTH_OF_ARRAY(vtxInterpFactors); const tcu::Vec4& f = vtxInterpFactors[fNdx]; switch (numComponents) { case 1: setVertex(layoutEntry.pointer, vtxNdx, interpolateRange(valueRange, f.toWidth<1>())); break; case 2: setVertex(layoutEntry.pointer, vtxNdx, interpolateRange(valueRange, f.toWidth<2>())); break; case 3: setVertex(layoutEntry.pointer, vtxNdx, interpolateRange(valueRange, f.toWidth<3>())); break; case 4: setVertex(layoutEntry.pointer, vtxNdx, interpolateRange(valueRange, f.toWidth<4>())); break; default: DE_ASSERT(false); } } } for (vector::iterator cmdIter = renderCmds.begin(); cmdIter != renderCmds.end(); ++cmdIter) computeRandomRenderCommand(rnd, *cmdIter, m_renderCtx.getType().getAPI(), m_viewportSize.x(), m_viewportSize.y()); // Workaround for inaccurate barycentric/depth computation in current reference renderer: // Small bias is added to the draw call depths, in increasing order, to avoid accuracy issues in depth comparison. for (int cmdNdx = 0; cmdNdx < (int)renderCmds.size(); cmdNdx++) renderCmds[cmdNdx].depth += 0.0231725f * float(cmdNdx); { const glu::VertexArrayPointer posPtr = getEntryWithPointer(vertexData, positionNdx).pointer; sglr::Context* const contexts[] = { m_glCtx, &m_referenceCtx->context }; const deUint32 programs[] = { m_glProgram, m_refProgram }; tcu::PixelBufferAccess readDst[] = { rendered.getAccess(), reference.getAccess() }; const tcu::Vec4 accurateClearColor = tcu::Vec4(0.0f, 0.25f, 0.5f, 1.0f); const tcu::Vec4 clearColor = getWellBehavingColor(accurateClearColor, m_renderCtx.getRenderTarget().getPixelFormat()); for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(contexts); ndx++) { sglr::Context& ctx = *contexts[ndx]; const deUint32 program = programs[ndx]; setupAttributes(ctx, vertexData, program); ctx.disable (GL_SCISSOR_TEST); ctx.colorMask (GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); ctx.depthMask (GL_TRUE); ctx.stencilMask (~0u); ctx.clearColor (clearColor.x(), clearColor.y(), clearColor.z(), clearColor.w()); ctx.clear (GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT); ctx.useProgram (program); for (vector::const_iterator uniformIter = uniformValues.begin(); uniformIter != uniformValues.end(); ++uniformIter) setUniformValue(ctx, ctx.getUniformLocation(program, uniformIter->getVariable()->getName()), uniformIter->getValue()); for (vector::const_iterator cmdIter = renderCmds.begin(); cmdIter != renderCmds.end(); ++cmdIter) render(ctx, posPtr, *cmdIter); GLU_EXPECT_NO_ERROR(ctx.getError(), "Rendering failed"); ctx.readPixels(0, 0, m_viewportSize.x(), m_viewportSize.y(), GL_RGBA, GL_UNSIGNED_BYTE, readDst[ndx].getDataPtr()); } } { const tcu::RGBA threshold = m_renderCtx.getRenderTarget().getPixelFormat().getColorThreshold()+tcu::RGBA(3,3,3,3); const bool compareOk = tcu::bilinearCompare(m_testCtx.getLog(), "CompareResult", "Image comparison result", reference.getAccess(), rendered.getAccess(), threshold, tcu::COMPARE_LOG_RESULT); if (!compareOk) { m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed"); return STOP; } } m_iterNdx += 1; return (m_iterNdx < NUM_ITERATIONS) ? CONTINUE : STOP; } } // gls } // deqp