/*------------------------------------------------------------------------- * drawElements Quality Program OpenGL ES 2.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 Special float stress tests. *//*--------------------------------------------------------------------*/ #include "es2sSpecialFloatTests.hpp" #include "gluRenderContext.hpp" #include "gluShaderProgram.hpp" #include "gluPixelTransfer.hpp" #include "gluStrUtil.hpp" #include "gluContextInfo.hpp" #include "glwEnums.hpp" #include "glwFunctions.hpp" #include "tcuRenderTarget.hpp" #include "tcuSurface.hpp" #include "tcuTestLog.hpp" #include "tcuVectorUtil.hpp" #include "deStringUtil.hpp" #include "deMath.h" #include "deRandom.hpp" #include <limits> #include <sstream> using namespace glw; namespace deqp { namespace gles2 { namespace Stress { namespace { static const int TEST_CANVAS_SIZE = 256; static const int TEST_TEXTURE_SIZE = 128; static const int TEST_TEXTURE_CUBE_SIZE = 32; static const deUint32 s_specialFloats[] = { 0x00000000, // zero 0x80000000, // negative zero 0x3F800000, // one 0xBF800000, // negative one 0x00800000, // minimum positive normalized value 0x80800000, // maximum negative normalized value 0x00000001, // minimum positive denorm value 0x80000001, // maximum negative denorm value 0x7F7FFFFF, // maximum finite value. 0xFF7FFFFF, // minimum finite value. 0x7F800000, // inf 0xFF800000, // -inf 0x34000000, // epsilon 0xB4000000, // negative epsilon 0x7FC00000, // quiet_NaN 0xFFC00000, // negative quiet_NaN 0x7FC00001, // signaling_NaN 0xFFC00001, // negative signaling_NaN 0x7FEAAAAA, // quiet payloaded NaN (payload of repeated pattern of 101010...) 0xFFEAAAAA, // negative quiet payloaded NaN ( .. ) 0x7FAAAAAA, // signaling payloaded NaN ( .. ) 0xFFAAAAAA, // negative signaling payloaded NaN ( .. ) }; static const char* const s_colorPassthroughFragmentShaderSource = "varying mediump vec4 v_out;\n" "void main ()\n" "{\n" " gl_FragColor = v_out;\n" "}\n"; static const char* const s_attrPassthroughVertexShaderSource = "attribute highp vec4 a_pos;\n" "attribute highp vec4 a_attr;\n" "varying mediump vec4 v_attr;\n" "void main ()\n" "{\n" " v_attr = a_attr;\n" " gl_Position = a_pos;\n" "}\n"; class RenderCase : public TestCase { public: enum RenderTargetType { RENDERTARGETTYPE_SCREEN, RENDERTARGETTYPE_FBO }; RenderCase (Context& context, const char* name, const char* desc, RenderTargetType renderTargetType = RENDERTARGETTYPE_SCREEN); virtual ~RenderCase (void); virtual void init (void); virtual void deinit (void); protected: bool checkResultImage (const tcu::Surface& result); bool drawTestPattern (bool useTexture); virtual std::string genVertexSource (void) const = 0; virtual std::string genFragmentSource (void) const = 0; const glu::ShaderProgram* m_program; const RenderTargetType m_renderTargetType; }; RenderCase::RenderCase (Context& context, const char* name, const char* desc, RenderTargetType renderTargetType) : TestCase (context, name, desc) , m_program (DE_NULL) , m_renderTargetType (renderTargetType) { } RenderCase::~RenderCase (void) { deinit(); } void RenderCase::init (void) { const int width = m_context.getRenderTarget().getWidth(); const int height = m_context.getRenderTarget().getHeight(); // check target size if (m_renderTargetType == RENDERTARGETTYPE_SCREEN) { if (width < TEST_CANVAS_SIZE || height < TEST_CANVAS_SIZE) throw tcu::NotSupportedError(std::string("Render target size must be at least ") + de::toString(TEST_CANVAS_SIZE) + "x" + de::toString(TEST_CANVAS_SIZE)); } else if (m_renderTargetType == RENDERTARGETTYPE_FBO) { GLint maxTexSize = 0; m_context.getRenderContext().getFunctions().getIntegerv(GL_MAX_TEXTURE_SIZE, &maxTexSize); if (maxTexSize < TEST_CANVAS_SIZE) throw tcu::NotSupportedError(std::string("GL_MAX_TEXTURE_SIZE must be at least ") + de::toString(TEST_CANVAS_SIZE)); } else DE_ASSERT(false); // gen shader m_testCtx.getLog() << tcu::TestLog::Message << "Creating test shader." << tcu::TestLog::EndMessage; m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(genVertexSource()) << glu::FragmentSource(genFragmentSource())); m_testCtx.getLog() << *m_program; if (!m_program->isOk()) throw tcu::TestError("shader compile failed"); } void RenderCase::deinit (void) { if (m_program) { delete m_program; m_program = DE_NULL; } } bool RenderCase::checkResultImage (const tcu::Surface& result) { tcu::Surface errorMask (TEST_CANVAS_SIZE, TEST_CANVAS_SIZE); bool error = false; m_testCtx.getLog() << tcu::TestLog::Message << "Verifying output image." << tcu::TestLog::EndMessage; for (int y = 0; y < TEST_CANVAS_SIZE; ++y) for (int x = 0; x < TEST_CANVAS_SIZE; ++x) { const tcu::RGBA col = result.getPixel(x, y); if (col.getGreen() == 255) errorMask.setPixel(x, y, tcu::RGBA::green); else { errorMask.setPixel(x, y, tcu::RGBA::red); error = true; } } if (error) { m_testCtx.getLog() << tcu::TestLog::Message << "Result image has missing or invalid pixels" << tcu::TestLog::EndMessage; m_testCtx.getLog() << tcu::TestLog::ImageSet("Results", "Result verification") << tcu::TestLog::Image("Result", "Result", result) << tcu::TestLog::Image("Error mask", "Error mask", errorMask) << tcu::TestLog::EndImageSet; } else { m_testCtx.getLog() << tcu::TestLog::ImageSet("Results", "Result verification") << tcu::TestLog::Image("Result", "Result", result) << tcu::TestLog::EndImageSet; } return !error; } bool RenderCase::drawTestPattern (bool useTexture) { static const tcu::Vec4 fullscreenQuad[4] = { tcu::Vec4(-1.0f, -1.0f, 0.0f, 1.0f), tcu::Vec4(-1.0f, 1.0f, 0.0f, 1.0f), tcu::Vec4( 1.0f, -1.0f, 0.0f, 1.0f), tcu::Vec4( 1.0f, 1.0f, 0.0f, 1.0f), }; const char* const vertexSource = "attribute highp vec4 a_pos;\n" "varying mediump vec4 v_position;\n" "void main ()\n" "{\n" " v_position = a_pos;\n" " gl_Position = a_pos;\n" "}\n"; const char* const fragmentSourceNoTex = "varying mediump vec4 v_position;\n" "void main ()\n" "{\n" " gl_FragColor = vec4((v_position.x + 1.0) * 0.5, 1.0, 1.0, 1.0);\n" "}\n"; const char* const fragmentSourceTex = "uniform mediump sampler2D u_sampler;\n" "varying mediump vec4 v_position;\n" "void main ()\n" "{\n" " gl_FragColor = texture2D(u_sampler, v_position.xy);\n" "}\n"; const char* const fragmentSource = (useTexture) ? (fragmentSourceTex) : (fragmentSourceNoTex); const tcu::RGBA formatThreshold = m_context.getRenderTarget().getPixelFormat().getColorThreshold(); tcu::Surface resultImage (TEST_CANVAS_SIZE, TEST_CANVAS_SIZE); tcu::Surface errorMask (TEST_CANVAS_SIZE, TEST_CANVAS_SIZE); bool error = false; m_testCtx.getLog() << tcu::TestLog::Message << "Drawing a test pattern to detect " << ((useTexture) ? ("texture sampling") : ("")) << " side-effects." << tcu::TestLog::EndMessage; // draw pattern { const glw::Functions& gl = m_context.getRenderContext().getFunctions(); const glu::ShaderProgram patternProgram (m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(vertexSource) << glu::FragmentSource(fragmentSource)); const GLint positionLoc = gl.getAttribLocation(patternProgram.getProgram(), "a_pos"); GLuint textureID = 0; if (useTexture) { const int textureSize = 32; std::vector<tcu::Vector<deUint8, 4> > buffer(textureSize*textureSize); for (int x = 0; x < textureSize; ++x) for (int y = 0; y < textureSize; ++y) { // sum of two axis aligned gradients. Each gradient is 127 at the edges and 0 at the center. // pattern is symmetric (x and y) => no discontinuity near boundary => no need to worry of results with LINEAR filtering near boundaries const deUint8 redComponent = (deUint8)de::clamp(de::abs((float)x / (float)textureSize - 0.5f) * 255.0f + de::abs((float)y / (float)textureSize - 0.5f) * 255.0f, 0.0f, 255.0f); buffer[x * textureSize + y] = tcu::Vector<deUint8, 4>(redComponent, 255, 255, 255); } gl.genTextures(1, &textureID); gl.bindTexture(GL_TEXTURE_2D, textureID); gl.texImage2D(GL_TEXTURE_2D, 0, GL_RGBA, textureSize, textureSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer[0].getPtr()); gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); } gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f); gl.clear(GL_COLOR_BUFFER_BIT); gl.viewport(0, 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE); gl.useProgram(patternProgram.getProgram()); if (useTexture) gl.uniform1i(gl.getUniformLocation(patternProgram.getProgram(), "u_sampler"), 0); gl.vertexAttribPointer(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, &fullscreenQuad[0]); gl.enableVertexAttribArray(positionLoc); gl.drawArrays(GL_TRIANGLE_STRIP, 0, 4); gl.disableVertexAttribArray(positionLoc); gl.useProgram(0); gl.finish(); GLU_EXPECT_NO_ERROR(gl.getError(), "RenderCase::drawTestPattern"); if (textureID) gl.deleteTextures(1, &textureID); glu::readPixels(m_context.getRenderContext(), 0, 0, resultImage.getAccess()); } // verify pattern for (int y = 0; y < TEST_CANVAS_SIZE; ++y) for (int x = 0; x < TEST_CANVAS_SIZE; ++x) { const float texGradientPosX = deFloatFrac((float)x * 2.0f / (float)TEST_CANVAS_SIZE); const float texGradientPosY = deFloatFrac((float)y * 2.0f / (float)TEST_CANVAS_SIZE); const deUint8 texRedComponent = (deUint8)de::clamp(de::abs(texGradientPosX - 0.5f) * 255.0f + de::abs(texGradientPosY - 0.5f) * 255.0f, 0.0f, 255.0f); const tcu::RGBA refColTexture = tcu::RGBA(texRedComponent, 255, 255, 255); const tcu::RGBA refColGradient = tcu::RGBA((int)((float)x / (float)TEST_CANVAS_SIZE * 255.0f), 255, 255, 255); const tcu::RGBA& refCol = (useTexture) ? (refColTexture) : (refColGradient); const int colorThreshold = 10; const tcu::RGBA col = resultImage.getPixel(x, y); const tcu::IVec4 colorDiff = tcu::abs(col.toIVec() - refCol.toIVec()); if (colorDiff.x() > formatThreshold.getRed() + colorThreshold || colorDiff.y() > formatThreshold.getGreen() + colorThreshold || colorDiff.z() > formatThreshold.getBlue() + colorThreshold) { errorMask.setPixel(x, y, tcu::RGBA::red); error = true; } else errorMask.setPixel(x, y, tcu::RGBA::green); } // report error if (error) { m_testCtx.getLog() << tcu::TestLog::Message << "Test pattern has missing/invalid pixels" << tcu::TestLog::EndMessage; m_testCtx.getLog() << tcu::TestLog::ImageSet("Results", "Result verification") << tcu::TestLog::Image("Result", "Result", resultImage) << tcu::TestLog::Image("Error mask", "Error mask", errorMask) << tcu::TestLog::EndImageSet; } else m_testCtx.getLog() << tcu::TestLog::Message << "No side-effects found." << tcu::TestLog::EndMessage; return !error; } class FramebufferRenderCase : public RenderCase { public: enum FrameBufferType { FBO_DEFAULT = 0, FBO_RGBA, FBO_RGBA4, FBO_RGB5_A1, FBO_RGB565, FBO_RGBA_FLOAT16, FBO_LAST }; FramebufferRenderCase (Context& context, const char* name, const char* desc, FrameBufferType fboType); virtual ~FramebufferRenderCase (void); virtual void init (void); virtual void deinit (void); IterateResult iterate (void); virtual void testFBO (void) = DE_NULL; protected: const FrameBufferType m_fboType; private: GLuint m_texID; GLuint m_fboID; }; FramebufferRenderCase::FramebufferRenderCase (Context& context, const char* name, const char* desc, FrameBufferType fboType) : RenderCase (context, name, desc, (fboType == FBO_DEFAULT) ? (RENDERTARGETTYPE_SCREEN) : (RENDERTARGETTYPE_FBO)) , m_fboType (fboType) , m_texID (0) , m_fboID (0) { DE_ASSERT(m_fboType < FBO_LAST); } FramebufferRenderCase::~FramebufferRenderCase (void) { deinit(); } void FramebufferRenderCase::init (void) { const glw::Functions& gl = m_context.getRenderContext().getFunctions(); // check requirements if (m_fboType == FBO_RGBA_FLOAT16) { // half float texture is allowed (OES_texture_half_float) and it is color renderable (EXT_color_buffer_half_float) if (!m_context.getContextInfo().isExtensionSupported("GL_OES_texture_half_float") || !m_context.getContextInfo().isExtensionSupported("GL_EXT_color_buffer_half_float")) throw tcu::NotSupportedError("Color renderable half float texture required."); } // gen shader RenderCase::init(); // create render target if (m_fboType == FBO_DEFAULT) { m_testCtx.getLog() << tcu::TestLog::Message << "Using default framebuffer." << tcu::TestLog::EndMessage; } else { GLuint internalFormat = 0; GLuint format = 0; GLuint type = 0; #if !defined(GL_HALF_FLOAT_OES) # define GL_HALF_FLOAT_OES 0x8D61 #endif switch (m_fboType) { case FBO_RGBA: internalFormat = GL_RGBA; format = GL_RGBA; type = GL_UNSIGNED_BYTE; break; case FBO_RGBA4: internalFormat = GL_RGBA; format = GL_RGBA; type = GL_UNSIGNED_SHORT_4_4_4_4; break; case FBO_RGB5_A1: internalFormat = GL_RGBA; format = GL_RGBA; type = GL_UNSIGNED_SHORT_5_5_5_1; break; case FBO_RGB565: internalFormat = GL_RGB; format = GL_RGB; type = GL_UNSIGNED_SHORT_5_6_5; break; case FBO_RGBA_FLOAT16: internalFormat = GL_RGBA; format = GL_RGBA; type = GL_HALF_FLOAT_OES; break; default: DE_ASSERT(false); break; } m_testCtx.getLog() << tcu::TestLog::Message << "Creating fbo. Texture internalFormat = " << glu::getPixelFormatStr(internalFormat) << ", format = " << glu::getPixelFormatStr(format) << ", type = " << glu::getTypeStr(type) << tcu::TestLog::EndMessage; // gen texture gl.genTextures(1, &m_texID); gl.bindTexture(GL_TEXTURE_2D, m_texID); gl.texImage2D(GL_TEXTURE_2D, 0, internalFormat, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE, 0, format, type, DE_NULL); GLU_EXPECT_NO_ERROR(gl.getError(), "texture init"); // gen fbo gl.genFramebuffers(1, &m_fboID); gl.bindFramebuffer(GL_FRAMEBUFFER, m_fboID); gl.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_texID, 0); GLU_EXPECT_NO_ERROR(gl.getError(), "fbo init"); if (gl.checkFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) throw tcu::NotSupportedError("could not create fbo for testing."); } } void FramebufferRenderCase::deinit (void) { const glw::Functions& gl = m_context.getRenderContext().getFunctions(); if (m_texID) { gl.deleteTextures(1, &m_texID); m_texID = 0; } if (m_fboID) { gl.deleteFramebuffers(1, &m_fboID); m_fboID = 0; } } FramebufferRenderCase::IterateResult FramebufferRenderCase::iterate (void) { const glw::Functions& gl = m_context.getRenderContext().getFunctions(); // bind fbo (or don't if we are using default) if (m_fboID) gl.bindFramebuffer(GL_FRAMEBUFFER, m_fboID); // do something with special floats testFBO(); return STOP; } /*--------------------------------------------------------------------*//*! * \brief Tests special floats as vertex attributes * * Tests that special floats transferred to the shader using vertex * attributes do not change the results of normal floating point * calculations. Special floats are put to 4-vector's x and y components and * value 1.0 is put to z and w. The resulting fragment's green channel * should be 1.0 everywhere. * * After the calculation test a test pattern is drawn to detect possible * floating point operation anomalies. *//*--------------------------------------------------------------------*/ class VertexAttributeCase : public RenderCase { public: enum Storage { STORAGE_BUFFER = 0, STORAGE_CLIENT, STORAGE_LAST }; enum ShaderType { TYPE_VERTEX = 0, TYPE_FRAGMENT, TYPE_LAST }; VertexAttributeCase (Context& context, const char* name, const char* desc, Storage storage, ShaderType type); ~VertexAttributeCase (void); void init (void); void deinit (void); IterateResult iterate (void); private: std::string genVertexSource (void) const; std::string genFragmentSource (void) const; const Storage m_storage; const ShaderType m_type; GLuint m_positionVboID; GLuint m_attribVboID; GLuint m_elementVboID; }; VertexAttributeCase::VertexAttributeCase (Context& context, const char* name, const char* desc, Storage storage, ShaderType type) : RenderCase (context, name, desc) , m_storage (storage) , m_type (type) , m_positionVboID (0) , m_attribVboID (0) , m_elementVboID (0) { DE_ASSERT(storage < STORAGE_LAST); DE_ASSERT(type < TYPE_LAST); } VertexAttributeCase::~VertexAttributeCase (void) { deinit(); } void VertexAttributeCase::init (void) { RenderCase::init(); // init gl resources if (m_storage == STORAGE_BUFFER) { const glw::Functions& gl = m_context.getRenderContext().getFunctions(); gl.genBuffers(1, &m_positionVboID); gl.genBuffers(1, &m_attribVboID); gl.genBuffers(1, &m_elementVboID); } } void VertexAttributeCase::deinit (void) { const glw::Functions& gl = m_context.getRenderContext().getFunctions(); RenderCase::deinit(); if (m_attribVboID) { gl.deleteBuffers(1, &m_attribVboID); m_attribVboID = 0; } if (m_positionVboID) { gl.deleteBuffers(1, &m_positionVboID); m_positionVboID = 0; } if (m_elementVboID) { gl.deleteBuffers(1, &m_elementVboID); m_elementVboID = 0; } } VertexAttributeCase::IterateResult VertexAttributeCase::iterate (void) { // Create a [s_specialFloats] X [s_specialFloats] grid of vertices with each vertex having 2 [s_specialFloats] values // and calculate some basic operations with the floating point values. If all goes well, nothing special should happen std::vector<tcu::Vec4> gridVertices (DE_LENGTH_OF_ARRAY(s_specialFloats) * DE_LENGTH_OF_ARRAY(s_specialFloats)); std::vector<tcu::UVec4> gridAttributes (DE_LENGTH_OF_ARRAY(s_specialFloats) * DE_LENGTH_OF_ARRAY(s_specialFloats)); std::vector<deUint16> indices ((DE_LENGTH_OF_ARRAY(s_specialFloats) - 1) * (DE_LENGTH_OF_ARRAY(s_specialFloats) - 1) * 6); tcu::Surface resultImage (TEST_CANVAS_SIZE, TEST_CANVAS_SIZE); // vertices for (int x = 0; x < DE_LENGTH_OF_ARRAY(s_specialFloats); ++x) for (int y = 0; y < DE_LENGTH_OF_ARRAY(s_specialFloats); ++y) { const deUint32 one = 0x3F800000; const float posX = (float)x / ((float)DE_LENGTH_OF_ARRAY(s_specialFloats) - 1.0f) * 2.0f - 1.0f; // map from [0, len(s_specialFloats) - 1] to [-1, 1] const float posY = (float)y / ((float)DE_LENGTH_OF_ARRAY(s_specialFloats) - 1.0f) * 2.0f - 1.0f; gridVertices[x * DE_LENGTH_OF_ARRAY(s_specialFloats) + y] = tcu::Vec4(posX, posY, 0.0f, 1.0f); gridAttributes[x * DE_LENGTH_OF_ARRAY(s_specialFloats) + y] = tcu::UVec4(s_specialFloats[x], s_specialFloats[y], one, one); } // tiles for (int x = 0; x < DE_LENGTH_OF_ARRAY(s_specialFloats) - 1; ++x) for (int y = 0; y < DE_LENGTH_OF_ARRAY(s_specialFloats) - 1; ++y) { const int baseNdx = (x * (DE_LENGTH_OF_ARRAY(s_specialFloats) - 1) + y) * 6; indices[baseNdx + 0] = (x+0) * DE_LENGTH_OF_ARRAY(s_specialFloats) + (y+0); indices[baseNdx + 1] = (x+1) * DE_LENGTH_OF_ARRAY(s_specialFloats) + (y+1); indices[baseNdx + 2] = (x+1) * DE_LENGTH_OF_ARRAY(s_specialFloats) + (y+0); indices[baseNdx + 3] = (x+0) * DE_LENGTH_OF_ARRAY(s_specialFloats) + (y+0); indices[baseNdx + 4] = (x+1) * DE_LENGTH_OF_ARRAY(s_specialFloats) + (y+1); indices[baseNdx + 5] = (x+0) * DE_LENGTH_OF_ARRAY(s_specialFloats) + (y+1); } m_testCtx.getLog() << tcu::TestLog::Message << "Drawing a grid with the shader. Setting a_attr for each vertex to (special, special, 1, 1)." << tcu::TestLog::EndMessage; // Draw grid { const glw::Functions& gl = m_context.getRenderContext().getFunctions(); const GLint positionLoc = gl.getAttribLocation(m_program->getProgram(), "a_pos"); const GLint attribLoc = gl.getAttribLocation(m_program->getProgram(), "a_attr"); if (m_storage == STORAGE_BUFFER) { gl.bindBuffer(GL_ARRAY_BUFFER, m_positionVboID); gl.bufferData(GL_ARRAY_BUFFER, (glw::GLsizeiptr)(gridVertices.size() * sizeof(tcu::Vec4)), &gridVertices[0], GL_STATIC_DRAW); GLU_EXPECT_NO_ERROR(gl.getError(), "VertexAttributeCase::iterate"); gl.bindBuffer(GL_ARRAY_BUFFER, m_attribVboID); gl.bufferData(GL_ARRAY_BUFFER, (glw::GLsizeiptr)(gridAttributes.size() * sizeof(tcu::UVec4)), &gridAttributes[0], GL_STATIC_DRAW); GLU_EXPECT_NO_ERROR(gl.getError(), "VertexAttributeCase::iterate"); gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_elementVboID); gl.bufferData(GL_ELEMENT_ARRAY_BUFFER, (glw::GLsizeiptr)(indices.size() * sizeof(deUint16)), &indices[0], GL_STATIC_DRAW); GLU_EXPECT_NO_ERROR(gl.getError(), "VertexAttributeCase::iterate"); } gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f); gl.clear(GL_COLOR_BUFFER_BIT); gl.viewport(0, 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE); gl.useProgram(m_program->getProgram()); if (m_storage == STORAGE_BUFFER) { gl.bindBuffer(GL_ARRAY_BUFFER, m_positionVboID); gl.vertexAttribPointer(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL); gl.bindBuffer(GL_ARRAY_BUFFER, m_attribVboID); gl.vertexAttribPointer(attribLoc, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL); gl.enableVertexAttribArray(positionLoc); gl.enableVertexAttribArray(attribLoc); gl.drawElements(GL_TRIANGLES, (glw::GLsizei)(indices.size()), GL_UNSIGNED_SHORT, DE_NULL); gl.disableVertexAttribArray(positionLoc); gl.disableVertexAttribArray(attribLoc); gl.bindBuffer(GL_ARRAY_BUFFER, 0); gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); } else if (m_storage == STORAGE_CLIENT) { gl.vertexAttribPointer(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, &gridVertices[0]); gl.vertexAttribPointer(attribLoc, 4, GL_FLOAT, GL_FALSE, 0, &gridAttributes[0]); gl.enableVertexAttribArray(positionLoc); gl.enableVertexAttribArray(attribLoc); gl.drawElements(GL_TRIANGLES, (glw::GLsizei)(indices.size()), GL_UNSIGNED_SHORT, &indices[0]); gl.disableVertexAttribArray(positionLoc); gl.disableVertexAttribArray(attribLoc); } else DE_ASSERT(false); gl.useProgram(0); gl.finish(); GLU_EXPECT_NO_ERROR(gl.getError(), "VertexAttributeCase::iterate"); glu::readPixels(m_context.getRenderContext(), 0, 0, resultImage.getAccess()); } // verify everywhere was drawn (all pixels have Green = 255) if (!checkResultImage(resultImage)) { m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "missing or invalid fragments"); return STOP; } // test drawing still works if (!drawTestPattern(false)) { m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "test pattern failed"); return STOP; } // all ok m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); return STOP; } std::string VertexAttributeCase::genVertexSource (void) const { if (m_type == TYPE_VERTEX) return "attribute highp vec4 a_pos;\n" "attribute highp vec4 a_attr;\n" "varying mediump vec4 v_out;\n" "void main ()\n" "{\n" " highp vec2 a1 = a_attr.xz + a_attr.yw; // add\n" " highp vec2 a2 = a_attr.xz - a_attr.yw; // sub\n" " highp vec2 a3 = a_attr.xz * a_attr.yw; // mul\n" " highp vec2 a4 = a_attr.xz / a_attr.yw; // div\n" " highp vec2 a5 = a_attr.xz + a_attr.yw * a_attr.xz; // fma\n" "\n" " highp float green = 1.0 - abs((a1.y + a2.y + a3.y + a4.y + a5.y) - 6.0);\n" " v_out = vec4(a1.x*a3.x + a2.x*a4.x, green, a5.x, 1.0);\n" " gl_Position = a_pos;\n" "}\n"; else return s_attrPassthroughVertexShaderSource; } std::string VertexAttributeCase::genFragmentSource (void) const { if (m_type == TYPE_VERTEX) return s_colorPassthroughFragmentShaderSource; else return "varying mediump vec4 v_attr;\n" "void main ()\n" "{\n" " mediump vec2 a1 = v_attr.xz + v_attr.yw; // add\n" " mediump vec2 a2 = v_attr.xz - v_attr.yw; // sub\n" " mediump vec2 a3 = v_attr.xz * v_attr.yw; // mul\n" " mediump vec2 a4 = v_attr.xz / v_attr.yw; // div\n" " mediump vec2 a5 = v_attr.xz + v_attr.yw * v_attr.xz; // fma\n" "\n" " const mediump float epsilon = 0.1; // allow small differences. To results to be wrong they must be more wrong than that.\n" " mediump float green = 1.0 + epsilon - abs((a1.y + a2.y + a3.y + a4.y + a5.y) - 6.0);\n" " gl_FragColor = vec4(a1.x*a3.x + a2.x*a4.x, green, a5.x, 1.0);\n" "}\n"; } /*--------------------------------------------------------------------*//*! * \brief Tests special floats as uniforms * * Tests that special floats transferred to the shader as uniforms do * not change the results of normal floating point calculations. Special * floats are put to 4-vector's x and y components and value 1.0 is put to * z and w. The resulting fragment's green channel should be 1.0 * everywhere. * * After the calculation test a test pattern is drawn to detect possible * floating point operation anomalies. *//*--------------------------------------------------------------------*/ class UniformCase : public RenderCase { public: enum ShaderType { TYPE_VERTEX = 0, TYPE_FRAGMENT, }; UniformCase (Context& context, const char* name, const char* desc, ShaderType type); ~UniformCase (void); void init (void); void deinit (void); IterateResult iterate (void); private: std::string genVertexSource (void) const; std::string genFragmentSource (void) const; const ShaderType m_type; }; UniformCase::UniformCase (Context& context, const char* name, const char* desc, ShaderType type) : RenderCase (context, name, desc) , m_type (type) { } UniformCase::~UniformCase (void) { deinit(); } void UniformCase::init (void) { RenderCase::init(); } void UniformCase::deinit (void) { RenderCase::deinit(); } UniformCase::IterateResult UniformCase::iterate (void) { // Create a [s_specialFloats] X [s_specialFloats] grid of tile with each tile having 2 [s_specialFloats] values // and calculate some basic operations with the floating point values. If all goes well, nothing special should happen std::vector<tcu::Vec4> gridVertices ((DE_LENGTH_OF_ARRAY(s_specialFloats) + 1) * (DE_LENGTH_OF_ARRAY(s_specialFloats) + 1)); std::vector<deUint16> indices (DE_LENGTH_OF_ARRAY(s_specialFloats) * DE_LENGTH_OF_ARRAY(s_specialFloats) * 6); tcu::Surface resultImage (TEST_CANVAS_SIZE, TEST_CANVAS_SIZE); // vertices for (int x = 0; x < DE_LENGTH_OF_ARRAY(s_specialFloats) + 1; ++x) for (int y = 0; y < DE_LENGTH_OF_ARRAY(s_specialFloats) + 1; ++y) { const float posX = (float)x / (float)DE_LENGTH_OF_ARRAY(s_specialFloats) * 2.0f - 1.0f; // map from [0, len(s_specialFloats) ] to [-1, 1] const float posY = (float)y / (float)DE_LENGTH_OF_ARRAY(s_specialFloats) * 2.0f - 1.0f; gridVertices[x * (DE_LENGTH_OF_ARRAY(s_specialFloats)+1) + y] = tcu::Vec4(posX, posY, 0.0f, 1.0f); } // tiles for (int x = 0; x < DE_LENGTH_OF_ARRAY(s_specialFloats); ++x) for (int y = 0; y < DE_LENGTH_OF_ARRAY(s_specialFloats); ++y) { const int baseNdx = (x * (DE_LENGTH_OF_ARRAY(s_specialFloats)) + y) * 6; indices[baseNdx + 0] = (x+0) * (DE_LENGTH_OF_ARRAY(s_specialFloats) + 1) + (y+0); indices[baseNdx + 1] = (x+1) * (DE_LENGTH_OF_ARRAY(s_specialFloats) + 1) + (y+1); indices[baseNdx + 2] = (x+1) * (DE_LENGTH_OF_ARRAY(s_specialFloats) + 1) + (y+0); indices[baseNdx + 3] = (x+0) * (DE_LENGTH_OF_ARRAY(s_specialFloats) + 1) + (y+0); indices[baseNdx + 4] = (x+1) * (DE_LENGTH_OF_ARRAY(s_specialFloats) + 1) + (y+1); indices[baseNdx + 5] = (x+0) * (DE_LENGTH_OF_ARRAY(s_specialFloats) + 1) + (y+1); } m_testCtx.getLog() << tcu::TestLog::Message << "Drawing a grid with the shader. Setting u_special for vertex each tile to (special, special, 1, 1)." << tcu::TestLog::EndMessage; // Draw grid { const glw::Functions& gl = m_context.getRenderContext().getFunctions(); const GLint positionLoc = gl.getAttribLocation(m_program->getProgram(), "a_pos"); const GLint specialLoc = gl.getUniformLocation(m_program->getProgram(), "u_special"); gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f); gl.clear(GL_COLOR_BUFFER_BIT); gl.viewport(0, 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE); gl.useProgram(m_program->getProgram()); gl.vertexAttribPointer(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, &gridVertices[0]); gl.enableVertexAttribArray(positionLoc); for (int x = 0; x < DE_LENGTH_OF_ARRAY(s_specialFloats); ++x) for (int y = 0; y < DE_LENGTH_OF_ARRAY(s_specialFloats); ++y) { const deUint32 one = 0x3F800000; const tcu::UVec4 uniformValue = tcu::UVec4(s_specialFloats[x], s_specialFloats[y], one, one); const int indexIndex = (x * DE_LENGTH_OF_ARRAY(s_specialFloats) + y) * 6; gl.uniform4fv(specialLoc, 1, (const float*)uniformValue.getPtr()); gl.drawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, &indices[indexIndex]); } gl.disableVertexAttribArray(positionLoc); gl.useProgram(0); gl.finish(); GLU_EXPECT_NO_ERROR(gl.getError(), "UniformCase::iterate"); glu::readPixels(m_context.getRenderContext(), 0, 0, resultImage.getAccess()); } // verify everywhere was drawn (all pixels have Green = 255) if (!checkResultImage(resultImage)) { m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "missing or invalid fragments"); return STOP; } // test drawing still works if (!drawTestPattern(false)) { m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "test pattern failed"); return STOP; } // all ok m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); return STOP; } std::string UniformCase::genVertexSource (void) const { if (m_type == TYPE_VERTEX) return "attribute highp vec4 a_pos;\n" "uniform highp vec4 u_special;\n" "varying mediump vec4 v_out;\n" "void main ()\n" "{\n" " highp vec2 a1 = u_special.xz + u_special.yw; // add\n" " highp vec2 a2 = u_special.xz - u_special.yw; // sub\n" " highp vec2 a3 = u_special.xz * u_special.yw; // mul\n" " highp vec2 a4 = u_special.xz / u_special.yw; // div\n" " highp vec2 a5 = u_special.xz + u_special.yw * u_special.xz; // fma\n" "\n" " highp float green = 1.0 - abs((a1.y + a2.y + a3.y + a4.y + a5.y) - 6.0);\n" " v_out = vec4(a1.x*a3.x + a2.x*a4.x, green, a5.x, 1.0);\n" " gl_Position = a_pos;\n" "}\n"; else return "attribute highp vec4 a_pos;\n" "void main ()\n" "{\n" " gl_Position = a_pos;\n" "}\n"; } std::string UniformCase::genFragmentSource (void) const { if (m_type == TYPE_VERTEX) return s_colorPassthroughFragmentShaderSource; else return "uniform mediump vec4 u_special;\n" "void main ()\n" "{\n" " mediump vec2 a1 = u_special.xz + u_special.yw; // add\n" " mediump vec2 a2 = u_special.xz - u_special.yw; // sub\n" " mediump vec2 a3 = u_special.xz * u_special.yw; // mul\n" " mediump vec2 a4 = u_special.xz / u_special.yw; // div\n" " mediump vec2 a5 = u_special.xz + u_special.yw * u_special.xz; // fma\n" " mediump vec2 a6 = mod(u_special.xz, u_special.yw);\n" " mediump vec2 a7 = mix(u_special.xz, u_special.yw, a6);\n" "\n" " mediump float green = 1.0 - abs((a1.y + a2.y + a3.y + a4.y + a5.y + a6.y + a7.y) - 7.0);\n" " gl_FragColor = vec4(a1.x*a3.x, green, a5.x*a4.x + a2.x*a7.x, 1.0);\n" "}\n"; } /*--------------------------------------------------------------------*//*! * \brief Tests special floats as texture samping arguments * * Tests that special floats given as texture coordinates or LOD levels * to sampling functions do not return invalid values (values not in the * texture). Every texel's green component is 1.0. * * After the calculation test a test pattern is drawn to detect possible * texture sampling anomalies. *//*--------------------------------------------------------------------*/ class TextureSamplerCase : public RenderCase { public: enum ShaderType { TYPE_VERTEX = 0, TYPE_FRAGMENT, TYPE_LAST }; enum TestType { TEST_TEX_COORD = 0, TEST_LOD, TEST_TEX_COORD_CUBE, TEST_LAST }; TextureSamplerCase (Context& context, const char* name, const char* desc, ShaderType type, TestType testType); ~TextureSamplerCase (void); void init (void); void deinit (void); IterateResult iterate (void); private: std::string genVertexSource (void) const; std::string genFragmentSource (void) const; const ShaderType m_type; const TestType m_testType; GLuint m_textureID; }; TextureSamplerCase::TextureSamplerCase (Context& context, const char* name, const char* desc, ShaderType type, TestType testType) : RenderCase (context, name, desc) , m_type (type) , m_testType (testType) , m_textureID (0) { DE_ASSERT(type < TYPE_LAST); DE_ASSERT(testType < TEST_LAST); } TextureSamplerCase::~TextureSamplerCase (void) { deinit(); } void TextureSamplerCase::init (void) { // requirements { GLint maxTextureSize = 0; m_context.getRenderContext().getFunctions().getIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize); if (maxTextureSize < TEST_TEXTURE_SIZE) throw tcu::NotSupportedError(std::string("GL_MAX_TEXTURE_SIZE must be at least ") + de::toString(TEST_TEXTURE_SIZE)); } // vertex shader supports textures? if (m_type == TYPE_VERTEX) { GLint maxVertexTexUnits = 0; m_context.getRenderContext().getFunctions().getIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &maxVertexTexUnits); if (maxVertexTexUnits < 1) throw tcu::NotSupportedError("GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS must be at least 1"); } RenderCase::init(); // gen texture { const glw::Functions& gl = m_context.getRenderContext().getFunctions(); std::vector<deUint8> texData (TEST_TEXTURE_SIZE*TEST_TEXTURE_SIZE*4); de::Random rnd (12345); gl.genTextures(1, &m_textureID); for (int x = 0; x < TEST_TEXTURE_SIZE; ++x) for (int y = 0; y < TEST_TEXTURE_SIZE; ++y) { // RGBA8, green and alpha channel are always 255 for verification texData[(x * TEST_TEXTURE_SIZE + y) * 4 + 0] = rnd.getUint32() & 0xFF; texData[(x * TEST_TEXTURE_SIZE + y) * 4 + 1] = 0xFF; texData[(x * TEST_TEXTURE_SIZE + y) * 4 + 2] = rnd.getUint32() & 0xFF; texData[(x * TEST_TEXTURE_SIZE + y) * 4 + 3] = 0xFF; } if (m_testType == TEST_TEX_COORD) { m_testCtx.getLog() << tcu::TestLog::Message << "Creating a 2D texture with a test pattern." << tcu::TestLog::EndMessage; gl.bindTexture(GL_TEXTURE_2D, m_textureID); gl.texImage2D(GL_TEXTURE_2D, 0, GL_RGBA, TEST_TEXTURE_SIZE, TEST_TEXTURE_SIZE, 0, GL_RGBA, GL_UNSIGNED_BYTE, &texData[0]); gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); GLU_EXPECT_NO_ERROR(gl.getError(), "TextureSamplerCase::init"); } else if (m_testType == TEST_LOD) { m_testCtx.getLog() << tcu::TestLog::Message << "Creating a mipmapped 2D texture with a test pattern." << tcu::TestLog::EndMessage; gl.bindTexture(GL_TEXTURE_2D, m_textureID); for (int level = 0; (TEST_TEXTURE_SIZE >> level); ++level) gl.texImage2D(GL_TEXTURE_2D, level, GL_RGBA, TEST_TEXTURE_SIZE >> level, TEST_TEXTURE_SIZE >> level, 0, GL_RGBA, GL_UNSIGNED_BYTE, &texData[0]); gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); GLU_EXPECT_NO_ERROR(gl.getError(), "TextureSamplerCase::init"); } else if (m_testType == TEST_TEX_COORD_CUBE) { DE_STATIC_ASSERT(TEST_TEXTURE_CUBE_SIZE <= TEST_TEXTURE_SIZE); static const GLenum faces[] = { GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, }; m_testCtx.getLog() << tcu::TestLog::Message << "Creating a cube map with a test pattern." << tcu::TestLog::EndMessage; gl.bindTexture(GL_TEXTURE_CUBE_MAP, m_textureID); for (int faceNdx = 0; faceNdx < DE_LENGTH_OF_ARRAY(faces); ++faceNdx) gl.texImage2D(faces[faceNdx], 0, GL_RGBA, TEST_TEXTURE_CUBE_SIZE, TEST_TEXTURE_CUBE_SIZE, 0, GL_RGBA, GL_UNSIGNED_BYTE, &texData[0]); gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); GLU_EXPECT_NO_ERROR(gl.getError(), "TextureSamplerCase::init"); } else DE_ASSERT(DE_FALSE); } } void TextureSamplerCase::deinit (void) { RenderCase::deinit(); if (m_textureID) { const glw::Functions& gl = m_context.getRenderContext().getFunctions(); gl.deleteTextures(1, &m_textureID); m_textureID = 0; } } TextureSamplerCase::IterateResult TextureSamplerCase::iterate (void) { // Draw a grid and texture it with a texture and sample it using special special values. The result samples should all have the green channel at 255 as per the test image. std::vector<tcu::Vec4> gridVertices (DE_LENGTH_OF_ARRAY(s_specialFloats) * DE_LENGTH_OF_ARRAY(s_specialFloats)); std::vector<tcu::UVec2> gridTexCoords (DE_LENGTH_OF_ARRAY(s_specialFloats) * DE_LENGTH_OF_ARRAY(s_specialFloats)); std::vector<deUint16> indices ((DE_LENGTH_OF_ARRAY(s_specialFloats) - 1) * (DE_LENGTH_OF_ARRAY(s_specialFloats) - 1) * 6); tcu::Surface resultImage (TEST_CANVAS_SIZE, TEST_CANVAS_SIZE); // vertices for (int x = 0; x < DE_LENGTH_OF_ARRAY(s_specialFloats); ++x) for (int y = 0; y < DE_LENGTH_OF_ARRAY(s_specialFloats); ++y) { const float posX = (float)x / ((float)DE_LENGTH_OF_ARRAY(s_specialFloats) - 1.0f) * 2.0f - 1.0f; // map from [0, len(s_specialFloats) - 1] to [-1, 1] const float posY = (float)y / ((float)DE_LENGTH_OF_ARRAY(s_specialFloats) - 1.0f) * 2.0f - 1.0f; gridVertices[x * DE_LENGTH_OF_ARRAY(s_specialFloats) + y] = tcu::Vec4(posX, posY, 0.0f, 1.0f); gridTexCoords[x * DE_LENGTH_OF_ARRAY(s_specialFloats) + y] = tcu::UVec2(s_specialFloats[x], s_specialFloats[y]); } // tiles for (int x = 0; x < DE_LENGTH_OF_ARRAY(s_specialFloats) - 1; ++x) for (int y = 0; y < DE_LENGTH_OF_ARRAY(s_specialFloats) - 1; ++y) { const int baseNdx = (x * (DE_LENGTH_OF_ARRAY(s_specialFloats) - 1) + y) * 6; indices[baseNdx + 0] = (x+0) * DE_LENGTH_OF_ARRAY(s_specialFloats) + (y+0); indices[baseNdx + 1] = (x+1) * DE_LENGTH_OF_ARRAY(s_specialFloats) + (y+1); indices[baseNdx + 2] = (x+1) * DE_LENGTH_OF_ARRAY(s_specialFloats) + (y+0); indices[baseNdx + 3] = (x+0) * DE_LENGTH_OF_ARRAY(s_specialFloats) + (y+0); indices[baseNdx + 4] = (x+1) * DE_LENGTH_OF_ARRAY(s_specialFloats) + (y+1); indices[baseNdx + 5] = (x+0) * DE_LENGTH_OF_ARRAY(s_specialFloats) + (y+1); } m_testCtx.getLog() << tcu::TestLog::Message << "Drawing a textured grid with the shader. Sampling from the texture using special floating point values." << tcu::TestLog::EndMessage; // Draw grid { const glw::Functions& gl = m_context.getRenderContext().getFunctions(); const GLint positionLoc = gl.getAttribLocation(m_program->getProgram(), "a_pos"); const GLint texCoordLoc = gl.getAttribLocation(m_program->getProgram(), "a_attr"); const GLint samplerLoc = gl.getUniformLocation(m_program->getProgram(), "u_sampler"); gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f); gl.clear(GL_COLOR_BUFFER_BIT); gl.viewport(0, 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE); gl.useProgram(m_program->getProgram()); gl.uniform1i(samplerLoc, 0); if (m_testType != TEST_TEX_COORD_CUBE) gl.bindTexture(GL_TEXTURE_2D, m_textureID); else gl.bindTexture(GL_TEXTURE_CUBE_MAP, m_textureID); gl.vertexAttribPointer(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, &gridVertices[0]); gl.vertexAttribPointer(texCoordLoc, 2, GL_FLOAT, GL_FALSE, 0, &gridTexCoords[0]); gl.enableVertexAttribArray(positionLoc); gl.enableVertexAttribArray(texCoordLoc); gl.drawElements(GL_TRIANGLES, (glw::GLsizei)(indices.size()), GL_UNSIGNED_SHORT, &indices[0]); gl.disableVertexAttribArray(positionLoc); gl.disableVertexAttribArray(texCoordLoc); gl.useProgram(0); gl.finish(); GLU_EXPECT_NO_ERROR(gl.getError(), "TextureSamplerCase::iterate"); glu::readPixels(m_context.getRenderContext(), 0, 0, resultImage.getAccess()); } // verify everywhere was drawn and samples were from the texture (all pixels have Green = 255) if (!checkResultImage(resultImage)) { m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "missing or invalid fragments"); return STOP; } // test drawing and textures still works if (!drawTestPattern(true)) { m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "test pattern failed"); return STOP; } // all ok m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); return STOP; } std::string TextureSamplerCase::genVertexSource (void) const { // vertex shader is passthrough, fragment does the calculations if (m_type == TYPE_FRAGMENT) return s_attrPassthroughVertexShaderSource; // vertex shader does the calculations std::ostringstream buf; buf << "attribute highp vec4 a_pos;\n" "attribute highp vec2 a_attr;\n"; if (m_testType != TEST_TEX_COORD_CUBE) buf << "uniform highp sampler2D u_sampler;\n"; else buf << "uniform highp samplerCube u_sampler;\n"; buf << "varying mediump vec4 v_out;\n" "void main ()\n" "{\n"; if (m_testType == TEST_TEX_COORD) buf << " v_out = texture2DLod(u_sampler, a_attr, 0.0);\n"; else if (m_testType == TEST_LOD) buf << " v_out = texture2DLod(u_sampler, a_attr, a_attr.x);\n"; else if (m_testType == TEST_TEX_COORD_CUBE) buf << " v_out = textureCubeLod(u_sampler, vec3(a_attr, a_attr.x+a_attr.y), 0.0);\n"; else DE_ASSERT(DE_FALSE); buf << "\n" " gl_Position = a_pos;\n" "}\n"; return buf.str(); } std::string TextureSamplerCase::genFragmentSource (void) const { // fragment shader is passthrough if (m_type == TYPE_VERTEX) return s_colorPassthroughFragmentShaderSource; // fragment shader does the calculations std::ostringstream buf; if (m_testType != TEST_TEX_COORD_CUBE) buf << "uniform mediump sampler2D u_sampler;\n"; else buf << "uniform mediump samplerCube u_sampler;\n"; buf << "varying mediump vec4 v_attr;\n" "void main ()\n" "{\n"; if (m_testType == TEST_TEX_COORD) buf << " gl_FragColor = texture2D(u_sampler, v_attr.xy);\n"; else if (m_testType == TEST_LOD) buf << " gl_FragColor = texture2D(u_sampler, v_attr.xy, v_attr.x);\n"; else if (m_testType == TEST_TEX_COORD_CUBE) buf << " gl_FragColor = textureCube(u_sampler, vec3(v_attr.xy, v_attr.x + v_attr.y));\n"; else DE_ASSERT(DE_FALSE); buf << "}\n"; return buf.str(); } /*--------------------------------------------------------------------*//*! * \brief Tests special floats as fragment shader outputs * * Tests that outputting special floats from a fragment shader does not change * the normal floating point values of outputted from a fragment shader. Special * floats are outputted in the green component, normal floating point values * in the red and blue component. Potential changes are tested by rendering * test pattern two times with different floating point values. The resulting * images' red and blue channels should be equal. *//*--------------------------------------------------------------------*/ class OutputCase : public FramebufferRenderCase { public: OutputCase (Context& context, const char* name, const char* desc, FramebufferRenderCase::FrameBufferType type); ~OutputCase (void); void testFBO (void); private: std::string genVertexSource (void) const; std::string genFragmentSource (void) const; }; OutputCase::OutputCase (Context& context, const char* name, const char* desc, FramebufferRenderCase::FrameBufferType type) : FramebufferRenderCase (context, name, desc, type) { } OutputCase::~OutputCase (void) { deinit(); } void OutputCase::testFBO (void) { // Create a 1 X [s_specialFloats] grid of tiles (stripes). std::vector<tcu::Vec4> gridVertices ((DE_LENGTH_OF_ARRAY(s_specialFloats) + 1) * 2); std::vector<deUint16> indices (DE_LENGTH_OF_ARRAY(s_specialFloats) * 6); tcu::TextureFormat textureFormat (tcu::TextureFormat::RGBA, (m_fboType == FBO_RGBA_FLOAT16) ? (tcu::TextureFormat::FLOAT) : (tcu::TextureFormat::UNORM_INT8)); tcu::TextureLevel specialImage (textureFormat, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE); tcu::TextureLevel normalImage (textureFormat, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE); // vertices for (int y = 0; y < DE_LENGTH_OF_ARRAY(s_specialFloats) + 1; ++y) { const float posY = (float)y / (float)DE_LENGTH_OF_ARRAY(s_specialFloats) * 2.0f - 1.0f; // map from [0, len(s_specialFloats) ] to [-1, 1] gridVertices[y * 2 + 0] = tcu::Vec4(-1.0, posY, 0.0f, 1.0f); gridVertices[y * 2 + 1] = tcu::Vec4( 1.0, posY, 0.0f, 1.0f); } // tiles for (int y = 0; y < DE_LENGTH_OF_ARRAY(s_specialFloats); ++y) { const int baseNdx = y * 6; indices[baseNdx + 0] = (y + 0) * 2; indices[baseNdx + 1] = (y + 1) * 2; indices[baseNdx + 2] = (y + 1) * 2 + 1; indices[baseNdx + 3] = (y + 0) * 2; indices[baseNdx + 4] = (y + 1) * 2 + 1; indices[baseNdx + 5] = (y + 0) * 2 + 1; } // Draw grids { const glw::Functions& gl = m_context.getRenderContext().getFunctions(); const GLint positionLoc = gl.getAttribLocation(m_program->getProgram(), "a_pos"); const GLint specialLoc = gl.getUniformLocation(m_program->getProgram(), "u_special"); gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f); gl.viewport(0, 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE); gl.useProgram(m_program->getProgram()); GLU_EXPECT_NO_ERROR(gl.getError(), "pre-draw"); gl.vertexAttribPointer(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, &gridVertices[0]); gl.enableVertexAttribArray(positionLoc); // draw 2 passes. Special and normal. for (int passNdx = 0; passNdx < 2; ++passNdx) { const bool specialPass = (passNdx == 0); m_testCtx.getLog() << tcu::TestLog::Message << "Pass " << passNdx << ": Drawing stripes with the shader. Setting u_special for each stripe to (" << ((specialPass) ? ("special") : ("1.0")) << ")." << tcu::TestLog::EndMessage; // draw stripes gl.clear(GL_COLOR_BUFFER_BIT); for (int y = 0; y < DE_LENGTH_OF_ARRAY(s_specialFloats); ++y) { const deUint32 one = 0x3F800000; const deUint32 special = s_specialFloats[y]; const deUint32 uniformValue = (specialPass) ? (special) : (one); const int indexIndex = y * 6; gl.uniform1fv(specialLoc, 1, (const float*)&uniformValue); gl.drawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, &indices[indexIndex]); } gl.finish(); glu::readPixels(m_context.getRenderContext(), 0, 0, ((specialPass) ? (specialImage) : (normalImage)).getAccess()); } gl.disableVertexAttribArray(positionLoc); gl.useProgram(0); GLU_EXPECT_NO_ERROR(gl.getError(), "OutputCase::iterate"); } // Check results { tcu::Surface errorMask (TEST_CANVAS_SIZE, TEST_CANVAS_SIZE); const tcu::RGBA badPixelColor = tcu::RGBA::red; const tcu::RGBA okPixelColor = tcu::RGBA::green; int badPixels = 0; m_testCtx.getLog() << tcu::TestLog::Message << "Checking passes have identical red and blue channels and the green channel is correct in the constant pass." << tcu::TestLog::EndMessage; for (int y = 0; y < specialImage.getHeight(); ++y) for (int x = 0; x < specialImage.getWidth(); ++x) { const float greenThreshold = 0.1f; const tcu::Vec4 cNormal = normalImage.getAccess().getPixel(x, y); const tcu::Vec4 cSpecial = specialImage.getAccess().getPixel(x, y); if (cNormal.x() != cSpecial.x() || cNormal.z() != cSpecial.z() || cNormal.y() < 1.0f - greenThreshold) { ++badPixels; errorMask.setPixel(x, y, badPixelColor); } else errorMask.setPixel(x, y, okPixelColor); } m_testCtx.getLog() << tcu::TestLog::Message << "Found " << badPixels << " invalid pixel(s)." << tcu::TestLog::EndMessage; if (badPixels) { m_testCtx.getLog() << tcu::TestLog::ImageSet("Results", "Result verification") << tcu::TestLog::Image("Image with special green channel", "Image with special green channel", specialImage) << tcu::TestLog::Image("Image with constant green channel", "Image with constant green channel", normalImage) << tcu::TestLog::Image("Error Mask", "Error Mask", errorMask) << tcu::TestLog::EndImageSet; // all ok? m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed"); } else m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); } } std::string OutputCase::genVertexSource (void) const { return "attribute highp vec4 a_pos;\n" "varying mediump vec2 v_pos;\n" "void main ()\n" "{\n" " gl_Position = a_pos;\n" " v_pos = a_pos.xy;\n" "}\n"; } std::string OutputCase::genFragmentSource (void) const { return "uniform mediump float u_special;\n" "varying mediump vec2 v_pos;\n" "void main ()\n" "{\n" " gl_FragColor = vec4((v_pos.x + 1.0) * 0.5, u_special, (v_pos.y + 1.0) * 0.5, 1.0);\n" "}\n"; } /*--------------------------------------------------------------------*//*! * \brief Tests special floats in blending * * Tests special floats as alpha and color components with various blending * modes. Test draws a test pattern and then does various blend operations * with special float values. After the blending test another test pattern * is drawn to detect possible blending anomalies. Test patterns should be * identical. *//*--------------------------------------------------------------------*/ class BlendingCase : public FramebufferRenderCase { public: BlendingCase (Context& context, const char* name, const char* desc, FramebufferRenderCase::FrameBufferType type); ~BlendingCase (void); void testFBO (void); private: void drawTestImage (tcu::PixelBufferAccess dst, GLuint uColorLoc, int maxVertexIndex); std::string genVertexSource (void) const; std::string genFragmentSource (void) const; }; BlendingCase::BlendingCase (Context& context, const char* name, const char* desc, FramebufferRenderCase::FrameBufferType type) : FramebufferRenderCase (context, name, desc, type) { } BlendingCase::~BlendingCase (void) { deinit(); } void BlendingCase::testFBO (void) { static const GLenum equations[] = { GL_FUNC_ADD, GL_FUNC_SUBTRACT, GL_FUNC_REVERSE_SUBTRACT, }; static const GLenum functions[] = { GL_ZERO, GL_ONE, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, }; // Create a [BlendFuncs] X [s_specialFloats] grid of tiles. ( BlendFuncs = equations x functions ) const int numBlendFuncs = DE_LENGTH_OF_ARRAY(equations) * DE_LENGTH_OF_ARRAY(functions); std::vector<tcu::Vec4> gridVertices ((numBlendFuncs + 1) * (DE_LENGTH_OF_ARRAY(s_specialFloats) + 1)); std::vector<deUint16> indices (numBlendFuncs * DE_LENGTH_OF_ARRAY(s_specialFloats) * 6); tcu::TextureFormat textureFormat (tcu::TextureFormat::RGBA, (m_fboType == FBO_RGBA_FLOAT16) ? (tcu::TextureFormat::FLOAT) : (tcu::TextureFormat::UNORM_INT8)); tcu::TextureLevel beforeImage (textureFormat, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE); tcu::TextureLevel afterImage (textureFormat, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE); // vertices for (int x = 0; x < DE_LENGTH_OF_ARRAY(s_specialFloats) + 1; ++x) for (int y = 0; y < numBlendFuncs + 1; ++y) { const float posX = (float)x / (float)DE_LENGTH_OF_ARRAY(s_specialFloats) * 2.0f - 1.0f; // map from [0, len(s_specialFloats)] to [-1, 1] const float posY = (float)y / (float)numBlendFuncs * 2.0f - 1.0f; gridVertices[x * (numBlendFuncs + 1) + y] = tcu::Vec4(posX, posY, 0.0f, 1.0f); } // tiles for (int x = 0; x < DE_LENGTH_OF_ARRAY(s_specialFloats); ++x) for (int y = 0; y < numBlendFuncs; ++y) { const int baseNdx = (x * numBlendFuncs + y) * 6; indices[baseNdx + 0] = (x+0) * (numBlendFuncs + 1) + (y+0); indices[baseNdx + 1] = (x+1) * (numBlendFuncs + 1) + (y+1); indices[baseNdx + 2] = (x+1) * (numBlendFuncs + 1) + (y+0); indices[baseNdx + 3] = (x+0) * (numBlendFuncs + 1) + (y+0); indices[baseNdx + 4] = (x+1) * (numBlendFuncs + 1) + (y+1); indices[baseNdx + 5] = (x+0) * (numBlendFuncs + 1) + (y+1); } // Draw tiles { const int numPasses = 5; const glw::Functions& gl = m_context.getRenderContext().getFunctions(); const GLint positionLoc = gl.getAttribLocation(m_program->getProgram(), "a_pos"); const GLint specialLoc = gl.getUniformLocation(m_program->getProgram(), "u_special"); gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f); gl.clear(GL_COLOR_BUFFER_BIT); gl.viewport(0, 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE); gl.useProgram(m_program->getProgram()); gl.enable(GL_BLEND); GLU_EXPECT_NO_ERROR(gl.getError(), "pre-draw"); gl.vertexAttribPointer(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, &gridVertices[0]); gl.enableVertexAttribArray(positionLoc); // draw "before" image m_testCtx.getLog() << tcu::TestLog::Message << "Drawing pre-draw pattern." << tcu::TestLog::EndMessage; drawTestImage(beforeImage.getAccess(), specialLoc, (int)gridVertices.size() - 1); GLU_EXPECT_NO_ERROR(gl.getError(), "pre-draw pattern"); // draw multiple passes with special floats gl.clear(GL_COLOR_BUFFER_BIT); for (int passNdx = 0; passNdx < numPasses; ++passNdx) { de::Random rnd(123 + 567 * passNdx); m_testCtx.getLog() << tcu::TestLog::Message << "Pass " << passNdx << ": Drawing tiles with the shader.\n" << "\tVarying u_special for each tile.\n" << "\tVarying blend function and blend equation for each tile.\n" << tcu::TestLog::EndMessage; // draw tiles for (int x = 0; x < DE_LENGTH_OF_ARRAY(s_specialFloats); ++x) for (int y = 0; y < numBlendFuncs; ++y) { const GLenum blendEquation = equations[y % DE_LENGTH_OF_ARRAY(equations)]; const GLenum blendFunction = functions[y / DE_LENGTH_OF_ARRAY(equations)]; const GLenum blendFunctionDst = rnd.choose<GLenum>(DE_ARRAY_BEGIN(functions), DE_ARRAY_END(functions)); const int indexIndex = (x * numBlendFuncs + y) * 6; // "rnd.get"s are run in a deterministic order const deUint32 componentR = rnd.choose<deUint32>(DE_ARRAY_BEGIN(s_specialFloats), DE_ARRAY_END(s_specialFloats)); const deUint32 componentG = rnd.choose<deUint32>(DE_ARRAY_BEGIN(s_specialFloats), DE_ARRAY_END(s_specialFloats)); const deUint32 componentB = rnd.choose<deUint32>(DE_ARRAY_BEGIN(s_specialFloats), DE_ARRAY_END(s_specialFloats)); const deUint32 componentA = rnd.choose<deUint32>(DE_ARRAY_BEGIN(s_specialFloats), DE_ARRAY_END(s_specialFloats)); const tcu::UVec4 uniformValue = tcu::UVec4(componentR, componentG, componentB, componentA); gl.uniform4fv(specialLoc, 1, (const float*)uniformValue.getPtr()); gl.blendEquation(blendEquation); gl.blendFunc(blendFunction, blendFunctionDst); gl.drawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, &indices[indexIndex]); } } GLU_EXPECT_NO_ERROR(gl.getError(), "special passes"); // draw "after" image m_testCtx.getLog() << tcu::TestLog::Message << "Drawing post-draw pattern." << tcu::TestLog::EndMessage; drawTestImage(afterImage.getAccess(), specialLoc, (int)gridVertices.size() - 1); GLU_EXPECT_NO_ERROR(gl.getError(), "post-draw pattern"); gl.disableVertexAttribArray(positionLoc); gl.useProgram(0); GLU_EXPECT_NO_ERROR(gl.getError(), "OutputCase::iterate"); } // Check results { tcu::Surface errorMask (TEST_CANVAS_SIZE, TEST_CANVAS_SIZE); const tcu::RGBA badPixelColor = tcu::RGBA::red; const tcu::RGBA okPixelColor = tcu::RGBA::green; int badPixels = 0; m_testCtx.getLog() << tcu::TestLog::Message << "Checking patterns are identical." << tcu::TestLog::EndMessage; for (int y = 0; y < beforeImage.getHeight(); ++y) for (int x = 0; x < beforeImage.getWidth(); ++x) { const tcu::Vec4 cBefore = beforeImage.getAccess().getPixel(x, y); const tcu::Vec4 cAfter = afterImage.getAccess().getPixel(x, y); if (cBefore != cAfter) { ++badPixels; errorMask.setPixel(x, y, badPixelColor); } else errorMask.setPixel(x, y, okPixelColor); } m_testCtx.getLog() << tcu::TestLog::Message << "Found " << badPixels << " invalid pixel(s)." << tcu::TestLog::EndMessage; if (badPixels) { m_testCtx.getLog() << tcu::TestLog::ImageSet("Results", "Result verification") << tcu::TestLog::Image("Pattern drawn before special float blending", "Pattern drawn before special float blending", beforeImage) << tcu::TestLog::Image("Pattern drawn after special float blending", "Pattern drawn after special float blending", afterImage) << tcu::TestLog::EndImageSet; // all ok? m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed"); } else m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); } } void BlendingCase::drawTestImage (tcu::PixelBufferAccess dst, GLuint uColorLoc, int maxVertexIndex) { const glw::Functions& gl = m_context.getRenderContext().getFunctions(); de::Random rnd (123); gl.clear(GL_COLOR_BUFFER_BIT); gl.blendEquation(GL_FUNC_ADD); gl.blendFunc(GL_ONE, GL_ONE); for (int tri = 0; tri < 20; ++tri) { tcu::Vec4 color; color.x() = rnd.getFloat(); color.y() = rnd.getFloat(); color.z() = rnd.getFloat(); color.w() = rnd.getFloat(); gl.uniform4fv(uColorLoc, 1, color.getPtr()); deUint16 indices[3]; indices[0] = rnd.getInt(0, maxVertexIndex); indices[1] = rnd.getInt(0, maxVertexIndex); indices[2] = rnd.getInt(0, maxVertexIndex); gl.drawElements(GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, indices); } gl.finish(); glu::readPixels(m_context.getRenderContext(), 0, 0, dst); } std::string BlendingCase::genVertexSource (void) const { return "attribute highp vec4 a_pos;\n" "void main ()\n" "{\n" " gl_Position = a_pos;\n" "}\n"; } std::string BlendingCase::genFragmentSource (void) const { return "uniform mediump vec4 u_special;\n" "void main ()\n" "{\n" " gl_FragColor = u_special;\n" "}\n"; } } //anonymous SpecialFloatTests::SpecialFloatTests (Context& context) : TestCaseGroup(context, "special_float", "Special float tests") { } SpecialFloatTests::~SpecialFloatTests (void) { } void SpecialFloatTests::init (void) { tcu::TestCaseGroup* const vertexGroup = new tcu::TestCaseGroup(m_testCtx, "vertex", "Run vertex shader with special float values"); tcu::TestCaseGroup* const fragmentGroup = new tcu::TestCaseGroup(m_testCtx, "fragment", "Run fragment shader with special float values"); tcu::TestCaseGroup* const framebufferGroup = new tcu::TestCaseGroup(m_testCtx, "framebuffer", "Test framebuffers containing special float values"); // .vertex { vertexGroup->addChild(new VertexAttributeCase (m_context, "attribute_buffer", "special attribute values in a buffer", VertexAttributeCase::STORAGE_BUFFER, VertexAttributeCase::TYPE_VERTEX)); vertexGroup->addChild(new VertexAttributeCase (m_context, "attribute_client", "special attribute values in a client storage", VertexAttributeCase::STORAGE_CLIENT, VertexAttributeCase::TYPE_VERTEX)); vertexGroup->addChild(new UniformCase (m_context, "uniform", "special uniform values", UniformCase::TYPE_VERTEX)); vertexGroup->addChild(new TextureSamplerCase (m_context, "sampler_tex_coord", "special texture coords", TextureSamplerCase::TYPE_VERTEX, TextureSamplerCase::TEST_TEX_COORD)); vertexGroup->addChild(new TextureSamplerCase (m_context, "sampler_tex_coord_cube", "special texture coords to cubemap", TextureSamplerCase::TYPE_VERTEX, TextureSamplerCase::TEST_TEX_COORD_CUBE)); vertexGroup->addChild(new TextureSamplerCase (m_context, "sampler_lod", "special texture lod", TextureSamplerCase::TYPE_VERTEX, TextureSamplerCase::TEST_LOD)); addChild(vertexGroup); } // .fragment { fragmentGroup->addChild(new VertexAttributeCase (m_context, "attribute_buffer", "special attribute values in a buffer", VertexAttributeCase::STORAGE_BUFFER, VertexAttributeCase::TYPE_FRAGMENT)); fragmentGroup->addChild(new VertexAttributeCase (m_context, "attribute_client", "special attribute values in a client storage", VertexAttributeCase::STORAGE_CLIENT, VertexAttributeCase::TYPE_FRAGMENT)); fragmentGroup->addChild(new UniformCase (m_context, "uniform", "special uniform values", UniformCase::TYPE_FRAGMENT)); fragmentGroup->addChild(new TextureSamplerCase (m_context, "sampler_tex_coord", "special texture coords", TextureSamplerCase::TYPE_FRAGMENT, TextureSamplerCase::TEST_TEX_COORD)); fragmentGroup->addChild(new TextureSamplerCase (m_context, "sampler_tex_coord_cube", "special texture coords to cubemap", TextureSamplerCase::TYPE_FRAGMENT, TextureSamplerCase::TEST_TEX_COORD_CUBE)); fragmentGroup->addChild(new TextureSamplerCase (m_context, "sampler_lod", "special texture lod", TextureSamplerCase::TYPE_FRAGMENT, TextureSamplerCase::TEST_LOD)); addChild(fragmentGroup); } // .framebuffer { framebufferGroup->addChild(new OutputCase (m_context, "write_default", "write special floating point values to default framebuffer", FramebufferRenderCase::FBO_DEFAULT)); framebufferGroup->addChild(new OutputCase (m_context, "write_rgba", "write special floating point values to RGBA framebuffer", FramebufferRenderCase::FBO_RGBA)); framebufferGroup->addChild(new OutputCase (m_context, "write_rgba4", "write special floating point values to RGBA4 framebuffer", FramebufferRenderCase::FBO_RGBA4)); framebufferGroup->addChild(new OutputCase (m_context, "write_rgb5_a1", "write special floating point values to RGB5_A1 framebuffer", FramebufferRenderCase::FBO_RGB5_A1)); framebufferGroup->addChild(new OutputCase (m_context, "write_rgb565", "write special floating point values to RGB565 framebuffer", FramebufferRenderCase::FBO_RGB565)); framebufferGroup->addChild(new OutputCase (m_context, "write_float16", "write special floating point values to float16 framebuffer", FramebufferRenderCase::FBO_RGBA_FLOAT16)); framebufferGroup->addChild(new BlendingCase (m_context, "blend_default", "blend special floating point values in a default framebuffer", FramebufferRenderCase::FBO_DEFAULT)); framebufferGroup->addChild(new BlendingCase (m_context, "blend_rgba", "blend special floating point values in a RGBA framebuffer", FramebufferRenderCase::FBO_RGBA)); framebufferGroup->addChild(new BlendingCase (m_context, "blend_float16", "blend special floating point values in a float16 framebuffer", FramebufferRenderCase::FBO_RGBA_FLOAT16)); addChild(framebufferGroup); } } } // Stress } // gles2 } // deqp