blob: e318fe5a90b6df852947c0fd404c2d25f9bc28f5 [file] [log] [blame]
/*-------------------------------------------------------------------------
* drawElements Quality Program OpenGL ES 3.1 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 Tessellation and geometry shader interaction tests.
*//*--------------------------------------------------------------------*/
#include "es31fTessellationGeometryInteractionTests.hpp"
#include "tcuTestLog.hpp"
#include "tcuRenderTarget.hpp"
#include "tcuSurface.hpp"
#include "tcuImageCompare.hpp"
#include "tcuVectorUtil.hpp"
#include "tcuTextureUtil.hpp"
#include "tcuStringTemplate.hpp"
#include "gluRenderContext.hpp"
#include "gluShaderProgram.hpp"
#include "gluStrUtil.hpp"
#include "gluContextInfo.hpp"
#include "gluObjectWrapper.hpp"
#include "gluPixelTransfer.hpp"
#include "glwFunctions.hpp"
#include "glwEnums.hpp"
#include "deStringUtil.hpp"
#include "deUniquePtr.hpp"
#include <sstream>
#include <algorithm>
#include <iterator>
namespace deqp
{
namespace gles31
{
namespace Functional
{
namespace
{
static std::string specializeShader(const std::string &shaderSource, const glu::ContextType &contextType)
{
const bool supportsES32orGL45 = glu::contextSupports(contextType, glu::ApiType::es(3, 2)) ||
glu::contextSupports(contextType, glu::ApiType::core(4, 5));
const bool supportsGL45 = glu::contextSupports(contextType, glu::ApiType::core(4, 5));
std::map<std::string, std::string> shaderArgs;
shaderArgs["VERSION_DECL"] = glu::getGLSLVersionDeclaration(glu::getContextTypeGLSLVersion(contextType));
shaderArgs["EXTENSION_GEOMETRY_SHADER"] =
(supportsES32orGL45) ? ("") : ("#extension GL_EXT_geometry_shader : require\n");
shaderArgs["EXTENSION_TESSELATION_SHADER"] =
(supportsES32orGL45) ? ("") : ("#extension GL_EXT_tessellation_shader : require\n");
shaderArgs["EXTENSION_TESSELATION_POINT_SIZE"] =
(supportsGL45) ? ("") : ("#extension GL_EXT_tessellation_point_size : require\n");
shaderArgs["EXTENSION_GEOMETRY_POINT_SIZE"] =
(supportsGL45) ? ("") : ("#extension GL_EXT_geometry_point_size : require\n");
return tcu::StringTemplate(shaderSource).specialize(shaderArgs);
}
static const char *const s_positionVertexShader = "${VERSION_DECL}\n"
"in highp vec4 a_position;\n"
"void main (void)\n"
"{\n"
" gl_Position = a_position;\n"
"}\n";
static const char *const s_whiteOutputFragmentShader = "${VERSION_DECL}\n"
"layout(location = 0) out mediump vec4 fragColor;\n"
"void main (void)\n"
"{\n"
" fragColor = vec4(1.0);\n"
"}\n";
static bool isBlack(const tcu::RGBA &c)
{
return c.getRed() == 0 && c.getGreen() == 0 && c.getBlue() == 0;
}
class IdentityShaderCase : public TestCase
{
public:
IdentityShaderCase(Context &context, const char *name, const char *description);
protected:
std::string getVertexSource(void) const;
std::string getFragmentSource(void) const;
};
IdentityShaderCase::IdentityShaderCase(Context &context, const char *name, const char *description)
: TestCase(context, name, description)
{
}
std::string IdentityShaderCase::getVertexSource(void) const
{
std::string source = "${VERSION_DECL}\n"
"in highp vec4 a_position;\n"
"out highp vec4 v_vertex_color;\n"
"void main (void)\n"
"{\n"
" gl_Position = a_position;\n"
" v_vertex_color = vec4(a_position.x * 0.5 + 0.5, a_position.y * 0.5 + 0.5, 1.0, 0.4);\n"
"}\n";
return specializeShader(source, m_context.getRenderContext().getType());
}
std::string IdentityShaderCase::getFragmentSource(void) const
{
std::string source = "${VERSION_DECL}\n"
"in mediump vec4 v_fragment_color;\n"
"layout(location = 0) out mediump vec4 fragColor;\n"
"void main (void)\n"
"{\n"
" fragColor = v_fragment_color;\n"
"}\n";
return specializeShader(source, m_context.getRenderContext().getType());
}
class IdentityGeometryShaderCase : public IdentityShaderCase
{
public:
enum CaseType
{
CASE_TRIANGLES = 0,
CASE_QUADS,
CASE_ISOLINES,
};
IdentityGeometryShaderCase(Context &context, const char *name, const char *description, CaseType caseType);
~IdentityGeometryShaderCase(void);
private:
void init(void);
void deinit(void);
IterateResult iterate(void);
std::string getTessellationControlSource(void) const;
std::string getTessellationEvaluationSource(bool geometryActive) const;
std::string getGeometrySource(void) const;
enum
{
RENDER_SIZE = 128,
};
const CaseType m_case;
uint32_t m_patchBuffer;
};
IdentityGeometryShaderCase::IdentityGeometryShaderCase(Context &context, const char *name, const char *description,
CaseType caseType)
: IdentityShaderCase(context, name, description)
, m_case(caseType)
, m_patchBuffer(0)
{
}
IdentityGeometryShaderCase::~IdentityGeometryShaderCase(void)
{
deinit();
}
void IdentityGeometryShaderCase::init(void)
{
// Requirements
const bool supportsES32orGL45 =
glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2)) ||
glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::core(4, 5));
if (!supportsES32orGL45 && (!m_context.getContextInfo().isExtensionSupported("GL_EXT_tessellation_shader") ||
!m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader")))
throw tcu::NotSupportedError("Test requires GL_EXT_tessellation_shader and GL_EXT_geometry_shader extensions");
if (m_context.getRenderTarget().getWidth() < RENDER_SIZE || m_context.getRenderTarget().getHeight() < RENDER_SIZE)
throw tcu::NotSupportedError("Test requires " + de::toString<int>(RENDER_SIZE) + "x" +
de::toString<int>(RENDER_SIZE) + " or larger render target.");
// Log
m_testCtx.getLog()
<< tcu::TestLog::Message
<< "Testing tessellating shader program output does not change when a passthrough geometry shader is "
"attached.\n"
<< "Rendering two images, first with and second without a geometry shader. Expecting similar results.\n"
<< "Using additive blending to detect overlap.\n"
<< tcu::TestLog::EndMessage;
// Resources
{
static const tcu::Vec4 patchBufferData[4] = {
tcu::Vec4(-0.9f, -0.9f, 0.0f, 1.0f),
tcu::Vec4(-0.9f, 0.9f, 0.0f, 1.0f),
tcu::Vec4(0.9f, -0.9f, 0.0f, 1.0f),
tcu::Vec4(0.9f, 0.9f, 0.0f, 1.0f),
};
const glw::Functions &gl = m_context.getRenderContext().getFunctions();
gl.genBuffers(1, &m_patchBuffer);
gl.bindBuffer(GL_ARRAY_BUFFER, m_patchBuffer);
gl.bufferData(GL_ARRAY_BUFFER, sizeof(patchBufferData), patchBufferData, GL_STATIC_DRAW);
GLU_EXPECT_NO_ERROR(gl.getError(), "gen buffer");
}
}
void IdentityGeometryShaderCase::deinit(void)
{
if (m_patchBuffer)
{
m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_patchBuffer);
m_patchBuffer = 0;
}
}
IdentityGeometryShaderCase::IterateResult IdentityGeometryShaderCase::iterate(void)
{
const float innerTessellationLevel = 14.0f;
const float outerTessellationLevel = 14.0f;
const glw::Functions &gl = m_context.getRenderContext().getFunctions();
tcu::Surface resultWithGeometry(RENDER_SIZE, RENDER_SIZE);
tcu::Surface resultWithoutGeometry(RENDER_SIZE, RENDER_SIZE);
const struct
{
const char *name;
const char *description;
bool containsGeometryShader;
tcu::PixelBufferAccess surfaceAccess;
} renderTargets[] = {
{"RenderWithGeometryShader", "Render with geometry shader", true, resultWithGeometry.getAccess()},
{"RenderWithoutGeometryShader", "Render without geometry shader", false, resultWithoutGeometry.getAccess()},
};
gl.viewport(0, 0, RENDER_SIZE, RENDER_SIZE);
gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
GLU_EXPECT_NO_ERROR(gl.getError(), "set viewport");
gl.enable(GL_BLEND);
gl.blendFunc(GL_SRC_ALPHA, GL_ONE);
gl.blendEquation(GL_FUNC_ADD);
GLU_EXPECT_NO_ERROR(gl.getError(), "set blend");
m_testCtx.getLog() << tcu::TestLog::Message << "Tessellation level: inner " << innerTessellationLevel << ", outer "
<< outerTessellationLevel << tcu::TestLog::EndMessage;
// render with and without geometry shader
for (int renderNdx = 0; renderNdx < DE_LENGTH_OF_ARRAY(renderTargets); ++renderNdx)
{
const tcu::ScopedLogSection section(m_testCtx.getLog(), renderTargets[renderNdx].name,
renderTargets[renderNdx].description);
glu::ProgramSources sources;
sources << glu::VertexSource(getVertexSource()) << glu::FragmentSource(getFragmentSource())
<< glu::TessellationControlSource(getTessellationControlSource())
<< glu::TessellationEvaluationSource(
getTessellationEvaluationSource(renderTargets[renderNdx].containsGeometryShader));
if (renderTargets[renderNdx].containsGeometryShader)
sources << glu::GeometrySource(getGeometrySource());
{
const glu::ShaderProgram program(m_context.getRenderContext(), sources);
const glu::VertexArray vao(m_context.getRenderContext());
const int posLocation = gl.getAttribLocation(program.getProgram(), "a_position");
const int innerTessellationLoc = gl.getUniformLocation(program.getProgram(), "u_innerTessellationLevel");
const int outerTessellationLoc = gl.getUniformLocation(program.getProgram(), "u_outerTessellationLevel");
m_testCtx.getLog() << program;
if (!program.isOk())
throw tcu::TestError("could not build program");
if (posLocation == -1)
throw tcu::TestError("a_position location was -1");
if (outerTessellationLoc == -1)
throw tcu::TestError("u_outerTessellationLevel location was -1");
gl.bindVertexArray(*vao);
gl.bindBuffer(GL_ARRAY_BUFFER, m_patchBuffer);
gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
gl.enableVertexAttribArray(posLocation);
GLU_EXPECT_NO_ERROR(gl.getError(), "setup attribs");
gl.useProgram(program.getProgram());
gl.uniform1f(outerTessellationLoc, outerTessellationLevel);
if (innerTessellationLoc == -1)
gl.uniform1f(innerTessellationLoc, innerTessellationLevel);
GLU_EXPECT_NO_ERROR(gl.getError(), "use program");
gl.patchParameteri(GL_PATCH_VERTICES, (m_case == CASE_TRIANGLES) ? (3) : (4));
GLU_EXPECT_NO_ERROR(gl.getError(), "set patch param");
gl.clear(GL_COLOR_BUFFER_BIT);
GLU_EXPECT_NO_ERROR(gl.getError(), "clear");
gl.drawArrays(GL_PATCHES, 0, 4);
GLU_EXPECT_NO_ERROR(gl.getError(), "draw patches");
glu::readPixels(m_context.getRenderContext(), 0, 0, renderTargets[renderNdx].surfaceAccess);
}
}
if (tcu::intThresholdPositionDeviationCompare(m_testCtx.getLog(), "ImageCompare", "Image comparison",
resultWithoutGeometry.getAccess(), resultWithGeometry.getAccess(),
tcu::UVec4(8, 8, 8, 255), tcu::IVec3(1, 1, 0), true,
tcu::COMPARE_LOG_RESULT))
m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
else
m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
return STOP;
}
std::string IdentityGeometryShaderCase::getTessellationControlSource(void) const
{
std::ostringstream buf;
buf << "${VERSION_DECL}\n"
"${EXTENSION_TESSELATION_SHADER}"
"layout(vertices = 4) out;\n"
"\n"
"uniform highp float u_innerTessellationLevel;\n"
"uniform highp float u_outerTessellationLevel;\n"
"in highp vec4 v_vertex_color[];\n"
"out highp vec4 v_patch_color[];\n"
"\n"
"void main (void)\n"
"{\n"
" gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n"
" v_patch_color[gl_InvocationID] = v_vertex_color[gl_InvocationID];\n"
"\n";
if (m_case == CASE_TRIANGLES)
buf << " gl_TessLevelOuter[0] = u_outerTessellationLevel;\n"
" gl_TessLevelOuter[1] = u_outerTessellationLevel;\n"
" gl_TessLevelOuter[2] = u_outerTessellationLevel;\n"
" gl_TessLevelInner[0] = u_innerTessellationLevel;\n";
else if (m_case == CASE_QUADS)
buf << " gl_TessLevelOuter[0] = u_outerTessellationLevel;\n"
" gl_TessLevelOuter[1] = u_outerTessellationLevel;\n"
" gl_TessLevelOuter[2] = u_outerTessellationLevel;\n"
" gl_TessLevelOuter[3] = u_outerTessellationLevel;\n"
" gl_TessLevelInner[0] = u_innerTessellationLevel;\n"
" gl_TessLevelInner[1] = u_innerTessellationLevel;\n";
else if (m_case == CASE_ISOLINES)
buf << " gl_TessLevelOuter[0] = u_outerTessellationLevel;\n"
" gl_TessLevelOuter[1] = u_outerTessellationLevel;\n";
else
DE_ASSERT(false);
buf << "}\n";
return specializeShader(buf.str(), m_context.getRenderContext().getType());
}
std::string IdentityGeometryShaderCase::getTessellationEvaluationSource(bool geometryActive) const
{
const char *const colorOutputName = ((geometryActive) ? ("v_evaluated_color") : ("v_fragment_color"));
std::ostringstream buf;
buf << "${VERSION_DECL}\n"
"${EXTENSION_TESSELATION_SHADER}"
"layout("
<< ((m_case == CASE_TRIANGLES) ? ("triangles") :
(m_case == CASE_QUADS) ? ("quads") :
("isolines"))
<< ") in;\n"
"\n"
"in highp vec4 v_patch_color[];\n"
"out highp vec4 "
<< colorOutputName
<< ";\n"
"\n"
"// note: No need to use precise gl_Position since we do not require gapless geometry\n"
"void main (void)\n"
"{\n";
if (m_case == CASE_TRIANGLES)
buf << " vec3 weights = vec3(pow(gl_TessCoord.x, 1.3), pow(gl_TessCoord.y, 1.3), pow(gl_TessCoord.z, "
"1.3));\n"
" vec3 cweights = gl_TessCoord;\n"
" gl_Position = vec4(weights.x * gl_in[0].gl_Position.xyz + weights.y * gl_in[1].gl_Position.xyz + "
"weights.z * gl_in[2].gl_Position.xyz, 1.0);\n"
" "
<< colorOutputName
<< " = cweights.x * v_patch_color[0] + cweights.y * v_patch_color[1] + cweights.z * v_patch_color[2];\n";
else if (m_case == CASE_QUADS || m_case == CASE_ISOLINES)
buf << " vec2 normalizedCoord = (gl_TessCoord.xy * 2.0 - vec2(1.0));\n"
" vec2 normalizedWeights = normalizedCoord * (vec2(1.0) - 0.3 * cos(normalizedCoord.yx * 1.57));\n"
" vec2 weights = normalizedWeights * 0.5 + vec2(0.5);\n"
" vec2 cweights = gl_TessCoord.xy;\n"
" gl_Position = mix(mix(gl_in[0].gl_Position, gl_in[1].gl_Position, weights.y), "
"mix(gl_in[2].gl_Position, gl_in[3].gl_Position, weights.y), weights.x);\n"
" "
<< colorOutputName
<< " = mix(mix(v_patch_color[0], v_patch_color[1], cweights.y), mix(v_patch_color[2], v_patch_color[3], "
"cweights.y), cweights.x);\n";
else
DE_ASSERT(false);
buf << "}\n";
return specializeShader(buf.str(), m_context.getRenderContext().getType());
}
std::string IdentityGeometryShaderCase::getGeometrySource(void) const
{
const char *const geometryInputPrimitive = (m_case == CASE_ISOLINES) ? ("lines") : ("triangles");
const char *const geometryOutputPrimitive = (m_case == CASE_ISOLINES) ? ("line_strip") : ("triangle_strip");
const int numEmitVertices = (m_case == CASE_ISOLINES) ? (2) : (3);
std::ostringstream buf;
buf << "${VERSION_DECL}\n"
"${EXTENSION_GEOMETRY_SHADER}"
"layout("
<< geometryInputPrimitive
<< ") in;\n"
"layout("
<< geometryOutputPrimitive << ", max_vertices=" << numEmitVertices
<< ") out;\n"
"\n"
"in highp vec4 v_evaluated_color[];\n"
"out highp vec4 v_fragment_color;\n"
"\n"
"void main (void)\n"
"{\n"
" for (int ndx = 0; ndx < gl_in.length(); ++ndx)\n"
" {\n"
" gl_Position = gl_in[ndx].gl_Position;\n"
" v_fragment_color = v_evaluated_color[ndx];\n"
" EmitVertex();\n"
" }\n"
"}\n";
return specializeShader(buf.str(), m_context.getRenderContext().getType());
}
class IdentityTessellationShaderCase : public IdentityShaderCase
{
public:
enum CaseType
{
CASE_TRIANGLES = 0,
CASE_ISOLINES,
};
IdentityTessellationShaderCase(Context &context, const char *name, const char *description, CaseType caseType);
~IdentityTessellationShaderCase(void);
private:
void init(void);
void deinit(void);
IterateResult iterate(void);
std::string getTessellationControlSource(void) const;
std::string getTessellationEvaluationSource(void) const;
std::string getGeometrySource(bool tessellationActive) const;
enum
{
RENDER_SIZE = 256,
};
const CaseType m_case;
uint32_t m_dataBuffer;
};
IdentityTessellationShaderCase::IdentityTessellationShaderCase(Context &context, const char *name,
const char *description, CaseType caseType)
: IdentityShaderCase(context, name, description)
, m_case(caseType)
, m_dataBuffer(0)
{
}
IdentityTessellationShaderCase::~IdentityTessellationShaderCase(void)
{
deinit();
}
void IdentityTessellationShaderCase::init(void)
{
// Requirements
const bool supportsES32orGL45 =
glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2)) ||
glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::core(4, 5));
if (!supportsES32orGL45 && (!m_context.getContextInfo().isExtensionSupported("GL_EXT_tessellation_shader") ||
!m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader")))
throw tcu::NotSupportedError("Test requires GL_EXT_tessellation_shader and GL_EXT_geometry_shader extensions");
if (m_context.getRenderTarget().getWidth() < RENDER_SIZE || m_context.getRenderTarget().getHeight() < RENDER_SIZE)
throw tcu::NotSupportedError("Test requires " + de::toString<int>(RENDER_SIZE) + "x" +
de::toString<int>(RENDER_SIZE) + " or larger render target.");
// Log
m_testCtx.getLog()
<< tcu::TestLog::Message
<< "Testing geometry shading shader program output does not change when a passthrough tessellation shader is "
"attached.\n"
<< "Rendering two images, first with and second without a tessellation shader. Expecting similar results.\n"
<< "Using additive blending to detect overlap.\n"
<< tcu::TestLog::EndMessage;
// Resources
{
static const tcu::Vec4 pointData[] = {
tcu::Vec4(-0.4f, 0.4f, 0.0f, 1.0f),
tcu::Vec4(0.0f, -0.5f, 0.0f, 1.0f),
tcu::Vec4(0.4f, 0.4f, 0.0f, 1.0f),
};
const glw::Functions &gl = m_context.getRenderContext().getFunctions();
gl.genBuffers(1, &m_dataBuffer);
gl.bindBuffer(GL_ARRAY_BUFFER, m_dataBuffer);
gl.bufferData(GL_ARRAY_BUFFER, sizeof(pointData), pointData, GL_STATIC_DRAW);
GLU_EXPECT_NO_ERROR(gl.getError(), "gen buffer");
}
}
void IdentityTessellationShaderCase::deinit(void)
{
if (m_dataBuffer)
{
m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_dataBuffer);
m_dataBuffer = 0;
}
}
IdentityTessellationShaderCase::IterateResult IdentityTessellationShaderCase::iterate(void)
{
const glw::Functions &gl = m_context.getRenderContext().getFunctions();
tcu::Surface resultWithTessellation(RENDER_SIZE, RENDER_SIZE);
tcu::Surface resultWithoutTessellation(RENDER_SIZE, RENDER_SIZE);
const int numPrimitiveVertices = (m_case == CASE_TRIANGLES) ? (3) : (2);
const struct
{
const char *name;
const char *description;
bool containsTessellationShaders;
tcu::PixelBufferAccess surfaceAccess;
} renderTargets[] = {
{"RenderWithTessellationShader", "Render with tessellation shader", true, resultWithTessellation.getAccess()},
{"RenderWithoutTessellationShader", "Render without tessellation shader", false,
resultWithoutTessellation.getAccess()},
};
gl.viewport(0, 0, RENDER_SIZE, RENDER_SIZE);
gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
GLU_EXPECT_NO_ERROR(gl.getError(), "set viewport");
gl.enable(GL_BLEND);
gl.blendFunc(GL_SRC_ALPHA, GL_ONE);
gl.blendEquation(GL_FUNC_ADD);
GLU_EXPECT_NO_ERROR(gl.getError(), "set blend");
// render with and without tessellation shader
for (int renderNdx = 0; renderNdx < DE_LENGTH_OF_ARRAY(renderTargets); ++renderNdx)
{
const tcu::ScopedLogSection section(m_testCtx.getLog(), renderTargets[renderNdx].name,
renderTargets[renderNdx].description);
glu::ProgramSources sources;
sources << glu::VertexSource(getVertexSource()) << glu::FragmentSource(getFragmentSource())
<< glu::GeometrySource(getGeometrySource(renderTargets[renderNdx].containsTessellationShaders));
if (renderTargets[renderNdx].containsTessellationShaders)
sources << glu::TessellationControlSource(getTessellationControlSource())
<< glu::TessellationEvaluationSource(getTessellationEvaluationSource());
{
const glu::ShaderProgram program(m_context.getRenderContext(), sources);
const glu::VertexArray vao(m_context.getRenderContext());
const int posLocation = gl.getAttribLocation(program.getProgram(), "a_position");
m_testCtx.getLog() << program;
if (!program.isOk())
throw tcu::TestError("could not build program");
if (posLocation == -1)
throw tcu::TestError("a_position location was -1");
gl.bindVertexArray(*vao);
gl.bindBuffer(GL_ARRAY_BUFFER, m_dataBuffer);
gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
gl.enableVertexAttribArray(posLocation);
GLU_EXPECT_NO_ERROR(gl.getError(), "setup attribs");
gl.useProgram(program.getProgram());
GLU_EXPECT_NO_ERROR(gl.getError(), "use program");
gl.clear(GL_COLOR_BUFFER_BIT);
GLU_EXPECT_NO_ERROR(gl.getError(), "clear");
if (renderTargets[renderNdx].containsTessellationShaders)
{
gl.patchParameteri(GL_PATCH_VERTICES, numPrimitiveVertices);
GLU_EXPECT_NO_ERROR(gl.getError(), "set patch param");
gl.drawArrays(GL_PATCHES, 0, numPrimitiveVertices);
GLU_EXPECT_NO_ERROR(gl.getError(), "draw patches");
}
else
{
gl.drawArrays((m_case == CASE_TRIANGLES) ? (GL_TRIANGLES) : (GL_LINES), 0, numPrimitiveVertices);
GLU_EXPECT_NO_ERROR(gl.getError(), "draw primitives");
}
glu::readPixels(m_context.getRenderContext(), 0, 0, renderTargets[renderNdx].surfaceAccess);
}
}
// compare
{
bool imageOk;
if (m_context.getRenderTarget().getNumSamples() > 1)
imageOk = tcu::fuzzyCompare(m_testCtx.getLog(), "ImageCompare", "Image comparison",
resultWithoutTessellation.getAccess(), resultWithTessellation.getAccess(),
0.03f, tcu::COMPARE_LOG_RESULT);
else
imageOk = tcu::intThresholdPositionDeviationCompare(
m_testCtx.getLog(), "ImageCompare", "Image comparison", resultWithoutTessellation.getAccess(),
resultWithTessellation.getAccess(), tcu::UVec4(8, 8, 8, 255), //!< threshold
tcu::IVec3(1, 1, 0), //!< 3x3 search kernel
true, //!< fragments may end up over the viewport, just ignore them
tcu::COMPARE_LOG_RESULT);
if (imageOk)
m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
else
m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
}
return STOP;
}
std::string IdentityTessellationShaderCase::getTessellationControlSource(void) const
{
std::ostringstream buf;
buf << "${VERSION_DECL}\n"
"${EXTENSION_TESSELATION_SHADER}"
"layout(vertices = "
<< ((m_case == CASE_TRIANGLES) ? (3) : (2))
<< ") out;\n"
"\n"
"in highp vec4 v_vertex_color[];\n"
"out highp vec4 v_control_color[];\n"
"\n"
"void main (void)\n"
"{\n"
" gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n"
" v_control_color[gl_InvocationID] = v_vertex_color[gl_InvocationID];\n"
"\n";
if (m_case == CASE_TRIANGLES)
buf << " gl_TessLevelOuter[0] = 1.0;\n"
" gl_TessLevelOuter[1] = 1.0;\n"
" gl_TessLevelOuter[2] = 1.0;\n"
" gl_TessLevelInner[0] = 1.0;\n";
else if (m_case == CASE_ISOLINES)
buf << " gl_TessLevelOuter[0] = 1.0;\n"
" gl_TessLevelOuter[1] = 1.0;\n";
else
DE_ASSERT(false);
buf << "}\n";
return specializeShader(buf.str(), m_context.getRenderContext().getType());
}
std::string IdentityTessellationShaderCase::getTessellationEvaluationSource(void) const
{
std::ostringstream buf;
buf << "${VERSION_DECL}\n"
"${EXTENSION_TESSELATION_SHADER}"
"layout("
<< ((m_case == CASE_TRIANGLES) ? ("triangles") : ("isolines"))
<< ") in;\n"
"\n"
"in highp vec4 v_control_color[];\n"
"out highp vec4 v_evaluated_color;\n"
"\n"
"// note: No need to use precise gl_Position since we do not require gapless geometry\n"
"void main (void)\n"
"{\n";
if (m_case == CASE_TRIANGLES)
buf << " gl_Position = gl_TessCoord.x * gl_in[0].gl_Position + gl_TessCoord.y * gl_in[1].gl_Position + "
"gl_TessCoord.z * gl_in[2].gl_Position;\n"
" v_evaluated_color = gl_TessCoord.x * v_control_color[0] + gl_TessCoord.y * v_control_color[1] + "
"gl_TessCoord.z * v_control_color[2];\n";
else if (m_case == CASE_ISOLINES)
buf << " gl_Position = mix(gl_in[0].gl_Position, gl_in[1].gl_Position, gl_TessCoord.x);\n"
" v_evaluated_color = mix(v_control_color[0], v_control_color[1], gl_TessCoord.x);\n";
else
DE_ASSERT(false);
buf << "}\n";
return specializeShader(buf.str(), m_context.getRenderContext().getType());
}
std::string IdentityTessellationShaderCase::getGeometrySource(bool tessellationActive) const
{
const char *const colorSourceName = (tessellationActive) ? ("v_evaluated_color") : ("v_vertex_color");
const char *const geometryInputPrimitive = (m_case == CASE_ISOLINES) ? ("lines") : ("triangles");
const char *const geometryOutputPrimitive = (m_case == CASE_ISOLINES) ? ("line_strip") : ("triangle_strip");
const int numEmitVertices = (m_case == CASE_ISOLINES) ? (11) : (8);
std::ostringstream buf;
buf << "${VERSION_DECL}\n"
"${EXTENSION_GEOMETRY_SHADER}"
"layout("
<< geometryInputPrimitive
<< ") in;\n"
"layout("
<< geometryOutputPrimitive << ", max_vertices=" << numEmitVertices
<< ") out;\n"
"\n"
"in highp vec4 "
<< colorSourceName
<< "[];\n"
"out highp vec4 v_fragment_color;\n"
"\n"
"void main (void)\n"
"{\n";
if (m_case == CASE_TRIANGLES)
{
buf << " vec4 centerPos = (gl_in[0].gl_Position + gl_in[1].gl_Position + gl_in[2].gl_Position) / 3.0f;\n"
"\n"
" for (int ndx = 0; ndx < 4; ++ndx)\n"
" {\n"
" gl_Position = centerPos + (centerPos - gl_in[ndx % 3].gl_Position);\n"
" v_fragment_color = "
<< colorSourceName
<< "[ndx % 3];\n"
" EmitVertex();\n"
"\n"
" gl_Position = centerPos + 0.7 * (centerPos - gl_in[ndx % 3].gl_Position);\n"
" v_fragment_color = "
<< colorSourceName
<< "[ndx % 3];\n"
" EmitVertex();\n"
" }\n";
}
else if (m_case == CASE_ISOLINES)
{
buf << " vec4 mdir = vec4(gl_in[0].gl_Position.y - gl_in[1].gl_Position.y, gl_in[1].gl_Position.x - "
"gl_in[0].gl_Position.x, 0.0, 0.0);\n"
" for (int i = 0; i <= 10; ++i)\n"
" {\n"
" float xweight = cos(float(i) / 10.0 * 6.28) * 0.5 + 0.5;\n"
" float mweight = sin(float(i) / 10.0 * 6.28) * 0.1 + 0.1;\n"
" gl_Position = mix(gl_in[0].gl_Position, gl_in[1].gl_Position, xweight) + mweight * mdir;\n"
" v_fragment_color = mix("
<< colorSourceName << "[0], " << colorSourceName
<< "[1], xweight);\n"
" EmitVertex();\n"
" }\n";
}
else
DE_ASSERT(false);
buf << "}\n";
return specializeShader(buf.str(), m_context.getRenderContext().getType());
}
class FeedbackPrimitiveTypeCase : public TestCase
{
public:
enum TessellationOutputType
{
TESSELLATION_OUT_TRIANGLES = 0,
TESSELLATION_OUT_QUADS,
TESSELLATION_OUT_ISOLINES,
TESSELLATION_OUT_LAST
};
enum TessellationPointMode
{
TESSELLATION_POINTMODE_OFF = 0,
TESSELLATION_POINTMODE_ON,
TESSELLATION_POINTMODE_LAST
};
enum GeometryOutputType
{
GEOMETRY_OUTPUT_POINTS = 0,
GEOMETRY_OUTPUT_LINES,
GEOMETRY_OUTPUT_TRIANGLES,
GEOMETRY_OUTPUT_LAST
};
FeedbackPrimitiveTypeCase(Context &context, const char *name, const char *description,
TessellationOutputType tessellationOutput, TessellationPointMode tessellationPointMode,
GeometryOutputType geometryOutputType);
~FeedbackPrimitiveTypeCase(void);
private:
void init(void);
void deinit(void);
IterateResult iterate(void);
void renderWithFeedback(tcu::Surface &dst);
void renderWithoutFeedback(tcu::Surface &dst);
void verifyFeedbackResults(const std::vector<tcu::Vec4> &feedbackResult);
void verifyRenderedImage(const tcu::Surface &image, const std::vector<tcu::Vec4> &vertices);
void genTransformFeedback(void);
int getNumGeneratedElementsPerPrimitive(void) const;
int getNumGeneratedPrimitives(void) const;
int getNumTessellatedPrimitives(void) const;
int getGeometryAmplification(void) const;
std::string getVertexSource(void) const;
std::string getFragmentSource(void) const;
std::string getTessellationControlSource(void) const;
std::string getTessellationEvaluationSource(void) const;
std::string getGeometrySource(void) const;
static const char *getTessellationOutputDescription(TessellationOutputType tessellationOutput,
TessellationPointMode tessellationPointMode);
static const char *getGeometryInputDescription(TessellationOutputType tessellationOutput,
TessellationPointMode tessellationPointMode);
static const char *getGeometryOutputDescription(GeometryOutputType geometryOutput);
glw::GLenum getOutputPrimitiveGLType(void) const;
enum
{
RENDER_SIZE = 128,
};
const TessellationOutputType m_tessellationOutput;
const TessellationPointMode m_tessellationPointMode;
const GeometryOutputType m_geometryOutputType;
glu::ShaderProgram *m_feedbackProgram;
glu::ShaderProgram *m_nonFeedbackProgram;
uint32_t m_patchBuffer;
uint32_t m_feedbackID;
uint32_t m_feedbackBuffer;
};
FeedbackPrimitiveTypeCase::FeedbackPrimitiveTypeCase(Context &context, const char *name, const char *description,
TessellationOutputType tessellationOutput,
TessellationPointMode tessellationPointMode,
GeometryOutputType geometryOutputType)
: TestCase(context, name, description)
, m_tessellationOutput(tessellationOutput)
, m_tessellationPointMode(tessellationPointMode)
, m_geometryOutputType(geometryOutputType)
, m_feedbackProgram(DE_NULL)
, m_nonFeedbackProgram(DE_NULL)
, m_patchBuffer(0)
, m_feedbackID(0)
, m_feedbackBuffer(0)
{
DE_ASSERT(tessellationOutput < TESSELLATION_OUT_LAST);
DE_ASSERT(tessellationPointMode < TESSELLATION_POINTMODE_LAST);
DE_ASSERT(geometryOutputType < GEOMETRY_OUTPUT_LAST);
}
FeedbackPrimitiveTypeCase::~FeedbackPrimitiveTypeCase(void)
{
deinit();
}
void FeedbackPrimitiveTypeCase::init(void)
{
const glw::Functions &gl = m_context.getRenderContext().getFunctions();
// Requirements
const bool supportsES32orGL45 =
glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2)) ||
glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::core(4, 5));
if (!supportsES32orGL45 && (!m_context.getContextInfo().isExtensionSupported("GL_EXT_tessellation_shader") ||
!m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader")))
throw tcu::NotSupportedError("Test requires GL_EXT_tessellation_shader and GL_EXT_geometry_shader extensions");
if (m_context.getRenderTarget().getWidth() < RENDER_SIZE || m_context.getRenderTarget().getHeight() < RENDER_SIZE)
throw tcu::NotSupportedError("Test requires " + de::toString<int>(RENDER_SIZE) + "x" +
de::toString<int>(RENDER_SIZE) + " or larger render target.");
// Log
m_testCtx.getLog() << tcu::TestLog::Message << "Testing "
<< getTessellationOutputDescription(m_tessellationOutput, m_tessellationPointMode) << "->"
<< getGeometryInputDescription(m_tessellationOutput, m_tessellationPointMode)
<< " primitive conversion with and without transform feedback.\n"
<< "Sending a patch of 4 vertices (2x2 uniform grid) to tessellation control shader.\n"
<< "Control shader emits a patch of 9 vertices (3x3 uniform grid).\n"
<< "Setting outer tessellation level = 3, inner = 3.\n"
<< "Primitive generator emits "
<< getTessellationOutputDescription(m_tessellationOutput, m_tessellationPointMode) << "\n"
<< "Geometry shader transforms emitted primitives to "
<< getGeometryOutputDescription(m_geometryOutputType) << "\n"
<< "Reading back vertex positions of generated primitives using transform feedback.\n"
<< "Verifying rendered image and feedback vertices are consistent.\n"
<< "Rendering scene again with identical shader program, but without setting feedback varying. "
"Expecting similar output image."
<< tcu::TestLog::EndMessage;
// Resources
{
static const tcu::Vec4 patchBufferData[4] = {
tcu::Vec4(-0.9f, -0.9f, 0.0f, 1.0f),
tcu::Vec4(-0.9f, 0.9f, 0.0f, 1.0f),
tcu::Vec4(0.9f, -0.9f, 0.0f, 1.0f),
tcu::Vec4(0.9f, 0.9f, 0.0f, 1.0f),
};
gl.genBuffers(1, &m_patchBuffer);
gl.bindBuffer(GL_ARRAY_BUFFER, m_patchBuffer);
gl.bufferData(GL_ARRAY_BUFFER, sizeof(patchBufferData), patchBufferData, GL_STATIC_DRAW);
GLU_EXPECT_NO_ERROR(gl.getError(), "gen buffer");
}
m_feedbackProgram = new glu::ShaderProgram(
m_context.getRenderContext(),
glu::ProgramSources() << glu::VertexSource(getVertexSource()) << glu::FragmentSource(getFragmentSource())
<< glu::TessellationControlSource(getTessellationControlSource())
<< glu::TessellationEvaluationSource(getTessellationEvaluationSource())
<< glu::GeometrySource(getGeometrySource())
<< glu::TransformFeedbackVarying("tf_someVertexPosition")
<< glu::TransformFeedbackMode(GL_INTERLEAVED_ATTRIBS));
m_testCtx.getLog() << *m_feedbackProgram;
if (!m_feedbackProgram->isOk())
throw tcu::TestError("failed to build program");
m_nonFeedbackProgram = new glu::ShaderProgram(
m_context.getRenderContext(),
glu::ProgramSources() << glu::VertexSource(getVertexSource()) << glu::FragmentSource(getFragmentSource())
<< glu::TessellationControlSource(getTessellationControlSource())
<< glu::TessellationEvaluationSource(getTessellationEvaluationSource())
<< glu::GeometrySource(getGeometrySource()));
if (!m_nonFeedbackProgram->isOk())
{
m_testCtx.getLog() << *m_nonFeedbackProgram;
throw tcu::TestError("failed to build program");
}
genTransformFeedback();
}
void FeedbackPrimitiveTypeCase::deinit(void)
{
if (m_patchBuffer)
{
m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_patchBuffer);
m_patchBuffer = 0;
}
if (m_feedbackBuffer)
{
m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_feedbackBuffer);
m_feedbackBuffer = 0;
}
if (m_feedbackID)
{
m_context.getRenderContext().getFunctions().deleteTransformFeedbacks(1, &m_feedbackID);
m_feedbackID = 0;
}
if (m_feedbackProgram)
{
delete m_feedbackProgram;
m_feedbackProgram = DE_NULL;
}
if (m_nonFeedbackProgram)
{
delete m_nonFeedbackProgram;
m_nonFeedbackProgram = DE_NULL;
}
}
FeedbackPrimitiveTypeCase::IterateResult FeedbackPrimitiveTypeCase::iterate(void)
{
tcu::Surface feedbackResult(RENDER_SIZE, RENDER_SIZE);
tcu::Surface nonFeedbackResult(RENDER_SIZE, RENDER_SIZE);
m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
// render with and without XFB
renderWithFeedback(feedbackResult);
renderWithoutFeedback(nonFeedbackResult);
// compare
{
bool imageOk;
m_testCtx.getLog() << tcu::TestLog::Message
<< "Comparing the image rendered with no transform feedback against the image rendered with "
"enabled transform feedback."
<< tcu::TestLog::EndMessage;
if (m_context.getRenderTarget().getNumSamples() > 1)
imageOk =
tcu::fuzzyCompare(m_testCtx.getLog(), "ImageCompare", "Image comparison", feedbackResult.getAccess(),
nonFeedbackResult.getAccess(), 0.03f, tcu::COMPARE_LOG_RESULT);
else
imageOk = tcu::intThresholdPositionDeviationCompare(
m_testCtx.getLog(), "ImageCompare", "Image comparison", feedbackResult.getAccess(),
nonFeedbackResult.getAccess(), tcu::UVec4(8, 8, 8, 255), //!< threshold
tcu::IVec3(1, 1, 0), //!< 3x3 search kernel
true, //!< fragments may end up over the viewport, just ignore them
tcu::COMPARE_LOG_RESULT);
if (!imageOk)
m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
}
return STOP;
}
void FeedbackPrimitiveTypeCase::renderWithFeedback(tcu::Surface &dst)
{
const glw::Functions &gl = m_context.getRenderContext().getFunctions();
const glu::VertexArray vao(m_context.getRenderContext());
const glu::Query primitivesGeneratedQuery(m_context.getRenderContext());
const int posLocation = gl.getAttribLocation(m_feedbackProgram->getProgram(), "a_position");
const glw::GLenum feedbackPrimitiveMode = getOutputPrimitiveGLType();
if (posLocation == -1)
throw tcu::TestError("a_position was -1");
m_testCtx.getLog() << tcu::TestLog::Message << "Rendering with transform feedback" << tcu::TestLog::EndMessage;
gl.viewport(0, 0, dst.getWidth(), dst.getHeight());
gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
gl.clear(GL_COLOR_BUFFER_BIT);
GLU_EXPECT_NO_ERROR(gl.getError(), "clear");
gl.bindVertexArray(*vao);
gl.bindBuffer(GL_ARRAY_BUFFER, m_patchBuffer);
gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
gl.enableVertexAttribArray(posLocation);
GLU_EXPECT_NO_ERROR(gl.getError(), "setup attribs");
gl.useProgram(m_feedbackProgram->getProgram());
GLU_EXPECT_NO_ERROR(gl.getError(), "use program");
gl.patchParameteri(GL_PATCH_VERTICES, 4);
GLU_EXPECT_NO_ERROR(gl.getError(), "set patch param");
gl.beginQuery(GL_PRIMITIVES_GENERATED, *primitivesGeneratedQuery);
GLU_EXPECT_NO_ERROR(gl.getError(), "begin GL_PRIMITIVES_GENERATED query");
m_testCtx.getLog() << tcu::TestLog::Message << "Begin transform feedback with mode "
<< glu::getPrimitiveTypeStr(feedbackPrimitiveMode) << tcu::TestLog::EndMessage;
gl.beginTransformFeedback(feedbackPrimitiveMode);
GLU_EXPECT_NO_ERROR(gl.getError(), "begin xfb");
m_testCtx.getLog() << tcu::TestLog::Message << "Calling drawArrays with mode GL_PATCHES"
<< tcu::TestLog::EndMessage;
gl.drawArrays(GL_PATCHES, 0, 4);
GLU_EXPECT_NO_ERROR(gl.getError(), "draw patches");
gl.endTransformFeedback();
GLU_EXPECT_NO_ERROR(gl.getError(), "end xfb");
gl.endQuery(GL_PRIMITIVES_GENERATED);
GLU_EXPECT_NO_ERROR(gl.getError(), "end GL_PRIMITIVES_GENERATED query");
glu::readPixels(m_context.getRenderContext(), 0, 0, dst.getAccess());
GLU_EXPECT_NO_ERROR(gl.getError(), "readPixels");
// verify GL_PRIMITIVES_GENERATED
{
glw::GLuint primitivesGeneratedResult = 0;
gl.getQueryObjectuiv(*primitivesGeneratedQuery, GL_QUERY_RESULT, &primitivesGeneratedResult);
GLU_EXPECT_NO_ERROR(gl.getError(), "get GL_PRIMITIVES_GENERATED value");
m_testCtx.getLog() << tcu::TestLog::Message << "Verifying GL_PRIMITIVES_GENERATED, expecting "
<< getNumGeneratedPrimitives() << tcu::TestLog::EndMessage;
if ((int)primitivesGeneratedResult != getNumGeneratedPrimitives())
{
m_testCtx.getLog() << tcu::TestLog::Message << "Error, GL_PRIMITIVES_GENERATED was "
<< primitivesGeneratedResult << tcu::TestLog::EndMessage;
m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got unexpected GL_PRIMITIVES_GENERATED");
}
else
m_testCtx.getLog() << tcu::TestLog::Message << "GL_PRIMITIVES_GENERATED valid." << tcu::TestLog::EndMessage;
}
// feedback
{
std::vector<tcu::Vec4> feedbackResults(getNumGeneratedElementsPerPrimitive() * getNumGeneratedPrimitives());
const void *mappedPtr =
gl.mapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0,
(glw::GLsizeiptr)(feedbackResults.size() * sizeof(tcu::Vec4)), GL_MAP_READ_BIT);
glw::GLboolean unmapResult;
GLU_EXPECT_NO_ERROR(gl.getError(), "mapBufferRange");
m_testCtx.getLog() << tcu::TestLog::Message << "Reading transform feedback buffer." << tcu::TestLog::EndMessage;
if (!mappedPtr)
throw tcu::TestError("mapBufferRange returned null");
deMemcpy(feedbackResults[0].getPtr(), mappedPtr, (int)(feedbackResults.size() * sizeof(tcu::Vec4)));
unmapResult = gl.unmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER);
GLU_EXPECT_NO_ERROR(gl.getError(), "unmapBuffer");
if (unmapResult != GL_TRUE)
throw tcu::TestError("unmapBuffer failed, did not return true");
// verify transform results
verifyFeedbackResults(feedbackResults);
// verify feedback results are consistent with rendered image
verifyRenderedImage(dst, feedbackResults);
}
}
void FeedbackPrimitiveTypeCase::renderWithoutFeedback(tcu::Surface &dst)
{
const glw::Functions &gl = m_context.getRenderContext().getFunctions();
const glu::VertexArray vao(m_context.getRenderContext());
const int posLocation = gl.getAttribLocation(m_nonFeedbackProgram->getProgram(), "a_position");
if (posLocation == -1)
throw tcu::TestError("a_position was -1");
m_testCtx.getLog() << tcu::TestLog::Message << "Rendering without transform feedback" << tcu::TestLog::EndMessage;
gl.viewport(0, 0, dst.getWidth(), dst.getHeight());
gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
gl.clear(GL_COLOR_BUFFER_BIT);
GLU_EXPECT_NO_ERROR(gl.getError(), "clear");
gl.bindVertexArray(*vao);
gl.bindBuffer(GL_ARRAY_BUFFER, m_patchBuffer);
gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
gl.enableVertexAttribArray(posLocation);
GLU_EXPECT_NO_ERROR(gl.getError(), "setup attribs");
gl.useProgram(m_nonFeedbackProgram->getProgram());
GLU_EXPECT_NO_ERROR(gl.getError(), "use program");
gl.patchParameteri(GL_PATCH_VERTICES, 4);
GLU_EXPECT_NO_ERROR(gl.getError(), "set patch param");
m_testCtx.getLog() << tcu::TestLog::Message << "Calling drawArrays with mode GL_PATCHES"
<< tcu::TestLog::EndMessage;
gl.drawArrays(GL_PATCHES, 0, 4);
GLU_EXPECT_NO_ERROR(gl.getError(), "draw patches");
glu::readPixels(m_context.getRenderContext(), 0, 0, dst.getAccess());
GLU_EXPECT_NO_ERROR(gl.getError(), "readPixels");
}
void FeedbackPrimitiveTypeCase::verifyFeedbackResults(const std::vector<tcu::Vec4> &feedbackResult)
{
const int geometryAmplification = getGeometryAmplification();
const int elementsPerPrimitive = getNumGeneratedElementsPerPrimitive();
const int errorFloodThreshold = 8;
int readNdx = 0;
int numErrors = 0;
m_testCtx.getLog() << tcu::TestLog::Message << "Verifying feedback results." << tcu::TestLog::EndMessage;
for (int tessellatedPrimitiveNdx = 0; tessellatedPrimitiveNdx < getNumTessellatedPrimitives();
++tessellatedPrimitiveNdx)
{
const tcu::Vec4 primitiveVertex = feedbackResult[readNdx];
// check the generated vertices are in the proper range (range: -0.4 <-> 0.4)
{
const float equalThreshold = 1.0e-6f;
const bool centroidOk =
(primitiveVertex.x() >= -0.4f - equalThreshold) && (primitiveVertex.x() <= 0.4f + equalThreshold) &&
(primitiveVertex.y() >= -0.4f - equalThreshold) && (primitiveVertex.y() <= 0.4f + equalThreshold) &&
(de::abs(primitiveVertex.z()) < equalThreshold) &&
(de::abs(primitiveVertex.w() - 1.0f) < equalThreshold);
if (!centroidOk && numErrors++ < errorFloodThreshold)
{
m_testCtx.getLog() << tcu::TestLog::Message << "Element at index " << (readNdx)
<< " (tessellation invocation " << tessellatedPrimitiveNdx << ")\n"
<< "\texpected vertex in range: ( [-0.4, 0.4], [-0.4, 0.4], 0.0, 1.0 )\n"
<< "\tgot: " << primitiveVertex << tcu::TestLog::EndMessage;
m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "invalid feedback output");
++readNdx;
continue;
}
}
// check all other primitives generated from this tessellated primitive have the same feedback value
for (int generatedPrimitiveNdx = 0; generatedPrimitiveNdx < geometryAmplification; ++generatedPrimitiveNdx)
for (int primitiveVertexNdx = 0; primitiveVertexNdx < elementsPerPrimitive; ++primitiveVertexNdx)
{
const tcu::Vec4 generatedElementVertex = feedbackResult[readNdx];
const tcu::Vec4 equalThreshold(1.0e-6f);
if (tcu::boolAny(tcu::greaterThan(tcu::abs(primitiveVertex - generatedElementVertex), equalThreshold)))
{
if (numErrors++ < errorFloodThreshold)
{
m_testCtx.getLog()
<< tcu::TestLog::Message << "Element at index " << (readNdx) << " (tessellation invocation "
<< tessellatedPrimitiveNdx << ", geometry primitive " << generatedPrimitiveNdx
<< ", emitted vertex " << primitiveVertexNdx << "):\n"
<< "\tfeedback result was not contant over whole primitive.\n"
<< "\tfirst emitted value: " << primitiveVertex << "\n"
<< "\tcurrent emitted value:" << generatedElementVertex << "\n"
<< tcu::TestLog::EndMessage;
}
m_testCtx.setTestResult(QP_TEST_RESULT_FAIL,
"got multiple different feedback values for a single primitive");
}
readNdx++;
}
}
if (numErrors > errorFloodThreshold)
m_testCtx.getLog() << tcu::TestLog::Message << "Omitted " << (numErrors - errorFloodThreshold) << " error(s)."
<< tcu::TestLog::EndMessage;
}
static bool feedbackResultCompare(const tcu::Vec4 &a, const tcu::Vec4 &b)
{
if (a.x() < b.x())
return true;
if (a.x() > b.x())
return false;
return a.y() < b.y();
}
void FeedbackPrimitiveTypeCase::verifyRenderedImage(const tcu::Surface &image, const std::vector<tcu::Vec4> &tfVertices)
{
std::vector<tcu::Vec4> vertices;
m_testCtx.getLog() << tcu::TestLog::Message << "Comparing result image against feedback results."
<< tcu::TestLog::EndMessage;
// Check only unique vertices
std::unique_copy(tfVertices.begin(), tfVertices.end(), std::back_insert_iterator<std::vector<tcu::Vec4>>(vertices));
std::sort(vertices.begin(), vertices.end(), feedbackResultCompare);
vertices.erase(std::unique(vertices.begin(), vertices.end()), vertices.end());
// Verifying vertices recorded with feedback actually ended up on the result image
for (int ndx = 0; ndx < (int)vertices.size(); ++ndx)
{
// Rasterization (of lines) may deviate by one pixel. In addition to that, allow minimal errors in rasterized position vs. feedback result.
// This minimal error could result in a difference in rounding => allow one additional pixel in deviation
const int rasterDeviation = 2;
const tcu::IVec2 rasterPos((int)deFloatRound((vertices[ndx].x() * 0.5f + 0.5f) * (float)image.getWidth()),
(int)deFloatRound((vertices[ndx].y() * 0.5f + 0.5f) * (float)image.getHeight()));
// Find produced rasterization results
bool found = false;
for (int dy = -rasterDeviation; dy <= rasterDeviation && !found; ++dy)
for (int dx = -rasterDeviation; dx <= rasterDeviation && !found; ++dx)
{
// Raster result could end up outside the viewport
if (rasterPos.x() + dx < 0 || rasterPos.x() + dx >= image.getWidth() || rasterPos.y() + dy < 0 ||
rasterPos.y() + dy >= image.getHeight())
found = true;
else
{
const tcu::RGBA result = image.getPixel(rasterPos.x() + dx, rasterPos.y() + dy);
if (!isBlack(result))
found = true;
}
}
if (!found)
{
m_testCtx.getLog() << tcu::TestLog::Message << "Vertex " << vertices[ndx] << "\n"
<< "\tCould not find rasterization output for vertex.\n"
<< "\tExpected non-black pixels near " << rasterPos << tcu::TestLog::EndMessage;
m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "invalid result image");
}
}
}
void FeedbackPrimitiveTypeCase::genTransformFeedback(void)
{
const glw::Functions &gl = m_context.getRenderContext().getFunctions();
const int elementsPerPrimitive = getNumGeneratedElementsPerPrimitive();
const int feedbackPrimitives = getNumGeneratedPrimitives();
const int feedbackElements = elementsPerPrimitive * feedbackPrimitives;
const std::vector<tcu::Vec4> initialBuffer(feedbackElements, tcu::Vec4(-1.0f, -1.0f, -1.0f, -1.0f));
gl.genTransformFeedbacks(1, &m_feedbackID);
gl.bindTransformFeedback(GL_TRANSFORM_FEEDBACK, m_feedbackID);
GLU_EXPECT_NO_ERROR(gl.getError(), "gen transform feedback");
gl.genBuffers(1, &m_feedbackBuffer);
gl.bindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, m_feedbackBuffer);
gl.bufferData(GL_TRANSFORM_FEEDBACK_BUFFER, sizeof(tcu::Vec4) * initialBuffer.size(), initialBuffer[0].getPtr(),
GL_STATIC_COPY);
GLU_EXPECT_NO_ERROR(gl.getError(), "gen feedback buffer");
gl.bindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, m_feedbackBuffer);
GLU_EXPECT_NO_ERROR(gl.getError(), "bind feedback buffer");
}
static int getTriangleNumOutputPrimitives(int tessellationLevel)
{
if (tessellationLevel == 1)
return 1;
else if (tessellationLevel == 2)
return 6;
else
return 3 * (2 + 2 * (tessellationLevel - 2)) + getTriangleNumOutputPrimitives(tessellationLevel - 2);
}
static int getTriangleNumOutputPrimitivesPoints(int tessellationLevel)
{
if (tessellationLevel == 0)
return 1;
else if (tessellationLevel == 1)
return 3;
else
return 3 + 3 * (tessellationLevel - 1) + getTriangleNumOutputPrimitivesPoints(tessellationLevel - 2);
}
int FeedbackPrimitiveTypeCase::getNumGeneratedElementsPerPrimitive(void) const
{
if (m_geometryOutputType == GEOMETRY_OUTPUT_TRIANGLES)
return 3;
else if (m_geometryOutputType == GEOMETRY_OUTPUT_LINES)
return 2;
else if (m_geometryOutputType == GEOMETRY_OUTPUT_POINTS)
return 1;
else
{
DE_ASSERT(false);
return -1;
}
}
int FeedbackPrimitiveTypeCase::getNumGeneratedPrimitives(void) const
{
return getNumTessellatedPrimitives() * getGeometryAmplification();
}
int FeedbackPrimitiveTypeCase::getNumTessellatedPrimitives(void) const
{
const int tessellationLevel = 3;
if (m_tessellationPointMode == TESSELLATION_POINTMODE_OFF)
{
if (m_tessellationOutput == TESSELLATION_OUT_TRIANGLES)
return getTriangleNumOutputPrimitives(tessellationLevel);
else if (m_tessellationOutput == TESSELLATION_OUT_QUADS)
return tessellationLevel * tessellationLevel * 2; // tessellated as triangles
else if (m_tessellationOutput == TESSELLATION_OUT_ISOLINES)
return tessellationLevel * tessellationLevel;
}
else if (m_tessellationPointMode == TESSELLATION_POINTMODE_ON)
{
if (m_tessellationOutput == TESSELLATION_OUT_TRIANGLES)
return getTriangleNumOutputPrimitivesPoints(tessellationLevel);
else if (m_tessellationOutput == TESSELLATION_OUT_QUADS)
return (tessellationLevel + 1) * (tessellationLevel + 1);
else if (m_tessellationOutput == TESSELLATION_OUT_ISOLINES)
return tessellationLevel * (tessellationLevel + 1);
}
DE_ASSERT(false);
return -1;
}
int FeedbackPrimitiveTypeCase::getGeometryAmplification(void) const
{
const int outputAmplification = (m_geometryOutputType == GEOMETRY_OUTPUT_LINES) ? (2) : (1);
const int numInputVertices = (m_tessellationPointMode) ? (1) :
(m_tessellationOutput == TESSELLATION_OUT_ISOLINES) ? (2) :
(3);
return outputAmplification * numInputVertices;
}
glw::GLenum FeedbackPrimitiveTypeCase::getOutputPrimitiveGLType(void) const
{
if (m_geometryOutputType == GEOMETRY_OUTPUT_TRIANGLES)
return GL_TRIANGLES;
else if (m_geometryOutputType == GEOMETRY_OUTPUT_LINES)
return GL_LINES;
else if (m_geometryOutputType == GEOMETRY_OUTPUT_POINTS)
return GL_POINTS;
else
{
DE_ASSERT(false);
return -1;
}
}
std::string FeedbackPrimitiveTypeCase::getVertexSource(void) const
{
return specializeShader(s_positionVertexShader, m_context.getRenderContext().getType());
}
std::string FeedbackPrimitiveTypeCase::getFragmentSource(void) const
{
return specializeShader(s_whiteOutputFragmentShader, m_context.getRenderContext().getType());
}
std::string FeedbackPrimitiveTypeCase::getTessellationControlSource(void) const
{
std::ostringstream buf;
buf << "${VERSION_DECL}\n"
"${EXTENSION_TESSELATION_SHADER}"
"layout(vertices = 9) out;\n"
"\n"
"uniform highp float u_innerTessellationLevel;\n"
"uniform highp float u_outerTessellationLevel;\n"
"\n"
"void main (void)\n"
"{\n"
" if (gl_PatchVerticesIn != 4)\n"
" return;\n"
"\n"
" // Convert input 2x2 grid to 3x3 grid\n"
" float xweight = float(gl_InvocationID % 3) / 2.0f;\n"
" float yweight = float(gl_InvocationID / 3) / 2.0f;\n"
"\n"
" vec4 y0 = mix(gl_in[0].gl_Position, gl_in[1].gl_Position, yweight);\n"
" vec4 y1 = mix(gl_in[2].gl_Position, gl_in[3].gl_Position, yweight);\n"
"\n"
" gl_out[gl_InvocationID].gl_Position = mix(y0, y1, xweight);\n"
"\n";
if (m_tessellationOutput == TESSELLATION_OUT_TRIANGLES)
buf << " gl_TessLevelOuter[0] = 3.0;\n"
" gl_TessLevelOuter[1] = 3.0;\n"
" gl_TessLevelOuter[2] = 3.0;\n"
" gl_TessLevelInner[0] = 3.0;\n";
else if (m_tessellationOutput == TESSELLATION_OUT_QUADS)
buf << " gl_TessLevelOuter[0] = 3.0;\n"
" gl_TessLevelOuter[1] = 3.0;\n"
" gl_TessLevelOuter[2] = 3.0;\n"
" gl_TessLevelOuter[3] = 3.0;\n"
" gl_TessLevelInner[0] = 3.0;\n"
" gl_TessLevelInner[1] = 3.0;\n";
else if (m_tessellationOutput == TESSELLATION_OUT_ISOLINES)
buf << " gl_TessLevelOuter[0] = 3.0;\n"
" gl_TessLevelOuter[1] = 3.0;\n";
else
DE_ASSERT(false);
buf << "}\n";
return specializeShader(buf.str(), m_context.getRenderContext().getType());
}
std::string FeedbackPrimitiveTypeCase::getTessellationEvaluationSource(void) const
{
std::ostringstream buf;
buf << "${VERSION_DECL}\n"
"${EXTENSION_TESSELATION_SHADER}"
"layout("
<< ((m_tessellationOutput == TESSELLATION_OUT_TRIANGLES) ? ("triangles") :
(m_tessellationOutput == TESSELLATION_OUT_QUADS) ? ("quads") :
("isolines"))
<< ((m_tessellationPointMode) ? (", point_mode") : (""))
<< ") in;\n"
"\n"
"out highp vec4 v_tessellationCoords;\n"
"\n"
"// note: No need to use precise gl_Position since we do not require gapless geometry\n"
"void main (void)\n"
"{\n"
" if (gl_PatchVerticesIn != 9)\n"
" return;\n"
"\n"
" vec4 patchCentroid = vec4(0.0);\n"
" for (int ndx = 0; ndx < gl_PatchVerticesIn; ++ndx)\n"
" patchCentroid += gl_in[ndx].gl_Position;\n"
" patchCentroid /= patchCentroid.w;\n"
"\n";
if (m_tessellationOutput == TESSELLATION_OUT_TRIANGLES)
buf << " // map barycentric coords to 2d coords\n"
" const vec3 tessDirX = vec3( 0.4, 0.4, 0.0);\n"
" const vec3 tessDirY = vec3( 0.0, -0.4, 0.0);\n"
" const vec3 tessDirZ = vec3(-0.4, 0.4, 0.0);\n"
" gl_Position = patchCentroid + vec4(gl_TessCoord.x * tessDirX + gl_TessCoord.y * tessDirY + "
"gl_TessCoord.z * tessDirZ, 0.0);\n";
else if (m_tessellationOutput == TESSELLATION_OUT_QUADS || m_tessellationOutput == TESSELLATION_OUT_ISOLINES)
buf << " gl_Position = patchCentroid + vec4(gl_TessCoord.x * 0.8 - 0.4, gl_TessCoord.y * 0.8 - 0.4, 0.0, "
"0.0);\n";
else
DE_ASSERT(false);
buf << " v_tessellationCoords = vec4(gl_TessCoord, 0.0);\n"
"}\n";
return specializeShader(buf.str(), m_context.getRenderContext().getType());
}
std::string FeedbackPrimitiveTypeCase::getGeometrySource(void) const
{
const char *const geometryInputPrimitive = (m_tessellationPointMode) ? ("points") :
(m_tessellationOutput == TESSELLATION_OUT_ISOLINES) ? ("lines") :
("triangles");
const char *const geometryOutputPrimitive = (m_geometryOutputType == GEOMETRY_OUTPUT_POINTS) ? ("points") :
(m_geometryOutputType == GEOMETRY_OUTPUT_LINES) ? ("line_strip") :
("triangle_strip");
const int numInputVertices = (m_tessellationPointMode) ? (1) :
(m_tessellationOutput == TESSELLATION_OUT_ISOLINES) ? (2) :
(3);
const int numSingleVertexOutputVertices = (m_geometryOutputType == GEOMETRY_OUTPUT_POINTS) ? (1) :
(m_geometryOutputType == GEOMETRY_OUTPUT_LINES) ? (4) :
(3);
const int numEmitVertices = numInputVertices * numSingleVertexOutputVertices;
std::ostringstream buf;
buf << "${VERSION_DECL}\n"
"${EXTENSION_GEOMETRY_SHADER}"
"layout("
<< geometryInputPrimitive
<< ") in;\n"
"layout("
<< geometryOutputPrimitive << ", max_vertices=" << numEmitVertices
<< ") out;\n"
"\n"
"in highp vec4 v_tessellationCoords[];\n"
"out highp vec4 tf_someVertexPosition;\n"
"\n"
"void main (void)\n"
"{\n"
" // Emit primitive\n"
" for (int ndx = 0; ndx < gl_in.length(); ++ndx)\n"
" {\n";
switch (m_geometryOutputType)
{
case GEOMETRY_OUTPUT_POINTS:
buf << " // Draw point on vertex\n"
" gl_Position = gl_in[ndx].gl_Position;\n"
" tf_someVertexPosition = gl_in[gl_in.length() - 1].gl_Position;\n"
" EmitVertex();\n";
break;
case GEOMETRY_OUTPUT_LINES:
buf << " // Draw cross on vertex\n"
" gl_Position = gl_in[ndx].gl_Position + vec4(-0.02, -0.02, 0.0, 0.0);\n"
" tf_someVertexPosition = gl_in[gl_in.length() - 1].gl_Position;\n"
" EmitVertex();\n"
" gl_Position = gl_in[ndx].gl_Position + vec4( 0.02, 0.02, 0.0, 0.0);\n"
" tf_someVertexPosition = gl_in[gl_in.length() - 1].gl_Position;\n"
" EmitVertex();\n"
" EndPrimitive();\n"
" gl_Position = gl_in[ndx].gl_Position + vec4( 0.02, -0.02, 0.0, 0.0);\n"
" tf_someVertexPosition = gl_in[gl_in.length() - 1].gl_Position;\n"
" EmitVertex();\n"
" gl_Position = gl_in[ndx].gl_Position + vec4(-0.02, 0.02, 0.0, 0.0);\n"
" tf_someVertexPosition = gl_in[gl_in.length() - 1].gl_Position;\n"
" EmitVertex();\n"
" EndPrimitive();\n";
break;
case GEOMETRY_OUTPUT_TRIANGLES:
buf << " // Draw triangle on vertex\n"
" gl_Position = gl_in[ndx].gl_Position + vec4( 0.00, -0.02, 0.0, 0.0);\n"
" tf_someVertexPosition = gl_in[gl_in.length() - 1].gl_Position;\n"
" EmitVertex();\n"
" gl_Position = gl_in[ndx].gl_Position + vec4( 0.02, 0.00, 0.0, 0.0);\n"
" tf_someVertexPosition = gl_in[gl_in.length() - 1].gl_Position;\n"
" EmitVertex();\n"
" gl_Position = gl_in[ndx].gl_Position + vec4( -0.02, 0.00, 0.0, 0.0);\n"
" tf_someVertexPosition = gl_in[gl_in.length() - 1].gl_Position;\n"
" EmitVertex();\n"
" EndPrimitive();\n";
break;
default:
DE_ASSERT(false);
return "";
}
buf << " }\n"
"}\n";
return specializeShader(buf.str(), m_context.getRenderContext().getType());
}
const char *FeedbackPrimitiveTypeCase::getTessellationOutputDescription(TessellationOutputType tessellationOutput,
TessellationPointMode pointMode)
{
switch (tessellationOutput)
{
case TESSELLATION_OUT_TRIANGLES:
return (pointMode) ? ("points (triangles in point mode)") : ("triangles");
case TESSELLATION_OUT_QUADS:
return (pointMode) ? ("points (quads in point mode)") : ("quads");
case TESSELLATION_OUT_ISOLINES:
return (pointMode) ? ("points (isolines in point mode)") : ("isolines");
default:
DE_ASSERT(false);
return DE_NULL;
}
}
const char *FeedbackPrimitiveTypeCase::getGeometryInputDescription(TessellationOutputType tessellationOutput,
TessellationPointMode pointMode)
{
switch (tessellationOutput)
{
case TESSELLATION_OUT_TRIANGLES:
return (pointMode) ? ("points") : ("triangles");
case TESSELLATION_OUT_QUADS:
return (pointMode) ? ("points") : ("triangles");
case TESSELLATION_OUT_ISOLINES:
return (pointMode) ? ("points") : ("lines");
default:
DE_ASSERT(false);
return DE_NULL;
}
}
const char *FeedbackPrimitiveTypeCase::getGeometryOutputDescription(GeometryOutputType geometryOutput)
{
switch (geometryOutput)
{
case GEOMETRY_OUTPUT_POINTS:
return "points";
case GEOMETRY_OUTPUT_LINES:
return "lines";
case GEOMETRY_OUTPUT_TRIANGLES:
return "triangles";
default:
DE_ASSERT(false);
return DE_NULL;
}
}
class PointSizeCase : public TestCase
{
public:
enum Flags
{
FLAG_VERTEX_SET = 0x01, // !< set gl_PointSize in vertex shader
FLAG_TESSELLATION_CONTROL_SET = 0x02, // !< set gl_PointSize in tessellation evaluation shader
FLAG_TESSELLATION_EVALUATION_SET = 0x04, // !< set gl_PointSize in tessellation control shader
FLAG_TESSELLATION_ADD = 0x08, // !< read and add to gl_PointSize in tessellation shader pair
FLAG_TESSELLATION_DONT_SET = 0x10, // !< don't set gl_PointSize in tessellation shader
FLAG_GEOMETRY_SET = 0x20, // !< set gl_PointSize in geometry shader
FLAG_GEOMETRY_ADD = 0x40, // !< read and add to gl_PointSize in geometry shader
FLAG_GEOMETRY_DONT_SET = 0x80, // !< don't set gl_PointSize in geometry shader
};
PointSizeCase(Context &context, const char *name, const char *description, int flags);
~PointSizeCase(void);
static std::string genTestCaseName(int flags);
static std::string genTestCaseDescription(int flags);
private:
void init(void);
void deinit(void);
IterateResult iterate(void);
void checkExtensions(void) const;
void checkPointSizeRequirements(void) const;
void renderTo(tcu::Surface &dst);
bool verifyImage(const tcu::Surface &src);
int getExpectedPointSize(void) const;
std::string genVertexSource(void) const;
std::string genFragmentSource(void) const;
std::string genTessellationControlSource(void) const;
std::string genTessellationEvaluationSource(void) const;
std::string genGeometrySource(void) const;
enum
{
RENDER_SIZE = 32,
};
const int m_flags;
glu::ShaderProgram *m_program;
};
PointSizeCase::PointSizeCase(Context &context, const char *name, const char *description, int flags)
: TestCase(context, name, description)
, m_flags(flags)
, m_program(DE_NULL)
{
}
PointSizeCase::~PointSizeCase(void)
{
deinit();
}
std::string PointSizeCase::genTestCaseName(int flags)
{
std::ostringstream buf;
// join per-bit descriptions into a single string with '_' separator
if (flags & FLAG_VERTEX_SET)
buf << "vertex_set";
if (flags & FLAG_TESSELLATION_CONTROL_SET)
buf << ((flags & (FLAG_TESSELLATION_CONTROL_SET - 1)) ? ("_") : ("")) << "control_set";
if (flags & FLAG_TESSELLATION_EVALUATION_SET)
buf << ((flags & (FLAG_TESSELLATION_EVALUATION_SET - 1)) ? ("_") : ("")) << "evaluation_set";
if (flags & FLAG_TESSELLATION_ADD)
buf << ((flags & (FLAG_TESSELLATION_ADD - 1)) ? ("_") : ("")) << "control_pass_eval_add";
if (flags & FLAG_TESSELLATION_DONT_SET)
buf << ((flags & (FLAG_TESSELLATION_DONT_SET - 1)) ? ("_") : ("")) << "eval_default";
if (flags & FLAG_GEOMETRY_SET)
buf << ((flags & (FLAG_GEOMETRY_SET - 1)) ? ("_") : ("")) << "geometry_set";
if (flags & FLAG_GEOMETRY_ADD)
buf << ((flags & (FLAG_GEOMETRY_ADD - 1)) ? ("_") : ("")) << "geometry_add";
if (flags & FLAG_GEOMETRY_DONT_SET)
buf << ((flags & (FLAG_GEOMETRY_DONT_SET - 1)) ? ("_") : ("")) << "geometry_default";
return buf.str();
}
std::string PointSizeCase::genTestCaseDescription(int flags)
{
std::ostringstream buf;
// join per-bit descriptions into a single string with ", " separator
if (flags & FLAG_VERTEX_SET)
buf << "set point size in vertex shader";
if (flags & FLAG_TESSELLATION_CONTROL_SET)
buf << ((flags & (FLAG_TESSELLATION_CONTROL_SET - 1)) ? (", ") : (""))
<< "set point size in tessellation control shader";
if (flags & FLAG_TESSELLATION_EVALUATION_SET)
buf << ((flags & (FLAG_TESSELLATION_EVALUATION_SET - 1)) ? (", ") : (""))
<< "set point size in tessellation evaluation shader";
if (flags & FLAG_TESSELLATION_ADD)
buf << ((flags & (FLAG_TESSELLATION_ADD - 1)) ? (", ") : ("")) << "add to point size in tessellation shader";
if (flags & FLAG_TESSELLATION_DONT_SET)
buf << ((flags & (FLAG_TESSELLATION_DONT_SET - 1)) ? (", ") : (""))
<< "don't set point size in tessellation evaluation shader";
if (flags & FLAG_GEOMETRY_SET)
buf << ((flags & (FLAG_GEOMETRY_SET - 1)) ? (", ") : ("")) << "set point size in geometry shader";
if (flags & FLAG_GEOMETRY_ADD)
buf << ((flags & (FLAG_GEOMETRY_ADD - 1)) ? (", ") : ("")) << "add to point size in geometry shader";
if (flags & FLAG_GEOMETRY_DONT_SET)
buf << ((flags & (FLAG_GEOMETRY_DONT_SET - 1)) ? (", ") : ("")) << "don't set point size in geometry shader";
return buf.str();
}
void PointSizeCase::init(void)
{
checkExtensions();
checkPointSizeRequirements();
if (glu::isContextTypeGLCore(m_context.getRenderContext().getType()))
{
m_context.getRenderContext().getFunctions().enable(GL_PROGRAM_POINT_SIZE);
}
// log
if (m_flags & FLAG_VERTEX_SET)
m_testCtx.getLog() << tcu::TestLog::Message << "Setting point size in vertex shader to 2.0."
<< tcu::TestLog::EndMessage;
if (m_flags & FLAG_TESSELLATION_CONTROL_SET)
m_testCtx.getLog()
<< tcu::TestLog::Message
<< "Setting point size in tessellation control shader to 4.0. (And ignoring it in evaluation)."
<< tcu::TestLog::EndMessage;
if (m_flags & FLAG_TESSELLATION_EVALUATION_SET)
m_testCtx.getLog() << tcu::TestLog::Message << "Setting point size in tessellation evaluation shader to 4.0."
<< tcu::TestLog::EndMessage;
if (m_flags & FLAG_TESSELLATION_ADD)
m_testCtx.getLog() << tcu::TestLog::Message
<< "Reading point size in tessellation control shader and adding 2.0 to it in evaluation."
<< tcu::TestLog::EndMessage;
if (m_flags & FLAG_TESSELLATION_DONT_SET)
m_testCtx.getLog()
<< tcu::TestLog::Message
<< "Not setting point size in tessellation evaluation shader (resulting in the default point size)."
<< tcu::TestLog::EndMessage;
if (m_flags & FLAG_GEOMETRY_SET)
m_testCtx.getLog() << tcu::TestLog::Message << "Setting point size in geometry shader to 6.0."
<< tcu::TestLog::EndMessage;
if (m_flags & FLAG_GEOMETRY_ADD)
m_testCtx.getLog() << tcu::TestLog::Message << "Reading point size in geometry shader and adding 2.0."
<< tcu::TestLog::EndMessage;
if (m_flags & FLAG_GEOMETRY_DONT_SET)
m_testCtx.getLog() << tcu::TestLog::Message
<< "Not setting point size in geometry shader (resulting in the default point size)."
<< tcu::TestLog::EndMessage;
// program
{
glu::ProgramSources sources;
sources << glu::VertexSource(genVertexSource()) << glu::FragmentSource(genFragmentSource());
if (m_flags & (FLAG_TESSELLATION_CONTROL_SET | FLAG_TESSELLATION_EVALUATION_SET | FLAG_TESSELLATION_ADD |
FLAG_TESSELLATION_DONT_SET))
sources << glu::TessellationControlSource(genTessellationControlSource())
<< glu::TessellationEvaluationSource(genTessellationEvaluationSource());
if (m_flags & (FLAG_GEOMETRY_SET | FLAG_GEOMETRY_ADD | FLAG_GEOMETRY_DONT_SET))
sources << glu::GeometrySource(genGeometrySource());
m_program = new glu::ShaderProgram(m_context.getRenderContext(), sources);
m_testCtx.getLog() << *m_program;
if (!m_program->isOk())
throw tcu::TestError("failed to build program");
}
}
void PointSizeCase::deinit(void)
{
if (glu::isContextTypeGLCore(m_context.getRenderContext().getType()) &&
m_context.getRenderContext().getFunctions().disable)
{
m_context.getRenderContext().getFunctions().disable(GL_PROGRAM_POINT_SIZE);
}
delete m_program;
m_program = DE_NULL;
}
PointSizeCase::IterateResult PointSizeCase::iterate(void)
{
tcu::Surface resultImage(RENDER_SIZE, RENDER_SIZE);
renderTo(resultImage);
if (verifyImage(resultImage))
m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
else
m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
return STOP;
}
void PointSizeCase::checkExtensions(void) const
{
glu::ContextType contextType = m_context.getRenderContext().getType();
if (glu::contextSupports(contextType, glu::ApiType::core(4, 5)))
return;
std::vector<std::string> requiredExtensions;
const bool supportsES32 = glu::contextSupports(contextType, glu::ApiType::es(3, 2));
bool allOk = true;
if ((m_flags & (FLAG_TESSELLATION_CONTROL_SET | FLAG_TESSELLATION_EVALUATION_SET | FLAG_TESSELLATION_ADD |
FLAG_TESSELLATION_DONT_SET)) &&
!supportsES32)
requiredExtensions.push_back("GL_EXT_tessellation_shader");
if (m_flags & (FLAG_TESSELLATION_CONTROL_SET | FLAG_TESSELLATION_EVALUATION_SET | FLAG_TESSELLATION_ADD))
requiredExtensions.push_back("GL_EXT_tessellation_point_size");
if ((m_flags & (m_flags & (FLAG_GEOMETRY_SET | FLAG_GEOMETRY_ADD | FLAG_GEOMETRY_DONT_SET))) && !supportsES32)
requiredExtensions.push_back("GL_EXT_geometry_shader");
if (m_flags & (m_flags & (FLAG_GEOMETRY_SET | FLAG_GEOMETRY_ADD)))
requiredExtensions.push_back("GL_EXT_geometry_point_size");
for (int ndx = 0; ndx < (int)requiredExtensions.size(); ++ndx)
if (!m_context.getContextInfo().isExtensionSupported(requiredExtensions[ndx].c_str()))
allOk = false;
if (!allOk)
{
std::ostringstream extensionList;
for (int ndx = 0; ndx < (int)requiredExtensions.size(); ++ndx)
{
if (ndx != 0)
extensionList << ", ";
extensionList << requiredExtensions[ndx];
}
throw tcu::NotSupportedError("Test requires {" + extensionList.str() + "} extension(s)");
}
}
void PointSizeCase::checkPointSizeRequirements(void) const
{
const glw::Functions &gl = m_context.getRenderContext().getFunctions();
float aliasedSizeRange[2] = {0.0f, 0.0f};
const int requiredSize = getExpectedPointSize();
gl.getFloatv(GL_ALIASED_POINT_SIZE_RANGE, aliasedSizeRange);
if (float(requiredSize) > aliasedSizeRange[1])
throw tcu::NotSupportedError("Test requires point size " + de::toString(requiredSize));
}
void PointSizeCase::renderTo(tcu::Surface &dst)
{
const glw::Functions &gl = m_context.getRenderContext().getFunctions();
const bool tessellationActive = (m_flags & (FLAG_TESSELLATION_CONTROL_SET | FLAG_TESSELLATION_EVALUATION_SET |
FLAG_TESSELLATION_ADD | FLAG_TESSELLATION_DONT_SET)) != 0;
const int positionLocation = gl.getAttribLocation(m_program->getProgram(), "a_position");
const glu::VertexArray vao(m_context.getRenderContext());
m_testCtx.getLog() << tcu::TestLog::Message << "Rendering single point." << tcu::TestLog::EndMessage;
if (positionLocation == -1)
throw tcu::TestError("Attribute a_position location was -1");
gl.viewport(0, 0, RENDER_SIZE, RENDER_SIZE);
gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
gl.clear(GL_COLOR_BUFFER_BIT);
GLU_EXPECT_NO_ERROR(gl.getError(), "clear");
gl.bindVertexArray(*vao);
GLU_EXPECT_NO_ERROR(gl.getError(), "bind vao");
gl.useProgram(m_program->getProgram());
GLU_EXPECT_NO_ERROR(gl.getError(), "use program");
gl.vertexAttrib4f(positionLocation, 0.0f, 0.0f, 0.0f, 1.0f);
if (tessellationActive)
{
gl.patchParameteri(GL_PATCH_VERTICES, 1);
GLU_EXPECT_NO_ERROR(gl.getError(), "set patch param");
gl.drawArrays(GL_PATCHES, 0, 1);
GLU_EXPECT_NO_ERROR(gl.getError(), "draw patches");
}
else
{
gl.drawArrays(GL_POINTS, 0, 1);
GLU_EXPECT_NO_ERROR(gl.getError(), "draw points");
}
glu::readPixels(m_context.getRenderContext(), 0, 0, dst.getAccess());
}
bool PointSizeCase::verifyImage(const tcu::Surface &src)
{
const bool MSAATarget = (m_context.getRenderTarget().getNumSamples() > 1);
const int expectedSize = getExpectedPointSize();
m_testCtx.getLog() << tcu::TestLog::Message << "Verifying rendered point size. Expecting " << expectedSize
<< " pixels." << tcu::TestLog::EndMessage;
m_testCtx.getLog() << tcu::TestLog::Image("RenderImage", "Rendered image", src.getAccess());
{
bool resultAreaFound = false;
tcu::IVec4 resultArea;
// Find rasterization output area
for (int y = 0; y < src.getHeight(); ++y)
for (int x = 0; x < src.getWidth(); ++x)
{
if (!isBlack(src.getPixel(x, y)))
{
if (!resultAreaFound)
{
// first fragment
resultArea = tcu::IVec4(x, y, x + 1, y + 1);
resultAreaFound = true;
}
else
{
// union area
resultArea.x() = de::min(resultArea.x(), x);
resultArea.y() = de::min(resultArea.y(), y);
resultArea.z() = de::max(resultArea.z(), x + 1);
resultArea.w() = de::max(resultArea.w(), y + 1);
}
}
}
if (!resultAreaFound)
{
m_testCtx.getLog() << tcu::TestLog::Message << "Verification failed, could not find any point fragments."
<< tcu::TestLog::EndMessage;
return false;
}
// verify area size
if (MSAATarget)
{
const tcu::IVec2 pointSize = resultArea.swizzle(2, 3) - resultArea.swizzle(0, 1);
// MSAA: edges may be a little fuzzy
if (de::abs(pointSize.x() - pointSize.y()) > 1)
{
m_testCtx.getLog() << tcu::TestLog::Message
<< "ERROR! Rasterized point is not a square. Detected point size was " << pointSize
<< tcu::TestLog::EndMessage;
return false;
}
// MSAA may produce larger areas, allow one pixel larger
if (expectedSize != de::max(pointSize.x(), pointSize.y()) &&
(expectedSize + 1) != de::max(pointSize.x(), pointSize.y()))
{
m_testCtx.getLog() << tcu::TestLog::Message << "ERROR! Point size invalid, expected " << expectedSize
<< ", got " << de::max(pointSize.x(), pointSize.y()) << tcu::TestLog::EndMessage;
return false;
}
}
else
{
const tcu::IVec2 pointSize = resultArea.swizzle(2, 3) - resultArea.swizzle(0, 1);
if (pointSize.x() != pointSize.y())
{
m_testCtx.getLog() << tcu::TestLog::Message
<< "ERROR! Rasterized point is not a square. Point size was " << pointSize
<< tcu::TestLog::EndMessage;
return false;
}
if (pointSize.x() != expectedSize)
{
m_testCtx.getLog() << tcu::TestLog::Message << "ERROR! Point size invalid, expected " << expectedSize
<< ", got " << pointSize.x() << tcu::TestLog::EndMessage;
return false;
}
}
}
return true;
}
int PointSizeCase::getExpectedPointSize(void) const
{
int addition = 0;
// geometry
if (m_flags & FLAG_GEOMETRY_DONT_SET)
return 1;
else if (m_flags & FLAG_GEOMETRY_SET)
return 6;
else if (m_flags & FLAG_GEOMETRY_ADD)
addition += 2;
// tessellation
if (m_flags & FLAG_TESSELLATION_EVALUATION_SET)
return 4 + addition;
else if (m_flags & FLAG_TESSELLATION_ADD)
addition += 2;
else if (m_flags & (FLAG_TESSELLATION_CONTROL_SET | FLAG_TESSELLATION_DONT_SET))
{
DE_ASSERT((m_flags & FLAG_GEOMETRY_ADD) == 0); // reading pointSize undefined
return 1;
}
// vertex
if (m_flags & FLAG_VERTEX_SET)
return 2 + addition;
// undefined
DE_ASSERT(false);
return -1;
}
std::string PointSizeCase::genVertexSource(void) const
{
std::ostringstream buf;
buf << "${VERSION_DECL}\n"
<< "in highp vec4 a_position;\n"
<< "void main ()\n"
<< "{\n"
<< " gl_Position = a_position;\n";
if (m_flags & FLAG_VERTEX_SET)
buf << " gl_PointSize = 2.0;\n";
buf << "}\n";
return specializeShader(buf.str(), m_context.getRenderContext().getType());
}
std::string PointSizeCase::genFragmentSource(void) const
{
return specializeShader(s_whiteOutputFragmentShader, m_context.getRenderContext().getType());
}
std::string PointSizeCase::genTessellationControlSource(void) const
{
std::ostringstream buf;
buf << "${VERSION_DECL}\n"
<< "${EXTENSION_TESSELATION_SHADER}"
<< ((m_flags & FLAG_TESSELLATION_DONT_SET) ? ("") : ("${EXTENSION_TESSELATION_POINT_SIZE}"))
<< "layout(vertices = 1) out;\n"
<< "void main ()\n"
<< "{\n"
<< " gl_TessLevelOuter[0] = 3.0;\n"
<< " gl_TessLevelOuter[1] = 3.0;\n"
<< " gl_TessLevelOuter[2] = 3.0;\n"
<< " gl_TessLevelInner[0] = 3.0;\n"
<< " gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n";
if (m_flags & FLAG_TESSELLATION_ADD)
buf << " // pass as is to eval\n"
<< " gl_out[gl_InvocationID].gl_PointSize = gl_in[gl_InvocationID].gl_PointSize;\n";
else if (m_flags & FLAG_TESSELLATION_CONTROL_SET)
buf << " // thrown away\n"
<< " gl_out[gl_InvocationID].gl_PointSize = 4.0;\n";
buf << "}\n";
return specializeShader(buf.str(), m_context.getRenderContext().getType());
}
std::string PointSizeCase::genTessellationEvaluationSource(void) const
{
std::ostringstream buf;
buf << "${VERSION_DECL}\n"
<< "${EXTENSION_TESSELATION_SHADER}"
<< ((m_flags & FLAG_TESSELLATION_DONT_SET) ? ("") : ("${EXTENSION_TESSELATION_POINT_SIZE}"))
<< "layout(triangles, point_mode) in;\n"
<< "void main ()\n"
<< "{\n"
<< " // hide all but one vertex\n"
<< " if (gl_TessCoord.x < 0.99)\n"
<< " gl_Position = vec4(-2.0, 0.0, 0.0, 1.0);\n"
<< " else\n"
<< " gl_Position = gl_in[0].gl_Position;\n";
if (m_flags & FLAG_TESSELLATION_ADD)
buf << "\n"
<< " // add to point size\n"
<< " gl_PointSize = gl_in[0].gl_PointSize + 2.0;\n";
else if (m_flags & FLAG_TESSELLATION_EVALUATION_SET)
buf << "\n"
<< " // set point size\n"
<< " gl_PointSize = 4.0;\n";
buf << "}\n";
return specializeShader(buf.str(), m_context.getRenderContext().getType());
}
std::string PointSizeCase::genGeometrySource(void) const
{
std::ostringstream buf;
buf << "${VERSION_DECL}\n"
<< "${EXTENSION_GEOMETRY_SHADER}"
<< ((m_flags & FLAG_GEOMETRY_DONT_SET) ? ("") : ("${EXTENSION_GEOMETRY_POINT_SIZE}")) << "layout (points) in;\n"
<< "layout (points, max_vertices=1) out;\n"
<< "\n"
<< "void main ()\n"
<< "{\n";
if (m_flags & FLAG_GEOMETRY_SET)
buf << " gl_Position = gl_in[0].gl_Position;\n"
<< " gl_PointSize = 6.0;\n";
else if (m_flags & FLAG_GEOMETRY_ADD)
buf << " gl_Position = gl_in[0].gl_Position;\n"
<< " gl_PointSize = gl_in[0].gl_PointSize + 2.0;\n";
else if (m_flags & FLAG_GEOMETRY_DONT_SET)
buf << " gl_Position = gl_in[0].gl_Position;\n";
buf << " EmitVertex();\n"
<< "}\n";
return specializeShader(buf.str(), m_context.getRenderContext().getType());
}
class AllowedRenderFailureException : public std::runtime_error
{
public:
AllowedRenderFailureException(const char *message) : std::runtime_error(message)
{
}
};
class GridRenderCase : public TestCase
{
public:
enum Flags
{
FLAG_TESSELLATION_MAX_SPEC = 0x0001,
FLAG_TESSELLATION_MAX_IMPLEMENTATION = 0x0002,
FLAG_GEOMETRY_MAX_SPEC = 0x0004,
FLAG_GEOMETRY_MAX_IMPLEMENTATION = 0x0008,
FLAG_GEOMETRY_INVOCATIONS_MAX_SPEC = 0x0010,
FLAG_GEOMETRY_INVOCATIONS_MAX_IMPLEMENTATION = 0x0020,
FLAG_GEOMETRY_SCATTER_INSTANCES = 0x0040,
FLAG_GEOMETRY_SCATTER_PRIMITIVES = 0x0080,
FLAG_GEOMETRY_SEPARATE_PRIMITIVES =
0x0100, //!< if set, geometry shader outputs separate grid cells and not continuous slices
FLAG_GEOMETRY_SCATTER_LAYERS = 0x0200,
FLAG_ALLOW_OUT_OF_MEMORY = 0x0400, //!< allow draw command to set GL_OUT_OF_MEMORY
};
GridRenderCase(Context &context, const char *name, const char *description, int flags);
~GridRenderCase(void);
private:
void init(void);
void deinit(void);
IterateResult iterate(void);
void renderTo(std::vector<tcu::Surface> &dst);
bool verifyResultLayer(int layerNdx, const tcu::Surface &dst);
std::string getVertexSource(void);
std::string getFragmentSource(void);
std::string getTessellationControlSource(int tessLevel);
std::string getTessellationEvaluationSource(int tessLevel);
std::string getGeometryShaderSource(int numPrimitives, int numInstances, int tessLevel);
enum
{
RENDER_SIZE = 256
};
const int m_flags;
glu::ShaderProgram *m_program;
uint32_t m_texture;
int m_numLayers;
};
GridRenderCase::GridRenderCase(Context &context, const char *name, const char *description, int flags)
: TestCase(context, name, description)
, m_flags(flags)
, m_program(DE_NULL)
, m_texture(0)
, m_numLayers(1)
{
DE_ASSERT(((m_flags & FLAG_TESSELLATION_MAX_SPEC) == 0) || ((m_flags & FLAG_TESSELLATION_MAX_IMPLEMENTATION) == 0));
DE_ASSERT(((m_flags & FLAG_GEOMETRY_MAX_SPEC) == 0) || ((m_flags & FLAG_GEOMETRY_MAX_IMPLEMENTATION) == 0));
DE_ASSERT(((m_flags & FLAG_GEOMETRY_INVOCATIONS_MAX_SPEC) == 0) ||
((m_flags & FLAG_GEOMETRY_INVOCATIONS_MAX_IMPLEMENTATION) == 0));
DE_ASSERT(((m_flags & (FLAG_GEOMETRY_SCATTER_PRIMITIVES | FLAG_GEOMETRY_SCATTER_LAYERS)) != 0) ==
((m_flags & FLAG_GEOMETRY_SEPARATE_PRIMITIVES) != 0));
}
GridRenderCase::~GridRenderCase(void)
{
deinit();
}
void GridRenderCase::init(void)
{
const glw::Functions &gl = m_context.getRenderContext().getFunctions();
glu::ContextType contextType = m_context.getRenderContext().getType();
const bool supportsES32orGL45 = glu::contextSupports(contextType, glu::ApiType::es(3, 2)) ||
glu::contextSupports(contextType, glu::ApiType::core(4, 5));
// Requirements
if (!supportsES32orGL45 && (!m_context.getContextInfo().isExtensionSupported("GL_EXT_tessellation_shader") ||
!m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader")))
throw tcu::NotSupportedError("Test requires GL_EXT_tessellation_shader and GL_EXT_geometry_shader extensions");
if ((m_flags & FLAG_GEOMETRY_SCATTER_LAYERS) == 0)
{
if (m_context.getRenderTarget().getWidth() < RENDER_SIZE ||
m_context.getRenderTarget().getHeight() < RENDER_SIZE)
throw tcu::NotSupportedError("Test requires " + de::toString<int>(RENDER_SIZE) + "x" +
de::toString<int>(RENDER_SIZE) + " or larger render target.");
}
// Log
m_testCtx.getLog() << tcu::TestLog::Message
<< "Testing tessellation and geometry shaders that output a large number of primitives.\n"
<< getDescription() << tcu::TestLog::EndMessage;
// Render target
if (m_flags & FLAG_GEOMETRY_SCATTER_LAYERS)
{
// set limits
m_numLayers = 8;
m_testCtx.getLog() << tcu::TestLog::Message << "Rendering to 2d texture array, numLayers = " << m_numLayers
<< tcu::TestLog::EndMessage;
gl.genTextures(1, &m_texture);
gl.bindTexture(GL_TEXTURE_2D_ARRAY, m_texture);
gl.texStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, RENDER_SIZE, RENDER_SIZE, m_numLayers);
gl.texParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
gl.texParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
gl.texParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
gl.texParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
GLU_EXPECT_NO_ERROR(gl.getError(), "gen texture");
}
// Gen program
{
glu::ProgramSources sources;
int tessGenLevel = -1;
sources << glu::VertexSource(getVertexSource()) << glu::FragmentSource(getFragmentSource());
// Tessellation limits
{
if (m_flags & FLAG_TESSELLATION_MAX_IMPLEMENTATION)
{
gl.getIntegerv(GL_MAX_TESS_GEN_LEVEL, &tessGenLevel);
GLU_EXPECT_NO_ERROR(gl.getError(), "query tessellation limits");
}
else if (m_flags & FLAG_TESSELLATION_MAX_SPEC)
{
tessGenLevel = 64;
}
else
{
tessGenLevel = 5;
}
m_testCtx.getLog() << tcu::TestLog::Message << "Tessellation level: " << tessGenLevel << ", mode = quad.\n"
<< "\tEach input patch produces " << (tessGenLevel * tessGenLevel) << " ("
<< (tessGenLevel * tessGenLevel * 2) << " triangles)\n"
<< tcu::TestLog::EndMessage;
sources << glu::TessellationControlSource(getTessellationControlSource(tessGenLevel))
<< glu::TessellationEvaluationSource(getTessellationEvaluationSource(tessGenLevel));
}
// Geometry limits
{
int geometryOutputComponents = -1;
int geometryOutputVertices = -1;
int geometryTotalOutputComponents = -1;
int geometryShaderInvocations = -1;
bool logGeometryLimits = false;
bool logInvocationLimits = false;
if (m_flags & FLAG_GEOMETRY_MAX_IMPLEMENTATION)
{
m_testCtx.getLog() << tcu::TestLog::Message
<< "Using implementation maximum geometry shader output limits."
<< tcu::TestLog::EndMessage;
gl.getIntegerv(GL_MAX_GEOMETRY_OUTPUT_COMPONENTS, &geometryOutputComponents);
gl.getIntegerv(GL_MAX_GEOMETRY_OUTPUT_VERTICES, &geometryOutputVertices);
gl.getIntegerv(GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS, &geometryTotalOutputComponents);
GLU_EXPECT_NO_ERROR(gl.getError(), "query geometry limits");
logGeometryLimits = true;
}
else if (m_flags & FLAG_GEOMETRY_MAX_SPEC)
{
m_testCtx.getLog() << tcu::TestLog::Message
<< "Using geometry shader extension minimum maximum output limits."
<< tcu::TestLog::EndMessage;
geometryOutputComponents = 128;
geometryOutputVertices = 256;
geometryTotalOutputComponents = 1024;
logGeometryLimits = true;
}
else
{
geometryOutputComponents = 128;
geometryOutputVertices = 16;
geometryTotalOutputComponents = 1024;
}
if (m_flags & FLAG_GEOMETRY_INVOCATIONS_MAX_IMPLEMENTATION)
{
gl.getIntegerv(GL_MAX_GEOMETRY_SHADER_INVOCATIONS, &geometryShaderInvocations);
GLU_EXPECT_NO_ERROR(gl.getError(), "query geometry invocation limits");
logInvocationLimits = true;
}
else if (m_flags & FLAG_GEOMETRY_INVOCATIONS_MAX_SPEC)
{
geometryShaderInvocations = 32;
logInvocationLimits = true;
}
else
{
geometryShaderInvocations = 4;
}
if (logGeometryLimits || logInvocationLimits)
{
tcu::MessageBuilder msg(&m_testCtx.getLog());
msg << "Geometry shader, targeting following limits:\n";
if (logGeometryLimits)
msg << "\tGL_MAX_GEOMETRY_OUTPUT_COMPONENTS = " << geometryOutputComponents << "\n"
<< "\tGL_MAX_GEOMETRY_OUTPUT_VERTICES = " << geometryOutputVertices << "\n"
<< "\tGL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS = " << geometryTotalOutputComponents << "\n";
if (logInvocationLimits)
msg << "\tGL_MAX_GEOMETRY_SHADER_INVOCATIONS = " << geometryShaderInvocations;
msg << tcu::TestLog::EndMessage;
}
{
const bool separatePrimitives = (m_flags & FLAG_GEOMETRY_SEPARATE_PRIMITIVES) != 0;
const int numComponentsPerVertex = 8; // vec4 pos, vec4 color
int numVerticesPerInvocation;
int numPrimitivesPerInvocation;
int geometryVerticesPerPrimitive;
int geometryPrimitivesOutPerPrimitive;
if (separatePrimitives)
{
const int numComponentLimit = geometryTotalOutputComponents / (4 * numComponentsPerVertex);
const int numOutputLimit = geometryOutputVertices / 4;
numPrimitivesPerInvocation = de::min(numComponentLimit, numOutputLimit);
numVerticesPerInvocation = numPrimitivesPerInvocation * 4;
}
else
{
// If FLAG_GEOMETRY_SEPARATE_PRIMITIVES is not set, geometry shader fills a rectangle area in slices.
// Each slice is a triangle strip and is generated by a single shader invocation.
// One slice with 4 segment ends (nodes) and 3 segments:
// .__.__.__.
// |\ |\ |\ |
// |_\|_\|_\|
const int numSliceNodesComponentLimit =
geometryTotalOutputComponents / (2 * numComponentsPerVertex); // each node 2 vertices
const int numSliceNodesOutputLimit = geometryOutputVertices / 2; // each node 2 vertices
const int numSliceNodes = de::min(numSliceNodesComponentLimit, numSliceNodesOutputLimit);
numVerticesPerInvocation = numSliceNodes * 2;
numPrimitivesPerInvocation = (numSliceNodes - 1) * 2;
}
geometryVerticesPerPrimitive = numVerticesPerInvocation * geometryShaderInvocations;
geometryPrimitivesOutPerPrimitive = numPrimitivesPerInvocation * geometryShaderInvocations;
m_testCtx.getLog() << tcu::TestLog::Message << "Geometry shader:\n"
<< "\tTotal output vertex count per invocation: " << (numVerticesPerInvocation)
<< "\n"
<< "\tTotal output primitive count per invocation: " << (numPrimitivesPerInvocation)
<< "\n"
<< "\tNumber of invocations per primitive: " << geometryShaderInvocations << "\n"
<< "\tTotal output vertex count per input primitive: "
<< (geometryVerticesPerPrimitive) << "\n"
<< "\tTotal output primitive count per input primitive: "
<< (geometryPrimitivesOutPerPrimitive) << "\n"
<< tcu::TestLog::EndMessage;
sources << glu::GeometrySource(
getGeometryShaderSource(numPrimitivesPerInvocation, geometryShaderInvocations, tessGenLevel));
m_testCtx.getLog() << tcu::TestLog::Message << "Program:\n"
<< "\tTotal program output vertices count per input patch: "
<< (tessGenLevel * tessGenLevel * 2 * geometryVerticesPerPrimitive) << "\n"
<< "\tTotal program output primitive count per input patch: "
<< (tessGenLevel * tessGenLevel * 2 * geometryPrimitivesOutPerPrimitive) << "\n"
<< tcu::TestLog::EndMessage;
}
}
m_program = new glu::ShaderProgram(m_context.getRenderContext(), sources);
m_testCtx.getLog() << *m_program;
if (!m_program->isOk())
throw tcu::TestError("failed to build program");
}
}
void GridRenderCase::deinit(void)
{
delete m_program;
m_program = DE_NULL;
if (m_texture)
{
m_context.getRenderContext().getFunctions().deleteTextures(1, &m_texture);
m_texture = 0;
}
}
GridRenderCase::IterateResult GridRenderCase::iterate(void)
{
std::vector<tcu::Surface> renderedLayers(m_numLayers);
bool allLayersOk = true;
for (int ndx = 0; ndx < m_numLayers; ++ndx)
renderedLayers[ndx].setSize(RENDER_SIZE, RENDER_SIZE);
m_testCtx.getLog() << tcu::TestLog::Message
<< "Rendering single point at the origin. Expecting yellow and green colored grid-like image. "
"(High-frequency grid may appear unicolored)."
<< tcu::TestLog::EndMessage;
try
{
renderTo(renderedLayers);
}
catch (const AllowedRenderFailureException &ex)
{
// Got accepted failure
m_testCtx.getLog() << tcu::TestLog::Message << "Could not render, reason: " << ex.what() << "\n"
<< "Failure is allowed." << tcu::TestLog::EndMessage;
m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
return STOP;
}
for (int ndx = 0; ndx < m_numLayers; ++ndx)
allLayersOk &= verifyResultLayer(ndx, renderedLayers[ndx]);
if (allLayersOk)
m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
else
m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
return STOP;
}
void GridRenderCase::renderTo(std::vector<tcu::Surface> &dst)
{
const glw::Functions &gl = m_context.getRenderContext().getFunctions();
const int positionLocation = gl.getAttribLocation(m_program->getProgram(), "a_position");
const glu::VertexArray vao(m_context.getRenderContext());
de::MovePtr<glu::Framebuffer> fbo;
if (positionLocation == -1)
throw tcu::TestError("Attribute a_position location was -1");
gl.viewport(0, 0, dst.front().getWidth(), dst.front().getHeight());
gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
GLU_EXPECT_NO_ERROR(gl.getError(), "viewport");
gl.bindVertexArray(*vao);
GLU_EXPECT_NO_ERROR(gl.getError(), "bind vao");
gl.useProgram(m_program->getProgram());
GLU_EXPECT_NO_ERROR(gl.getError(), "use program");
gl.patchParameteri(GL_PATCH_VERTICES, 1);
GLU_EXPECT_NO_ERROR(gl.getError(), "set patch param");
gl.vertexAttrib4f(positionLocation, 0.0f, 0.0f, 0.0f, 1.0f);
if (m_flags & FLAG_GEOMETRY_SCATTER_LAYERS)
{
// clear texture contents
{
glu::Framebuffer clearFbo(m_context.getRenderContext());
gl.bindFramebuffer(GL_FRAMEBUFFER, *clearFbo);
for (int layerNdx = 0; layerNdx < m_numLayers; ++layerNdx)
{
gl.framebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_texture, 0, layerNdx);
gl.clear(GL_COLOR_BUFFER_BIT);
}
GLU_EXPECT_NO_ERROR(gl.getError(), "clear tex contents");
}
// create and bind layered fbo
fbo = de::MovePtr<glu::Framebuffer>(new glu::Framebuffer(m_context.getRenderContext()));
gl.bindFramebuffer(GL_FRAMEBUFFER, **fbo);
gl.framebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_texture, 0);
GLU_EXPECT_NO_ERROR(gl.getError(), "gen fbo");
}
else
{
// clear viewport
gl.clear(GL_COLOR_BUFFER_BIT);
}
// draw
{
glw::GLenum glerror;
gl.drawArrays(GL_PATCHES, 0, 1);
glerror = gl.getError();
if (glerror == GL_OUT_OF_MEMORY && (m_flags & FLAG_ALLOW_OUT_OF_MEMORY))
throw AllowedRenderFailureException("got GL_OUT_OF_MEMORY while drawing");
GLU_EXPECT_NO_ERROR(glerror, "draw patches");
}
// Read layers
if (m_flags & FLAG_GEOMETRY_SCATTER_LAYERS)
{
glu::Framebuffer readFbo(m_context.getRenderContext());
gl.bindFramebuffer(GL_FRAMEBUFFER, *readFbo);
for (int layerNdx = 0; layerNdx < m_numLayers; ++layerNdx)
{
gl.framebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_texture, 0, layerNdx);
glu::readPixels(m_context.getRenderContext(), 0, 0, dst[layerNdx].getAccess());
GLU_EXPECT_NO_ERROR(gl.getError(), "read pixels");
}
}
else
{
glu::readPixels(m_context.getRenderContext(), 0, 0, dst.front().getAccess());
GLU_EXPECT_NO_ERROR(gl.getError(), "read pixels");
}
}
bool GridRenderCase::verifyResultLayer(int layerNdx, const tcu::Surface &image)
{
tcu::Surface errorMask(image.getWidth(), image.getHeight());
bool foundError = false;
tcu::clear(errorMask.getAccess(), tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f));
m_testCtx.getLog() << tcu::TestLog::Message << "Verifying output layer " << layerNdx << tcu::TestLog::EndMessage;
for (int y = 0; y < image.getHeight(); ++y)
for (int x = 0; x < image.getWidth(); ++x)
{
const int threshold = 8;
const tcu::RGBA color = image.getPixel(x, y);
// Color must be a linear combination of green and yellow
if (color.getGreen() < 255 - threshold || color.getBlue() > threshold)
{
errorMask.setPixel(x, y, tcu::RGBA::red());
foundError = true;
}
}
if (!foundError)
{
m_testCtx.getLog() << tcu::TestLog::Message << "Image valid." << tcu::TestLog::EndMessage
<< tcu::TestLog::ImageSet("ImageVerification", "Image verification")
<< tcu::TestLog::Image("Result", "Rendered result", image.getAccess())
<< tcu::TestLog::EndImageSet;
return true;
}
else
{
m_testCtx.getLog() << tcu::TestLog::Message << "Image verification failed, found invalid pixels."
<< tcu::TestLog::EndMessage
<< tcu::TestLog::ImageSet("ImageVerification", "Image verification")
<< tcu::TestLog::Image("Result", "Rendered result", image.getAccess())
<< tcu::TestLog::Image("ErrorMask", "Error mask", errorMask.getAccess())
<< tcu::TestLog::EndImageSet;
return false;
}
}
std::string GridRenderCase::getVertexSource(void)
{
return specializeShader(s_positionVertexShader, m_context.getRenderContext().getType());
}
std::string GridRenderCase::getFragmentSource(void)
{
const char *source = "${VERSION_DECL}\n"
"flat in mediump vec4 v_color;\n"
"layout(location = 0) out mediump vec4 fragColor;\n"
"void main (void)\n"
"{\n"
" fragColor = v_color;\n"
"}\n";
return specializeShader(source, m_context.getRenderContext().getType());
}
std::string GridRenderCase::getTessellationControlSource(int tessLevel)
{
std::ostringstream buf;
buf << "${VERSION_DECL}\n"
"${EXTENSION_TESSELATION_SHADER}"
"layout(vertices=1) out;\n"
"\n"
"void main()\n"
"{\n"
" gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n"
" gl_TessLevelOuter[0] = "
<< tessLevel
<< ".0;\n"
" gl_TessLevelOuter[1] = "
<< tessLevel
<< ".0;\n"
" gl_TessLevelOuter[2] = "
<< tessLevel
<< ".0;\n"
" gl_TessLevelOuter[3] = "
<< tessLevel
<< ".0;\n"
" gl_TessLevelInner[0] = "
<< tessLevel
<< ".0;\n"
" gl_TessLevelInner[1] = "
<< tessLevel
<< ".0;\n"
"}\n";
return specializeShader(buf.str(), m_context.getRenderContext().getType());
}
std::string GridRenderCase::getTessellationEvaluationSource(int tessLevel)
{
std::ostringstream buf;
buf << "${VERSION_DECL}\n"
"${EXTENSION_TESSELATION_SHADER}"
"layout(quads) in;\n"
"\n"
"out mediump ivec2 v_tessellationGridPosition;\n"
"\n"
"// note: No need to use precise gl_Position since position does not depend on order\n"
"void main (void)\n"
"{\n";
if (m_flags & (FLAG_GEOMETRY_SCATTER_INSTANCES | FLAG_GEOMETRY_SCATTER_PRIMITIVES | FLAG_GEOMETRY_SCATTER_LAYERS))
buf << " // Cover only a small area in a corner. The area will be expanded in geometry shader to cover "
"whole viewport\n"
" gl_Position = vec4(gl_TessCoord.x * 0.3 - 1.0, gl_TessCoord.y * 0.3 - 1.0, 0.0, 1.0);\n";
else
buf << " // Fill the whole viewport\n"
" gl_Position = vec4(gl_TessCoord.x * 2.0 - 1.0, gl_TessCoord.y * 2.0 - 1.0, 0.0, 1.0);\n";
buf << " // Calculate position in tessellation grid\n"
" v_tessellationGridPosition = ivec2(round(gl_TessCoord.xy * float("
<< tessLevel
<< ")));\n"
"}\n";
return specializeShader(buf.str(), m_context.getRenderContext().getType());
}
std::string GridRenderCase::getGeometryShaderSource(int numPrimitives, int numInstances, int tessLevel)
{
std::ostringstream buf;
buf << "${VERSION_DECL}\n"
"${EXTENSION_GEOMETRY_SHADER}"
"layout(triangles, invocations="
<< numInstances
<< ") in;\n"
"layout(triangle_strip, max_vertices="
<< ((m_flags & FLAG_GEOMETRY_SEPARATE_PRIMITIVES) ? (4 * numPrimitives) : (numPrimitives + 2))
<< ") out;\n"
"\n"
"in mediump ivec2 v_tessellationGridPosition[];\n"
"flat out highp vec4 v_color;\n"
"\n"
"void main ()\n"
"{\n"
" const float equalThreshold = 0.001;\n"
" const float gapOffset = 0.0001; // subdivision performed by the geometry shader might produce gaps. "
"Fill potential gaps by enlarging the output slice a little.\n"
"\n"
" // Input triangle is generated from an axis-aligned rectangle by splitting it in half\n"
" // Original rectangle can be found by finding the bounding AABB of the triangle\n"
" vec4 aabb = vec4(min(gl_in[0].gl_Position.x, min(gl_in[1].gl_Position.x, gl_in[2].gl_Position.x)),\n"
" min(gl_in[0].gl_Position.y, min(gl_in[1].gl_Position.y, gl_in[2].gl_Position.y)),\n"
" max(gl_in[0].gl_Position.x, max(gl_in[1].gl_Position.x, gl_in[2].gl_Position.x)),\n"
" max(gl_in[0].gl_Position.y, max(gl_in[1].gl_Position.y, gl_in[2].gl_Position.y)));\n"
"\n"
" // Location in tessellation grid\n"
" ivec2 gridPosition = ivec2(min(v_tessellationGridPosition[0], min(v_tessellationGridPosition[1], "
"v_tessellationGridPosition[2])));\n"
"\n"
" // Which triangle of the two that split the grid cell\n"
" int numVerticesOnBottomEdge = 0;\n"
" for (int ndx = 0; ndx < 3; ++ndx)\n"
" if (abs(gl_in[ndx].gl_Position.y - aabb.w) < equalThreshold)\n"
" ++numVerticesOnBottomEdge;\n"
" bool isBottomTriangle = numVerticesOnBottomEdge == 2;\n"
"\n";
if (m_flags & FLAG_GEOMETRY_SCATTER_PRIMITIVES)
{
// scatter primitives
buf << " // Draw grid cells\n"
" int inputTriangleNdx = gl_InvocationID * 2 + ((isBottomTriangle) ? (1) : (0));\n"
" for (int ndx = 0; ndx < "
<< numPrimitives
<< "; ++ndx)\n"
" {\n"
" ivec2 dstGridSize = ivec2("
<< tessLevel << " * " << numPrimitives << ", 2 * " << tessLevel << " * " << numInstances
<< ");\n"
" ivec2 dstGridNdx = ivec2("
<< tessLevel << " * ndx + gridPosition.x, " << tessLevel
<< " * inputTriangleNdx + 2 * gridPosition.y + ndx * 127) % dstGridSize;\n"
" vec4 dstArea;\n"
" dstArea.x = float(dstGridNdx.x) / float(dstGridSize.x) * 2.0 - 1.0 - gapOffset;\n"
" dstArea.y = float(dstGridNdx.y) / float(dstGridSize.y) * 2.0 - 1.0 - gapOffset;\n"
" dstArea.z = float(dstGridNdx.x+1) / float(dstGridSize.x) * 2.0 - 1.0 + gapOffset;\n"
" dstArea.w = float(dstGridNdx.y+1) / float(dstGridSize.y) * 2.0 - 1.0 + gapOffset;\n"
"\n"
" vec4 green = vec4(0.0, 1.0, 0.0, 1.0);\n"
" vec4 yellow = vec4(1.0, 1.0, 0.0, 1.0);\n"
" vec4 outputColor = (((dstGridNdx.y + dstGridNdx.x) % 2) == 0) ? (green) : (yellow);\n"
"\n"
" gl_Position = vec4(dstArea.x, dstArea.y, 0.0, 1.0);\n"
" v_color = outputColor;\n"
" EmitVertex();\n"
"\n"
" gl_Position = vec4(dstArea.x, dstArea.w, 0.0, 1.0);\n"
" v_color = outputColor;\n"
" EmitVertex();\n"
"\n"
" gl_Position = vec4(dstArea.z, dstArea.y, 0.0, 1.0);\n"
" v_color = outputColor;\n"
" EmitVertex();\n"
"\n"
" gl_Position = vec4(dstArea.z, dstArea.w, 0.0, 1.0);\n"
" v_color = outputColor;\n"
" EmitVertex();\n"
" EndPrimitive();\n"
" }\n";
}
else if (m_flags & FLAG_GEOMETRY_SCATTER_LAYERS)
{
// Number of subrectangle instances = num layers
DE_ASSERT(m_numLayers == numInstances * 2);
buf << " // Draw grid cells, send each primitive to a separate layer\n"
" int baseLayer = gl_InvocationID * 2 + ((isBottomTriangle) ? (1) : (0));\n"
" for (int ndx = 0; ndx < "
<< numPrimitives
<< "; ++ndx)\n"
" {\n"
" ivec2 dstGridSize = ivec2("
<< tessLevel << " * " << numPrimitives << ", " << tessLevel
<< ");\n"
" ivec2 dstGridNdx = ivec2((gridPosition.x * "
<< numPrimitives
<< " * 7 + ndx)*13, (gridPosition.y * 127 + ndx) * 19) % dstGridSize;\n"
" vec4 dstArea;\n"
" dstArea.x = float(dstGridNdx.x) / float(dstGridSize.x) * 2.0 - 1.0 - gapOffset;\n"
" dstArea.y = float(dstGridNdx.y) / float(dstGridSize.y) * 2.0 - 1.0 - gapOffset;\n"
" dstArea.z = float(dstGridNdx.x+1) / float(dstGridSize.x) * 2.0 - 1.0 + gapOffset;\n"
" dstArea.w = float(dstGridNdx.y+1) / float(dstGridSize.y) * 2.0 - 1.0 + gapOffset;\n"
"\n"
" vec4 green = vec4(0.0, 1.0, 0.0, 1.0);\n"
" vec4 yellow = vec4(1.0, 1.0, 0.0, 1.0);\n"
" vec4 outputColor = (((dstGridNdx.y + dstGridNdx.x) % 2) == 0) ? (green) : (yellow);\n"
"\n"
" gl_Position = vec4(dstArea.x, dstArea.y, 0.0, 1.0);\n"
" v_color = outputColor;\n"
" gl_Layer = ((baseLayer + ndx) * 11) % "
<< m_numLayers
<< ";\n"
" EmitVertex();\n"
"\n"
" gl_Position = vec4(dstArea.x, dstArea.w, 0.0, 1.0);\n"
" v_color = outputColor;\n"
" gl_Layer = ((baseLayer + ndx) * 11) % "
<< m_numLayers
<< ";\n"
" EmitVertex();\n"
"\n"
" gl_Position = vec4(dstArea.z, dstArea.y, 0.0, 1.0);\n"
" v_color = outputColor;\n"
" gl_Layer = ((baseLayer + ndx) * 11) % "
<< m_numLayers
<< ";\n"
" EmitVertex();\n"
"\n"
" gl_Position = vec4(dstArea.z, dstArea.w, 0.0, 1.0);\n"
" v_color = outputColor;\n"
" gl_Layer = ((baseLayer + ndx) * 11) % "
<< m_numLayers
<< ";\n"
" EmitVertex();\n"
" EndPrimitive();\n"
" }\n";
}
else
{
if (m_flags & FLAG_GEOMETRY_SCATTER_INSTANCES)
{
buf << " // Scatter slices\n"
" int inputTriangleNdx = gl_InvocationID * 2 + ((isBottomTriangle) ? (1) : (0));\n"
" ivec2 srcSliceNdx = ivec2(gridPosition.x, gridPosition.y * "
<< (numInstances * 2)
<< " + inputTriangleNdx);\n"
" ivec2 dstSliceNdx = ivec2(7 * srcSliceNdx.x, 127 * srcSliceNdx.y) % ivec2("
<< tessLevel << ", " << tessLevel << " * " << (numInstances * 2)
<< ");\n"
"\n"
" // Draw slice to the dstSlice slot\n"
" vec4 outputSliceArea;\n"
" outputSliceArea.x = float(dstSliceNdx.x) / float("
<< tessLevel
<< ") * 2.0 - 1.0 - gapOffset;\n"
" outputSliceArea.y = float(dstSliceNdx.y) / float("
<< (tessLevel * numInstances * 2)
<< ") * 2.0 - 1.0 - gapOffset;\n"
" outputSliceArea.z = float(dstSliceNdx.x+1) / float("
<< tessLevel
<< ") * 2.0 - 1.0 + gapOffset;\n"
" outputSliceArea.w = float(dstSliceNdx.y+1) / float("
<< (tessLevel * numInstances * 2) << ") * 2.0 - 1.0 + gapOffset;\n";
}
else
{
buf << " // Fill the input area with slices\n"
" // Upper triangle produces slices only to the upper half of the quad and vice-versa\n"
" float triangleOffset = (isBottomTriangle) ? ((aabb.w + aabb.y) / 2.0) : (aabb.y);\n"
" // Each slice is a invocation\n"
" float sliceHeight = (aabb.w - aabb.y) / float(2 * "
<< numInstances
<< ");\n"
" float invocationOffset = float(gl_InvocationID) * sliceHeight;\n"
"\n"
" vec4 outputSliceArea;\n"
" outputSliceArea.x = aabb.x - gapOffset;\n"
" outputSliceArea.y = triangleOffset + invocationOffset - gapOffset;\n"
" outputSliceArea.z = aabb.z + gapOffset;\n"
" outputSliceArea.w = triangleOffset + invocationOffset + sliceHeight + gapOffset;\n";
}
buf << "\n"
" // Draw slice\n"
" for (int ndx = 0; ndx < "
<< ((numPrimitives + 2) / 2)
<< "; ++ndx)\n"
" {\n"
" vec4 green = vec4(0.0, 1.0, 0.0, 1.0);\n"
" vec4 yellow = vec4(1.0, 1.0, 0.0, 1.0);\n"
" vec4 outputColor = (((gl_InvocationID + ndx) % 2) == 0) ? (green) : (yellow);\n"
" float xpos = mix(outputSliceArea.x, outputSliceArea.z, float(ndx) / float("
<< (numPrimitives / 2)
<< "));\n"
"\n"
" gl_Position = vec4(xpos, outputSliceArea.y, 0.0, 1.0);\n"
" v_color = outputColor;\n"
" EmitVertex();\n"
"\n"
" gl_Position = vec4(xpos, outputSliceArea.w, 0.0, 1.0);\n"
" v_color = outputColor;\n"
" EmitVertex();\n"
" }\n";
}
buf << "}\n";
return specializeShader(buf.str(), m_context.getRenderContext().getType());
}
class FeedbackRecordVariableSelectionCase : public TestCase
{
public:
FeedbackRecordVariableSelectionCase(Context &context, const char *name, const char *description);
~FeedbackRecordVariableSelectionCase(void);
private:
void init(void);
void deinit(void);
IterateResult iterate(void);
std::string getVertexSource(void);
std::string getFragmentSource(void);
std::string getTessellationControlSource(void);
std::string getTessellationEvaluationSource(void);
std::string getGeometrySource(void);
glu::ShaderProgram *m_program;
uint32_t m_xfbBuf;
};
FeedbackRecordVariableSelectionCase::FeedbackRecordVariableSelectionCase(Context &context, const char *name,
const char *description)
: TestCase(context, name, description)
, m_program(DE_NULL)
, m_xfbBuf(0)
{
}
FeedbackRecordVariableSelectionCase::~FeedbackRecordVariableSelectionCase(void)
{
deinit();
}
void FeedbackRecordVariableSelectionCase::init(void)
{
const bool supportsES32 = glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2));
const bool supportsCore40 = glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::core(4, 0));
if ((!supportsES32 && !supportsCore40) &&
(!m_context.getContextInfo().isExtensionSupported("GL_EXT_tessellation_shader") ||
!m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader")))
throw tcu::NotSupportedError("Test requires GL_EXT_tessellation_shader and GL_EXT_geometry_shader extensions");
m_testCtx.getLog() << tcu::TestLog::Message
<< "Declaring multiple output variables with the same name in multiple shader stages. Capturing "
"the value of the varying using transform feedback."
<< tcu::TestLog::EndMessage;
// gen feedback buffer fit for 1 triangle (4 components)
{
static const tcu::Vec4 initialData[3] = {
tcu::Vec4(-1.0f, -1.0f, -1.0f, -1.0f),
tcu::Vec4(-1.0f, -1.0f, -1.0f, -1.0f),
tcu::Vec4(-1.0f, -1.0f, -1.0f, -1.0f),
};
const glw::Functions &gl = m_context.getRenderContext().getFunctions();
m_testCtx.getLog()
<< tcu::TestLog::Message
<< "Creating buffer for transform feedback. Allocating storage for one triangle. Filling with -1.0"
<< tcu::TestLog::EndMessage;
gl.genBuffers(1, &m_xfbBuf);
gl.bindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, m_xfbBuf);
gl.bufferData(GL_TRANSFORM_FEEDBACK_BUFFER, (int)(sizeof(tcu::Vec4[3])), initialData, GL_DYNAMIC_READ);
GLU_EXPECT_NO_ERROR(gl.getError(), "gen xfb buf");
}
// gen shader
m_program = new glu::ShaderProgram(
m_context.getRenderContext(),
glu::ProgramSources() << glu::VertexSource(getVertexSource()) << glu::FragmentSource(getFragmentSource())
<< glu::TessellationControlSource(getTessellationControlSource())
<< glu::TessellationEvaluationSource(getTessellationEvaluationSource())
<< glu::GeometrySource(getGeometrySource())
<< glu::TransformFeedbackMode(GL_INTERLEAVED_ATTRIBS)
<< glu::TransformFeedbackVarying("tf_feedback"));
m_testCtx.getLog() << *m_program;
if (!m_program->isOk())
throw tcu::TestError("could not build program");
}
void FeedbackRecordVariableSelectionCase::deinit(void)
{
delete m_program;
m_program = DE_NULL;
if (m_xfbBuf)
{
m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_xfbBuf);
m_xfbBuf = 0;
}
}
FeedbackRecordVariableSelectionCase::IterateResult FeedbackRecordVariableSelectionCase::iterate(void)
{
const glw::Functions &gl = m_context.getRenderContext().getFunctions();
const int posLoc = gl.getAttribLocation(m_program->getProgram(), "a_position");
const glu::VertexArray vao(m_context.getRenderContext());
if (posLoc == -1)
throw tcu::TestError("a_position attribute location was -1");
m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
m_testCtx.getLog() << tcu::TestLog::Message << "Rendering a patch of size 3." << tcu::TestLog::EndMessage;
// Render and feed back
gl.viewport(0, 0, 1, 1);
gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
gl.clear(GL_COLOR_BUFFER_BIT);
GLU_EXPECT_NO_ERROR(gl.getError(), "clear");
gl.bindVertexArray(*vao);
GLU_EXPECT_NO_ERROR(gl.getError(), "bindVertexArray");
gl.vertexAttrib4f(posLoc, 0.0f, 0.0f, 0.0f, 1.0f);
GLU_EXPECT_NO_ERROR(gl.getError(), "vertexAttrib4f");
gl.useProgram(m_program->getProgram());
GLU_EXPECT_NO_ERROR(gl.getError(), "use program");
gl.patchParameteri(GL_PATCH_VERTICES, 3);
GLU_EXPECT_NO_ERROR(gl.getError(), "set patch param");
gl.bindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, m_xfbBuf);
GLU_EXPECT_NO_ERROR(gl.getError(), "bind xfb buf");
gl.beginTransformFeedback(GL_TRIANGLES);
GLU_EXPECT_NO_ERROR(gl.getError(), "beginTransformFeedback");
gl.drawArrays(GL_PATCHES, 0, 3);
GLU_EXPECT_NO_ERROR(gl.getError(), "drawArrays");
gl.endTransformFeedback();
GLU_EXPECT_NO_ERROR(gl.getError(), "beginTransformFeedback");
m_testCtx.getLog() << tcu::TestLog::Message
<< "Verifying the value of tf_feedback using transform feedback, expecting (3.0, 3.0, 3.0, 3.0)."
<< tcu::TestLog::EndMessage;
// Read back result (one triangle)
{
tcu::Vec4 feedbackValues[3];
const void *mapPtr =
gl.mapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, (int)sizeof(feedbackValues), GL_MAP_READ_BIT);
GLU_EXPECT_NO_ERROR(gl.getError(), "mapBufferRange");
if (mapPtr == DE_NULL)
throw tcu::TestError("mapBufferRange returned null");
deMemcpy(feedbackValues, mapPtr, sizeof(feedbackValues));
if (gl.unmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER) != GL_TRUE)
throw tcu::TestError("unmapBuffer did not return TRUE");
for (int ndx = 0; ndx < 3; ++ndx)
{
if (!tcu::boolAll(tcu::lessThan(tcu::abs(feedbackValues[ndx] - tcu::Vec4(3.0f)), tcu::Vec4(0.001f))))
{
m_testCtx.getLog() << tcu::TestLog::Message << "Feedback vertex " << ndx
<< ": expected (3.0, 3.0, 3.0, 3.0), got " << feedbackValues[ndx]
<< tcu::TestLog::EndMessage;
m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got unexpected feedback results");
}
}
}
return STOP;
}
std::string FeedbackRecordVariableSelectionCase::getVertexSource(void)
{
std::string source = "${VERSION_DECL}\n"
"in highp vec4 a_position;\n"
"out highp vec4 tf_feedback;\n"
"void main()\n"
"{\n"
" gl_Position = a_position;\n"
" tf_feedback = vec4(1.0, 1.0, 1.0, 1.0);\n"
"}\n";
return specializeShader(source, m_context.getRenderContext().getType());
}
std::string FeedbackRecordVariableSelectionCase::getFragmentSource(void)
{
return specializeShader(s_whiteOutputFragmentShader, m_context.getRenderContext().getType());
}
std::string FeedbackRecordVariableSelectionCase::getTessellationControlSource(void)
{
std::string source = "${VERSION_DECL}\n"
"${EXTENSION_TESSELATION_SHADER}"
"layout(vertices=3) out;\n"
"void main()\n"
"{\n"
" gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n"
" gl_TessLevelOuter[0] = 1.0;\n"
" gl_TessLevelOuter[1] = 1.0;\n"
" gl_TessLevelOuter[2] = 1.0;\n"
" gl_TessLevelInner[0] = 1.0;\n"
"}\n";
return specializeShader(source, m_context.getRenderContext().getType());
}
std::string FeedbackRecordVariableSelectionCase::getTessellationEvaluationSource(void)
{
std::string source = "${VERSION_DECL}\n"
"${EXTENSION_TESSELATION_SHADER}"
"layout(triangles) in;\n"
"out highp vec4 tf_feedback;\n"
"void main()\n"
"{\n"
" gl_Position = gl_in[0].gl_Position * gl_TessCoord.x + gl_in[1].gl_Position * "
"gl_TessCoord.y + gl_in[2].gl_Position * gl_TessCoord.z;\n"
" tf_feedback = vec4(2.0, 2.0, 2.0, 2.0);\n"
"}\n";
return specializeShader(source, m_context.getRenderContext().getType());
}
std::string FeedbackRecordVariableSelectionCase::getGeometrySource(void)
{
std::string source =
"${VERSION_DECL}\n"
"${EXTENSION_GEOMETRY_SHADER}"
"layout (triangles) in;\n"
"layout (triangle_strip, max_vertices=3) out;\n"
"out highp vec4 tf_feedback;\n"
"void main()\n"
"{\n"
" for (int ndx = 0; ndx < 3; ++ndx)\n"
" {\n"
" gl_Position = gl_in[ndx].gl_Position + vec4(float(ndx), float(ndx)*float(ndx), 0.0, 0.0);\n"
" tf_feedback = vec4(3.0, 3.0, 3.0, 3.0);\n"
" EmitVertex();\n"
" }\n"
" EndPrimitive();\n"
"}\n";
return specializeShader(source, m_context.getRenderContext().getType());
}
} // namespace
TessellationGeometryInteractionTests::TessellationGeometryInteractionTests(Context &context, bool isGL45)
: TestCaseGroup(context, "tessellation_geometry_interaction", "Tessellation and geometry shader interaction tests")
, m_isGL45(isGL45)
{
}
TessellationGeometryInteractionTests::~TessellationGeometryInteractionTests(void)
{
}
void TessellationGeometryInteractionTests::init(void)
{
tcu::TestCaseGroup *const renderGroup = new tcu::TestCaseGroup(m_testCtx, "render", "Various render tests");
tcu::TestCaseGroup *const feedbackGroup = new tcu::TestCaseGroup(m_testCtx, "feedback", "Test transform feedback");
tcu::TestCaseGroup *const pointSizeGroup = new tcu::TestCaseGroup(m_testCtx, "point_size", "Test point size");
addChild(renderGroup);
addChild(feedbackGroup);
addChild(pointSizeGroup);
// .render
{
tcu::TestCaseGroup *const passthroughGroup = new tcu::TestCaseGroup(
m_testCtx, "passthrough", "Render various types with either passthrough geometry or tessellation shader");
tcu::TestCaseGroup *const limitGroup =
new tcu::TestCaseGroup(m_testCtx, "limits", "Render with properties near their limits");
tcu::TestCaseGroup *const scatterGroup =
new tcu::TestCaseGroup(m_testCtx, "scatter", "Scatter output primitives");
renderGroup->addChild(passthroughGroup);
renderGroup->addChild(limitGroup);
renderGroup->addChild(scatterGroup);
// .passthrough
{
// tessellate_tris_passthrough_geometry_no_change
// tessellate_quads_passthrough_geometry_no_change
// tessellate_isolines_passthrough_geometry_no_change
passthroughGroup->addChild(new IdentityGeometryShaderCase(
m_context, "tessellate_tris_passthrough_geometry_no_change",
"Passthrough geometry shader has no effect", IdentityGeometryShaderCase::CASE_TRIANGLES));
passthroughGroup->addChild(new IdentityGeometryShaderCase(
m_context, "tessellate_quads_passthrough_geometry_no_change",
"Passthrough geometry shader has no effect", IdentityGeometryShaderCase::CASE_QUADS));
passthroughGroup->addChild(new IdentityGeometryShaderCase(
m_context, "tessellate_isolines_passthrough_geometry_no_change",
"Passthrough geometry shader has no effect", IdentityGeometryShaderCase::CASE_ISOLINES));
// passthrough_tessellation_geometry_shade_triangles_no_change
// passthrough_tessellation_geometry_shade_lines_no_change
passthroughGroup->addChild(new IdentityTessellationShaderCase(
m_context, "passthrough_tessellation_geometry_shade_triangles_no_change",
"Passthrough tessellation shader has no effect", IdentityTessellationShaderCase::CASE_TRIANGLES));
passthroughGroup->addChild(new IdentityTessellationShaderCase(
m_context, "passthrough_tessellation_geometry_shade_lines_no_change",
"Passthrough tessellation shader has no effect", IdentityTessellationShaderCase::CASE_ISOLINES));
}
// .limits
{
static const struct LimitCaseDef
{
const char *name;
const char *desc;
int flags;
} cases[] = {
// Test single limit
{"output_required_max_tessellation", "Minimum maximum tessellation level",
GridRenderCase::FLAG_TESSELLATION_MAX_SPEC},
{"output_implementation_max_tessellation", "Maximum tessellation level supported by the implementation",
GridRenderCase::FLAG_TESSELLATION_MAX_IMPLEMENTATION},
{"output_required_max_geometry", "Output minimum maximum number of vertices the geometry shader",
GridRenderCase::FLAG_GEOMETRY_MAX_SPEC},
{"output_implementation_max_geometry",
"Output maximum number of vertices in the geometry shader supported by the implementation",
GridRenderCase::FLAG_GEOMETRY_MAX_IMPLEMENTATION},
{"output_required_max_invocations", "Minimum maximum number of geometry shader invocations",
GridRenderCase::FLAG_GEOMETRY_INVOCATIONS_MAX_SPEC},
{"output_implementation_max_invocations",
"Maximum number of geometry shader invocations supported by the implementation",
GridRenderCase::FLAG_GEOMETRY_INVOCATIONS_MAX_IMPLEMENTATION},
};
for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(cases); ++ndx)
limitGroup->addChild(new GridRenderCase(m_context, cases[ndx].name, cases[ndx].desc, cases[ndx].flags));
}
// .scatter
{
scatterGroup->addChild(new GridRenderCase(
m_context, "geometry_scatter_instances",
"Each geometry shader instance outputs its primitives far from other instances of the same execution",
GridRenderCase::FLAG_GEOMETRY_SCATTER_INSTANCES));
scatterGroup->addChild(new GridRenderCase(
m_context, "geometry_scatter_primitives",
"Each geometry shader instance outputs its primitives far from other primitives of the same instance",
GridRenderCase::FLAG_GEOMETRY_SCATTER_PRIMITIVES | GridRenderCase::FLAG_GEOMETRY_SEPARATE_PRIMITIVES));
scatterGroup->addChild(new GridRenderCase(
m_context, "geometry_scatter_layers",
"Each geometry shader instance outputs its primitives to multiple layers and far from other primitives "
"of the same instance",
GridRenderCase::FLAG_GEOMETRY_SCATTER_LAYERS | GridRenderCase::FLAG_GEOMETRY_SEPARATE_PRIMITIVES));
}
}
// .feedback
{
static const struct PrimitiveCaseConfig
{
const char *name;
const char *description;
FeedbackPrimitiveTypeCase::TessellationOutputType tessellationOutput;
FeedbackPrimitiveTypeCase::TessellationPointMode tessellationPointMode;
FeedbackPrimitiveTypeCase::GeometryOutputType geometryOutputType;
} caseConfigs[] = {
// tess output triangles -> geo input triangles, output points
{"tessellation_output_triangles_geometry_output_points",
"Tessellation outputs triangles, geometry outputs points",
FeedbackPrimitiveTypeCase::TESSELLATION_OUT_TRIANGLES,
FeedbackPrimitiveTypeCase::TESSELLATION_POINTMODE_OFF, FeedbackPrimitiveTypeCase::GEOMETRY_OUTPUT_POINTS},
// tess output quads <-> geo input triangles, output points
{"tessellation_output_quads_geometry_output_points", "Tessellation outputs quads, geometry outputs points",
FeedbackPrimitiveTypeCase::TESSELLATION_OUT_QUADS, FeedbackPrimitiveTypeCase::TESSELLATION_POINTMODE_OFF,
FeedbackPrimitiveTypeCase::GEOMETRY_OUTPUT_POINTS},
// tess output isolines <-> geo input lines, output points
{"tessellation_output_isolines_geometry_output_points",
"Tessellation outputs isolines, geometry outputs points",
FeedbackPrimitiveTypeCase::TESSELLATION_OUT_ISOLINES,
FeedbackPrimitiveTypeCase::TESSELLATION_POINTMODE_OFF, FeedbackPrimitiveTypeCase::GEOMETRY_OUTPUT_POINTS},
// tess output triangles, point_mode <-> geo input points, output lines
{"tessellation_output_triangles_point_mode_geometry_output_lines",
"Tessellation outputs triangles in point mode, geometry outputs lines",
FeedbackPrimitiveTypeCase::TESSELLATION_OUT_TRIANGLES,
FeedbackPrimitiveTypeCase::TESSELLATION_POINTMODE_ON, FeedbackPrimitiveTypeCase::GEOMETRY_OUTPUT_LINES},
// tess output quads, point_mode <-> geo input points, output lines
{"tessellation_output_quads_point_mode_geometry_output_lines",
"Tessellation outputs quads in point mode, geometry outputs lines",
FeedbackPrimitiveTypeCase::TESSELLATION_OUT_QUADS, FeedbackPrimitiveTypeCase::TESSELLATION_POINTMODE_ON,
FeedbackPrimitiveTypeCase::GEOMETRY_OUTPUT_LINES},
// tess output isolines, point_mode <-> geo input points, output triangles
{"tessellation_output_isolines_point_mode_geometry_output_triangles",
"Tessellation outputs isolines in point mode, geometry outputs triangles",
FeedbackPrimitiveTypeCase::TESSELLATION_OUT_ISOLINES, FeedbackPrimitiveTypeCase::TESSELLATION_POINTMODE_ON,
FeedbackPrimitiveTypeCase::GEOMETRY_OUTPUT_TRIANGLES},
};
for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(caseConfigs); ++ndx)
{
feedbackGroup->addChild(new FeedbackPrimitiveTypeCase(
m_context, caseConfigs[ndx].name, caseConfigs[ndx].description, caseConfigs[ndx].tessellationOutput,
caseConfigs[ndx].tessellationPointMode, caseConfigs[ndx].geometryOutputType));
}
feedbackGroup->addChild(new FeedbackRecordVariableSelectionCase(
m_context, "record_variable_selection",
"Record a variable that has been declared as an output variable in multiple shader stages"));
}
// .point_size
{
static const struct PointSizeCaseConfig
{
const int caseMask;
const bool isSupportedInGL; // is this case supported in OpenGL
} caseConfigs[] = {
{PointSizeCase::FLAG_VERTEX_SET, true},
{PointSizeCase::FLAG_TESSELLATION_EVALUATION_SET, true},
{PointSizeCase::FLAG_GEOMETRY_SET, true},
{PointSizeCase::FLAG_VERTEX_SET | PointSizeCase::FLAG_TESSELLATION_CONTROL_SET, false},
{PointSizeCase::FLAG_VERTEX_SET | PointSizeCase::FLAG_TESSELLATION_EVALUATION_SET, true},
{PointSizeCase::FLAG_VERTEX_SET | PointSizeCase::FLAG_TESSELLATION_DONT_SET, false},
{PointSizeCase::FLAG_VERTEX_SET | PointSizeCase::FLAG_GEOMETRY_SET, true},
{PointSizeCase::FLAG_VERTEX_SET | PointSizeCase::FLAG_TESSELLATION_EVALUATION_SET |
PointSizeCase::FLAG_GEOMETRY_SET,
true},
{PointSizeCase::FLAG_VERTEX_SET | PointSizeCase::FLAG_TESSELLATION_ADD | PointSizeCase::FLAG_GEOMETRY_ADD,
true},
{PointSizeCase::FLAG_VERTEX_SET | PointSizeCase::FLAG_TESSELLATION_EVALUATION_SET |
PointSizeCase::FLAG_GEOMETRY_DONT_SET,
false},
};
for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(caseConfigs); ++ndx)
{
if (m_isGL45 && !caseConfigs[ndx].isSupportedInGL)
continue;
const std::string name = PointSizeCase::genTestCaseName(caseConfigs[ndx].caseMask);
const std::string desc = PointSizeCase::genTestCaseDescription(caseConfigs[ndx].caseMask);
pointSizeGroup->addChild(
new PointSizeCase(m_context, name.c_str(), desc.c_str(), caseConfigs[ndx].caseMask));
}
}
}
} // namespace Functional
} // namespace gles31
} // namespace deqp