| /*------------------------------------------------------------------------- |
| * drawElements Quality Program OpenGL ES 3.0 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 precision tests. |
| * |
| * \note Floating-point case uses R32UI render target and uses |
| * floatBitsToUint() in shader to write out floating-point value bits. |
| * This is done since ES3 core doesn't support FP render targets. |
| *//*--------------------------------------------------------------------*/ |
| |
| #include "es3fShaderPrecisionTests.hpp" |
| #include "tcuVector.hpp" |
| #include "tcuTestLog.hpp" |
| #include "tcuVectorUtil.hpp" |
| #include "tcuFloat.hpp" |
| #include "tcuFormatUtil.hpp" |
| #include "gluRenderContext.hpp" |
| #include "gluShaderProgram.hpp" |
| #include "gluShaderUtil.hpp" |
| #include "gluDrawUtil.hpp" |
| #include "deRandom.hpp" |
| #include "deString.h" |
| |
| #include "glwEnums.hpp" |
| #include "glwFunctions.hpp" |
| |
| #include <algorithm> |
| |
| namespace deqp |
| { |
| namespace gles3 |
| { |
| namespace Functional |
| { |
| |
| using std::string; |
| using std::vector; |
| using std::ostringstream; |
| using tcu::TestLog; |
| |
| enum |
| { |
| FRAMEBUFFER_WIDTH = 32, |
| FRAMEBUFFER_HEIGHT = 32 |
| }; |
| |
| static glu::ShaderProgram* createFloatPrecisionEvalProgram (const glu::RenderContext& context, glu::Precision precision, const char* evalOp, bool isVertexCase) |
| { |
| glu::DataType type = glu::TYPE_FLOAT; |
| glu::DataType outType = glu::TYPE_UINT; |
| const char* typeName = glu::getDataTypeName(type); |
| const char* outTypeName = glu::getDataTypeName(outType); |
| const char* precName = glu::getPrecisionName(precision); |
| ostringstream vtx; |
| ostringstream frag; |
| ostringstream& op = isVertexCase ? vtx : frag; |
| |
| vtx << "#version 300 es\n" |
| << "in highp vec4 a_position;\n" |
| << "in " << precName << " " << typeName << " a_in0;\n" |
| << "in " << precName << " " << typeName << " a_in1;\n"; |
| frag << "#version 300 es\n" |
| << "layout(location = 0) out highp " << outTypeName << " o_out;\n"; |
| |
| if (isVertexCase) |
| { |
| vtx << "flat out " << precName << " " << typeName << " v_out;\n"; |
| frag << "flat in " << precName << " " << typeName << " v_out;\n"; |
| } |
| else |
| { |
| vtx << "flat out " << precName << " " << typeName << " v_in0;\n" |
| << "flat out " << precName << " " << typeName << " v_in1;\n"; |
| frag << "flat in " << precName << " " << typeName << " v_in0;\n" |
| << "flat in " << precName << " " << typeName << " v_in1;\n"; |
| } |
| |
| vtx << "\nvoid main (void)\n{\n" |
| << " gl_Position = a_position;\n"; |
| frag << "\nvoid main (void)\n{\n"; |
| |
| op << "\t" << precName << " " << typeName << " in0 = " << (isVertexCase ? "a_" : "v_") << "in0;\n" |
| << "\t" << precName << " " << typeName << " in1 = " << (isVertexCase ? "a_" : "v_") << "in1;\n"; |
| |
| if (!isVertexCase) |
| op << "\t" << precName << " " << typeName << " res;\n"; |
| |
| op << "\t" << (isVertexCase ? "v_out" : "res") << " = " << evalOp << ";\n"; |
| |
| if (isVertexCase) |
| { |
| frag << " o_out = floatBitsToUint(v_out);\n"; |
| } |
| else |
| { |
| vtx << " v_in0 = a_in0;\n" |
| << " v_in1 = a_in1;\n"; |
| frag << " o_out = floatBitsToUint(res);\n"; |
| } |
| |
| vtx << "}\n"; |
| frag << "}\n"; |
| |
| return new glu::ShaderProgram(context, glu::makeVtxFragSources(vtx.str(), frag.str())); |
| } |
| |
| static glu::ShaderProgram* createIntUintPrecisionEvalProgram (const glu::RenderContext& context, glu::DataType type, glu::Precision precision, const char* evalOp, bool isVertexCase) |
| { |
| const char* typeName = glu::getDataTypeName(type); |
| const char* precName = glu::getPrecisionName(precision); |
| ostringstream vtx; |
| ostringstream frag; |
| ostringstream& op = isVertexCase ? vtx : frag; |
| |
| vtx << "#version 300 es\n" |
| << "in highp vec4 a_position;\n" |
| << "in " << precName << " " << typeName << " a_in0;\n" |
| << "in " << precName << " " << typeName << " a_in1;\n"; |
| frag << "#version 300 es\n" |
| << "layout(location = 0) out " << precName << " " << typeName << " o_out;\n"; |
| |
| if (isVertexCase) |
| { |
| vtx << "flat out " << precName << " " << typeName << " v_out;\n"; |
| frag << "flat in " << precName << " " << typeName << " v_out;\n"; |
| } |
| else |
| { |
| vtx << "flat out " << precName << " " << typeName << " v_in0;\n" |
| << "flat out " << precName << " " << typeName << " v_in1;\n"; |
| frag << "flat in " << precName << " " << typeName << " v_in0;\n" |
| << "flat in " << precName << " " << typeName << " v_in1;\n"; |
| } |
| |
| vtx << "\nvoid main (void)\n{\n" |
| << " gl_Position = a_position;\n"; |
| frag << "\nvoid main (void)\n{\n"; |
| |
| op << "\t" << precName << " " << typeName << " in0 = " << (isVertexCase ? "a_" : "v_") << "in0;\n" |
| << "\t" << precName << " " << typeName << " in1 = " << (isVertexCase ? "a_" : "v_") << "in1;\n"; |
| |
| op << "\t" << (isVertexCase ? "v_" : "o_") << "out = " << evalOp << ";\n"; |
| |
| if (isVertexCase) |
| { |
| frag << " o_out = v_out;\n"; |
| } |
| else |
| { |
| vtx << " v_in0 = a_in0;\n" |
| << " v_in1 = a_in1;\n"; |
| } |
| |
| vtx << "}\n"; |
| frag << "}\n"; |
| |
| return new glu::ShaderProgram(context, glu::makeVtxFragSources(vtx.str(), frag.str())); |
| } |
| |
| class ShaderFloatPrecisionCase : public TestCase |
| { |
| public: |
| typedef double (*EvalFunc) (double in0, double in1); |
| |
| ShaderFloatPrecisionCase (Context& context, const char* name, const char* desc, const char* op, EvalFunc evalFunc, glu::Precision precision, const tcu::Vec2& rangeA, const tcu::Vec2& rangeB, bool isVertexCase); |
| ~ShaderFloatPrecisionCase (void); |
| |
| void init (void); |
| void deinit (void); |
| IterateResult iterate (void); |
| |
| protected: |
| bool compare (float in0, float in1, double reference, float result); |
| |
| private: |
| ShaderFloatPrecisionCase (const ShaderFloatPrecisionCase& other); |
| ShaderFloatPrecisionCase& operator= (const ShaderFloatPrecisionCase& other); |
| |
| // Case parameters. |
| std::string m_op; |
| EvalFunc m_evalFunc; |
| glu::Precision m_precision; |
| tcu::Vec2 m_rangeA; |
| tcu::Vec2 m_rangeB; |
| bool m_isVertexCase; |
| |
| int m_numTestsPerIter; |
| int m_numIters; |
| de::Random m_rnd; |
| |
| // Iteration state. |
| glu::ShaderProgram* m_program; |
| deUint32 m_framebuffer; |
| deUint32 m_renderbuffer; |
| int m_iterNdx; |
| }; |
| |
| ShaderFloatPrecisionCase::ShaderFloatPrecisionCase (Context& context, const char* name, const char* desc, const char* op, EvalFunc evalFunc, glu::Precision precision, const tcu::Vec2& rangeA, const tcu::Vec2& rangeB, bool isVertexCase) |
| : TestCase (context, name, desc) |
| , m_op (op) |
| , m_evalFunc (evalFunc) |
| , m_precision (precision) |
| , m_rangeA (rangeA) |
| , m_rangeB (rangeB) |
| , m_isVertexCase (isVertexCase) |
| , m_numTestsPerIter (32) |
| , m_numIters (4) |
| , m_rnd (deStringHash(name)) |
| , m_program (DE_NULL) |
| , m_framebuffer (0) |
| , m_renderbuffer (0) |
| , m_iterNdx (0) |
| { |
| } |
| |
| ShaderFloatPrecisionCase::~ShaderFloatPrecisionCase (void) |
| { |
| ShaderFloatPrecisionCase::deinit(); |
| } |
| |
| void ShaderFloatPrecisionCase::init (void) |
| { |
| const glw::Functions& gl = m_context.getRenderContext().getFunctions(); |
| TestLog& log = m_testCtx.getLog(); |
| |
| DE_ASSERT(!m_program && !m_framebuffer && !m_renderbuffer); |
| |
| // Create program. |
| m_program = createFloatPrecisionEvalProgram(m_context.getRenderContext(), m_precision, m_op.c_str(), m_isVertexCase); |
| log << *m_program; |
| |
| TCU_CHECK(m_program->isOk()); |
| |
| // Create framebuffer. |
| gl.genFramebuffers(1, &m_framebuffer); |
| gl.genRenderbuffers(1, &m_renderbuffer); |
| |
| gl.bindRenderbuffer(GL_RENDERBUFFER, m_renderbuffer); |
| gl.renderbufferStorage(GL_RENDERBUFFER, GL_R32UI, FRAMEBUFFER_WIDTH, FRAMEBUFFER_HEIGHT); |
| |
| gl.bindFramebuffer(GL_FRAMEBUFFER, m_framebuffer); |
| gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_renderbuffer); |
| |
| GLU_EXPECT_NO_ERROR(gl.getError(), "Post framebuffer setup"); |
| TCU_CHECK(gl.checkFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE); |
| |
| gl.bindFramebuffer(GL_FRAMEBUFFER, m_context.getRenderContext().getDefaultFramebuffer()); |
| |
| // Initialize test result to pass. |
| m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); |
| m_iterNdx = 0; |
| } |
| |
| void ShaderFloatPrecisionCase::deinit (void) |
| { |
| delete m_program; |
| |
| if (m_framebuffer) |
| m_context.getRenderContext().getFunctions().deleteFramebuffers(1, &m_framebuffer); |
| |
| if (m_renderbuffer) |
| m_context.getRenderContext().getFunctions().deleteRenderbuffers(1, &m_renderbuffer); |
| |
| m_program = DE_NULL; |
| m_framebuffer = 0; |
| m_renderbuffer = 0; |
| } |
| |
| bool ShaderFloatPrecisionCase::compare (float in0, float in1, double reference, float result) |
| { |
| // Comparison is done using 64-bit reference value to accurately evaluate rounding mode error. |
| // If 32-bit reference value is used, 2 bits of rounding error must be allowed. |
| |
| // For mediump and lowp types the comparison currently allows 3 bits of rounding error: |
| // two bits from conversions and one from actual operation. |
| |
| // \todo [2013-09-30 pyry] Make this more strict: determine if rounding can actually happen. |
| |
| const int mantissaBits = m_precision == glu::PRECISION_HIGHP ? 23 : 10; |
| const int numPrecBits = 52 - mantissaBits; |
| |
| const int in0Exp = tcu::Float32(in0).exponent(); |
| const int in1Exp = tcu::Float32(in1).exponent(); |
| const int resExp = tcu::Float32(result).exponent(); |
| const int numLostBits = de::max(de::max(in0Exp-resExp, in1Exp-resExp), 0); // Lost due to mantissa shift. |
| |
| const int roundingUlpError = m_precision == glu::PRECISION_HIGHP ? 1 : 3; |
| const int maskBits = numLostBits + numPrecBits; |
| |
| m_testCtx.getLog() << TestLog::Message << "Assuming " << mantissaBits << " mantissa bits, " << numLostBits << " bits lost in operation, and " << roundingUlpError << " ULP rounding error." |
| << TestLog::EndMessage; |
| |
| { |
| const deUint64 refBits = tcu::Float64(reference).bits(); |
| const deUint64 resBits = tcu::Float64(result).bits(); |
| const deUint64 accurateRefBits = maskBits < 64 ? refBits >> (deUint64)maskBits : 0u; |
| const deUint64 accurateResBits = maskBits < 64 ? resBits >> (deUint64)maskBits : 0u; |
| const deUint64 ulpDiff = (deUint64)de::abs((deInt64)accurateRefBits - (deInt64)accurateResBits); |
| |
| if (ulpDiff > (deUint64)roundingUlpError) |
| { |
| m_testCtx.getLog() << TestLog::Message << "ERROR: comparison failed! ULP diff (ignoring lost/undefined bits) = " << ulpDiff << TestLog::EndMessage; |
| return false; |
| } |
| else |
| return true; |
| } |
| } |
| |
| ShaderFloatPrecisionCase::IterateResult ShaderFloatPrecisionCase::iterate (void) |
| { |
| // Constant data. |
| const float position[] = |
| { |
| -1.0f, -1.0f, 0.0f, 1.0f, |
| -1.0f, 1.0f, 0.0f, 1.0f, |
| 1.0f, -1.0f, 0.0f, 1.0f, |
| 1.0f, 1.0f, 0.0f, 1.0f |
| }; |
| const deUint16 indices[] = { 0, 1, 2, 2, 1, 3 }; |
| |
| const int numVertices = 4; |
| float in0Arr[4] = { 0.0f }; |
| float in1Arr[4] = { 0.0f }; |
| |
| TestLog& log = m_testCtx.getLog(); |
| const glw::Functions& gl = m_context.getRenderContext().getFunctions(); |
| vector<glu::VertexArrayBinding> vertexArrays; |
| |
| // Image read from GL. |
| std::vector<float> pixels (FRAMEBUFFER_WIDTH*FRAMEBUFFER_HEIGHT*4); |
| |
| // \todo [2012-05-03 pyry] Could be cached. |
| deUint32 prog = m_program->getProgram(); |
| |
| gl.useProgram(prog); |
| gl.bindFramebuffer(GL_FRAMEBUFFER, m_framebuffer); |
| |
| vertexArrays.push_back(glu::va::Float("a_position", 4, numVertices, 0, &position[0])); |
| vertexArrays.push_back(glu::va::Float("a_in0", 1, numVertices, 0, &in0Arr[0])); |
| vertexArrays.push_back(glu::va::Float("a_in1", 1, numVertices, 0, &in1Arr[0])); |
| |
| GLU_EXPECT_NO_ERROR(gl.getError(), "After program setup"); |
| |
| // Compute values and reference. |
| for (int testNdx = 0; testNdx < m_numTestsPerIter; testNdx++) |
| { |
| const float in0 = m_rnd.getFloat(m_rangeA.x(), m_rangeA.y()); |
| const float in1 = m_rnd.getFloat(m_rangeB.x(), m_rangeB.y()); |
| const double refD = m_evalFunc((double)in0, (double)in1); |
| const float refF = tcu::Float64(refD).asFloat(); // Uses RTE rounding mode. |
| |
| log << TestLog::Message << "iter " << m_iterNdx << ", test " << testNdx << ": " |
| << "in0 = " << in0 << " / " << tcu::toHex(tcu::Float32(in0).bits()) |
| << ", in1 = " << in1 << " / " << tcu::toHex(tcu::Float32(in1).bits()) |
| << TestLog::EndMessage |
| << TestLog::Message << " reference = " << refF << " / " << tcu::toHex(tcu::Float32(refF).bits()) << TestLog::EndMessage; |
| |
| std::fill(&in0Arr[0], &in0Arr[0] + DE_LENGTH_OF_ARRAY(in0Arr), in0); |
| std::fill(&in1Arr[0], &in1Arr[0] + DE_LENGTH_OF_ARRAY(in1Arr), in1); |
| |
| glu::draw(m_context.getRenderContext(), prog, (int)vertexArrays.size(), &vertexArrays[0], |
| glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indices), &indices[0])); |
| gl.readPixels(0, 0, FRAMEBUFFER_WIDTH, FRAMEBUFFER_HEIGHT, GL_RGBA_INTEGER, GL_UNSIGNED_INT, &pixels[0]); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "After render"); |
| |
| log << TestLog::Message << " result = " << pixels[0] << " / " << tcu::toHex(tcu::Float32(pixels[0]).bits()) << TestLog::EndMessage; |
| |
| // Verify results |
| { |
| const bool firstPixelOk = compare(in0, in1, refD, pixels[0]); |
| |
| if (firstPixelOk) |
| { |
| // Check that rest of pixels match to first one. |
| const deUint32 firstPixelBits = tcu::Float32(pixels[0]).bits(); |
| bool allPixelsOk = true; |
| |
| for (int y = 0; y < FRAMEBUFFER_HEIGHT; y++) |
| { |
| for (int x = 0; x < FRAMEBUFFER_WIDTH; x++) |
| { |
| const deUint32 pixelBits = tcu::Float32(pixels[(y*FRAMEBUFFER_WIDTH + x)*4]).bits(); |
| |
| if (pixelBits != firstPixelBits) |
| { |
| log << TestLog::Message << "ERROR: Inconsistent results, got " << tcu::toHex(pixelBits) << " at (" << x << ", " << y << ")" << TestLog::EndMessage; |
| allPixelsOk = false; |
| } |
| } |
| |
| if (!allPixelsOk) |
| break; |
| } |
| |
| if (!allPixelsOk) |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Inconsistent values in framebuffer"); |
| } |
| else |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Result comparison failed"); |
| } |
| |
| if (m_testCtx.getTestResult() != QP_TEST_RESULT_PASS) |
| break; |
| } |
| |
| gl.bindFramebuffer(GL_FRAMEBUFFER, m_context.getRenderContext().getDefaultFramebuffer()); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "After iteration"); |
| |
| m_iterNdx += 1; |
| return (m_iterNdx < m_numIters && m_testCtx.getTestResult() == QP_TEST_RESULT_PASS) ? CONTINUE : STOP; |
| } |
| |
| class ShaderIntPrecisionCase : public TestCase |
| { |
| public: |
| typedef int (*EvalFunc) (int a, int b); |
| |
| ShaderIntPrecisionCase (Context& context, const char* name, const char* desc, const char* op, EvalFunc evalFunc, glu::Precision precision, int bits, const tcu::IVec2& rangeA, const tcu::IVec2& rangeB, bool isVertexCase); |
| ~ShaderIntPrecisionCase (void); |
| |
| void init (void); |
| void deinit (void); |
| IterateResult iterate (void); |
| |
| private: |
| ShaderIntPrecisionCase (const ShaderIntPrecisionCase& other); |
| ShaderIntPrecisionCase& operator= (const ShaderIntPrecisionCase& other); |
| |
| // Case parameters. |
| std::string m_op; |
| EvalFunc m_evalFunc; |
| glu::Precision m_precision; |
| int m_bits; |
| tcu::IVec2 m_rangeA; |
| tcu::IVec2 m_rangeB; |
| bool m_isVertexCase; |
| |
| int m_numTestsPerIter; |
| int m_numIters; |
| de::Random m_rnd; |
| |
| // Iteration state. |
| glu::ShaderProgram* m_program; |
| deUint32 m_framebuffer; |
| deUint32 m_renderbuffer; |
| int m_iterNdx; |
| }; |
| |
| ShaderIntPrecisionCase::ShaderIntPrecisionCase (Context& context, const char* name, const char* desc, const char* op, EvalFunc evalFunc, glu::Precision precision, int bits, const tcu::IVec2& rangeA, const tcu::IVec2& rangeB, bool isVertexCase) |
| : TestCase (context, name, desc) |
| , m_op (op) |
| , m_evalFunc (evalFunc) |
| , m_precision (precision) |
| , m_bits (bits) |
| , m_rangeA (rangeA) |
| , m_rangeB (rangeB) |
| , m_isVertexCase (isVertexCase) |
| , m_numTestsPerIter (32) |
| , m_numIters (4) |
| , m_rnd (deStringHash(name)) |
| , m_program (DE_NULL) |
| , m_framebuffer (0) |
| , m_renderbuffer (0) |
| , m_iterNdx (0) |
| { |
| } |
| |
| ShaderIntPrecisionCase::~ShaderIntPrecisionCase (void) |
| { |
| ShaderIntPrecisionCase::deinit(); |
| } |
| |
| void ShaderIntPrecisionCase::init (void) |
| { |
| const glw::Functions& gl = m_context.getRenderContext().getFunctions(); |
| TestLog& log = m_testCtx.getLog(); |
| |
| DE_ASSERT(!m_program && !m_framebuffer && !m_renderbuffer); |
| |
| // Create program. |
| m_program = createIntUintPrecisionEvalProgram(m_context.getRenderContext(), glu::TYPE_INT, m_precision, m_op.c_str(), m_isVertexCase); |
| log << *m_program; |
| |
| TCU_CHECK(m_program->isOk()); |
| |
| // Create framebuffer. |
| gl.genFramebuffers(1, &m_framebuffer); |
| gl.genRenderbuffers(1, &m_renderbuffer); |
| |
| gl.bindRenderbuffer(GL_RENDERBUFFER, m_renderbuffer); |
| gl.renderbufferStorage(GL_RENDERBUFFER, GL_R32I, FRAMEBUFFER_WIDTH, FRAMEBUFFER_HEIGHT); |
| |
| gl.bindFramebuffer(GL_FRAMEBUFFER, m_framebuffer); |
| gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_renderbuffer); |
| |
| GLU_EXPECT_NO_ERROR(gl.getError(), "Post framebuffer setup"); |
| TCU_CHECK(gl.checkFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE); |
| |
| gl.bindFramebuffer(GL_FRAMEBUFFER, m_context.getRenderContext().getDefaultFramebuffer()); |
| |
| // Initialize test result to pass. |
| m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); |
| m_iterNdx = 0; |
| |
| log << TestLog::Message << "Number of accurate bits assumed = " << m_bits << TestLog::EndMessage; |
| } |
| |
| void ShaderIntPrecisionCase::deinit (void) |
| { |
| delete m_program; |
| |
| if (m_framebuffer) |
| m_context.getRenderContext().getFunctions().deleteFramebuffers(1, &m_framebuffer); |
| |
| if (m_renderbuffer) |
| m_context.getRenderContext().getFunctions().deleteRenderbuffers(1, &m_renderbuffer); |
| |
| m_program = DE_NULL; |
| m_framebuffer = 0; |
| m_renderbuffer = 0; |
| } |
| |
| ShaderIntPrecisionCase::IterateResult ShaderIntPrecisionCase::iterate (void) |
| { |
| // Constant data. |
| const float position[] = |
| { |
| -1.0f, -1.0f, 0.0f, 1.0f, |
| -1.0f, 1.0f, 0.0f, 1.0f, |
| 1.0f, -1.0f, 0.0f, 1.0f, |
| 1.0f, 1.0f, 0.0f, 1.0f |
| }; |
| const deUint16 indices[] = { 0, 1, 2, 2, 1, 3 }; |
| |
| const int numVertices = 4; |
| int in0Arr[4] = { 0 }; |
| int in1Arr[4] = { 0 }; |
| |
| TestLog& log = m_testCtx.getLog(); |
| const glw::Functions& gl = m_context.getRenderContext().getFunctions(); |
| deUint32 mask = m_bits == 32 ? 0xffffffffu : ((1u<<m_bits)-1); |
| vector<int> pixels (FRAMEBUFFER_WIDTH*FRAMEBUFFER_HEIGHT*4); |
| vector<glu::VertexArrayBinding> vertexArrays; |
| |
| deUint32 prog = m_program->getProgram(); |
| |
| // \todo [2012-05-03 pyry] A bit hacky. getInt() should work fine with ranges like this. |
| bool isMaxRangeA = m_rangeA.x() == (int)0x80000000 && m_rangeA.y() == (int)0x7fffffff; |
| bool isMaxRangeB = m_rangeB.x() == (int)0x80000000 && m_rangeB.y() == (int)0x7fffffff; |
| |
| gl.useProgram(prog); |
| gl.bindFramebuffer(GL_FRAMEBUFFER, m_framebuffer); |
| |
| vertexArrays.push_back(glu::va::Float("a_position", 4, numVertices, 0, &position[0])); |
| vertexArrays.push_back(glu::va::Int32("a_in0", 1, numVertices, 0, &in0Arr[0])); |
| vertexArrays.push_back(glu::va::Int32("a_in1", 1, numVertices, 0, &in1Arr[0])); |
| |
| GLU_EXPECT_NO_ERROR(gl.getError(), "After program setup"); |
| |
| // Compute values and reference. |
| for (int testNdx = 0; testNdx < m_numTestsPerIter; testNdx++) |
| { |
| int in0 = deSignExtendTo32(((isMaxRangeA ? (int)m_rnd.getUint32() : m_rnd.getInt(m_rangeA.x(), m_rangeA.y())) & mask), m_bits); |
| int in1 = deSignExtendTo32(((isMaxRangeB ? (int)m_rnd.getUint32() : m_rnd.getInt(m_rangeB.x(), m_rangeB.y())) & mask), m_bits); |
| int refMasked = m_evalFunc(in0, in1) & mask; |
| int refOut = deSignExtendTo32(refMasked, m_bits); |
| |
| log << TestLog::Message << "iter " << m_iterNdx << ", test " << testNdx << ": " |
| << "in0 = " << in0 << ", in1 = " << in1 << ", ref out = " << refOut << " / " << tcu::toHex(refMasked) |
| << TestLog::EndMessage; |
| |
| std::fill(&in0Arr[0], &in0Arr[0] + DE_LENGTH_OF_ARRAY(in0Arr), in0); |
| std::fill(&in1Arr[0], &in1Arr[0] + DE_LENGTH_OF_ARRAY(in1Arr), in1); |
| |
| glu::draw(m_context.getRenderContext(), prog, (int)vertexArrays.size(), &vertexArrays[0], |
| glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indices), &indices[0])); |
| gl.readPixels(0, 0, FRAMEBUFFER_WIDTH, FRAMEBUFFER_HEIGHT, GL_RGBA_INTEGER, GL_INT, &pixels[0]); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "After render"); |
| |
| // Compare pixels. |
| for (int y = 0; y < FRAMEBUFFER_HEIGHT; y++) |
| { |
| for (int x = 0; x < FRAMEBUFFER_WIDTH; x++) |
| { |
| int cmpOut = pixels[(y*FRAMEBUFFER_WIDTH + x)*4]; |
| int cmpMasked = cmpOut & mask; |
| |
| if (cmpMasked != refMasked) |
| { |
| log << TestLog::Message << "Comparison failed (at " << x << ", " << y << "): " |
| << "got " << cmpOut << " / " << tcu::toHex(cmpOut) |
| << TestLog::EndMessage; |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail"); |
| return STOP; |
| } |
| } |
| } |
| } |
| |
| gl.bindFramebuffer(GL_FRAMEBUFFER, m_context.getRenderContext().getDefaultFramebuffer()); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "After iteration"); |
| |
| m_iterNdx += 1; |
| return (m_iterNdx < m_numIters) ? CONTINUE : STOP; |
| } |
| |
| class ShaderUintPrecisionCase : public TestCase |
| { |
| public: |
| typedef deUint32 (*EvalFunc) (deUint32 a, deUint32 b); |
| |
| ShaderUintPrecisionCase (Context& context, const char* name, const char* desc, const char* op, EvalFunc evalFunc, glu::Precision precision, int bits, const tcu::UVec2& rangeA, const tcu::UVec2& rangeB, bool isVertexCase); |
| ~ShaderUintPrecisionCase (void); |
| |
| void init (void); |
| void deinit (void); |
| IterateResult iterate (void); |
| |
| private: |
| ShaderUintPrecisionCase (const ShaderUintPrecisionCase& other); |
| ShaderUintPrecisionCase& operator= (const ShaderUintPrecisionCase& other); |
| |
| // Case parameters. |
| std::string m_op; |
| EvalFunc m_evalFunc; |
| glu::Precision m_precision; |
| int m_bits; |
| tcu::UVec2 m_rangeA; |
| tcu::UVec2 m_rangeB; |
| bool m_isVertexCase; |
| |
| int m_numTestsPerIter; |
| int m_numIters; |
| de::Random m_rnd; |
| |
| // Iteration state. |
| glu::ShaderProgram* m_program; |
| deUint32 m_framebuffer; |
| deUint32 m_renderbuffer; |
| int m_iterNdx; |
| }; |
| |
| ShaderUintPrecisionCase::ShaderUintPrecisionCase (Context& context, const char* name, const char* desc, const char* op, EvalFunc evalFunc, glu::Precision precision, int bits, const tcu::UVec2& rangeA, const tcu::UVec2& rangeB, bool isVertexCase) |
| : TestCase (context, name, desc) |
| , m_op (op) |
| , m_evalFunc (evalFunc) |
| , m_precision (precision) |
| , m_bits (bits) |
| , m_rangeA (rangeA) |
| , m_rangeB (rangeB) |
| , m_isVertexCase (isVertexCase) |
| , m_numTestsPerIter (32) |
| , m_numIters (4) |
| , m_rnd (deStringHash(name)) |
| , m_program (DE_NULL) |
| , m_framebuffer (0) |
| , m_renderbuffer (0) |
| , m_iterNdx (0) |
| { |
| } |
| |
| ShaderUintPrecisionCase::~ShaderUintPrecisionCase (void) |
| { |
| ShaderUintPrecisionCase::deinit(); |
| } |
| |
| void ShaderUintPrecisionCase::init (void) |
| { |
| const glw::Functions& gl = m_context.getRenderContext().getFunctions(); |
| TestLog& log = m_testCtx.getLog(); |
| |
| DE_ASSERT(!m_program && !m_framebuffer && !m_renderbuffer); |
| |
| // Create program. |
| m_program = createIntUintPrecisionEvalProgram(m_context.getRenderContext(), glu::TYPE_UINT, m_precision, m_op.c_str(), m_isVertexCase); |
| log << *m_program; |
| |
| TCU_CHECK(m_program->isOk()); |
| |
| // Create framebuffer. |
| gl.genFramebuffers(1, &m_framebuffer); |
| gl.genRenderbuffers(1, &m_renderbuffer); |
| |
| gl.bindRenderbuffer(GL_RENDERBUFFER, m_renderbuffer); |
| gl.renderbufferStorage(GL_RENDERBUFFER, GL_R32UI, FRAMEBUFFER_WIDTH, FRAMEBUFFER_HEIGHT); |
| |
| gl.bindFramebuffer(GL_FRAMEBUFFER, m_framebuffer); |
| gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_renderbuffer); |
| |
| GLU_EXPECT_NO_ERROR(gl.getError(), "Post framebuffer setup"); |
| TCU_CHECK(gl.checkFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE); |
| |
| gl.bindFramebuffer(GL_FRAMEBUFFER, m_context.getRenderContext().getDefaultFramebuffer()); |
| |
| // Initialize test result to pass. |
| m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); |
| m_iterNdx = 0; |
| |
| log << TestLog::Message << "Number of accurate bits assumed = " << m_bits << TestLog::EndMessage; |
| } |
| |
| void ShaderUintPrecisionCase::deinit (void) |
| { |
| delete m_program; |
| |
| if (m_framebuffer) |
| m_context.getRenderContext().getFunctions().deleteFramebuffers(1, &m_framebuffer); |
| |
| if (m_renderbuffer) |
| m_context.getRenderContext().getFunctions().deleteRenderbuffers(1, &m_renderbuffer); |
| |
| m_program = DE_NULL; |
| m_framebuffer = 0; |
| m_renderbuffer = 0; |
| } |
| |
| ShaderUintPrecisionCase::IterateResult ShaderUintPrecisionCase::iterate (void) |
| { |
| // Constant data. |
| const float position[] = |
| { |
| -1.0f, -1.0f, 0.0f, 1.0f, |
| -1.0f, 1.0f, 0.0f, 1.0f, |
| 1.0f, -1.0f, 0.0f, 1.0f, |
| 1.0f, 1.0f, 0.0f, 1.0f |
| }; |
| const deUint16 indices[] = { 0, 1, 2, 2, 1, 3 }; |
| |
| const int numVertices = 4; |
| deUint32 in0Arr[4] = { 0 }; |
| deUint32 in1Arr[4] = { 0 }; |
| |
| TestLog& log = m_testCtx.getLog(); |
| const glw::Functions& gl = m_context.getRenderContext().getFunctions(); |
| deUint32 mask = m_bits == 32 ? 0xffffffffu : ((1u<<m_bits)-1); |
| vector<deUint32> pixels (FRAMEBUFFER_WIDTH*FRAMEBUFFER_HEIGHT*4); |
| vector<glu::VertexArrayBinding> vertexArrays; |
| |
| deUint32 prog = m_program->getProgram(); |
| |
| // \todo [2012-05-03 pyry] A bit hacky. |
| bool isMaxRangeA = m_rangeA.x() == 0 && m_rangeA.y() == 0xffffffff; |
| bool isMaxRangeB = m_rangeB.x() == 0 && m_rangeB.y() == 0xffffffff; |
| |
| gl.useProgram(prog); |
| gl.bindFramebuffer(GL_FRAMEBUFFER, m_framebuffer); |
| |
| vertexArrays.push_back(glu::va::Float("a_position", 4, numVertices, 0, &position[0])); |
| vertexArrays.push_back(glu::va::Uint32("a_in0", 1, numVertices, 0, &in0Arr[0])); |
| vertexArrays.push_back(glu::va::Uint32("a_in1", 1, numVertices, 0, &in1Arr[0])); |
| |
| GLU_EXPECT_NO_ERROR(gl.getError(), "After program setup"); |
| |
| // Compute values and reference. |
| for (int testNdx = 0; testNdx < m_numTestsPerIter; testNdx++) |
| { |
| deUint32 in0 = (isMaxRangeA ? m_rnd.getUint32() : (m_rangeA.x() + m_rnd.getUint32()%(m_rangeA.y()-m_rangeA.x()+1))) & mask; |
| deUint32 in1 = (isMaxRangeB ? m_rnd.getUint32() : (m_rangeB.x() + m_rnd.getUint32()%(m_rangeB.y()-m_rangeB.x()+1))) & mask; |
| deUint32 refOut = m_evalFunc(in0, in1) & mask; |
| |
| log << TestLog::Message << "iter " << m_iterNdx << ", test " << testNdx << ": " |
| << "in0 = " << tcu::toHex(in0) << ", in1 = " << tcu::toHex(in1) << ", ref out = " << tcu::toHex(refOut) |
| << TestLog::EndMessage; |
| |
| std::fill(&in0Arr[0], &in0Arr[0] + DE_LENGTH_OF_ARRAY(in0Arr), in0); |
| std::fill(&in1Arr[0], &in1Arr[0] + DE_LENGTH_OF_ARRAY(in1Arr), in1); |
| |
| glu::draw(m_context.getRenderContext(), prog, (int)vertexArrays.size(), &vertexArrays[0], |
| glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indices), &indices[0])); |
| gl.readPixels(0, 0, FRAMEBUFFER_WIDTH, FRAMEBUFFER_HEIGHT, GL_RGBA_INTEGER, GL_UNSIGNED_INT, &pixels[0]); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "After render"); |
| |
| // Compare pixels. |
| for (int y = 0; y < FRAMEBUFFER_HEIGHT; y++) |
| { |
| for (int x = 0; x < FRAMEBUFFER_WIDTH; x++) |
| { |
| deUint32 cmpOut = pixels[(y*FRAMEBUFFER_WIDTH + x)*4]; |
| deUint32 cmpMasked = cmpOut & mask; |
| |
| if (cmpMasked != refOut) |
| { |
| log << TestLog::Message << "Comparison failed (at " << x << ", " << y << "): " |
| << "got " << tcu::toHex(cmpOut) |
| << TestLog::EndMessage; |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail"); |
| return STOP; |
| } |
| } |
| } |
| } |
| |
| gl.bindFramebuffer(GL_FRAMEBUFFER, 0); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "After iteration"); |
| |
| m_iterNdx += 1; |
| return (m_iterNdx < m_numIters) ? CONTINUE : STOP; |
| } |
| |
| ShaderPrecisionTests::ShaderPrecisionTests (Context& context) |
| : TestCaseGroup(context, "precision", "Shader precision requirements validation tests") |
| { |
| } |
| |
| ShaderPrecisionTests::~ShaderPrecisionTests (void) |
| { |
| } |
| |
| void ShaderPrecisionTests::init (void) |
| { |
| using tcu::add; |
| using tcu::sub; |
| using tcu::mul; |
| using tcu::div; |
| using tcu::Vec2; |
| using tcu::IVec2; |
| using tcu::UVec2; |
| |
| // Exp = Emax-2, Mantissa = 0 |
| float minF32 = tcu::Float32((1u<<31) | (0xfdu<<23) | 0x0u).asFloat(); |
| float maxF32 = tcu::Float32((0u<<31) | (0xfdu<<23) | 0x0u).asFloat(); |
| float minF16 = tcu::Float16((deUint16)((1u<<15) | (0x1du<<10) | 0x0u)).asFloat(); |
| float maxF16 = tcu::Float16((deUint16)((0u<<15) | (0x1du<<10) | 0x0u)).asFloat(); |
| tcu::Vec2 fullRange32F (minF32, maxF32); |
| tcu::Vec2 fullRange16F (minF16, maxF16); |
| tcu::IVec2 fullRange32I (0x80000000, 0x7fffffff); |
| tcu::IVec2 fullRange16I (-(1<<15), (1<<15)-1); |
| tcu::IVec2 fullRange8I (-(1<<7), (1<<7)-1); |
| tcu::UVec2 fullRange32U (0u, 0xffffffffu); |
| tcu::UVec2 fullRange16U (0u, 0xffffu); |
| tcu::UVec2 fullRange8U (0u, 0xffu); |
| |
| // \note Right now it is not programmatically verified that the results shouldn't end up being inf/nan but |
| // actual values used are ok. |
| |
| static const struct |
| { |
| const char* name; |
| const char* op; |
| ShaderFloatPrecisionCase::EvalFunc evalFunc; |
| glu::Precision precision; |
| tcu::Vec2 rangeA; |
| tcu::Vec2 rangeB; |
| } floatCases[] = |
| { |
| // Name Op Eval Precision RangeA RangeB |
| { "highp_add", "in0 + in1", add<double>, glu::PRECISION_HIGHP, fullRange32F, fullRange32F }, |
| { "highp_sub", "in0 - in1", sub<double>, glu::PRECISION_HIGHP, fullRange32F, fullRange32F }, |
| { "highp_mul", "in0 * in1", mul<double>, glu::PRECISION_HIGHP, Vec2(-1e5f, 1e5f), Vec2(-1e5f, 1e5f) }, |
| { "highp_div", "in0 / in1", div<double>, glu::PRECISION_HIGHP, Vec2(-1e5f, 1e5f), Vec2(-1e5f, 1e5f) }, |
| { "mediump_add", "in0 + in1", add<double>, glu::PRECISION_MEDIUMP, fullRange16F, fullRange16F }, |
| { "mediump_sub", "in0 - in1", sub<double>, glu::PRECISION_MEDIUMP, fullRange16F, fullRange16F }, |
| { "mediump_mul", "in0 * in1", mul<double>, glu::PRECISION_MEDIUMP, Vec2(-1e2f, 1e2f), Vec2(-1e2f, 1e2f) }, |
| { "mediump_div", "in0 / in1", div<double>, glu::PRECISION_MEDIUMP, Vec2(-1e2f, 1e2f), Vec2(-1e2f, 1e2f) } |
| }; |
| |
| static const struct |
| { |
| const char* name; |
| const char* op; |
| ShaderIntPrecisionCase::EvalFunc evalFunc; |
| glu::Precision precision; |
| int bits; |
| tcu::IVec2 rangeA; |
| tcu::IVec2 rangeB; |
| } intCases[] = |
| { |
| // Name Op Eval Precision Bits RangeA RangeB |
| { "highp_add", "in0 + in1", add<int>, glu::PRECISION_HIGHP, 32, fullRange32I, fullRange32I }, |
| { "highp_sub", "in0 - in1", sub<int>, glu::PRECISION_HIGHP, 32, fullRange32I, fullRange32I }, |
| { "highp_mul", "in0 * in1", mul<int>, glu::PRECISION_HIGHP, 32, fullRange32I, fullRange32I }, |
| { "highp_div", "in0 / in1", div<int>, glu::PRECISION_HIGHP, 32, fullRange32I, IVec2(-10000, -1) }, |
| { "mediump_add", "in0 + in1", add<int>, glu::PRECISION_MEDIUMP, 16, fullRange16I, fullRange16I }, |
| { "mediump_sub", "in0 - in1", sub<int>, glu::PRECISION_MEDIUMP, 16, fullRange16I, fullRange16I }, |
| { "mediump_mul", "in0 * in1", mul<int>, glu::PRECISION_MEDIUMP, 16, fullRange16I, fullRange16I }, |
| { "mediump_div", "in0 / in1", div<int>, glu::PRECISION_MEDIUMP, 16, fullRange16I, IVec2(1, 1000) }, |
| { "lowp_add", "in0 + in1", add<int>, glu::PRECISION_LOWP, 8, fullRange8I, fullRange8I }, |
| { "lowp_sub", "in0 - in1", sub<int>, glu::PRECISION_LOWP, 8, fullRange8I, fullRange8I }, |
| { "lowp_mul", "in0 * in1", mul<int>, glu::PRECISION_LOWP, 8, fullRange8I, fullRange8I }, |
| { "lowp_div", "in0 / in1", div<int>, glu::PRECISION_LOWP, 8, fullRange8I, IVec2(-50, -1) } |
| }; |
| |
| static const struct |
| { |
| const char* name; |
| const char* op; |
| ShaderUintPrecisionCase::EvalFunc evalFunc; |
| glu::Precision precision; |
| int bits; |
| tcu::UVec2 rangeA; |
| tcu::UVec2 rangeB; |
| } uintCases[] = |
| { |
| // Name Op Eval Precision Bits RangeA RangeB |
| { "highp_add", "in0 + in1", add<deUint32>, glu::PRECISION_HIGHP, 32, fullRange32U, fullRange32U }, |
| { "highp_sub", "in0 - in1", sub<deUint32>, glu::PRECISION_HIGHP, 32, fullRange32U, fullRange32U }, |
| { "highp_mul", "in0 * in1", mul<deUint32>, glu::PRECISION_HIGHP, 32, fullRange32U, fullRange32U }, |
| { "highp_div", "in0 / in1", div<deUint32>, glu::PRECISION_HIGHP, 32, fullRange32U, UVec2(1u, 10000u) }, |
| { "mediump_add", "in0 + in1", add<deUint32>, glu::PRECISION_MEDIUMP, 16, fullRange16U, fullRange16U }, |
| { "mediump_sub", "in0 - in1", sub<deUint32>, glu::PRECISION_MEDIUMP, 16, fullRange16U, fullRange16U }, |
| { "mediump_mul", "in0 * in1", mul<deUint32>, glu::PRECISION_MEDIUMP, 16, fullRange16U, fullRange16U }, |
| { "mediump_div", "in0 / in1", div<deUint32>, glu::PRECISION_MEDIUMP, 16, fullRange16U, UVec2(1, 1000u) }, |
| { "lowp_add", "in0 + in1", add<deUint32>, glu::PRECISION_LOWP, 8, fullRange8U, fullRange8U }, |
| { "lowp_sub", "in0 - in1", sub<deUint32>, glu::PRECISION_LOWP, 8, fullRange8U, fullRange8U }, |
| { "lowp_mul", "in0 * in1", mul<deUint32>, glu::PRECISION_LOWP, 8, fullRange8U, fullRange8U }, |
| { "lowp_div", "in0 / in1", div<deUint32>, glu::PRECISION_LOWP, 8, fullRange8U, UVec2(1, 50u) } |
| }; |
| |
| tcu::TestCaseGroup* floatGroup = new tcu::TestCaseGroup(m_testCtx, "float", "Floating-point precision tests"); |
| addChild(floatGroup); |
| for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(floatCases); ndx++) |
| { |
| floatGroup->addChild(new ShaderFloatPrecisionCase(m_context, |
| (string(floatCases[ndx].name) + "_vertex").c_str(), "", |
| floatCases[ndx].op, |
| floatCases[ndx].evalFunc, |
| floatCases[ndx].precision, |
| floatCases[ndx].rangeA, |
| floatCases[ndx].rangeB, |
| true)); |
| floatGroup->addChild(new ShaderFloatPrecisionCase(m_context, |
| (string(floatCases[ndx].name) + "_fragment").c_str(), "", |
| floatCases[ndx].op, |
| floatCases[ndx].evalFunc, |
| floatCases[ndx].precision, |
| floatCases[ndx].rangeA, |
| floatCases[ndx].rangeB, |
| false)); |
| } |
| |
| tcu::TestCaseGroup* intGroup = new tcu::TestCaseGroup(m_testCtx, "int", "Integer precision tests"); |
| addChild(intGroup); |
| for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(intCases); ndx++) |
| { |
| intGroup->addChild(new ShaderIntPrecisionCase(m_context, |
| (string(intCases[ndx].name) + "_vertex").c_str(), "", |
| intCases[ndx].op, |
| intCases[ndx].evalFunc, |
| intCases[ndx].precision, |
| intCases[ndx].bits, |
| intCases[ndx].rangeA, |
| intCases[ndx].rangeB, |
| true)); |
| intGroup->addChild(new ShaderIntPrecisionCase(m_context, |
| (string(intCases[ndx].name) + "_fragment").c_str(), "", |
| intCases[ndx].op, |
| intCases[ndx].evalFunc, |
| intCases[ndx].precision, |
| intCases[ndx].bits, |
| intCases[ndx].rangeA, |
| intCases[ndx].rangeB, |
| false)); |
| } |
| |
| tcu::TestCaseGroup* uintGroup = new tcu::TestCaseGroup(m_testCtx, "uint", "Unsigned integer precision tests"); |
| addChild(uintGroup); |
| for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(uintCases); ndx++) |
| { |
| uintGroup->addChild(new ShaderUintPrecisionCase(m_context, |
| (string(uintCases[ndx].name) + "_vertex").c_str(), "", |
| uintCases[ndx].op, |
| uintCases[ndx].evalFunc, |
| uintCases[ndx].precision, |
| uintCases[ndx].bits, |
| uintCases[ndx].rangeA, |
| uintCases[ndx].rangeB, |
| true)); |
| uintGroup->addChild(new ShaderUintPrecisionCase(m_context, |
| (string(uintCases[ndx].name) + "_fragment").c_str(), "", |
| uintCases[ndx].op, |
| uintCases[ndx].evalFunc, |
| uintCases[ndx].precision, |
| uintCases[ndx].bits, |
| uintCases[ndx].rangeA, |
| uintCases[ndx].rangeB, |
| false)); |
| } |
| } |
| |
| } // Functional |
| } // gles3 |
| } // deqp |