| /*------------------------------------------------------------------------- |
| * OpenGL Conformance Test Suite |
| * ----------------------------- |
| * |
| * Copyright (c) 2020 Google Inc. |
| * Copyright (c) 2020 The Khronos Group Inc. |
| * |
| * 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 es3cNumberParsingTests.cpp |
| * \brief Tests for numeric value parsing in GLSL ES 3.0 |
| */ /*-------------------------------------------------------------------*/ |
| |
| #include "es3cNumberParsingTests.hpp" |
| |
| #include "gluDefs.hpp" |
| #include "gluTextureUtil.hpp" |
| #include "gluDrawUtil.hpp" |
| #include "gluShaderProgram.hpp" |
| |
| #include "glwDefs.hpp" |
| #include "glwFunctions.hpp" |
| #include "glwEnums.hpp" |
| |
| #include "tcuTestLog.hpp" |
| #include "tcuRenderTarget.hpp" |
| #include "tcuStringTemplate.hpp" |
| |
| #include <string> |
| #include <vector> |
| #include <map> |
| |
| #include <functional> |
| |
| namespace es3cts |
| { |
| |
| namespace |
| { |
| using std::string; |
| using std::vector; |
| using std::map; |
| |
| using std::function; |
| using std::bind; |
| using namespace std::placeholders; |
| |
| static const string defaultVertexShader = |
| "#version 300 es\n" |
| "in vec4 vPosition;\n" |
| "void main()\n" |
| "{\n" |
| " gl_Position = vPosition;\n" |
| "}\n"; |
| |
| static const string fragmentShaderTemplate = |
| "#version 300 es\n" |
| "precision highp float;\n" |
| "out vec4 my_FragColor;\n" |
| "${TEST_GLOBALS}" |
| "void main()\n" |
| "{\n" |
| "${TEST_CODE}" |
| " my_FragColor = vec4(0.0, correct, 0.0, 1.0);\n" |
| "}\n"; |
| |
| typedef function<void (const glu::ShaderProgram&, const glw::Functions&)> SetupUniformsFn; |
| |
| enum struct TestType |
| { |
| NORMAL = 0, |
| EXPECT_SHADER_FAIL |
| }; |
| |
| struct TestParams |
| { |
| TestType testType; |
| string name; |
| string description; |
| string testGlobals; |
| string testCode; |
| SetupUniformsFn setupUniformsFn; |
| }; |
| |
| static void initializeExpectedValue(const glu::ShaderProgram& program, const glw::Functions& gl, const deUint32 value); |
| static void initializeZeroValue(const glu::ShaderProgram& program, const glw::Functions& gl); |
| |
| static const TestParams tests[] = |
| { |
| { |
| TestType::NORMAL, // TestType testType |
| "unsigned_integer_above_signed_range_decimal", // string name |
| "Test that uint value higher than INT_MAX is parsed correctly", // string description |
| "uniform uint expected;\n", // string testGlobals |
| " uint i = 3221225472u;\n" |
| " float correct = (i == expected) ? 1.0 : 0.0;\n", |
| bind(initializeExpectedValue, _1, _2, 3221225472u) // SetupUniformsFn setupUniformsFn |
| }, |
| { |
| TestType::NORMAL, // TestType testType |
| "unsigned_integer_above_signed_range_base8", // string name |
| "Test that uint value higher than INT_MAX is parsed correctly in base 8 (octal)", // string description |
| "uniform uint expected;\n", // string testGlobals |
| " uint i = 030000000000u;\n" |
| " float correct = (i == expected) ? 1.0 : 0.0;\n", |
| bind(initializeExpectedValue, _1, _2, 3221225472u) // SetupUniformsFn setupUniformsFn |
| }, |
| { |
| TestType::NORMAL, // TestType testType |
| "unsigned_integer_above_signed_range_base16", // string name |
| "Test that uint value higher than INT_MAX is parsed correctly in base 16 (hex)", // string description |
| "uniform uint expected;\n", // string testGlobals |
| " uint i = 0xc0000000u;\n" |
| " float correct = (i == expected) ? 1.0 : 0.0;\n", |
| bind(initializeExpectedValue, _1, _2, 3221225472u) // SetupUniformsFn setupUniformsFn |
| }, |
| { |
| TestType::NORMAL, // TestType testType |
| "unsigned_integer_smallest_value_above_signed_range_decimal", // string name |
| "Test that uint value equal to INT_MAX+1 is parsed correctly", // string description |
| "uniform uint expected;\n", // string testGlobals |
| " uint i = 2147483648u;\n" |
| " float correct = (i == expected) ? 1.0 : 0.0;\n", |
| bind(initializeExpectedValue, _1, _2, 2147483648u) // SetupUniformsFn setupUniformsFn |
| }, |
| { |
| TestType::NORMAL, // TestType testType |
| "unsigned_integer_smallest_value_above_signed_range_base8", // string name |
| "Test that uint value equal to INT_MAX+1 is parsed correctly in base 8 (octal)", // string description |
| "uniform uint expected;\n", // string testGlobals |
| " uint i = 020000000000u;\n" |
| " float correct = (i == expected) ? 1.0 : 0.0;\n", |
| bind(initializeExpectedValue, _1, _2, 2147483648u) // SetupUniformsFn setupUniformsFn |
| }, |
| { |
| TestType::NORMAL, // TestType testType |
| "unsigned_integer_smallest_value_above_signed_range_base16", // string name |
| "Test that uint value equal to INT_MAX+1 is parsed correctly in base 16 (hex)", // string description |
| "uniform uint expected;\n", // string testGlobals |
| " uint i = 0x80000000u;\n" |
| " float correct = (i == expected) ? 1.0 : 0.0;\n", |
| bind(initializeExpectedValue, _1, _2, 2147483648u) // SetupUniformsFn setupUniformsFn |
| }, |
| { |
| TestType::NORMAL, // TestType testType |
| "unsigned_integer_max_value_decimal", // string name |
| "Test that uint value equal to UINT_MAX is parsed correctly", // string description |
| "uniform uint expected;\n", // string testGlobals |
| " uint i = 4294967295u;\n" |
| " float correct = (i == expected) ? 1.0 : 0.0;\n", |
| bind(initializeExpectedValue, _1, _2, 4294967295u) // SetupUniformsFn setupUniformsFn |
| }, |
| { |
| TestType::NORMAL, // TestType testType |
| "unsigned_integer_max_value_base8", // string name |
| "Test that uint value equal to UINT_MAX is parsed correctly in base 8 (octal)", // string description |
| "uniform uint expected;\n", // string testGlobals |
| " uint i = 037777777777u;\n" |
| " float correct = (i == expected) ? 1.0 : 0.0;\n", |
| bind(initializeExpectedValue, _1, _2, 4294967295u) // SetupUniformsFn setupUniformsFn |
| }, |
| { |
| TestType::NORMAL, // TestType testType |
| "unsigned_integer_max_value_base16", // string name |
| "Test that uint value equal to UINT_MAX is parsed correctly in base 16 (hex)", // string description |
| "uniform uint expected;\n", // string testGlobals |
| " uint i = 0xffffffffu;\n" |
| " float correct = (i == expected) ? 1.0 : 0.0;\n", |
| bind(initializeExpectedValue, _1, _2, 4294967295u) // SetupUniformsFn setupUniformsFn |
| }, |
| { |
| TestType::EXPECT_SHADER_FAIL, // TestType testType |
| "unsigned_integer_too_large_value_invalid", // string name |
| "Test that uint value outside uint range fails to compile", // string description |
| "", // string testGlobals |
| " uint i = 0xfffffffffu;" |
| " float correct = 0.0;", |
| nullptr // SetupUniformsFn setupUniformsFn |
| }, |
| { |
| TestType::NORMAL, // TestType testType |
| "unsigned_integer_negative_value_as_uint", // string name |
| "Test that -1u is parsed correctly", // string description |
| "uniform uint expected;\n", // string testGlobals |
| " uint i = -1u;" |
| " float correct = (i == expected) ? 1.0 : 0.0;\n", |
| bind(initializeExpectedValue, _1, _2, 0xffffffffu) // SetupUniformsFn setupUniformsFn |
| }, |
| /* The following floating point parsing tests are taken from the Khronos WebGL conformance tests at: |
| * https://www.khronos.org/registry/webgl/sdk/tests/conformance2/glsl3/float-parsing.html */ |
| { |
| TestType::NORMAL, // TestType testType |
| "float_out_of_range_as_infinity", // string name |
| "Floats of too large magnitude should be converted infinity", // string description |
| "", // string testGlobals |
| " // Out-of-range floats should overflow to infinity\n" // string testCode |
| " // GLSL ES 3.00.6 section 4.1.4 Floats:\n" |
| " // \"If the value of the floating point number is too large (small) to be stored as a single precision value, it is converted to positive (negative) infinity\"\n" |
| " float correct = isinf(1.0e40) ? 1.0 : 0.0;\n", |
| nullptr // SetupUniformsFn setupUniformsFn |
| }, |
| { |
| TestType::NORMAL, // TestType testType |
| "float_out_of_range_as_zero", // string name |
| "Floats of too small magnitude should be converted to zero", // string description |
| "", // string testGlobals |
| " // GLSL ES 3.00.6 section 4.1.4 Floats:\n" // string testCode |
| " // \"A value with a magnitude too small to be represented as a mantissa and exponent is converted to zero.\"\n" |
| " // 1.0e-50 is small enough that it can't even be stored as subnormal.\n" |
| " float correct = (1.0e-50 == 0.0) ? 1.0 : 0.0;\n", |
| nullptr // SetupUniformsFn setupUniformsFn |
| }, |
| { |
| TestType::NORMAL, // TestType testType |
| "float_no_limit_on_number_of_digits_positive_exponent", // string name |
| "Number of digits in any digit-sequence is not limited - test with a small mantissa and large exponent", // string description |
| "", // string testGlobals |
| " // GLSL ES 3.00.6 section 4.1.4 Floats:\n" // string testCode |
| " // \"There is no limit on the number of digits in any digit-sequence.\"\n" |
| " // The below float string has 100 zeros after the decimal point, but represents 1.0.\n" |
| " float x = 0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001e101;\n" |
| " float correct = (x == 1.0) ? 1.0 : 0.0;\n", |
| nullptr // SetupUniformsFn setupUniformsFn |
| }, |
| { |
| TestType::NORMAL, // TestType testType |
| "float_no_limit_on_number_of_digits_negative_exponent", // string name |
| "Number of digits in any digit-sequence is not limited - test with a large mantissa and negative exponent", // string description |
| "", // string testGlobals |
| " // GLSL ES 3.00.6 section 4.1.4 Floats:\n" // string testCode |
| " // \"There is no limit on the number of digits in any digit-sequence.\"\n" |
| " // The below float string has 100 zeros, but represents 1.0.\n" |
| " float x = 10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0e-100;\n" |
| " float correct = (x == 1.0) ? 1.0 : 0.0;\n", |
| nullptr // SetupUniformsFn setupUniformsFn |
| }, |
| { |
| TestType::NORMAL, // TestType testType |
| "float_slightly_out_of_range_exponent_as_positive_infinity", // string name |
| "Test that an exponent that slightly overflows signed 32-bit int range works", // string description |
| "", // string testGlobals |
| " // Out-of-range floats should overflow to infinity\n" // string testCode |
| " // GLSL ES 3.00.6 section 4.1.4 Floats:\n" |
| " // \"If the value of the floating point number is too large (small) to be stored as a single precision value, it is converted to positive (negative) infinity\"\n" |
| " float correct = isinf(1.0e2147483649) ? 1.0 : 0.0;\n", |
| nullptr // SetupUniformsFn setupUniformsFn |
| }, |
| { |
| TestType::NORMAL, // TestType testType |
| "float_overflow_to_positive_infinity", // string name |
| "Out-of-range floats greater than zero should overflow to positive infinity", // string description |
| "uniform float zero;\n", // string testGlobals |
| " // Out-of-range floats should overflow to infinity\n" // string testCode |
| " // GLSL ES 3.00.6 section 4.1.4 Floats:\n" |
| " // \"If the value of the floating point number is too large (small) to be stored as a single precision value, it is converted to positive (negative) infinity\"\n" |
| " float f = 1.0e2048 - zero;\n" |
| " float correct = (isinf(f) && f > 0.0) ? 1.0 : 0.0;\n", |
| initializeZeroValue // SetupUniformsFn setupUniformsFn |
| }, |
| { |
| TestType::NORMAL, // TestType testType |
| "float_overflow_to_negative_infinity", // string name |
| "Out-of-range floats less than zero should overflow to negative infinity", // string description |
| "uniform float zero;\n", // string testGlobals |
| " // Out-of-range floats should overflow to infinity\n" // string testCode |
| " // GLSL ES 3.00.6 section 4.1.4 Floats:\n" |
| " // \"If the value of the floating point number is too large (small) to be stored as a single precision value, it is converted to positive (negative) infinity\"\n" |
| " float f = -1.0e2048 + zero;\n" |
| " float correct = (isinf(f) && f < 0.0) ? 1.0 : 0.0;\n", |
| initializeZeroValue // SetupUniformsFn setupUniformsFn |
| } |
| }; |
| |
| static void initializeExpectedValue(const glu::ShaderProgram& program, const glw::Functions& gl, const deUint32 value) |
| { |
| const auto location = gl.getUniformLocation(program.getProgram(), "expected"); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "GetAttribLocation call failed"); |
| |
| gl.uniform1ui(location, value); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "Set uniform value failed"); |
| } |
| |
| static void initializeZeroValue(const glu::ShaderProgram& program, const glw::Functions& gl) |
| { |
| const auto location = gl.getUniformLocation(program.getProgram(), "zero"); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "GetAttribLocation call failed"); |
| |
| gl.uniform1f(location, 0.0f); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "Set uniform value failed"); |
| } |
| |
| static string replacePlaceholders(const string& shaderTemplate, const TestParams& params) |
| { |
| map<string,string> fields; |
| fields["TEST_GLOBALS"] = params.testGlobals; |
| fields["TEST_CODE"] = params.testCode; |
| |
| tcu::StringTemplate output(shaderTemplate); |
| return output.specialize(fields); |
| } |
| |
| static const std::vector<float> positions = |
| { |
| -1.0f, -1.0f, |
| 1.0f, -1.0f, |
| -1.0f, 1.0f, |
| 1.0f, 1.0f |
| }; |
| |
| static const std::vector<deUint32> indices = { 0, 1, 2, 3 }; |
| |
| const deInt32 RENDERTARGET_WIDTH = 16; |
| const deInt32 RENDERTARGET_HEIGHT = 16; |
| |
| class NumberParsingCase : public deqp::TestCase |
| { |
| public: |
| NumberParsingCase(deqp::Context& context, const string& name, const TestParams& params, const string& vertexShader, const string& fragmentShader); |
| |
| IterateResult iterate(); |
| |
| private: |
| void setupRenderTarget(); |
| void releaseRenderTarget(); |
| |
| glw::GLuint m_fboId; |
| glw::GLuint m_rboId; |
| |
| const TestParams& m_params; |
| string m_vertexShader; |
| string m_fragmentShader; |
| }; |
| |
| NumberParsingCase::NumberParsingCase(deqp::Context& context, const string& name, const TestParams& params, const string& vertexShader, const string& fragmentShader) |
| : TestCase(context, name.c_str(), params.description.c_str()) |
| , m_params(params) |
| , m_vertexShader(vertexShader) |
| , m_fragmentShader(fragmentShader) |
| { |
| } |
| |
| NumberParsingCase::IterateResult NumberParsingCase::iterate(void) |
| { |
| const auto& renderContext = m_context.getRenderContext(); |
| const auto& gl = renderContext.getFunctions(); |
| const auto textureFormat = tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8); |
| const auto transferFormat = glu::getTransferFormat(textureFormat); |
| |
| setupRenderTarget(); |
| |
| glu::ShaderProgram program(renderContext, glu::makeVtxFragSources(m_vertexShader, m_fragmentShader)); |
| if (!program.isOk()) |
| switch(m_params.testType) |
| { |
| case TestType::EXPECT_SHADER_FAIL: |
| m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); |
| return STOP; |
| default: |
| TCU_FAIL("Shader compilation failed:\nVertex shader:\n" + m_vertexShader + "\nFragment shader:\n" + m_fragmentShader); |
| break; |
| } |
| |
| const std::vector<glu::VertexArrayBinding> vertexArrays = |
| { |
| glu::va::Float("vPosition", 2, positions.size(), 0, positions.data()), |
| }; |
| |
| gl.useProgram(program.getProgram()); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram failed"); |
| |
| if (m_params.setupUniformsFn != DE_NULL) |
| m_params.setupUniformsFn(program, gl); |
| |
| gl.clear(GL_COLOR_BUFFER_BIT); |
| |
| |
| glu::draw(renderContext, program.getProgram(), |
| static_cast<int>(vertexArrays.size()), vertexArrays.data(), |
| glu::pr::TriangleStrip(static_cast<int>(indices.size()), indices.data())); |
| |
| const auto pixelSize = tcu::getPixelSize(textureFormat); |
| std::vector<deUint8> fbData (RENDERTARGET_WIDTH * RENDERTARGET_HEIGHT * pixelSize); |
| |
| if (pixelSize < 4) |
| gl.pixelStorei(GL_PACK_ALIGNMENT, 1); |
| |
| gl.readPixels(0, 0, RENDERTARGET_WIDTH, RENDERTARGET_HEIGHT, transferFormat.format, transferFormat.dataType, fbData.data()); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "glReadPixels"); |
| |
| tcu::ConstPixelBufferAccess fbAccess { textureFormat, RENDERTARGET_WIDTH, RENDERTARGET_HEIGHT, 1, fbData.data() }; |
| const auto expectedColor = tcu::RGBA::green().toVec(); |
| bool pass = true; |
| for(int y = 0; pass && y < RENDERTARGET_HEIGHT; ++y) |
| for(int x = 0; x < RENDERTARGET_WIDTH; ++x) |
| if (fbAccess.getPixel(x,y) != expectedColor) |
| { |
| pass = false; |
| break; |
| } |
| |
| releaseRenderTarget(); |
| |
| const qpTestResult result = (pass ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL); |
| const char* desc = (pass ? "Pass" : "Pixel mismatch; numeric value parsed incorrectly"); |
| |
| m_testCtx.setTestResult(result, desc); |
| |
| return STOP; |
| } |
| |
| void NumberParsingCase::setupRenderTarget() |
| { |
| const auto& renderContext = m_context.getRenderContext(); |
| const auto& gl = renderContext.getFunctions(); |
| |
| gl.genFramebuffers(1, &m_fboId); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "GenFramebuffers"); |
| |
| gl.genRenderbuffers(1, &m_rboId); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "GenRenderBuffers"); |
| |
| gl.bindRenderbuffer(GL_RENDERBUFFER, m_rboId); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "BindRenderBuffer"); |
| |
| gl.renderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, RENDERTARGET_WIDTH, RENDERTARGET_HEIGHT); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "RenderBufferStorage"); |
| |
| gl.bindFramebuffer(GL_FRAMEBUFFER, m_fboId); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "BindFrameBuffer"); |
| |
| gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_rboId); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "FrameBufferRenderBuffer"); |
| |
| glw::GLenum drawBuffer = GL_COLOR_ATTACHMENT0; |
| gl.drawBuffers(1, &drawBuffer); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "DrawBuffers"); |
| |
| glw::GLfloat clearColor[4] = { 0, 0, 0, 0 }; |
| gl.clearBufferfv(GL_COLOR, 0, clearColor); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "ClearBuffers"); |
| |
| gl.viewport(0, 0, RENDERTARGET_WIDTH, RENDERTARGET_HEIGHT); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "Viewport"); |
| } |
| |
| void NumberParsingCase::releaseRenderTarget() |
| { |
| const auto& renderContext = m_context.getRenderContext(); |
| const auto& gl = renderContext.getFunctions(); |
| if (m_fboId != 0) |
| { |
| gl.deleteFramebuffers(1, &m_fboId); |
| m_fboId = 0; |
| } |
| if (m_rboId != 0) |
| { |
| gl.deleteRenderbuffers(1, &m_rboId); |
| m_rboId = 0; |
| } |
| } |
| |
| } |
| |
| NumberParsingTests::NumberParsingTests(deqp::Context& context) |
| : deqp::TestCaseGroup(context, "number_parsing", "GLSL number parsing tests") |
| { |
| } |
| |
| NumberParsingTests::~NumberParsingTests(void) |
| { |
| } |
| |
| void NumberParsingTests::init(void) |
| { |
| for(const auto& params : tests) |
| { |
| addChild(new NumberParsingCase(m_context, params.name, params, defaultVertexShader, replacePlaceholders(fragmentShaderTemplate, params))); |
| } |
| } |
| |
| } |