blob: c5b80f850c806b284552ddea5e4bb16eb2125d58 [file] [log] [blame]
/*-------------------------------------------------------------------------
* 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 <string>
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<const glu::ShaderProgram> 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<const glu::ShaderProgram>(
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 */