/*------------------------------------------------------------------------- * OpenGL Conformance Test Suite * ----------------------------- * * Copyright (c) 2015-2016 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 * \brief */ /*-------------------------------------------------------------------*/ /*! * \file esextcDrawBuffersIndexedBlending.hpp * \brief Draw Buffers Indexed tests 5. Blending */ /*-------------------------------------------------------------------*/ #include "esextcDrawBuffersIndexedBlending.hpp" #include "gluPixelTransfer.hpp" #include "gluShaderProgram.hpp" #include "tcuTestLog.hpp" #include namespace glcts { /** Constructor * * @param context Test context * @param name Test case's name * @param description Test case's description **/ DrawBuffersIndexedBlending::DrawBuffersIndexedBlending(Context& context, const ExtParameters& extParams, const char* name, const char* description) : DrawBuffersIndexedBase(context, extParams, name, description) , m_fbo(0) { /* Left blank on purpose */ } void DrawBuffersIndexedBlending::prepareFramebuffer() { const glw::Functions& gl = m_context.getRenderContext().getFunctions(); glw::GLint maxDrawBuffers = 0; gl.getIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers); if (maxDrawBuffers < 4) { throw tcu::ResourceError("Minimum number of draw buffers too low"); } gl.genFramebuffers(1, &m_fbo); gl.bindFramebuffer(GL_FRAMEBUFFER, m_fbo); std::vector bufs(maxDrawBuffers); for (int i = 0; i < maxDrawBuffers; ++i) { bufs[i] = GL_COLOR_ATTACHMENT0 + i; } gl.drawBuffers(maxDrawBuffers, &bufs[0]); } void DrawBuffersIndexedBlending::releaseFramebuffer() { const glw::Functions& gl = m_context.getRenderContext().getFunctions(); glw::GLint maxDrawBuffers = 0; gl.getIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers); if (maxDrawBuffers < 4) { throw tcu::ResourceError("Minimum number of draw buffers too low"); } BlendMaskStateMachine state(m_context, m_testCtx.getLog(), maxDrawBuffers); state.SetDefaults(); gl.deleteFramebuffers(1, &m_fbo); gl.bindFramebuffer(GL_FRAMEBUFFER, 0); glw::GLenum bufs[1] = { GL_BACK }; gl.drawBuffers(1, bufs); gl.readBuffer(GL_BACK); } tcu::TestNode::IterateResult DrawBuffersIndexedBlending::iterate() { static const glw::GLenum BlendFormats[] = { GL_R8, GL_RG8, GL_RGB8, GL_RGB565, GL_RGBA4, GL_RGBA8, }; static const int kSize = 32; static unsigned int formatId = 0; const glw::Functions& gl = m_context.getRenderContext().getFunctions(); glw::GLenum format = BlendFormats[formatId]; prepareFramebuffer(); // Check number of available draw buffers glw::GLint maxDrawBuffers = 0; gl.getIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers); if (maxDrawBuffers < 4) { m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Minimum number of draw buffers too low"); return STOP; } // Prepare render targets glw::GLuint tex; gl.genTextures(1, &tex); gl.bindTexture(GL_TEXTURE_2D_ARRAY, tex); gl.texStorage3D(GL_TEXTURE_2D_ARRAY, 1, format, kSize, kSize, maxDrawBuffers); for (int i = 0; i < maxDrawBuffers; ++i) { gl.framebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, tex, 0, i); } // Clear background color tcu::Vec4 background(0.5f, 0.5f, 0.5f, 0.5f); for (int i = 0; i < maxDrawBuffers; ++i) { gl.clearBufferfv(GL_COLOR, i, &background[0]); } // Prepare expected, blended color values tcu::Vec4 colors[] = { tcu::Vec4(0.86f, 0.22f, 0.31f, 0.45f), tcu::Vec4(0.12f, 0.83f, 0.34f, 0.42f), tcu::Vec4(0.56f, 0.63f, 0.76f, 0.99f), tcu::Vec4(0.14f, 0.34f, 0.34f, 0.22f) }; int numComponents = NumComponents(format); tcu::RGBA expected[] = { // GL_MIN tcu::RGBA(static_cast(background.x() * 255), static_cast((numComponents >= 2 ? colors[0].y() : 0.0f) * 255), static_cast((numComponents >= 3 ? colors[0].z() : 0.0f) * 255), static_cast((numComponents == 4 ? background.w() : 1.0f) * 255)), // GL_FUNC_ADD tcu::RGBA(static_cast(background.x() * 255), static_cast((numComponents >= 2 ? background.y() : 0.0f) * 255), static_cast((numComponents >= 3 ? background.z() : 0.0f) * 255), static_cast((numComponents == 4 ? colors[1].w() : 1.0f) * 255)), // GL_FUNC_SUBTRACT tcu::RGBA( static_cast((colors[2].x() * (numComponents == 4 ? colors[2].w() : 1.0f) - background.x() * (numComponents == 4 ? background.w() : 1.0f)) * 255), static_cast((numComponents >= 2 ? (colors[2].y() * (numComponents == 4 ? colors[2].w() : 1.0f) - background.y() * (numComponents == 4 ? background.w() : 1.0f)) : 0.0f) * 255), static_cast((numComponents >= 3 ? (colors[2].z() * (numComponents == 4 ? colors[2].w() : 1.0f) - background.z() * (numComponents == 4 ? background.w() : 1.0f)) : 0.0f) * 255), static_cast( (numComponents == 4 ? (colors[2].w() * colors[2].w() - background.w() * background.w()) : 1.0f) * 255)), // GL_FUNC_REVERSE_SUBTRACT tcu::RGBA(static_cast((background.x() - colors[3].x()) * 255), static_cast((numComponents >= 2 ? (background.y() - colors[3].y()) : 0.0f) * 255), static_cast((numComponents >= 3 ? (background.z() - colors[3].z()) : 0.0f) * 255), static_cast((numComponents == 4 ? (background.w() - colors[3].w()) : 1.0f) * 255)) }; // Setup blending operations BlendMaskStateMachine state(m_context, m_testCtx.getLog(), maxDrawBuffers); for (int i = 0; i < maxDrawBuffers; ++i) { switch (i % 4) { case 0: // GL_MIN state.SetEnablei(i); state.SetBlendEquationSeparatei(i, GL_MIN, GL_MAX); state.SetBlendFunci(i, GL_ONE, GL_ONE); break; case 1: // GL_FUNC_ADD state.SetEnablei(i); state.SetBlendEquationi(i, GL_FUNC_ADD); state.SetBlendFuncSeparatei(i, GL_ZERO, GL_ONE, GL_ONE, GL_ZERO); break; case 2: // GL_FUNC_SUBTRACT state.SetEnablei(i); state.SetBlendEquationi(i, GL_FUNC_SUBTRACT); state.SetBlendFunci(i, GL_SRC_ALPHA, GL_DST_ALPHA); break; case 3: // GL_FUNC_REVERSE_SUBTRACT state.SetEnablei(i); state.SetBlendEquationi(i, GL_FUNC_REVERSE_SUBTRACT); state.SetBlendFunci(i, GL_ONE, GL_ONE); break; } } // Prepare shader programs and draw fullscreen quad glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(GenVS().c_str(), GenFS(maxDrawBuffers).c_str())); if (!program.isOk()) { m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Could not create shader program"); return STOP; } gl.useProgram(program.getProgram()); glw::GLuint positionLocation = gl.getAttribLocation(program.getProgram(), "position"); tcu::Vec3 vertices[] = { tcu::Vec3(-1.0f, -1.0f, 0.0f), tcu::Vec3(1.0f, -1.0f, 0.0f), tcu::Vec3(-1.0f, 1.0f, 0.0f), tcu::Vec3(1.0f, 1.0f, 0.0f), tcu::Vec3(-1.0f, 1.0f, 0.0f), tcu::Vec3(1.0f, -1.0f, 0.0f) }; gl.vertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, vertices); gl.enableVertexAttribArray(positionLocation); for (int i = 0; i < maxDrawBuffers; ++i) { std::ostringstream os; os << "c" << i; // i.e.: glUniform4fv(glGetUniformLocation(m_program, "c0"), 1, &colors[i].r); gl.uniform4fv(gl.getUniformLocation(program.getProgram(), os.str().c_str()), 1, &colors[i % 4][0]); } gl.drawArrays(GL_TRIANGLES, 0, 6); // Read buffer colors and validate proper blending behaviour bool success = true; tcu::RGBA epsilon = GetEpsilon(); for (int i = 0; i < maxDrawBuffers; ++i) { gl.readBuffer(GL_COLOR_ATTACHMENT0 + i); tcu::TextureLevel textureLevel(tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8), kSize, kSize); glu::readPixels(m_context.getRenderContext(), 0, 0, textureLevel.getAccess()); if (!VerifyImg(textureLevel, expected[i % 4], epsilon)) { m_testCtx.getLog() << tcu::TestLog::Message << "Blending error in texture format " << format << " occurred for draw buffer #" << i << "\n" << tcu::TestLog::EndMessage; m_testCtx.getLog() << tcu::TestLog::Image("Result", "Rendered result image", textureLevel.getAccess()); success = false; } } gl.disable(GL_BLEND); gl.useProgram(0); gl.bindTexture(GL_TEXTURE_2D_ARRAY, 0); gl.deleteTextures(1, &tex); releaseFramebuffer(); // Check for error glw::GLenum error_code = gl.getError(); if (error_code != GL_NO_ERROR) { m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Some functions generated error"); formatId = 0; return STOP; } if (!success) { m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Blending error occurred"); formatId = 0; return STOP; } else { ++formatId; if (formatId < (sizeof(BlendFormats) / sizeof(BlendFormats[0]))) { return CONTINUE; } else { m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); formatId = 0; return STOP; } } } std::string DrawBuffersIndexedBlending::GenVS() { std::ostringstream os; os << "#version 300 es \n" "precision highp float; \n" "precision highp int; \n" "layout(location = 0) in vec4 position; \n" "void main() { \n" " gl_Position = position; \n" "}"; return os.str(); } std::string DrawBuffersIndexedBlending::GenFS(int maxDrawBuffers) { std::ostringstream os; os << "#version 300 es \n" "precision highp float; \n" "precision highp int; \n"; for (int i = 0; i < maxDrawBuffers; ++i) { os << "\nlayout(location = " << i << ") out vec4 color" << i << ";"; } for (int i = 0; i < maxDrawBuffers; ++i) { os << "\nuniform vec4 c" << i << ";"; } os << "\nvoid main() {"; for (int i = 0; i < maxDrawBuffers; ++i) { os << "\n color" << i << " = c" << i << ";"; } os << "\n}"; return os.str(); } unsigned int DrawBuffersIndexedBlending::NumComponents(glw::GLenum format) { switch (format) { case GL_R8: case GL_R8I: case GL_R8UI: case GL_R16I: case GL_R16UI: case GL_R32I: case GL_R32UI: return 1; case GL_RG8: case GL_RG8I: case GL_RG8UI: case GL_RG16I: case GL_RG16UI: case GL_RG32I: case GL_RG32UI: return 2; case GL_RGB8: case GL_RGB565: return 3; case GL_RGBA4: case GL_RGB5_A1: case GL_RGBA8: case GL_RGB10_A2: case GL_RGBA8I: case GL_RGBA8UI: case GL_RGBA16I: case GL_RGBA16UI: case GL_RGBA32I: case GL_RGBA32UI: return 4; default: return 0; } } tcu::RGBA DrawBuffersIndexedBlending::GetEpsilon() { const glw::Functions& gl = m_context.getRenderContext().getFunctions(); tcu::IVec4 bits; tcu::UVec4 epsilon; for (int i = 0; i < 4; ++i) { gl.getIntegerv(GL_RED_BITS + i, &bits[i]); epsilon[i] = de::min( 255u, static_cast(ceil(1.0 + 255.0 * (1.0 / pow(2.0, static_cast(bits[i])))))); } return tcu::RGBA(epsilon.x(), epsilon.y(), epsilon.z(), epsilon.w()); } bool DrawBuffersIndexedBlending::VerifyImg(const tcu::TextureLevel& textureLevel, tcu::RGBA expectedColor, tcu::RGBA epsilon) { for (int y = 0; y < textureLevel.getHeight(); ++y) { for (int x = 0; x < textureLevel.getWidth(); ++x) { tcu::RGBA pixel(textureLevel.getAccess().getPixel(x, y)); if (!tcu::compareThreshold(pixel, expectedColor, epsilon)) { m_testCtx.getLog() << tcu::TestLog::Message << "Expected value: " << expectedColor << "\n" << "Read value: " << pixel << "\n" << "Epsilon: " << epsilon << tcu::TestLog::EndMessage; return false; } } } return true; } } // namespace glcts