/*------------------------------------------------------------------------- * OpenGL Conformance Test Suite * ----------------------------- * * Copyright (c) 2017 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 esextcTessellationShaderWinding.cpp * \brief Test winding order with tessellation shaders */ /*-------------------------------------------------------------------*/ #include "esextcTessellationShaderWinding.hpp" #include "deSharedPtr.hpp" #include "esextcTessellationShaderUtils.hpp" #include "gluContextInfo.hpp" #include "gluDefs.hpp" #include "gluPixelTransfer.hpp" #include "gluShaderProgram.hpp" #include "glwEnums.hpp" #include "glwFunctions.hpp" #include "tcuRGBA.hpp" #include "tcuSurface.hpp" #include "tcuTestLog.hpp" #include namespace glcts { class WindingCase : public TestCaseBase { public: WindingCase(glcts::Context& context, const ExtParameters& extParams, std::string name, std::string primitiveType, std::string winding); void init(void); void deinit(void); IterateResult iterate(void); void prepareFramebuffer(); private: static const int RENDER_SIZE = 64; de::SharedPtr m_program; glw::GLuint m_rbo; glw::GLuint m_fbo; }; WindingCase::WindingCase(glcts::Context& context, const ExtParameters& extParams, std::string name, std::string primitiveType, std::string winding) : TestCaseBase(context, extParams, name.c_str(), "") { DE_ASSERT((primitiveType.compare("triangles") == 0) || (primitiveType.compare("quads") == 0)); DE_ASSERT((winding.compare("cw") == 0) || (winding.compare("ccw") == 0)); m_specializationMap["PRIMITIVE_TYPE"] = primitiveType; m_specializationMap["WINDING"] = winding; m_rbo = 0; m_fbo = 0; } void WindingCase::init(void) { TestCaseBase::init(); if (!m_is_tessellation_shader_supported) { TCU_THROW(NotSupportedError, TESSELLATION_SHADER_EXTENSION_NOT_SUPPORTED); } TestCaseBase::init(); const char* vs("${VERSION}\n" "void main (void)\n" "{\n" "}\n"); const char* tcs("${VERSION}\n" "${TESSELLATION_SHADER_REQUIRE}\n" "layout (vertices = 1) out;\n" "void main (void)\n" "{\n" " gl_TessLevelInner[0] = 5.0;\n" " gl_TessLevelInner[1] = 5.0;\n" "\n" " gl_TessLevelOuter[0] = 5.0;\n" " gl_TessLevelOuter[1] = 5.0;\n" " gl_TessLevelOuter[2] = 5.0;\n" " gl_TessLevelOuter[3] = 5.0;\n" "}\n"); const char* tes("${VERSION}\n" "${TESSELLATION_SHADER_REQUIRE}\n" "layout (${PRIMITIVE_TYPE}, ${WINDING}) in;\n" "void main (void)\n" "{\n" " gl_Position = vec4(gl_TessCoord.xy*2.0 - 1.0, 0.0, 1.0);\n" "}\n"); const char* fs("${VERSION}\n" "layout (location = 0) out mediump vec4 o_color;\n" "void main (void)\n" "{\n" " o_color = vec4(1.0);\n" "}\n"); m_program = de::SharedPtr( new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(specializeShader(1, &vs)) << glu::TessellationControlSource(specializeShader(1, &tcs)) << glu::TessellationEvaluationSource(specializeShader(1, &tes)) << glu::FragmentSource(specializeShader(1, &fs)))); m_testCtx.getLog() << *m_program; if (!m_program->isOk()) TCU_FAIL("Program compilation failed"); } void WindingCase::deinit(void) { const glw::Functions& gl = m_context.getRenderContext().getFunctions(); if (m_fbo) { gl.deleteFramebuffers(1, &m_fbo); m_fbo = 0; } if (m_rbo) { gl.deleteRenderbuffers(1, &m_rbo); m_rbo = 0; } m_program.clear(); } /** @brief Bind default framebuffer object. * * @note The function may throw if unexpected error has occured. */ void WindingCase::prepareFramebuffer() { /* Shortcut for GL functionality */ const glw::Functions& gl = m_context.getRenderContext().getFunctions(); gl.genRenderbuffers(1, &m_rbo); GLU_EXPECT_NO_ERROR(gl.getError(), "glGenRenderbuffers call failed."); gl.bindRenderbuffer(GL_RENDERBUFFER, m_rbo); GLU_EXPECT_NO_ERROR(gl.getError(), "glBindRenderbuffer call failed."); gl.renderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, RENDER_SIZE, RENDER_SIZE); GLU_EXPECT_NO_ERROR(gl.getError(), "glRenderbufferStorage call failed."); gl.genFramebuffers(1, &m_fbo); GLU_EXPECT_NO_ERROR(gl.getError(), "glGenFramebuffers call failed."); gl.bindFramebuffer(GL_FRAMEBUFFER, m_fbo); GLU_EXPECT_NO_ERROR(gl.getError(), "glBindFramebuffer call failed."); gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_rbo); GLU_EXPECT_NO_ERROR(gl.getError(), "glFramebufferRenderbuffer call failed."); } WindingCase::IterateResult WindingCase::iterate(void) { const glu::RenderContext& renderCtx = m_context.getRenderContext(); const deUint32 programGL = m_program->getProgram(); const glw::Functions& gl = renderCtx.getFunctions(); const unsigned int windingTaken[2] = { GL_CW, GL_CCW }; const char* windingTakenName[2] = { "GL_CW", "GL_CCW" }; const bool testPrimitiveTypeIsTriangles = (m_specializationMap["PRIMITIVE_TYPE"].compare("triangles") == 0); const bool testWindingIsCW = (m_specializationMap["WINDING"].compare("cw") == 0); bool success = true; prepareFramebuffer(); gl.viewport(0, 0, RENDER_SIZE, RENDER_SIZE); gl.clearColor(1.0f, 0.0f, 0.0f, 1.0f); gl.useProgram(programGL); gl.patchParameteri(GL_PATCH_VERTICES, 1); gl.enable(GL_CULL_FACE); deUint32 vaoGL; gl.genVertexArrays(1, &vaoGL); gl.bindVertexArray(vaoGL); m_testCtx.getLog() << tcu::TestLog::Message << "Face culling enabled" << tcu::TestLog::EndMessage; for (int windingIndex = 0; windingIndex < 2; windingIndex++) { m_testCtx.getLog() << tcu::TestLog::Message << "Setting glFrontFace(" << windingTakenName[windingIndex] << ")" << tcu::TestLog::EndMessage; gl.frontFace(windingTaken[windingIndex]); gl.clear(GL_COLOR_BUFFER_BIT); gl.drawArrays(GL_PATCHES, 0, 1); GLU_EXPECT_NO_ERROR(gl.getError(), "Draw failed"); { tcu::Surface rendered(RENDER_SIZE, RENDER_SIZE); glu::readPixels(renderCtx, 0, 0, rendered.getAccess()); m_testCtx.getLog() << tcu::TestLog::Image("RenderedImage", "Rendered Image", rendered); { const int badPixelTolerance = testPrimitiveTypeIsTriangles ? 5 * de::max(rendered.getWidth(), rendered.getHeight()) : 0; const int totalNumPixels = rendered.getWidth() * rendered.getHeight(); int numWhitePixels = 0; int numRedPixels = 0; for (int y = 0; y < rendered.getHeight(); y++) for (int x = 0; x < rendered.getWidth(); x++) { numWhitePixels += rendered.getPixel(x, y) == tcu::RGBA::white() ? 1 : 0; numRedPixels += rendered.getPixel(x, y) == tcu::RGBA::red() ? 1 : 0; } DE_ASSERT(numWhitePixels + numRedPixels <= totalNumPixels); m_testCtx.getLog() << tcu::TestLog::Message << "Note: got " << numWhitePixels << " white and " << numRedPixels << " red pixels" << tcu::TestLog::EndMessage; if (totalNumPixels - numWhitePixels - numRedPixels > badPixelTolerance) { m_testCtx.getLog() << tcu::TestLog::Message << "Failure: Got " << totalNumPixels - numWhitePixels - numRedPixels << " other than white or red pixels (maximum tolerance " << badPixelTolerance << ")" << tcu::TestLog::EndMessage; success = false; break; } bool frontFaceWindingIsCW = (windingIndex == 0); if (frontFaceWindingIsCW == testWindingIsCW) { if (testPrimitiveTypeIsTriangles) { if (de::abs(numWhitePixels - totalNumPixels / 2) > badPixelTolerance) { m_testCtx.getLog() << tcu::TestLog::Message << "Failure: wrong number of white pixels; expected approximately " << totalNumPixels / 2 << tcu::TestLog::EndMessage; success = false; break; } } else // test primitive type is quads { if (numWhitePixels != totalNumPixels) { m_testCtx.getLog() << tcu::TestLog::Message << "Failure: expected only white pixels (full-viewport quad)" << tcu::TestLog::EndMessage; success = false; break; } } } else { if (numWhitePixels != 0) { m_testCtx.getLog() << tcu::TestLog::Message << "Failure: expected only red pixels (everything culled)" << tcu::TestLog::EndMessage; success = false; break; } } } } } gl.bindVertexArray(0); gl.deleteVertexArrays(1, &vaoGL); m_testCtx.setTestResult(success ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL, success ? "Pass" : "Image verification failed"); return STOP; } /** Constructor * * @param context Test context **/ TesselationShaderWindingTests::TesselationShaderWindingTests(glcts::Context& context, const ExtParameters& extParams) : TestCaseGroupBase(context, extParams, "winding", "Verifies winding order with tessellation shaders") { } /** * Initializes test groups for winding tests **/ void TesselationShaderWindingTests::init(void) { addChild(new WindingCase(m_context, m_extParams, "triangles_ccw", "triangles", "ccw")); addChild(new WindingCase(m_context, m_extParams, "triangles_cw", "triangles", "cw")); addChild(new WindingCase(m_context, m_extParams, "quads_ccw", "quads", "ccw")); addChild(new WindingCase(m_context, m_extParams, "quads_cw", "quads", "cw")); } } /* namespace glcts */