You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3255 lines
119 KiB
3255 lines
119 KiB
/*-------------------------------------------------------------------------
|
|
* 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 supportsES32 = glu::contextSupports(contextType, glu::ApiType::es(3, 2));
|
|
std::map<std::string, std::string> shaderArgs;
|
|
|
|
shaderArgs["VERSION_DECL"] = glu::getGLSLVersionDeclaration(glu::getContextTypeGLSLVersion(contextType));
|
|
shaderArgs["EXTENSION_GEOMETRY_SHADER"] = (supportsES32) ? ("") : ("#extension GL_EXT_geometry_shader : require\n");
|
|
shaderArgs["EXTENSION_TESSELATION_SHADER"] = (supportsES32) ? ("") : ("#extension GL_EXT_tessellation_shader : 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;
|
|
deUint32 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 supportsES32 = glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2));
|
|
|
|
if (!supportsES32 &&
|
|
(!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;
|
|
deUint32 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 supportsES32 = glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2));
|
|
|
|
if (!supportsES32 &&
|
|
(!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;
|
|
deUint32 m_patchBuffer;
|
|
deUint32 m_feedbackID;
|
|
deUint32 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 supportsES32 = glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2));
|
|
|
|
if (!supportsES32 &&
|
|
(!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();
|
|
|
|
// 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)
|
|
{
|
|
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
|
|
{
|
|
std::vector<std::string> requiredExtensions;
|
|
const bool supportsES32 = glu::contextSupports(m_context.getRenderContext().getType(), 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 GL_EXT_tessellation_point_size : require\n"))
|
|
<< "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 GL_EXT_tessellation_point_size : require\n"))
|
|
<< "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 GL_EXT_geometry_point_size : require\n"))
|
|
<< "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;
|
|
deUint32 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();
|
|
const bool supportsES32 = glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2));
|
|
|
|
// Requirements
|
|
|
|
if (!supportsES32 &&
|
|
(!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;
|
|
deUint32 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));
|
|
|
|
if (!supportsES32 &&
|
|
(!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());
|
|
}
|
|
|
|
} // anonymous
|
|
|
|
TessellationGeometryInteractionTests::TessellationGeometryInteractionTests (Context& context)
|
|
: TestCaseGroup(context, "tessellation_geometry_interaction", "Tessellation and geometry shader interaction tests")
|
|
{
|
|
}
|
|
|
|
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 int caseFlags[] =
|
|
{
|
|
PointSizeCase::FLAG_VERTEX_SET,
|
|
PointSizeCase::FLAG_TESSELLATION_EVALUATION_SET,
|
|
PointSizeCase::FLAG_GEOMETRY_SET,
|
|
PointSizeCase::FLAG_VERTEX_SET | PointSizeCase::FLAG_TESSELLATION_CONTROL_SET,
|
|
PointSizeCase::FLAG_VERTEX_SET | PointSizeCase::FLAG_TESSELLATION_EVALUATION_SET,
|
|
PointSizeCase::FLAG_VERTEX_SET | PointSizeCase::FLAG_TESSELLATION_DONT_SET,
|
|
PointSizeCase::FLAG_VERTEX_SET | PointSizeCase::FLAG_GEOMETRY_SET,
|
|
PointSizeCase::FLAG_VERTEX_SET | PointSizeCase::FLAG_TESSELLATION_EVALUATION_SET | PointSizeCase::FLAG_GEOMETRY_SET,
|
|
PointSizeCase::FLAG_VERTEX_SET | PointSizeCase::FLAG_TESSELLATION_ADD | PointSizeCase::FLAG_GEOMETRY_ADD,
|
|
PointSizeCase::FLAG_VERTEX_SET | PointSizeCase::FLAG_TESSELLATION_EVALUATION_SET | PointSizeCase::FLAG_GEOMETRY_DONT_SET,
|
|
};
|
|
|
|
for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(caseFlags); ++ndx)
|
|
{
|
|
const std::string name = PointSizeCase::genTestCaseName(caseFlags[ndx]);
|
|
const std::string desc = PointSizeCase::genTestCaseDescription(caseFlags[ndx]);
|
|
|
|
pointSizeGroup->addChild(new PointSizeCase(m_context, name.c_str(), desc.c_str(), caseFlags[ndx]));
|
|
}
|
|
}
|
|
}
|
|
|
|
} // Functional
|
|
} // gles31
|
|
} // deqp
|