blob: 3bac8e9ca74146963dc3e0d5b15ec5863a1c1c51 [file] [log] [blame]
/*------------------------------------------------------------------------
* Vulkan Conformance Tests
* ------------------------
*
* Copyright (c) 2014 The Android Open Source Project
* Copyright (c) 2016 The Khronos Group Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*//*!
* \file
* \brief Tessellation Invariance Tests
*//*--------------------------------------------------------------------*/
#include "vktTessellationInvarianceTests.hpp"
#include "vktTestCaseUtil.hpp"
#include "vktTessellationUtil.hpp"
#include "tcuTestLog.hpp"
#include "tcuVectorUtil.hpp"
#include "vkDefs.hpp"
#include "vkBarrierUtil.hpp"
#include "vkQueryUtil.hpp"
#include "vkBuilderUtil.hpp"
#include "vkImageUtil.hpp"
#include "vkTypeUtil.hpp"
#include "vkCmdUtil.hpp"
#include "vkObjUtil.hpp"
#include "deUniquePtr.hpp"
#include "deStringUtil.hpp"
#include "deRandom.hpp"
#include <string>
#include <vector>
#include <set>
namespace vkt
{
namespace tessellation
{
using namespace vk;
namespace
{
enum Constants
{
NUM_EXTRA_TESS_GEOM_INVOCATIONS =
4, // Need to set this value properly to allocate enough memory to store vertices data
NUM_TESS_LEVELS = 6, // two inner and four outer levels
};
enum WindingUsage
{
WINDING_USAGE_CCW = 0,
WINDING_USAGE_CW,
WINDING_USAGE_VARY,
WINDING_USAGE_LAST,
};
inline WindingUsage getWindingUsage(const Winding winding)
{
const WindingUsage usage = winding == WINDING_CCW ? WINDING_USAGE_CCW :
winding == WINDING_CW ? WINDING_USAGE_CW :
WINDING_USAGE_LAST;
DE_ASSERT(usage != WINDING_USAGE_LAST);
return usage;
}
std::vector<Winding> getWindingCases(const WindingUsage windingUsage)
{
std::vector<Winding> cases;
switch (windingUsage)
{
case WINDING_USAGE_CCW:
cases.push_back(WINDING_CCW);
break;
case WINDING_USAGE_CW:
cases.push_back(WINDING_CW);
break;
case WINDING_USAGE_VARY:
cases.push_back(WINDING_CCW);
cases.push_back(WINDING_CW);
break;
default:
DE_ASSERT(false);
break;
}
return cases;
}
enum PointModeUsage
{
POINT_MODE_USAGE_DONT_USE = 0,
POINT_MODE_USAGE_USE,
POINT_MODE_USAGE_VARY,
POINT_MODE_USAGE_LAST,
};
inline PointModeUsage getPointModeUsage(const bool usePointMode)
{
return usePointMode ? POINT_MODE_USAGE_USE : POINT_MODE_USAGE_DONT_USE;
}
std::vector<bool> getUsePointModeCases(const PointModeUsage pointModeUsage)
{
std::vector<bool> cases;
switch (pointModeUsage)
{
case POINT_MODE_USAGE_DONT_USE:
cases.push_back(false);
break;
case POINT_MODE_USAGE_USE:
cases.push_back(true);
break;
case POINT_MODE_USAGE_VARY:
cases.push_back(false);
cases.push_back(true);
break;
default:
DE_ASSERT(false);
break;
}
return cases;
}
//! Data captured in the shader per output primitive (in geometry stage).
struct PerPrimitive
{
int32_t patchPrimitiveID; //!< gl_PrimitiveID in tessellation evaluation shader
int32_t primitiveID; //!< ID of an output primitive in geometry shader (user-defined)
int32_t unused_padding[2];
tcu::Vec4 tessCoord[3]; //!< 3 coords for triangles/quads, 2 for isolines, 1 for point mode. Vec4 due to alignment.
};
typedef std::vector<PerPrimitive> PerPrimitiveVec;
inline bool byPatchPrimitiveID(const PerPrimitive &a, const PerPrimitive &b)
{
return a.patchPrimitiveID < b.patchPrimitiveID;
}
inline std::string getProgramName(const std::string &baseName, const Winding winding, const bool usePointMode)
{
std::ostringstream str;
str << baseName << "_" << getWindingShaderName(winding) << (usePointMode ? "_point_mode" : "");
return str.str();
}
inline std::string getProgramName(const std::string &baseName, const bool usePointMode)
{
std::ostringstream str;
str << baseName << (usePointMode ? "_point_mode" : "");
return str.str();
}
inline std::string getProgramDescription(const Winding winding, const bool usePointMode)
{
std::ostringstream str;
str << "winding mode " << getWindingShaderName(winding) << ", " << (usePointMode ? "" : "don't ")
<< "use point mode";
return str.str();
}
template <typename T, int N>
std::vector<T> arrayToVector(const T (&arr)[N])
{
return std::vector<T>(DE_ARRAY_BEGIN(arr), DE_ARRAY_END(arr));
}
template <typename T, int N>
T arrayMax(const T (&arr)[N])
{
return *std::max_element(DE_ARRAY_BEGIN(arr), DE_ARRAY_END(arr));
}
template <int Size>
inline tcu::Vector<bool, Size> singleTrueMask(int index)
{
DE_ASSERT(de::inBounds(index, 0, Size));
tcu::Vector<bool, Size> result;
result[index] = true;
return result;
}
template <typename ContainerT, typename T>
inline bool contains(const ContainerT &c, const T &key)
{
return c.find(key) != c.end();
}
template <typename SeqT, int Size, typename Pred>
class LexCompare
{
public:
LexCompare(void) : m_pred(Pred())
{
}
bool operator()(const SeqT &a, const SeqT &b) const
{
for (int i = 0; i < Size; ++i)
{
if (m_pred(a[i], b[i]))
return true;
if (m_pred(b[i], a[i]))
return false;
}
return false;
}
private:
Pred m_pred;
};
template <int Size>
class VecLexLessThan : public LexCompare<tcu::Vector<float, Size>, Size, std::less<float>>
{
};
//! Add default programs for invariance tests.
//! Creates multiple shader programs for combinations of winding and point mode.
//! mirrorCoords - special mode where some tessellation coordinates are mirrored in tessellation evaluation shader.
//! This is used by symmetric outer edge test.
void addDefaultPrograms(vk::SourceCollections &programCollection, const TessPrimitiveType primitiveType,
const SpacingMode spacingMode, const WindingUsage windingUsage,
const PointModeUsage pointModeUsage, const bool mirrorCoords = false)
{
// Vertex shader
{
std::ostringstream src;
src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES) << "\n"
<< "\n"
<< "layout(location = 0) in highp float in_v_attr;\n"
<< "layout(location = 0) out highp float in_tc_attr;\n"
<< "\n"
<< "void main (void)\n"
<< "{\n"
<< " in_tc_attr = in_v_attr;\n"
<< "}\n";
programCollection.glslSources.add("vert") << glu::VertexSource(src.str());
}
// Tessellation control shader
{
std::ostringstream src;
src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES) << "\n"
<< "#extension GL_EXT_tessellation_shader : require\n"
<< "\n"
<< "layout(vertices = 1) out;\n"
<< "\n"
<< "layout(location = 0) in highp float in_tc_attr[];\n"
<< "\n"
<< "void main (void)\n"
<< "{\n"
<< " gl_TessLevelInner[0] = in_tc_attr[0];\n"
<< " gl_TessLevelInner[1] = in_tc_attr[1];\n"
<< "\n"
<< " gl_TessLevelOuter[0] = in_tc_attr[2];\n"
<< " gl_TessLevelOuter[1] = in_tc_attr[3];\n"
<< " gl_TessLevelOuter[2] = in_tc_attr[4];\n"
<< " gl_TessLevelOuter[3] = in_tc_attr[5];\n"
<< "}\n";
programCollection.glslSources.add("tesc") << glu::TessellationControlSource(src.str());
}
const std::string perVertexInterfaceBlock = "VertexData {\n" // no in/out qualifier
" vec4 in_gs_tessCoord;\n" // w component is used by mirroring test
" int in_gs_primitiveID;\n"
"}"; // no newline nor semicolon
// Alternative tess coordinates handling code
std::ostringstream tessEvalCoordSrc;
if (mirrorCoords)
switch (primitiveType)
{
case TESSPRIMITIVETYPE_TRIANGLES:
tessEvalCoordSrc
<< " float x = gl_TessCoord.x;\n"
<< " float y = gl_TessCoord.y;\n"
<< " float z = gl_TessCoord.z;\n"
<< "\n"
<< " // Mirror one half of each outer edge onto the other half, except the endpoints (because they "
"belong to two edges)\n"
<< " ib_out.in_gs_tessCoord = z == 0.0 && x > 0.5 && x != 1.0 ? vec4(1.0-x, 1.0-y, 0.0, 1.0)\n"
<< " : y == 0.0 && z > 0.5 && z != 1.0 ? vec4(1.0-x, 0.0, 1.0-z, 1.0)\n"
<< " : x == 0.0 && y > 0.5 && y != 1.0 ? vec4( 0.0, 1.0-y, 1.0-z, 1.0)\n"
<< " : vec4(x, y, z, 0.0);\n";
break;
case TESSPRIMITIVETYPE_QUADS:
tessEvalCoordSrc << " float x = gl_TessCoord.x;\n"
<< " float y = gl_TessCoord.y;\n"
<< "\n"
<< " // Mirror one half of each outer edge onto the other half, except the endpoints "
"(because they belong to two edges)\n"
<< " ib_out.in_gs_tessCoord = (x == 0.0 || x == 1.0) && y > 0.5 && y != 1.0 ? vec4( "
" x, 1.0-y, 0.0, 1.0)\n"
<< " : (y == 0.0 || y == 1.0) && x > 0.5 && x != 1.0 ? "
"vec4(1.0-x, y, 0.0, 1.0)\n"
<< " : vec4(x, y, 0.0, 0.0);\n";
break;
case TESSPRIMITIVETYPE_ISOLINES:
tessEvalCoordSrc
<< " float x = gl_TessCoord.x;\n"
<< " float y = gl_TessCoord.y;\n"
<< "\n"
<< " // Mirror one half of each outer edge onto the other half\n"
<< " ib_out.in_gs_tessCoord = (x == 0.0 || x == 1.0) && y > 0.5 ? vec4(x, 1.0-y, 0.0, 1.0)\n"
<< " : vec4(x, y, 0.0, 0.0);\n";
break;
default:
DE_ASSERT(false);
return;
}
else
tessEvalCoordSrc << " ib_out.in_gs_tessCoord = vec4(gl_TessCoord, 0.0);\n";
const std::vector<Winding> windingCases = getWindingCases(windingUsage);
const std::vector<bool> usePointModeCases = getUsePointModeCases(pointModeUsage);
for (std::vector<Winding>::const_iterator windingIter = windingCases.begin(); windingIter != windingCases.end();
++windingIter)
for (std::vector<bool>::const_iterator usePointModeIter = usePointModeCases.begin();
usePointModeIter != usePointModeCases.end(); ++usePointModeIter)
{
// Tessellation evaluation shader
{
std::ostringstream src;
src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES) << "\n"
<< "#extension GL_EXT_tessellation_shader : require\n"
<< "\n"
<< "layout(" << getTessPrimitiveTypeShaderName(primitiveType) << ", "
<< getSpacingModeShaderName(spacingMode) << ", " << getWindingShaderName(*windingIter)
<< (*usePointModeIter ? ", point_mode" : "") << ") in;\n"
<< "\n"
<< "layout(location = 0) out " << perVertexInterfaceBlock << " ib_out;\n"
<< "\n"
<< "void main (void)\n"
<< "{\n"
<< tessEvalCoordSrc.str() << " ib_out.in_gs_primitiveID = gl_PrimitiveID;\n"
<< "}\n";
programCollection.glslSources.add(getProgramName("tese", *windingIter, *usePointModeIter))
<< glu::TessellationEvaluationSource(src.str());
}
} // for windingNdx, usePointModeNdx
// Geometry shader: data is captured here.
{
for (std::vector<bool>::const_iterator usePointModeIter = usePointModeCases.begin();
usePointModeIter != usePointModeCases.end(); ++usePointModeIter)
{
const int numVertices = numVerticesPerPrimitive(
primitiveType, *usePointModeIter); // Primitives that the tessellated patch comprises of.
std::ostringstream src;
src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES) << "\n"
<< "#extension GL_EXT_geometry_shader : require\n"
<< "\n"
<< "layout(" << getGeometryShaderInputPrimitiveTypeShaderName(primitiveType, *usePointModeIter)
<< ") in;\n"
<< "layout(" << getGeometryShaderOutputPrimitiveTypeShaderName(primitiveType, *usePointModeIter)
<< ", max_vertices = " << numVertices << ") out;\n"
<< "\n"
<< "layout(location = 0) in " << perVertexInterfaceBlock << " ib_in[];\n"
<< "\n"
<< "struct PerPrimitive {\n"
<< " int patchPrimitiveID;\n"
<< " int primitiveID;\n"
<< " vec4 tessCoord[3];\n"
<< "};\n"
<< "\n"
<< "layout(set = 0, binding = 0, std430) coherent restrict buffer Output {\n"
<< " int numPrimitives;\n"
<< " PerPrimitive primitive[];\n"
<< "} sb_out;\n"
<< "\n"
<< "void main (void)\n"
<< "{\n"
<< " int index = atomicAdd(sb_out.numPrimitives, 1);\n"
<< " sb_out.primitive[index].patchPrimitiveID = ib_in[0].in_gs_primitiveID;\n"
<< " sb_out.primitive[index].primitiveID = index;\n";
for (int i = 0; i < numVertices; ++i)
src << " sb_out.primitive[index].tessCoord[" << i << "] = ib_in[" << i << "].in_gs_tessCoord;\n";
for (int i = 0; i < numVertices; ++i)
src << "\n"
<< " gl_Position = vec4(0.0);\n"
<< " EmitVertex();\n";
src << "}\n";
programCollection.glslSources.add(getProgramName("geom", *usePointModeIter))
<< glu::GeometrySource(src.str());
}
}
}
//! A description of an outer edge of a triangle, quad or isolines.
//! An outer edge can be described by the index of a u/v/w coordinate
//! and the coordinate's value along that edge.
struct OuterEdgeDescription
{
int constantCoordinateIndex;
float constantCoordinateValueChoices[2];
int numConstantCoordinateValueChoices;
OuterEdgeDescription(const int i, const float c0) : constantCoordinateIndex(i), numConstantCoordinateValueChoices(1)
{
constantCoordinateValueChoices[0] = c0;
}
OuterEdgeDescription(const int i, const float c0, const float c1)
: constantCoordinateIndex(i)
, numConstantCoordinateValueChoices(2)
{
constantCoordinateValueChoices[0] = c0;
constantCoordinateValueChoices[1] = c1;
}
std::string description(void) const
{
static const char *const coordinateNames[] = {"u", "v", "w"};
std::string result;
for (int i = 0; i < numConstantCoordinateValueChoices; ++i)
result += std::string() + (i > 0 ? " or " : "") + coordinateNames[constantCoordinateIndex] + "=" +
de::toString(constantCoordinateValueChoices[i]);
return result;
}
bool contains(const tcu::Vec3 &v) const
{
for (int i = 0; i < numConstantCoordinateValueChoices; ++i)
if (v[constantCoordinateIndex] == constantCoordinateValueChoices[i])
return true;
return false;
}
};
std::vector<OuterEdgeDescription> outerEdgeDescriptions(const TessPrimitiveType primType)
{
static const OuterEdgeDescription triangleOuterEdgeDescriptions[3] = {
OuterEdgeDescription(0, 0.0f), OuterEdgeDescription(1, 0.0f), OuterEdgeDescription(2, 0.0f)};
static const OuterEdgeDescription quadOuterEdgeDescriptions[4] = {
OuterEdgeDescription(0, 0.0f), OuterEdgeDescription(1, 0.0f), OuterEdgeDescription(0, 1.0f),
OuterEdgeDescription(1, 1.0f)};
static const OuterEdgeDescription isolinesOuterEdgeDescriptions[1] = {
OuterEdgeDescription(0, 0.0f, 1.0f),
};
switch (primType)
{
case TESSPRIMITIVETYPE_TRIANGLES:
return arrayToVector(triangleOuterEdgeDescriptions);
case TESSPRIMITIVETYPE_QUADS:
return arrayToVector(quadOuterEdgeDescriptions);
case TESSPRIMITIVETYPE_ISOLINES:
return arrayToVector(isolinesOuterEdgeDescriptions);
default:
DE_ASSERT(false);
return std::vector<OuterEdgeDescription>();
}
}
namespace InvariantOuterEdge
{
struct CaseDefinition
{
TessPrimitiveType primitiveType;
SpacingMode spacingMode;
Winding winding;
bool usePointMode;
};
typedef std::set<tcu::Vec3, VecLexLessThan<3>> Vec3Set;
std::vector<float> generateRandomPatchTessLevels(const int numPatches, const int constantOuterLevelIndex,
const float constantOuterLevel, de::Random &rnd)
{
std::vector<float> tessLevels(numPatches * NUM_TESS_LEVELS);
for (int patchNdx = 0; patchNdx < numPatches; ++patchNdx)
{
float *const inner = &tessLevels[patchNdx * NUM_TESS_LEVELS + 0];
float *const outer = &tessLevels[patchNdx * NUM_TESS_LEVELS + 2];
for (int j = 0; j < 2; ++j)
inner[j] = rnd.getFloat(1.0f, 62.0f);
for (int j = 0; j < 4; ++j)
outer[j] = j == constantOuterLevelIndex ? constantOuterLevel : rnd.getFloat(1.0f, 62.0f);
}
return tessLevels;
}
std::vector<float> generatePatchTessLevels(const int numPatches, const int constantOuterLevelIndex,
const float constantOuterLevel)
{
de::Random rnd(123);
return generateRandomPatchTessLevels(numPatches, constantOuterLevelIndex, constantOuterLevel, rnd);
}
int multiplePatchReferencePrimitiveCount(const TessPrimitiveType primitiveType, const SpacingMode spacingMode,
const bool usePointMode, const float *levels, int numPatches)
{
int result = 0;
for (int patchNdx = 0; patchNdx < numPatches; ++patchNdx)
result +=
referencePrimitiveCount(primitiveType, spacingMode, usePointMode, &levels[NUM_TESS_LEVELS * patchNdx + 0],
&levels[NUM_TESS_LEVELS * patchNdx + 2]);
return result;
}
template <std::size_t N>
int computeMaxPrimitiveCount(const int numPatchesToDraw, const TessPrimitiveType primitiveType,
const SpacingMode spacingMode, const bool usePointMode,
const float (&singleOuterEdgeLevels)[N])
{
const int outerEdgeIndex = 0; // outer-edge index doesn't affect vertex count
const std::vector<float> patchTessLevels =
generatePatchTessLevels(numPatchesToDraw, outerEdgeIndex, arrayMax(singleOuterEdgeLevels));
return multiplePatchReferencePrimitiveCount(primitiveType, spacingMode, usePointMode, &patchTessLevels[0],
numPatchesToDraw);
}
void logOuterTessellationLevel(tcu::TestLog &log, const float tessLevel, const OuterEdgeDescription &edgeDesc)
{
log << tcu::TestLog::Message << "Testing with outer tessellation level " << tessLevel << " for the "
<< edgeDesc.description() << " edge, and with various levels for other edges, and with all programs"
<< tcu::TestLog::EndMessage;
}
void logPrimitiveCountError(tcu::TestLog &log, const int numPatchesToDraw, int numPrimitives,
const int refNumPrimitives, const std::vector<float> &patchTessLevels)
{
log << tcu::TestLog::Message << "Failure: the number of generated primitives is " << numPrimitives
<< ", expected at least " << refNumPrimitives << tcu::TestLog::EndMessage;
if (numPatchesToDraw == 1)
log << tcu::TestLog::Message
<< "Note: rendered one patch; tessellation levels are (in order [inner0, inner1, outer0, outer1, outer2, "
"outer3]):\n"
<< containerStr(patchTessLevels, NUM_TESS_LEVELS) << tcu::TestLog::EndMessage;
else
log << tcu::TestLog::Message << "Note: rendered " << numPatchesToDraw << " patches in one draw call; "
<< "tessellation levels for each patch are (in order [inner0, inner1, outer0, outer1, outer2, outer3]):\n"
<< containerStr(patchTessLevels, NUM_TESS_LEVELS) << tcu::TestLog::EndMessage;
}
class BaseTestInstance : public TestInstance
{
public:
struct DrawResult
{
bool success;
int refNumPrimitives;
int numPrimitiveVertices;
int32_t numPrimitives;
PerPrimitiveVec primitives;
};
BaseTestInstance(Context &context, const CaseDefinition caseDef, const int numPatchesToDraw);
DrawResult draw(const uint32_t vertexCount, const std::vector<float> &patchTessLevels, const Winding winding,
const bool usePointMode);
void uploadVertexAttributes(const std::vector<float> &vertexData);
protected:
static const float m_singleOuterEdgeLevels[];
const CaseDefinition m_caseDef;
const int m_numPatchesToDraw;
const VkFormat m_vertexFormat;
const uint32_t m_vertexStride;
const std::vector<OuterEdgeDescription> m_edgeDescriptions;
const int m_maxNumPrimitivesInDrawCall;
const VkDeviceSize m_vertexDataSizeBytes;
const Buffer m_vertexBuffer;
const int m_resultBufferPrimitiveDataOffset;
const VkDeviceSize m_resultBufferSizeBytes;
const Buffer m_resultBuffer;
Unique<VkDescriptorSetLayout> m_descriptorSetLayout;
Unique<VkDescriptorPool> m_descriptorPool;
Unique<VkDescriptorSet> m_descriptorSet;
Unique<VkRenderPass> m_renderPass;
Unique<VkFramebuffer> m_framebuffer;
Unique<VkPipelineLayout> m_pipelineLayout;
Unique<VkCommandPool> m_cmdPool;
Unique<VkCommandBuffer> m_cmdBuffer;
};
const float BaseTestInstance::m_singleOuterEdgeLevels[] = {1.0f, 1.2f, 1.9f, 2.3f, 2.8f, 3.3f,
3.8f, 10.2f, 1.6f, 24.4f, 24.7f, 63.0f};
BaseTestInstance::BaseTestInstance(Context &context, const CaseDefinition caseDef, const int numPatchesToDraw)
: TestInstance(context)
, m_caseDef(caseDef)
, m_numPatchesToDraw(numPatchesToDraw)
, m_vertexFormat(VK_FORMAT_R32_SFLOAT)
, m_vertexStride(tcu::getPixelSize(mapVkFormat(m_vertexFormat)))
, m_edgeDescriptions(outerEdgeDescriptions(m_caseDef.primitiveType))
, m_maxNumPrimitivesInDrawCall(NUM_EXTRA_TESS_GEOM_INVOCATIONS *
computeMaxPrimitiveCount(m_numPatchesToDraw, caseDef.primitiveType,
caseDef.spacingMode, caseDef.usePointMode,
m_singleOuterEdgeLevels))
, m_vertexDataSizeBytes(NUM_TESS_LEVELS * m_numPatchesToDraw * m_vertexStride)
, m_vertexBuffer(m_context.getDeviceInterface(), m_context.getDevice(), m_context.getDefaultAllocator(),
makeBufferCreateInfo(m_vertexDataSizeBytes, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT),
MemoryRequirement::HostVisible)
, m_resultBufferPrimitiveDataOffset((int)sizeof(int32_t) * 4)
, m_resultBufferSizeBytes(m_resultBufferPrimitiveDataOffset + m_maxNumPrimitivesInDrawCall * sizeof(PerPrimitive))
, m_resultBuffer(m_context.getDeviceInterface(), m_context.getDevice(), m_context.getDefaultAllocator(),
makeBufferCreateInfo(m_resultBufferSizeBytes, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT),
MemoryRequirement::HostVisible)
, m_descriptorSetLayout(DescriptorSetLayoutBuilder()
.addSingleBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_GEOMETRY_BIT)
.build(m_context.getDeviceInterface(), m_context.getDevice()))
, m_descriptorPool(DescriptorPoolBuilder()
.addType(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER)
.build(m_context.getDeviceInterface(), m_context.getDevice(),
VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, 1u))
, m_descriptorSet(makeDescriptorSet(m_context.getDeviceInterface(), m_context.getDevice(), *m_descriptorPool,
*m_descriptorSetLayout))
, m_renderPass(makeRenderPassWithoutAttachments(m_context.getDeviceInterface(), m_context.getDevice()))
, m_framebuffer(
makeFramebuffer(m_context.getDeviceInterface(), m_context.getDevice(), *m_renderPass, 0u, DE_NULL, 1u, 1u))
, m_pipelineLayout(
makePipelineLayout(m_context.getDeviceInterface(), m_context.getDevice(), *m_descriptorSetLayout))
, m_cmdPool(makeCommandPool(m_context.getDeviceInterface(), m_context.getDevice(),
m_context.getUniversalQueueFamilyIndex()))
, m_cmdBuffer(allocateCommandBuffer(m_context.getDeviceInterface(), m_context.getDevice(), *m_cmdPool,
VK_COMMAND_BUFFER_LEVEL_PRIMARY))
{
requireFeatures(context.getInstanceInterface(), context.getPhysicalDevice(),
FEATURE_TESSELLATION_SHADER | FEATURE_GEOMETRY_SHADER | FEATURE_VERTEX_PIPELINE_STORES_AND_ATOMICS);
const VkDescriptorBufferInfo resultBufferInfo =
makeDescriptorBufferInfo(m_resultBuffer.get(), 0ull, m_resultBufferSizeBytes);
DescriptorSetUpdateBuilder()
.writeSingle(*m_descriptorSet, DescriptorSetUpdateBuilder::Location::binding(0u),
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, &resultBufferInfo)
.update(m_context.getDeviceInterface(), m_context.getDevice());
}
//! patchTessLevels are tessellation levels for all drawn patches.
BaseTestInstance::DrawResult BaseTestInstance::draw(const uint32_t vertexCount,
const std::vector<float> &patchTessLevels, const Winding winding,
const bool usePointMode)
{
const DeviceInterface &vk = m_context.getDeviceInterface();
const VkDevice device = m_context.getDevice();
const VkQueue queue = m_context.getUniversalQueue();
const Unique<VkPipeline> pipeline(
GraphicsPipelineBuilder()
.setPatchControlPoints(NUM_TESS_LEVELS)
.setVertexInputSingleAttribute(m_vertexFormat, m_vertexStride)
.setShader(vk, device, VK_SHADER_STAGE_VERTEX_BIT, m_context.getBinaryCollection().get("vert"), DE_NULL)
.setShader(vk, device, VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT,
m_context.getBinaryCollection().get("tesc"), DE_NULL)
.setShader(vk, device, VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT,
m_context.getBinaryCollection().get(getProgramName("tese", winding, usePointMode)), DE_NULL)
.setShader(vk, device, VK_SHADER_STAGE_GEOMETRY_BIT,
m_context.getBinaryCollection().get(getProgramName("geom", usePointMode)), DE_NULL)
.build(vk, device, *m_pipelineLayout, *m_renderPass));
{
const Allocation &alloc = m_resultBuffer.getAllocation();
deMemset(alloc.getHostPtr(), 0, static_cast<std::size_t>(m_resultBufferSizeBytes));
flushAlloc(vk, device, alloc);
}
beginCommandBuffer(vk, *m_cmdBuffer);
beginRenderPassWithRasterizationDisabled(vk, *m_cmdBuffer, *m_renderPass, *m_framebuffer);
vk.cmdBindPipeline(*m_cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline);
vk.cmdBindDescriptorSets(*m_cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *m_pipelineLayout, 0u, 1u,
&m_descriptorSet.get(), 0u, DE_NULL);
{
const VkDeviceSize vertexBufferOffset = 0ull;
vk.cmdBindVertexBuffers(*m_cmdBuffer, 0u, 1u, &m_vertexBuffer.get(), &vertexBufferOffset);
}
vk.cmdDraw(*m_cmdBuffer, vertexCount, 1u, 0u, 0u);
endRenderPass(vk, *m_cmdBuffer);
{
const VkBufferMemoryBarrier shaderWriteBarrier = makeBufferMemoryBarrier(
VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT, *m_resultBuffer, 0ull, m_resultBufferSizeBytes);
vk.cmdPipelineBarrier(*m_cmdBuffer, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0u, 0u,
DE_NULL, 1u, &shaderWriteBarrier, 0u, DE_NULL);
}
endCommandBuffer(vk, *m_cmdBuffer);
submitCommandsAndWait(vk, device, queue, *m_cmdBuffer);
// Read back and check results
const Allocation &resultAlloc = m_resultBuffer.getAllocation();
invalidateAlloc(vk, device, resultAlloc);
DrawResult result;
result.success = true;
result.refNumPrimitives = multiplePatchReferencePrimitiveCount(
m_caseDef.primitiveType, m_caseDef.spacingMode, usePointMode, &patchTessLevels[0], m_numPatchesToDraw);
result.numPrimitiveVertices = numVerticesPerPrimitive(m_caseDef.primitiveType, usePointMode);
result.numPrimitives = *static_cast<int32_t *>(resultAlloc.getHostPtr());
result.primitives =
sorted(readInterleavedData<PerPrimitive>(result.numPrimitives, resultAlloc.getHostPtr(),
m_resultBufferPrimitiveDataOffset, sizeof(PerPrimitive)),
byPatchPrimitiveID);
// If this fails then we didn't read all vertices from shader and test must be changed to allow more.
DE_ASSERT(result.numPrimitives <= m_maxNumPrimitivesInDrawCall);
tcu::TestLog &log = m_context.getTestContext().getLog();
if (result.numPrimitives < result.refNumPrimitives)
{
logPrimitiveCountError(log, m_numPatchesToDraw, result.numPrimitives, result.refNumPrimitives, patchTessLevels);
result.success = false;
}
return result;
}
void BaseTestInstance::uploadVertexAttributes(const std::vector<float> &vertexData)
{
const DeviceInterface &vk = m_context.getDeviceInterface();
const VkDevice device = m_context.getDevice();
const Allocation &alloc = m_vertexBuffer.getAllocation();
deMemcpy(alloc.getHostPtr(), &vertexData[0], sizeInBytes(vertexData));
flushAlloc(vk, device, alloc);
}
/*--------------------------------------------------------------------*//*!
* \brief Test invariance rule #2
*
* Test that the set of vertices along an outer edge of a quad or triangle
* only depends on that edge's tessellation level, and spacing.
*
* For each (outer) edge in the quad or triangle, draw multiple patches
* with identical tessellation levels for that outer edge but with
* different values for the other outer edges; compare, among the
* primitives, the vertices generated for that outer edge. Repeat with
* different programs, using different winding etc. settings. Compare
* the edge's vertices between different programs.
*//*--------------------------------------------------------------------*/
class OuterEdgeDivisionTestInstance : public BaseTestInstance
{
public:
OuterEdgeDivisionTestInstance(Context &context, const CaseDefinition caseDef)
: BaseTestInstance(context, caseDef, 10)
{
}
tcu::TestStatus iterate(void);
};
tcu::TestStatus OuterEdgeDivisionTestInstance::iterate(void)
{
for (int outerEdgeIndex = 0; outerEdgeIndex < static_cast<int>(m_edgeDescriptions.size()); ++outerEdgeIndex)
for (int outerEdgeLevelCaseNdx = 0; outerEdgeLevelCaseNdx < DE_LENGTH_OF_ARRAY(m_singleOuterEdgeLevels);
++outerEdgeLevelCaseNdx)
{
const OuterEdgeDescription &edgeDesc = m_edgeDescriptions[outerEdgeIndex];
const std::vector<float> patchTessLevels = generatePatchTessLevels(
m_numPatchesToDraw, outerEdgeIndex, m_singleOuterEdgeLevels[outerEdgeLevelCaseNdx]);
Vec3Set
firstOuterEdgeVertices; // Vertices of the outer edge of the first patch of the first program's draw call; used for comparison with other patches.
uploadVertexAttributes(patchTessLevels);
logOuterTessellationLevel(m_context.getTestContext().getLog(),
m_singleOuterEdgeLevels[outerEdgeLevelCaseNdx], edgeDesc);
for (int windingNdx = 0; windingNdx < WINDING_LAST; ++windingNdx)
for (int usePointModeNdx = 0; usePointModeNdx <= 1; ++usePointModeNdx)
{
const Winding winding = static_cast<Winding>(windingNdx);
const bool usePointMode = (usePointModeNdx != 0);
const bool isFirstProgram = (windingNdx == 0 && usePointModeNdx == 0);
const DrawResult result =
draw(static_cast<uint32_t>(patchTessLevels.size()), patchTessLevels, winding, usePointMode);
if (!result.success)
return tcu::TestStatus::fail("Invalid set of vertices");
// Check the vertices of each patch.
int primitiveNdx = 0;
for (int patchNdx = 0; patchNdx < m_numPatchesToDraw; ++patchNdx)
{
const float *const innerLevels = &patchTessLevels[NUM_TESS_LEVELS * patchNdx + 0];
const float *const outerLevels = &patchTessLevels[NUM_TESS_LEVELS * patchNdx + 2];
Vec3Set outerEdgeVertices;
// We're interested in just the vertices on the current outer edge.
for (; primitiveNdx < result.numPrimitives &&
result.primitives[primitiveNdx].patchPrimitiveID == patchNdx;
++primitiveNdx)
for (int i = 0; i < result.numPrimitiveVertices; ++i)
{
const tcu::Vec3 &coord = result.primitives[primitiveNdx].tessCoord[i].swizzle(0, 1, 2);
if (edgeDesc.contains(coord))
outerEdgeVertices.insert(coord);
}
// Compare the vertices to those of the first patch (unless this is the first patch).
if (isFirstProgram && patchNdx == 0)
firstOuterEdgeVertices = outerEdgeVertices;
else if (firstOuterEdgeVertices != outerEdgeVertices)
{
tcu::TestLog &log = m_context.getTestContext().getLog();
log << tcu::TestLog::Message
<< "Failure: vertices generated for the edge differ between the following cases:\n"
<< " - case A: " << getProgramDescription((Winding)0, (bool)0)
<< ", tessellation levels: "
<< getTessellationLevelsString(&patchTessLevels[0], &patchTessLevels[2]) << "\n"
<< " - case B: " << getProgramDescription(winding, usePointMode)
<< ", tessellation levels: " << getTessellationLevelsString(innerLevels, outerLevels)
<< tcu::TestLog::EndMessage;
log << tcu::TestLog::Message
<< "Note: resulting vertices for the edge for the cases were:\n"
<< " - case A: " << containerStr(firstOuterEdgeVertices, 5, 14) << "\n"
<< " - case B: " << containerStr(outerEdgeVertices, 5, 14) << tcu::TestLog::EndMessage;
return tcu::TestStatus::fail("Invalid set of vertices");
}
}
DE_ASSERT(primitiveNdx == result.numPrimitives);
} // for windingNdx, usePointModeNdx
} // for outerEdgeIndex, outerEdgeLevelCaseNdx
return tcu::TestStatus::pass("OK");
}
/*--------------------------------------------------------------------*//*!
* \brief Test invariance rule #4
*
* Test that the vertices on an outer edge don't depend on which of the
* edges it is, other than with respect to component order.
*//*--------------------------------------------------------------------*/
class OuterEdgeIndexIndependenceTestInstance : public BaseTestInstance
{
public:
OuterEdgeIndexIndependenceTestInstance(Context &context, const CaseDefinition caseDef)
: BaseTestInstance(context, caseDef, 1)
{
}
tcu::TestStatus iterate(void);
};
tcu::TestStatus OuterEdgeIndexIndependenceTestInstance::iterate(void)
{
for (int outerEdgeLevelCaseNdx = 0; outerEdgeLevelCaseNdx < DE_LENGTH_OF_ARRAY(m_singleOuterEdgeLevels);
++outerEdgeLevelCaseNdx)
{
Vec3Set firstEdgeVertices;
for (int outerEdgeIndex = 0; outerEdgeIndex < static_cast<int>(m_edgeDescriptions.size()); ++outerEdgeIndex)
{
const OuterEdgeDescription &edgeDesc = m_edgeDescriptions[outerEdgeIndex];
const std::vector<float> patchTessLevels = generatePatchTessLevels(
m_numPatchesToDraw, outerEdgeIndex, m_singleOuterEdgeLevels[outerEdgeLevelCaseNdx]);
uploadVertexAttributes(patchTessLevels);
logOuterTessellationLevel(m_context.getTestContext().getLog(),
m_singleOuterEdgeLevels[outerEdgeLevelCaseNdx], edgeDesc);
const DrawResult result = draw(static_cast<uint32_t>(patchTessLevels.size()), patchTessLevels,
m_caseDef.winding, m_caseDef.usePointMode);
// Verify case result
if (!result.success)
return tcu::TestStatus::fail("Invalid set of vertices");
Vec3Set currentEdgeVertices;
// Get the vertices on the current outer edge.
for (int primitiveNdx = 0; primitiveNdx < result.numPrimitives; ++primitiveNdx)
for (int i = 0; i < result.numPrimitiveVertices; ++i)
{
const tcu::Vec3 &coord = result.primitives[primitiveNdx].tessCoord[i].swizzle(0, 1, 2);
if (edgeDesc.contains(coord))
{
// Swizzle components to match the order of the first edge.
if (m_caseDef.primitiveType == TESSPRIMITIVETYPE_TRIANGLES)
currentEdgeVertices.insert(outerEdgeIndex == 0 ? coord :
outerEdgeIndex == 1 ? coord.swizzle(1, 0, 2) :
outerEdgeIndex == 2 ? coord.swizzle(2, 1, 0) :
tcu::Vec3(-1.0f));
else if (m_caseDef.primitiveType == TESSPRIMITIVETYPE_QUADS)
currentEdgeVertices.insert(tcu::Vec3(outerEdgeIndex == 0 ? coord.y() :
outerEdgeIndex == 1 ? coord.x() :
outerEdgeIndex == 2 ? coord.y() :
outerEdgeIndex == 3 ? coord.x() :
-1.0f,
0.0f, 0.0f));
else
DE_ASSERT(false);
}
}
if (outerEdgeIndex == 0)
firstEdgeVertices = currentEdgeVertices;
else
{
// Compare vertices of this edge to those of the first edge.
if (currentEdgeVertices != firstEdgeVertices)
{
const char *const swizzleDesc =
m_caseDef.primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? (outerEdgeIndex == 1 ? "(y, x, z)" :
outerEdgeIndex == 2 ? "(z, y, x)" :
DE_NULL) :
m_caseDef.primitiveType == TESSPRIMITIVETYPE_QUADS ? (outerEdgeIndex == 1 ? "(x, 0)" :
outerEdgeIndex == 2 ? "(y, 0)" :
outerEdgeIndex == 3 ? "(x, 0)" :
DE_NULL) :
DE_NULL;
tcu::TestLog &log = m_context.getTestContext().getLog();
log << tcu::TestLog::Message << "Failure: the set of vertices on the " << edgeDesc.description()
<< " edge"
<< " doesn't match the set of vertices on the " << m_edgeDescriptions[0].description()
<< " edge" << tcu::TestLog::EndMessage;
log << tcu::TestLog::Message << "Note: set of vertices on " << edgeDesc.description()
<< " edge, components swizzled like " << swizzleDesc
<< " to match component order on first edge:\n"
<< containerStr(currentEdgeVertices, 5) << "\non " << m_edgeDescriptions[0].description()
<< " edge:\n"
<< containerStr(firstEdgeVertices, 5) << tcu::TestLog::EndMessage;
return tcu::TestStatus::fail("Invalid set of vertices");
}
}
}
}
return tcu::TestStatus::pass("OK");
}
/*--------------------------------------------------------------------*//*!
* \brief Test invariance rule #3
*
* Test that the vertices along an outer edge are placed symmetrically.
*
* Draw multiple patches with different tessellation levels and different
* point_mode, winding etc. Before outputting tesscoords from shader, mirror
* the vertices in the TES such that every vertex on an outer edge -
* except the possible middle vertex - should be duplicated in the output.
* Check that appropriate duplicates exist.
*//*--------------------------------------------------------------------*/
class SymmetricOuterEdgeTestInstance : public BaseTestInstance
{
public:
SymmetricOuterEdgeTestInstance(Context &context, const CaseDefinition caseDef)
: BaseTestInstance(context, caseDef, 1)
{
}
tcu::TestStatus iterate(void);
};
tcu::TestStatus SymmetricOuterEdgeTestInstance::iterate(void)
{
for (int outerEdgeIndex = 0; outerEdgeIndex < static_cast<int>(m_edgeDescriptions.size()); ++outerEdgeIndex)
for (int outerEdgeLevelCaseNdx = 0; outerEdgeLevelCaseNdx < DE_LENGTH_OF_ARRAY(m_singleOuterEdgeLevels);
++outerEdgeLevelCaseNdx)
{
const OuterEdgeDescription &edgeDesc = m_edgeDescriptions[outerEdgeIndex];
const std::vector<float> patchTessLevels = generatePatchTessLevels(
m_numPatchesToDraw, outerEdgeIndex, m_singleOuterEdgeLevels[outerEdgeLevelCaseNdx]);
uploadVertexAttributes(patchTessLevels);
logOuterTessellationLevel(m_context.getTestContext().getLog(),
m_singleOuterEdgeLevels[outerEdgeLevelCaseNdx], edgeDesc);
const DrawResult result = draw(static_cast<uint32_t>(patchTessLevels.size()), patchTessLevels,
m_caseDef.winding, m_caseDef.usePointMode);
// Verify case result
if (!result.success)
return tcu::TestStatus::fail("Invalid set of vertices");
Vec3Set nonMirroredEdgeVertices;
Vec3Set mirroredEdgeVertices;
// Get the vertices on the current outer edge.
for (int primitiveNdx = 0; primitiveNdx < result.numPrimitives; ++primitiveNdx)
for (int i = 0; i < result.numPrimitiveVertices; ++i)
{
const tcu::Vec3 &coord = result.primitives[primitiveNdx].tessCoord[i].swizzle(0, 1, 2);
if (edgeDesc.contains(coord))
{
// Ignore the middle vertex of the outer edge, as it's exactly at the mirroring point;
// for isolines, also ignore (0, 0) and (1, 0) because there's no mirrored counterpart for them.
if (m_caseDef.primitiveType == TESSPRIMITIVETYPE_TRIANGLES &&
coord == tcu::select(tcu::Vec3(0.0f), tcu::Vec3(0.5f),
singleTrueMask<3>(edgeDesc.constantCoordinateIndex)))
continue;
if (m_caseDef.primitiveType == TESSPRIMITIVETYPE_QUADS &&
coord.swizzle(0, 1) == tcu::select(tcu::Vec2(edgeDesc.constantCoordinateValueChoices[0]),
tcu::Vec2(0.5f),
singleTrueMask<2>(edgeDesc.constantCoordinateIndex)))
continue;
if (m_caseDef.primitiveType == TESSPRIMITIVETYPE_ISOLINES &&
(coord == tcu::Vec3(0.0f, 0.5f, 0.0f) || coord == tcu::Vec3(1.0f, 0.5f, 0.0f) ||
coord == tcu::Vec3(0.0f, 0.0f, 0.0f) || coord == tcu::Vec3(1.0f, 0.0f, 0.0f)))
continue;
const bool isMirrored = result.primitives[primitiveNdx].tessCoord[i].w() > 0.5f;
if (isMirrored)
mirroredEdgeVertices.insert(coord);
else
nonMirroredEdgeVertices.insert(coord);
}
}
if (m_caseDef.primitiveType != TESSPRIMITIVETYPE_ISOLINES)
{
// Check that both endpoints are present. Note that endpoints aren't mirrored by the shader, since they belong to more than one edge.
tcu::Vec3 endpointA;
tcu::Vec3 endpointB;
if (m_caseDef.primitiveType == TESSPRIMITIVETYPE_TRIANGLES)
{
endpointA = tcu::select(tcu::Vec3(1.0f), tcu::Vec3(0.0f),
singleTrueMask<3>((edgeDesc.constantCoordinateIndex + 1) % 3));
endpointB = tcu::select(tcu::Vec3(1.0f), tcu::Vec3(0.0f),
singleTrueMask<3>((edgeDesc.constantCoordinateIndex + 2) % 3));
}
else if (m_caseDef.primitiveType == TESSPRIMITIVETYPE_QUADS)
{
endpointA.xy() = tcu::select(tcu::Vec2(edgeDesc.constantCoordinateValueChoices[0]), tcu::Vec2(0.0f),
singleTrueMask<2>(edgeDesc.constantCoordinateIndex));
endpointB.xy() = tcu::select(tcu::Vec2(edgeDesc.constantCoordinateValueChoices[0]), tcu::Vec2(1.0f),
singleTrueMask<2>(edgeDesc.constantCoordinateIndex));
}
else
DE_ASSERT(false);
if (!contains(nonMirroredEdgeVertices, endpointA) || !contains(nonMirroredEdgeVertices, endpointB))
{
m_context.getTestContext().getLog()
<< tcu::TestLog::Message << "Failure: edge doesn't contain both endpoints, " << endpointA
<< " and " << endpointB << tcu::TestLog::EndMessage << tcu::TestLog::Message
<< "Note: non-mirrored vertices:\n"
<< containerStr(nonMirroredEdgeVertices, 5) << "\nmirrored vertices:\n"
<< containerStr(mirroredEdgeVertices, 5) << tcu::TestLog::EndMessage;
return tcu::TestStatus::fail("Invalid set of vertices");
}
nonMirroredEdgeVertices.erase(endpointA);
nonMirroredEdgeVertices.erase(endpointB);
}
if (nonMirroredEdgeVertices != mirroredEdgeVertices)
{
m_context.getTestContext().getLog()
<< tcu::TestLog::Message
<< "Failure: the set of mirrored edges isn't equal to the set of non-mirrored edges (ignoring "
"endpoints and possible middle)"
<< tcu::TestLog::EndMessage << tcu::TestLog::Message << "Note: non-mirrored vertices:\n"
<< containerStr(nonMirroredEdgeVertices, 5) << "\nmirrored vertices:\n"
<< containerStr(mirroredEdgeVertices, 5) << tcu::TestLog::EndMessage;
return tcu::TestStatus::fail("Invalid set of vertices");
}
}
return tcu::TestStatus::pass("OK");
}
class OuterEdgeDivisionTest : public TestCase
{
public:
OuterEdgeDivisionTest(tcu::TestContext &testCtx, const std::string &name, const std::string &description,
const CaseDefinition caseDef)
: TestCase(testCtx, name, description)
, m_caseDef(caseDef)
{
}
void initPrograms(vk::SourceCollections &programCollection) const
{
addDefaultPrograms(programCollection, m_caseDef.primitiveType, m_caseDef.spacingMode, WINDING_USAGE_VARY,
POINT_MODE_USAGE_VARY);
}
TestInstance *createInstance(Context &context) const
{
return new OuterEdgeDivisionTestInstance(context, m_caseDef);
}
void checkSupport(Context &context) const
{
if (const vk::VkPhysicalDevicePortabilitySubsetFeaturesKHR *const features = getPortability(context))
checkPointMode(*features);
}
private:
const CaseDefinition m_caseDef;
};
class OuterEdgeIndexIndependenceTest : public TestCase
{
public:
OuterEdgeIndexIndependenceTest(tcu::TestContext &testCtx, const std::string &name, const std::string &description,
const CaseDefinition caseDef)
: TestCase(testCtx, name, description)
, m_caseDef(caseDef)
{
DE_ASSERT(m_caseDef.primitiveType == TESSPRIMITIVETYPE_TRIANGLES ||
m_caseDef.primitiveType == TESSPRIMITIVETYPE_QUADS);
}
void initPrograms(vk::SourceCollections &programCollection) const
{
addDefaultPrograms(programCollection, m_caseDef.primitiveType, m_caseDef.spacingMode,
getWindingUsage(m_caseDef.winding), getPointModeUsage(m_caseDef.usePointMode));
}
TestInstance *createInstance(Context &context) const
{
return new OuterEdgeIndexIndependenceTestInstance(context, m_caseDef);
}
void checkSupport(Context &context) const
{
checkSupportCase(context, m_caseDef);
}
private:
const CaseDefinition m_caseDef;
};
class SymmetricOuterEdgeTest : public TestCase
{
public:
SymmetricOuterEdgeTest(tcu::TestContext &testCtx, const std::string &name, const std::string &description,
const CaseDefinition caseDef)
: TestCase(testCtx, name, description)
, m_caseDef(caseDef)
{
}
void initPrograms(vk::SourceCollections &programCollection) const
{
const bool mirrorCoords = true;
addDefaultPrograms(programCollection, m_caseDef.primitiveType, m_caseDef.spacingMode,
getWindingUsage(m_caseDef.winding), getPointModeUsage(m_caseDef.usePointMode), mirrorCoords);
}
TestInstance *createInstance(Context &context) const
{
return new SymmetricOuterEdgeTestInstance(context, m_caseDef);
}
void checkSupport(Context &context) const
{
checkSupportCase(context, m_caseDef);
}
private:
const CaseDefinition m_caseDef;
};
tcu::TestCase *makeOuterEdgeDivisionTest(tcu::TestContext &testCtx, const std::string &name,
const std::string &description, const TessPrimitiveType primitiveType,
const SpacingMode spacingMode)
{
const CaseDefinition caseDef = {primitiveType, spacingMode, WINDING_LAST, false}; // winding is ignored by this test
return new OuterEdgeDivisionTest(testCtx, name, description, caseDef);
}
tcu::TestCase *makeOuterEdgeIndexIndependenceTest(tcu::TestContext &testCtx, const std::string &name,
const std::string &description, const TessPrimitiveType primitiveType,
const SpacingMode spacingMode, const Winding winding,
const bool usePointMode)
{
const CaseDefinition caseDef = {primitiveType, spacingMode, winding, usePointMode};
return new OuterEdgeIndexIndependenceTest(testCtx, name, description, caseDef);
}
tcu::TestCase *makeSymmetricOuterEdgeTest(tcu::TestContext &testCtx, const std::string &name,
const std::string &description, const TessPrimitiveType primitiveType,
const SpacingMode spacingMode, const Winding winding, const bool usePointMode)
{
const CaseDefinition caseDef = {primitiveType, spacingMode, winding, usePointMode};
return new SymmetricOuterEdgeTest(testCtx, name, description, caseDef);
}
} // namespace InvariantOuterEdge
namespace PrimitiveSetInvariance
{
enum CaseType
{
CASETYPE_INVARIANT_PRIMITIVE_SET,
CASETYPE_INVARIANT_TRIANGLE_SET,
CASETYPE_INVARIANT_OUTER_TRIANGLE_SET,
CASETYPE_INVARIANT_INNER_TRIANGLE_SET,
};
struct CaseDefinition
{
CaseType caseType;
TessPrimitiveType primitiveType;
SpacingMode spacingMode;
WindingUsage windingUsage;
bool usePointMode;
};
struct LevelCase
{
std::vector<TessLevels> levels;
int mem; //!< Subclass-defined arbitrary piece of data, for type of the levelcase, if needed.
LevelCase(const TessLevels &lev) : levels(std::vector<TessLevels>(1, lev)), mem(0)
{
}
LevelCase(void) : mem(0)
{
}
};
typedef tcu::Vector<tcu::Vec3, 3> Triangle;
inline Triangle makeTriangle(const PerPrimitive &primitive)
{
return Triangle(primitive.tessCoord[0].swizzle(0, 1, 2), primitive.tessCoord[1].swizzle(0, 1, 2),
primitive.tessCoord[2].swizzle(0, 1, 2));
}
//! Compare triangle sets, ignoring triangle order and vertex order within triangle, and possibly exclude some triangles too.
template <typename IsTriangleRelevantT>
bool compareTriangleSets(const PerPrimitiveVec &primitivesA, const PerPrimitiveVec &primitivesB, tcu::TestLog &log,
const IsTriangleRelevantT &isTriangleRelevant,
const char *ignoredTriangleDescription = DE_NULL)
{
typedef LexCompare<Triangle, 3, VecLexLessThan<3>> TriangleLexLessThan;
typedef std::set<Triangle, TriangleLexLessThan> TriangleSet;
const int numTrianglesA = static_cast<int>(primitivesA.size());
const int numTrianglesB = static_cast<int>(primitivesB.size());
TriangleSet trianglesA;
TriangleSet trianglesB;
for (int aOrB = 0; aOrB < 2; ++aOrB)
{
const PerPrimitiveVec &primitives = aOrB == 0 ? primitivesA : primitivesB;
const int numTriangles = aOrB == 0 ? numTrianglesA : numTrianglesB;
TriangleSet &triangles = aOrB == 0 ? trianglesA : trianglesB;
for (int triNdx = 0; triNdx < numTriangles; ++triNdx)
{
Triangle triangle = makeTriangle(primitives[triNdx]);
if (isTriangleRelevant(triangle.getPtr()))
{
std::sort(triangle.getPtr(), triangle.getPtr() + 3, VecLexLessThan<3>());
triangles.insert(triangle);
}
}
}
{
TriangleSet::const_iterator aIt = trianglesA.begin();
TriangleSet::const_iterator bIt = trianglesB.begin();
while (aIt != trianglesA.end() || bIt != trianglesB.end())
{
const bool aEnd = aIt == trianglesA.end();
const bool bEnd = bIt == trianglesB.end();
if (aEnd || bEnd || *aIt != *bIt)
{
log << tcu::TestLog::Message
<< "Failure: triangle sets in two cases are not equal (when ignoring triangle and vertex order"
<< (ignoredTriangleDescription == DE_NULL ? "" :
std::string() + ", and " + ignoredTriangleDescription)
<< ")" << tcu::TestLog::EndMessage;
if (!aEnd && (bEnd || TriangleLexLessThan()(*aIt, *bIt)))
log << tcu::TestLog::Message << "Note: e.g. triangle " << *aIt
<< " exists for first case but not for second" << tcu::TestLog::EndMessage;
else
log << tcu::TestLog::Message << "Note: e.g. triangle " << *bIt
<< " exists for second case but not for first" << tcu::TestLog::EndMessage;
return false;
}
++aIt;
++bIt;
}
return true;
}
}
template <typename ArgT, bool res>
struct ConstantUnaryPredicate
{
bool operator()(const ArgT &) const
{
return res;
}
};
bool compareTriangleSets(const PerPrimitiveVec &primitivesA, const PerPrimitiveVec &primitivesB, tcu::TestLog &log)
{
return compareTriangleSets(primitivesA, primitivesB, log, ConstantUnaryPredicate<const tcu::Vec3 *, true>());
}
//! Compare two sets of primitives. Order of primitives in each set is undefined, but within each primitive
//! vertex order and coordinates are expected to match exactly.
bool comparePrimitivesExact(const PerPrimitive *const primitivesA, const PerPrimitive *const primitivesB,
const int numPrimitivesPerPatch)
{
int ndxB = 0;
for (int ndxA = 0; ndxA < numPrimitivesPerPatch; ++ndxA)
{
const tcu::Vec4(&coordsA)[3] = primitivesA[ndxA].tessCoord;
bool match = false;
// Actually both sets are usually somewhat sorted, so don't reset ndxB after each match. Instead, continue from the next index.
for (int i = 0; i < numPrimitivesPerPatch; ++i)
{
const tcu::Vec4(&coordsB)[3] = primitivesB[ndxB].tessCoord;
ndxB = (ndxB + 1) % numPrimitivesPerPatch;
if (coordsA[0] == coordsB[0] && coordsA[1] == coordsB[1] && coordsA[2] == coordsB[2])
{
match = true;
break;
}
}
if (!match)
return false;
}
return true;
}
/*--------------------------------------------------------------------*//*!
* \brief Base class for testing invariance of entire primitive set
*
* Draws two patches with identical tessellation levels and compares the
* results. Repeats the same with other programs that are only different
* in irrelevant ways; compares the results between these two programs.
* Also potentially compares to results produced by different tessellation
* levels (see e.g. invariance rule #6).
* Furthermore, repeats the above with multiple different tessellation
* value sets.
*
* The manner of primitive set comparison is defined by subclass. E.g.
* case for invariance rule #1 tests that same vertices come out, in same
* order; rule #5 only requires that the same triangles are output, but
* not necessarily in the same order.
*//*--------------------------------------------------------------------*/
class InvarianceTestCase : public TestCase
{
public:
InvarianceTestCase(tcu::TestContext &context, const std::string &name, const std::string &description,
const CaseDefinition &caseDef)
: TestCase(context, name, description)
, m_caseDef(caseDef)
{
}
virtual ~InvarianceTestCase(void)
{
}
void initPrograms(SourceCollections &programCollection) const;
void checkSupport(Context &context) const;
TestInstance *createInstance(Context &context) const;
private:
const CaseDefinition m_caseDef;
};
void InvarianceTestCase::initPrograms(SourceCollections &programCollection) const
{
addDefaultPrograms(programCollection, m_caseDef.primitiveType, m_caseDef.spacingMode, m_caseDef.windingUsage,
getPointModeUsage(m_caseDef.usePointMode));
}
void InvarianceTestCase::checkSupport(Context &context) const
{
checkSupportCase(context, m_caseDef);
}
class InvarianceTestInstance : public TestInstance
{
public:
InvarianceTestInstance(Context &context, const CaseDefinition &caseDef) : TestInstance(context), m_caseDef(caseDef)
{
}
virtual ~InvarianceTestInstance(void)
{
}
tcu::TestStatus iterate(void);
protected:
virtual std::vector<LevelCase> genTessLevelCases(void) const;
virtual bool compare(const PerPrimitiveVec &primitivesA, const PerPrimitiveVec &primitivesB,
const int levelCaseMem) const = 0;
const CaseDefinition m_caseDef;
};
std::vector<LevelCase> InvarianceTestInstance::genTessLevelCases(void) const
{
static const TessLevels basicTessLevelCases[] = {
{{1.0f, 1.0f}, {1.0f, 1.0f, 1.0f, 1.0f}}, {{63.0f, 24.0f}, {15.0f, 42.0f, 10.0f, 12.0f}},
{{3.0f, 2.0f}, {6.0f, 8.0f, 7.0f, 9.0f}}, {{4.0f, 6.0f}, {2.0f, 3.0f, 1.0f, 4.0f}},
{{2.0f, 2.0f}, {6.0f, 8.0f, 7.0f, 9.0f}}, {{5.0f, 6.0f}, {1.0f, 1.0f, 1.0f, 1.0f}},
{{1.0f, 6.0f}, {2.0f, 3.0f, 1.0f, 4.0f}}, {{5.0f, 1.0f}, {2.0f, 3.0f, 1.0f, 4.0f}},
{{5.2f, 1.6f}, {2.9f, 3.4f, 1.5f, 4.1f}}};
std::vector<LevelCase> result;
for (int i = 0; i < DE_LENGTH_OF_ARRAY(basicTessLevelCases); ++i)
result.push_back(LevelCase(basicTessLevelCases[i]));
{
de::Random rnd(123);
for (int i = 0; i < 10; ++i)
{
TessLevels levels;
for (int j = 0; j < DE_LENGTH_OF_ARRAY(levels.inner); ++j)
levels.inner[j] = rnd.getFloat(1.0f, 16.0f);
for (int j = 0; j < DE_LENGTH_OF_ARRAY(levels.outer); ++j)
levels.outer[j] = rnd.getFloat(1.0f, 16.0f);
result.push_back(LevelCase(levels));
}
}
return result;
}
tcu::TestStatus InvarianceTestInstance::iterate(void)
{
requireFeatures(m_context.getInstanceInterface(), m_context.getPhysicalDevice(),
FEATURE_TESSELLATION_SHADER | FEATURE_GEOMETRY_SHADER | FEATURE_VERTEX_PIPELINE_STORES_AND_ATOMICS);
const DeviceInterface &vk = m_context.getDeviceInterface();
const VkDevice device = m_context.getDevice();
const VkQueue queue = m_context.getUniversalQueue();
const uint32_t queueFamilyIndex = m_context.getUniversalQueueFamilyIndex();
Allocator &allocator = m_context.getDefaultAllocator();
const std::vector<LevelCase> tessLevelCases = genTessLevelCases();
const int numPatchesPerDrawCall = 2;
int maxNumPrimitivesPerPatch = 0; // computed below
std::vector<std::vector<int>> primitiveCounts;
for (int caseNdx = 0; caseNdx < static_cast<int>(tessLevelCases.size()); ++caseNdx)
{
primitiveCounts.push_back(std::vector<int>());
for (int levelNdx = 0; levelNdx < static_cast<int>(tessLevelCases[caseNdx].levels.size()); ++levelNdx)
{
const int primitiveCount = referencePrimitiveCount(
m_caseDef.primitiveType, m_caseDef.spacingMode, m_caseDef.usePointMode,
&tessLevelCases[caseNdx].levels[levelNdx].inner[0], &tessLevelCases[caseNdx].levels[levelNdx].outer[0]);
primitiveCounts.back().push_back(primitiveCount);
maxNumPrimitivesPerPatch = de::max(maxNumPrimitivesPerPatch, primitiveCount);
}
}
// Allow for more primitievs in case tessellation/geometry has extra invocations
maxNumPrimitivesPerPatch *= NUM_EXTRA_TESS_GEOM_INVOCATIONS;
// Vertex input attributes buffer: to pass tessellation levels
const VkFormat vertexFormat = VK_FORMAT_R32_SFLOAT;
const uint32_t vertexStride = tcu::getPixelSize(mapVkFormat(vertexFormat));
const VkDeviceSize vertexDataSizeBytes = NUM_TESS_LEVELS * numPatchesPerDrawCall * vertexStride;
const Buffer vertexBuffer(vk, device, allocator,
makeBufferCreateInfo(vertexDataSizeBytes, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT),
MemoryRequirement::HostVisible);
// Output buffer: number of primitives and an array of PerPrimitive structures
const int resultBufferMaxVertices = numPatchesPerDrawCall * maxNumPrimitivesPerPatch *
numVerticesPerPrimitive(m_caseDef.primitiveType, m_caseDef.usePointMode);
const int resultBufferTessCoordsOffset = (int)sizeof(int32_t) * 4;
const VkDeviceSize resultBufferSizeBytes =
resultBufferTessCoordsOffset + resultBufferMaxVertices * sizeof(PerPrimitive);
const Buffer resultBuffer(vk, device, allocator,
makeBufferCreateInfo(resultBufferSizeBytes, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT),
MemoryRequirement::HostVisible);
// Descriptors
const Unique<VkDescriptorSetLayout> descriptorSetLayout(
DescriptorSetLayoutBuilder()
.addSingleBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_GEOMETRY_BIT)
.build(vk, device));
const Unique<VkDescriptorPool> descriptorPool(
DescriptorPoolBuilder()
.addType(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER)
.build(vk, device, VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, 1u));
const Unique<VkDescriptorSet> descriptorSet(makeDescriptorSet(vk, device, *descriptorPool, *descriptorSetLayout));
const VkDescriptorBufferInfo resultBufferInfo =
makeDescriptorBufferInfo(resultBuffer.get(), 0ull, resultBufferSizeBytes);
DescriptorSetUpdateBuilder()
.writeSingle(*descriptorSet, DescriptorSetUpdateBuilder::Location::binding(0u),
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, &resultBufferInfo)
.update(vk, device);
const Unique<VkRenderPass> renderPass(makeRenderPassWithoutAttachments(vk, device));
const Unique<VkFramebuffer> framebuffer(makeFramebuffer(vk, device, *renderPass, 0u, DE_NULL, 1u, 1u));
const Unique<VkPipelineLayout> pipelineLayout(makePipelineLayout(vk, device, *descriptorSetLayout));
const Unique<VkCommandPool> cmdPool(makeCommandPool(vk, device, queueFamilyIndex));
const Unique<VkCommandBuffer> cmdBuffer(
allocateCommandBuffer(vk, device, *cmdPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY));
for (int tessLevelCaseNdx = 0; tessLevelCaseNdx < static_cast<int>(tessLevelCases.size()); ++tessLevelCaseNdx)
{
const LevelCase &levelCase = tessLevelCases[tessLevelCaseNdx];
PerPrimitiveVec firstPrim;
{
tcu::TestLog &log = m_context.getTestContext().getLog();
std::ostringstream tessLevelsStr;
for (int i = 0; i < static_cast<int>(levelCase.levels.size()); ++i)
tessLevelsStr << (levelCase.levels.size() > 1u ? "\n" : "")
<< getTessellationLevelsString(levelCase.levels[i], m_caseDef.primitiveType);
log << tcu::TestLog::Message << "Tessellation level sets: " << tessLevelsStr.str()
<< tcu::TestLog::EndMessage;
}
for (int subTessLevelCaseNdx = 0; subTessLevelCaseNdx < static_cast<int>(levelCase.levels.size());
++subTessLevelCaseNdx)
{
const TessLevels &tessLevels = levelCase.levels[subTessLevelCaseNdx];
{
TessLevels data[2];
data[0] = tessLevels;
data[1] = tessLevels;
const Allocation &alloc = vertexBuffer.getAllocation();
deMemcpy(alloc.getHostPtr(), data, sizeof(data));
flushAlloc(vk, device, alloc);
}
int programNdx = 0;
const std::vector<Winding> windingCases = getWindingCases(m_caseDef.windingUsage);
for (std::vector<Winding>::const_iterator windingIter = windingCases.begin();
windingIter != windingCases.end(); ++windingIter)
{
const Unique<VkPipeline> pipeline(
GraphicsPipelineBuilder()
.setPatchControlPoints(NUM_TESS_LEVELS)
.setVertexInputSingleAttribute(vertexFormat, vertexStride)
.setShader(vk, device, VK_SHADER_STAGE_VERTEX_BIT, m_context.getBinaryCollection().get("vert"),
DE_NULL)
.setShader(vk, device, VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT,
m_context.getBinaryCollection().get("tesc"), DE_NULL)
.setShader(vk, device, VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT,
m_context.getBinaryCollection().get(
getProgramName("tese", *windingIter, m_caseDef.usePointMode)),
DE_NULL)
.setShader(vk, device, VK_SHADER_STAGE_GEOMETRY_BIT,
m_context.getBinaryCollection().get(getProgramName("geom", m_caseDef.usePointMode)),
DE_NULL)
.build(vk, device, *pipelineLayout, *renderPass));
{
const Allocation &alloc = resultBuffer.getAllocation();
deMemset(alloc.getHostPtr(), 0, static_cast<std::size_t>(resultBufferSizeBytes));
flushAlloc(vk, device, alloc);
}
beginCommandBuffer(vk, *cmdBuffer);
beginRenderPassWithRasterizationDisabled(vk, *cmdBuffer, *renderPass, *framebuffer);
vk.cmdBindPipeline(*cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline);
vk.cmdBindDescriptorSets(*cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *pipelineLayout, 0u, 1u,
&descriptorSet.get(), 0u, DE_NULL);
{
const VkDeviceSize vertexBufferOffset = 0ull;
vk.cmdBindVertexBuffers(*cmdBuffer, 0u, 1u, &vertexBuffer.get(), &vertexBufferOffset);
}
vk.cmdDraw(*cmdBuffer, numPatchesPerDrawCall * NUM_TESS_LEVELS, 1u, 0u, 0u);
endRenderPass(vk, *cmdBuffer);
{
const VkBufferMemoryBarrier shaderWriteBarrier =
makeBufferMemoryBarrier(VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT, *resultBuffer,
0ull, resultBufferSizeBytes);
vk.cmdPipelineBarrier(*cmdBuffer, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, VK_PIPELINE_STAGE_HOST_BIT,
0u, 0u, DE_NULL, 1u, &shaderWriteBarrier, 0u, DE_NULL);
}
endCommandBuffer(vk, *cmdBuffer);
submitCommandsAndWait(vk, device, queue, *cmdBuffer);
// Verify case result
{
const Allocation &resultAlloc = resultBuffer.getAllocation();
invalidateAlloc(vk, device, resultAlloc);
const int refNumPrimitives =
numPatchesPerDrawCall * primitiveCounts[tessLevelCaseNdx][subTessLevelCaseNdx];
const int numPrimitiveVertices =
numVerticesPerPrimitive(m_caseDef.primitiveType, m_caseDef.usePointMode);
const int32_t numPrimitives = *static_cast<int32_t *>(resultAlloc.getHostPtr());
const PerPrimitiveVec primitives =
sorted(readInterleavedData<PerPrimitive>(numPrimitives, resultAlloc.getHostPtr(),
resultBufferTessCoordsOffset, sizeof(PerPrimitive)),
byPatchPrimitiveID);
// If this fails then we didn't read all vertices from shader and test must be changed to allow more.
DE_ASSERT(numPrimitiveVertices * numPrimitives <= resultBufferMaxVertices);
DE_UNREF(numPrimitiveVertices);
tcu::TestLog &log = m_context.getTestContext().getLog();
if (numPrimitives < refNumPrimitives)
{
log << tcu::TestLog::Message << "Failure: got " << numPrimitives
<< " primitives, but expected at least" << refNumPrimitives << tcu::TestLog::EndMessage;
return tcu::TestStatus::fail("Invalid set of primitives");
}
const int half = static_cast<int>(primitives.size() / 2);
const PerPrimitiveVec prim0 = PerPrimitiveVec(primitives.begin(), primitives.begin() + half);
const PerPrimitive *const prim1 = &primitives[half];
if (!comparePrimitivesExact(&prim0[0], prim1, half))
{
log << tcu::TestLog::Message
<< "Failure: tessellation coordinates differ between two primitives drawn in one draw call"
<< tcu::TestLog::EndMessage << tcu::TestLog::Message
<< "Note: tessellation levels for both primitives were: "
<< getTessellationLevelsString(tessLevels, m_caseDef.primitiveType)
<< tcu::TestLog::EndMessage;
return tcu::TestStatus::fail("Invalid set of primitives");
}
if (programNdx == 0 && subTessLevelCaseNdx == 0)
firstPrim = prim0;
else
{
const bool compareOk = compare(firstPrim, prim0, levelCase.mem);
if (!compareOk)
{
log << tcu::TestLog::Message
<< "Note: comparison of tessellation coordinates failed; comparison was made between "
"following cases:\n"
<< " - case A: program 0, tessellation levels: "
<< getTessellationLevelsString(tessLevelCases[tessLevelCaseNdx].levels[0],
m_caseDef.primitiveType)
<< "\n"
<< " - case B: program " << programNdx << ", tessellation levels: "
<< getTessellationLevelsString(tessLevels, m_caseDef.primitiveType)
<< tcu::TestLog::EndMessage;
return tcu::TestStatus::fail("Invalid set of primitives");
}
}
}
++programNdx;
}
}
}
return tcu::TestStatus::pass("OK");
}
/*--------------------------------------------------------------------*//*!
* \brief Test invariance rule #1
*
* Test that the sequence of primitives input to the TES only depends on
* the tessellation levels, tessellation mode, spacing mode, winding, and
* point mode.
*//*--------------------------------------------------------------------*/
class InvariantPrimitiveSetTestInstance : public InvarianceTestInstance
{
public:
InvariantPrimitiveSetTestInstance(Context &context, const CaseDefinition &caseDef)
: InvarianceTestInstance(context, caseDef)
{
}
protected:
bool compare(const PerPrimitiveVec &primitivesA, const PerPrimitiveVec &primitivesB, const int) const
{
if (!comparePrimitivesExact(&primitivesA[0], &primitivesB[0], static_cast<int>(primitivesA.size())))
{
m_context.getTestContext().getLog()
<< tcu::TestLog::Message << "Failure: tessellation coordinates differ between two programs"
<< tcu::TestLog::EndMessage;
return false;
}
return true;
}
};
/*--------------------------------------------------------------------*//*!
* \brief Test invariance rule #5
*
* Test that the set of triangles input to the TES only depends on the
* tessellation levels, tessellation mode and spacing mode. Specifically,
* winding doesn't change the set of triangles, though it can change the
* order in which they are input to TES, and can (and will) change the
* vertex order within a triangle.
*//*--------------------------------------------------------------------*/
class InvariantTriangleSetTestInstance : public InvarianceTestInstance
{
public:
InvariantTriangleSetTestInstance(Context &context, const CaseDefinition &caseDef)
: InvarianceTestInstance(context, caseDef)
{
}
protected:
bool compare(const PerPrimitiveVec &primitivesA, const PerPrimitiveVec &primitivesB, const int) const
{
return compareTriangleSets(primitivesA, primitivesB, m_context.getTestContext().getLog());
}
};
/*--------------------------------------------------------------------*//*!
* \brief Test invariance rule #6
*
* Test that the set of inner triangles input to the TES only depends on
* the inner tessellation levels, tessellation mode and spacing mode.
*//*--------------------------------------------------------------------*/
class InvariantInnerTriangleSetTestInstance : public InvarianceTestInstance
{
public:
InvariantInnerTriangleSetTestInstance(Context &context, const CaseDefinition &caseDef)
: InvarianceTestInstance(context, caseDef)
{
}
protected:
std::vector<LevelCase> genTessLevelCases(void) const
{
const int numSubCases = 4;
const std::vector<LevelCase> baseResults = InvarianceTestInstance::genTessLevelCases();
std::vector<LevelCase> result;
de::Random rnd(123);
// Generate variants with different values for irrelevant levels.
for (int baseNdx = 0; baseNdx < static_cast<int>(baseResults.size()); ++baseNdx)
{
const TessLevels &base = baseResults[baseNdx].levels[0];
TessLevels levels = base;
LevelCase levelCase;
for (int subNdx = 0; subNdx < numSubCases; ++subNdx)
{
levelCase.levels.push_back(levels);
for (int i = 0; i < DE_LENGTH_OF_ARRAY(levels.outer); ++i)
levels.outer[i] = rnd.getFloat(2.0f, 16.0f);
if (m_caseDef.primitiveType == TESSPRIMITIVETYPE_TRIANGLES)
levels.inner[1] = rnd.getFloat(2.0f, 16.0f);
}
result.push_back(levelCase);
}
return result;
}
struct IsInnerTriangleTriangle
{
bool operator()(const tcu::Vec3 *vertices) const
{
for (int v = 0; v < 3; ++v)
for (int c = 0; c < 3; ++c)
if (vertices[v][c] == 0.0f)
return false;
return true;
}
};
struct IsInnerQuadTriangle
{
bool operator()(const tcu::Vec3 *vertices) const
{
for (int v = 0; v < 3; ++v)
for (int c = 0; c < 2; ++c)
if (vertices[v][c] == 0.0f || vertices[v][c] == 1.0f)
return false;
return true;
}
};
bool compare(const PerPrimitiveVec &primitivesA, const PerPrimitiveVec &primitivesB, const int) const
{
if (m_caseDef.primitiveType == TESSPRIMITIVETYPE_TRIANGLES)
return compareTriangleSets(primitivesA, primitivesB, m_context.getTestContext().getLog(),
IsInnerTriangleTriangle(), "outer triangles");
else if (m_caseDef.primitiveType == TESSPRIMITIVETYPE_QUADS)
return compareTriangleSets(primitivesA, primitivesB, m_context.getTestContext().getLog(),
IsInnerQuadTriangle(), "outer triangles");
else
{
DE_ASSERT(false);
return false;
}
}
};
/*--------------------------------------------------------------------*//*!
* \brief Test invariance rule #7
*
* Test that the set of outer triangles input to the TES only depends on
* tessellation mode, spacing mode and the inner and outer tessellation
* levels corresponding to the inner and outer edges relevant to that
* triangle.
*//*--------------------------------------------------------------------*/
class InvariantOuterTriangleSetTestInstance : public InvarianceTestInstance
{
public:
InvariantOuterTriangleSetTestInstance(Context &context, const CaseDefinition &caseDef)
: InvarianceTestInstance(context, caseDef)
{
}
protected:
std::vector<LevelCase> genTessLevelCases(void) const
{
const int numSubCasesPerEdge = 4;
const int numEdges = m_caseDef.primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? 3 :
m_caseDef.primitiveType == TESSPRIMITIVETYPE_QUADS ? 4 :
0;
const std::vector<LevelCase> baseResult = InvarianceTestInstance::genTessLevelCases();
std::vector<LevelCase> result;
de::Random rnd(123);
// Generate variants with different values for irrelevant levels.
for (int baseNdx = 0; baseNdx < static_cast<int>(baseResult.size()); ++baseNdx)
{
const TessLevels &base = baseResult[baseNdx].levels[0];
if (base.inner[0] == 1.0f || (m_caseDef.primitiveType == TESSPRIMITIVETYPE_QUADS && base.inner[1] == 1.0f))
continue;
for (int edgeNdx = 0; edgeNdx < numEdges; ++edgeNdx)
{
TessLevels levels = base;
LevelCase levelCase;
levelCase.mem = edgeNdx;
for (int subCaseNdx = 0; subCaseNdx < numSubCasesPerEdge; ++subCaseNdx)
{
levelCase.levels.push_back(levels);
for (int i = 0; i < DE_LENGTH_OF_ARRAY(levels.outer); ++i)
{
if (i != edgeNdx)
levels.outer[i] = rnd.getFloat(2.0f, 16.0f);
}
if (m_caseDef.primitiveType == TESSPRIMITIVETYPE_TRIANGLES)
levels.inner[1] = rnd.getFloat(2.0f, 16.0f);
}
result.push_back(levelCase);
}
}
return result;
}
class IsTriangleTriangleOnOuterEdge
{
public:
IsTriangleTriangleOnOuterEdge(int edgeNdx) : m_edgeNdx(edgeNdx)
{
}
bool operator()(const tcu::Vec3 *vertices) const
{
bool touchesAppropriateEdge = false;
for (int v = 0; v < 3; ++v)
if (vertices[v][m_edgeNdx] == 0.0f)
touchesAppropriateEdge = true;
if (touchesAppropriateEdge)
{
const tcu::Vec3 avg = (vertices[0] + vertices[1] + vertices[2]) / 3.0f;
return avg[m_edgeNdx] < avg[(m_edgeNdx + 1) % 3] && avg[m_edgeNdx] < avg[(m_edgeNdx + 2) % 3];
}
return false;
}
private:
const int m_edgeNdx;
};
class IsQuadTriangleOnOuterEdge
{
public:
IsQuadTriangleOnOuterEdge(int edgeNdx) : m_edgeNdx(edgeNdx)
{
}
bool onEdge(const tcu::Vec3 &v) const
{
return v[m_edgeNdx % 2] == (m_edgeNdx <= 1 ? 0.0f : 1.0f);
}
static inline bool onAnyEdge(const tcu::Vec3 &v)
{
return v[0] == 0.0f || v[0] == 1.0f || v[1] == 0.0f || v[1] == 1.0f;
}
bool operator()(const tcu::Vec3 *vertices) const
{
for (int v = 0; v < 3; ++v)
{
const tcu::Vec3 &a = vertices[v];
const tcu::Vec3 &b = vertices[(v + 1) % 3];
const tcu::Vec3 &c = vertices[(v + 2) % 3];
if (onEdge(a) && onEdge(b))
return true;
if (onEdge(c) && !onAnyEdge(a) && !onAnyEdge(b) && a[m_edgeNdx % 2] == b[m_edgeNdx % 2])
return true;
}
return false;
}
private:
const int m_edgeNdx;
};
bool compare(const PerPrimitiveVec &primitivesA, const PerPrimitiveVec &primitivesB, const int outerEdgeNdx) const
{
if (m_caseDef.primitiveType == TESSPRIMITIVETYPE_TRIANGLES)
{
return compareTriangleSets(primitivesA, primitivesB, m_context.getTestContext().getLog(),
IsTriangleTriangleOnOuterEdge(outerEdgeNdx),
("inner triangles, and outer triangles corresponding to other edge than edge " +
outerEdgeDescriptions(m_caseDef.primitiveType)[outerEdgeNdx].description())
.c_str());
}
else if (m_caseDef.primitiveType == TESSPRIMITIVETYPE_QUADS)
{
return compareTriangleSets(primitivesA, primitivesB, m_context.getTestContext().getLog(),
IsQuadTriangleOnOuterEdge(outerEdgeNdx),
("inner triangles, and outer triangles corresponding to other edge than edge " +
outerEdgeDescriptions(m_caseDef.primitiveType)[outerEdgeNdx].description())
.c_str());
}
else
DE_ASSERT(false);
return true;
}
};
TestInstance *InvarianceTestCase::createInstance(Context &context) const
{
switch (m_caseDef.caseType)
{
case CASETYPE_INVARIANT_PRIMITIVE_SET:
return new InvariantPrimitiveSetTestInstance(context, m_caseDef);
case CASETYPE_INVARIANT_TRIANGLE_SET:
return new InvariantTriangleSetTestInstance(context, m_caseDef);
case CASETYPE_INVARIANT_OUTER_TRIANGLE_SET:
return new InvariantOuterTriangleSetTestInstance(context, m_caseDef);
case CASETYPE_INVARIANT_INNER_TRIANGLE_SET:
return new InvariantInnerTriangleSetTestInstance(context, m_caseDef);
default:
DE_ASSERT(false);
return DE_NULL;
}
}
TestCase *makeInvariantPrimitiveSetTest(tcu::TestContext &testCtx, const std::string &name,
const std::string &description, const TessPrimitiveType primitiveType,
const SpacingMode spacingMode, const Winding winding, const bool usePointMode)
{
const CaseDefinition caseDef = {CASETYPE_INVARIANT_PRIMITIVE_SET, primitiveType, spacingMode,
getWindingUsage(winding), usePointMode};
return new InvarianceTestCase(testCtx, name, description, caseDef);
}
TestCase *makeInvariantTriangleSetTest(tcu::TestContext &testCtx, const std::string &name,
const std::string &description, const TessPrimitiveType primitiveType,
const SpacingMode spacingMode)
{
DE_ASSERT(primitiveType == TESSPRIMITIVETYPE_TRIANGLES || primitiveType == TESSPRIMITIVETYPE_QUADS);
const CaseDefinition caseDef = {CASETYPE_INVARIANT_TRIANGLE_SET, primitiveType, spacingMode, WINDING_USAGE_VARY,
false};
return new InvarianceTestCase(testCtx, name, description, caseDef);
}
TestCase *makeInvariantInnerTriangleSetTest(tcu::TestContext &testCtx, const std::string &name,
const std::string &description, const TessPrimitiveType primitiveType,
const SpacingMode spacingMode)
{
DE_ASSERT(primitiveType == TESSPRIMITIVETYPE_TRIANGLES || primitiveType == TESSPRIMITIVETYPE_QUADS);
const CaseDefinition caseDef = {CASETYPE_INVARIANT_INNER_TRIANGLE_SET, primitiveType, spacingMode,
WINDING_USAGE_VARY, false};
return new InvarianceTestCase(testCtx, name, description, caseDef);
}
TestCase *makeInvariantOuterTriangleSetTest(tcu::TestContext &testCtx, const std::string &name,
const std::string &description, const TessPrimitiveType primitiveType,
const SpacingMode spacingMode)
{
DE_ASSERT(primitiveType == TESSPRIMITIVETYPE_TRIANGLES || primitiveType == TESSPRIMITIVETYPE_QUADS);
const CaseDefinition caseDef = {CASETYPE_INVARIANT_OUTER_TRIANGLE_SET, primitiveType, spacingMode,
WINDING_USAGE_VARY, false};
return new InvarianceTestCase(testCtx, name, description, caseDef);
}
} // namespace PrimitiveSetInvariance
namespace TessCoordComponent
{
enum CaseType
{
CASETYPE_TESS_COORD_RANGE = 0, //!< Test that all (relevant) components of tess coord are in [0,1].
CASETYPE_ONE_MINUS_TESS_COORD, //!< Test that for every (relevant) component c of a tess coord, 1.0-c is exact.
CASETYPE_LAST
};
struct CaseDefinition
{
CaseType caseType;
TessPrimitiveType primitiveType;
SpacingMode spacingMode;
Winding winding;
bool usePointMode;
};
std::vector<TessLevels> genTessLevelCases(const int numCases)
{
de::Random rnd(123);
std::vector<TessLevels> result;
for (int i = 0; i < numCases; ++i)
{
TessLevels levels;
levels.inner[0] = rnd.getFloat(1.0f, 63.0f);
levels.inner[1] = rnd.getFloat(1.0f, 63.0f);
levels.outer[0] = rnd.getFloat(1.0f, 63.0f);
levels.outer[1] = rnd.getFloat(1.0f, 63.0f);
levels.outer[2] = rnd.getFloat(1.0f, 63.0f);
levels.outer[3] = rnd.getFloat(1.0f, 63.0f);
result.push_back(levels);
}
return result;
}
typedef bool (*CompareFunc)(tcu::TestLog &log, const float value);
bool compareTessCoordRange(tcu::TestLog &log, const float value)
{
if (!de::inRange(value, 0.0f, 1.0f))
{
log << tcu::TestLog::Message << "Failure: tess coord component isn't in range [0,1]"
<< tcu::TestLog::EndMessage;
return false;
}
return true;
}
bool compareOneMinusTessCoord(tcu::TestLog &log, const float value)
{
if (value != 1.0f)
{
log << tcu::TestLog::Message
<< "Failure: comp + (1.0-comp) doesn't equal 1.0 for some component of tessellation coordinate"
<< tcu::TestLog::EndMessage;
return false;
}
return true;
}
void initPrograms(vk::SourceCollections &programCollection, const CaseDefinition caseDef)
{
// Vertex shader
{
std::ostringstream src;
src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES) << "\n"
<< "\n"
<< "layout(location = 0) in highp float in_v_attr;\n"
<< "layout(location = 0) out highp float in_tc_attr;\n"
<< "\n"
<< "void main (void)\n"
<< "{\n"
<< " in_tc_attr = in_v_attr;\n"
<< "}\n";
programCollection.glslSources.add("vert") << glu::VertexSource(src.str());
}
// Tessellation control shader
{
std::ostringstream src;
src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES) << "\n"
<< "#extension GL_EXT_tessellation_shader : require\n"
<< "\n"
<< "layout(vertices = 1) out;\n"
<< "\n"
<< "layout(location = 0) in highp float in_tc_attr[];\n"
<< "\n"
<< "void main (void)\n"
<< "{\n"
<< " gl_TessLevelInner[0] = in_tc_attr[0];\n"
<< " gl_TessLevelInner[1] = in_tc_attr[1];\n"
<< "\n"
<< " gl_TessLevelOuter[0] = in_tc_attr[2];\n"
<< " gl_TessLevelOuter[1] = in_tc_attr[3];\n"
<< " gl_TessLevelOuter[2] = in_tc_attr[4];\n"
<< " gl_TessLevelOuter[3] = in_tc_attr[5];\n"
<< "}\n";
programCollection.glslSources.add("tesc") << glu::TessellationControlSource(src.str());
}
// Tessellation evaluation shader
{
std::ostringstream tessCoordSrc;
if (caseDef.caseType == CASETYPE_TESS_COORD_RANGE)
tessCoordSrc << " sb_out.tessCoord[index] = gl_TessCoord;\n";
else if (caseDef.caseType == CASETYPE_ONE_MINUS_TESS_COORD)
{
const char *components[] = {"x", "y", "z"};
const int numComponents = (caseDef.primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? 3 : 2);
for (int i = 0; i < numComponents; ++i)
tessCoordSrc << " {\n"
<< " float oneMinusComp = 1.0 - gl_TessCoord." << components[i] << ";\n"
<< " sb_out.tessCoord[index]." << components[i] << " = gl_TessCoord."
<< components[i] << " + oneMinusComp;\n"
<< " }\n";
}
else
{
DE_ASSERT(false);
return;
}
std::ostringstream src;
src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES) << "\n"
<< "#extension GL_EXT_tessellation_shader : require\n"
<< "\n"
<< "layout(" << getTessPrimitiveTypeShaderName(caseDef.primitiveType) << ", "
<< getSpacingModeShaderName(caseDef.spacingMode) << ", " << getWindingShaderName(caseDef.winding)
<< (caseDef.usePointMode ? ", point_mode" : "") << ") in;\n"
<< "\n"
<< "layout(set = 0, binding = 0, std430) coherent restrict buffer Output {\n"
<< " int numInvocations;\n"
<< " vec3 tessCoord[];\n"
<< "} sb_out;\n"
<< "\n"
<< "void main (void)\n"
<< "{\n"
<< " int index = atomicAdd(sb_out.numInvocations, 1);\n"
<< tessCoordSrc.str() << "}\n";
programCollection.glslSources.add("tese") << glu::TessellationEvaluationSource(src.str());
}
}
tcu::TestStatus test(Context &context, const CaseDefinition caseDef)
{
requireFeatures(context.getInstanceInterface(), context.getPhysicalDevice(),
FEATURE_TESSELLATION_SHADER | FEATURE_VERTEX_PIPELINE_STORES_AND_ATOMICS);
const DeviceInterface &vk = context.getDeviceInterface();
const VkDevice device = context.getDevice();
const VkQueue queue = context.getUniversalQueue();
const uint32_t queueFamilyIndex = context.getUniversalQueueFamilyIndex();
Allocator &allocator = context.getDefaultAllocator();
const int numTessLevelCases = 32;
const std::vector<TessLevels> tessLevelCases = genTessLevelCases(numTessLevelCases);
int maxNumVerticesInDrawCall = 0;
for (int i = 0; i < numTessLevelCases; ++i)
maxNumVerticesInDrawCall =
de::max(maxNumVerticesInDrawCall,
referenceVertexCount(caseDef.primitiveType, caseDef.spacingMode, caseDef.usePointMode,
&tessLevelCases[i].inner[0], &tessLevelCases[i].outer[0]));
// We may get more invocations than expected, so add some more space (arbitrary number).
maxNumVerticesInDrawCall += 4;
// Vertex input attributes buffer: to pass tessellation levels
const VkFormat vertexFormat = VK_FORMAT_R32_SFLOAT;
const uint32_t vertexStride = tcu::getPixelSize(mapVkFormat(vertexFormat));
const VkDeviceSize vertexDataSizeBytes = NUM_TESS_LEVELS * vertexStride;
const Buffer vertexBuffer(vk, device, allocator,
makeBufferCreateInfo(vertexDataSizeBytes, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT),
MemoryRequirement::HostVisible);
DE_ASSERT(vertexDataSizeBytes == sizeof(TessLevels));
// Output buffer: number of invocations and array of tess coords
const int resultBufferTessCoordsOffset = (int)sizeof(int32_t) * 4;
const VkDeviceSize resultBufferSizeBytes =
resultBufferTessCoordsOffset + maxNumVerticesInDrawCall * sizeof(tcu::Vec4);
const Buffer resultBuffer(vk, device, allocator,
makeBufferCreateInfo(resultBufferSizeBytes, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT),
MemoryRequirement::HostVisible);
// Descriptors
const Unique<VkDescriptorSetLayout> descriptorSetLayout(
DescriptorSetLayoutBuilder()
.addSingleBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT)
.build(vk, device));
const Unique<VkDescriptorPool> descriptorPool(
DescriptorPoolBuilder()
.addType(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER)
.build(vk, device, VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, 1u));
const Unique<VkDescriptorSet> descriptorSet(makeDescriptorSet(vk, device, *descriptorPool, *descriptorSetLayout));
const VkDescriptorBufferInfo resultBufferInfo =
makeDescriptorBufferInfo(resultBuffer.get(), 0ull, resultBufferSizeBytes);
DescriptorSetUpdateBuilder()
.writeSingle(*descriptorSet, DescriptorSetUpdateBuilder::Location::binding(0u),
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, &resultBufferInfo)
.update(vk, device);
const Unique<VkRenderPass> renderPass(makeRenderPassWithoutAttachments(vk, device));
const Unique<VkFramebuffer> framebuffer(makeFramebuffer(vk, device, *renderPass, 0u, DE_NULL, 1u, 1u));
const Unique<VkPipelineLayout> pipelineLayout(makePipelineLayout(vk, device, *descriptorSetLayout));
const Unique<VkCommandPool> cmdPool(makeCommandPool(vk, device, queueFamilyIndex));
const Unique<VkCommandBuffer> cmdBuffer(
allocateCommandBuffer(vk, device, *cmdPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY));
const Unique<VkPipeline> pipeline(
GraphicsPipelineBuilder()
.setPatchControlPoints(NUM_TESS_LEVELS)
.setVertexInputSingleAttribute(vertexFormat, vertexStride)
.setShader(vk, device, VK_SHADER_STAGE_VERTEX_BIT, context.getBinaryCollection().get("vert"), DE_NULL)
.setShader(vk, device, VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT, context.getBinaryCollection().get("tesc"),
DE_NULL)
.setShader(vk, device, VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT,
context.getBinaryCollection().get("tese"), DE_NULL)
.build(vk, device, *pipelineLayout, *renderPass));
for (int tessLevelCaseNdx = 0; tessLevelCaseNdx < numTessLevelCases; ++tessLevelCaseNdx)
{
context.getTestContext().getLog()
<< tcu::TestLog::Message << "Testing with tessellation levels: "
<< getTessellationLevelsString(tessLevelCases[tessLevelCaseNdx], caseDef.primitiveType)
<< tcu::TestLog::EndMessage;
{
const Allocation &alloc = vertexBuffer.getAllocation();
deMemcpy(alloc.getHostPtr(), &tessLevelCases[tessLevelCaseNdx], sizeof(TessLevels));
flushAlloc(vk, device, alloc);
}
{
const Allocation &alloc = resultBuffer.getAllocation();
deMemset(alloc.getHostPtr(), 0, static_cast<std::size_t>(resultBufferSizeBytes));
flushAlloc(vk, device, alloc);
}
beginCommandBuffer(vk, *cmdBuffer);
beginRenderPassWithRasterizationDisabled(vk, *cmdBuffer, *renderPass, *framebuffer);
vk.cmdBindPipeline(*cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline);
vk.cmdBindDescriptorSets(*cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *pipelineLayout, 0u, 1u,
&descriptorSet.get(), 0u, DE_NULL);
{
const VkDeviceSize vertexBufferOffset = 0ull;
vk.cmdBindVertexBuffers(*cmdBuffer, 0u, 1u, &vertexBuffer.get(), &vertexBufferOffset);
}
vk.cmdDraw(*cmdBuffer, NUM_TESS_LEVELS, 1u, 0u, 0u);
endRenderPass(vk, *cmdBuffer);
{
const VkBufferMemoryBarrier shaderWriteBarrier = makeBufferMemoryBarrier(
VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT, *resultBuffer, 0ull, resultBufferSizeBytes);
vk.cmdPipelineBarrier(*cmdBuffer, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0u, 0u,
DE_NULL, 1u, &shaderWriteBarrier, 0u, DE_NULL);
}
endCommandBuffer(vk, *cmdBuffer);
submitCommandsAndWait(vk, device, queue, *cmdBuffer);
// Verify case result
{
const Allocation &resultAlloc = resultBuffer.getAllocation();
invalidateAlloc(vk, device, resultAlloc);
const int32_t numVertices = *static_cast<int32_t *>(resultAlloc.getHostPtr());
const std::vector<tcu::Vec3> vertices = readInterleavedData<tcu::Vec3>(
numVertices, resultAlloc.getHostPtr(), resultBufferTessCoordsOffset, sizeof(tcu::Vec4));
// If this fails then we didn't read all vertices from shader and test must be changed to allow more.
DE_ASSERT(numVertices <= maxNumVerticesInDrawCall);
tcu::TestLog &log = context.getTestContext().getLog();
const int numComponents = (caseDef.primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? 3 : 2);
CompareFunc compare = (caseDef.caseType == CASETYPE_TESS_COORD_RANGE ? compareTessCoordRange :
caseDef.caseType == CASETYPE_ONE_MINUS_TESS_COORD ? compareOneMinusTessCoord :
DE_NULL);
DE_ASSERT(compare != DE_NULL);
for (std::vector<tcu::Vec3>::const_iterator vertexIter = vertices.begin(); vertexIter != vertices.end();
++vertexIter)
for (int i = 0; i < numComponents; ++i)
if (!compare(log, (*vertexIter)[i]))
{
log << tcu::TestLog::Message << "Note: got a wrong tessellation coordinate "
<< (numComponents == 3 ? de::toString(*vertexIter) :
de::toString(vertexIter->swizzle(0, 1)))
<< tcu::TestLog::EndMessage;
tcu::TestStatus::fail("Invalid tessellation coordinate component");
}
}
}
return tcu::TestStatus::pass("OK");
}
tcu::TestCase *makeTessCoordRangeTest(tcu::TestContext &testCtx, const std::string &name,
const std::string &description, const TessPrimitiveType primitiveType,
const SpacingMode spacingMode, const Winding winding, const bool usePointMode)
{
const CaseDefinition caseDef = {CASETYPE_TESS_COORD_RANGE, primitiveType, spacingMode, winding, usePointMode};
return createFunctionCaseWithPrograms(testCtx, tcu::NODETYPE_SELF_VALIDATE, name, description, checkSupportCase,
initPrograms, test, caseDef);
}
tcu::TestCase *makeOneMinusTessCoordTest(tcu::TestContext &testCtx, const std::string &name,
const std::string &description, const TessPrimitiveType primitiveType,
const SpacingMode spacingMode, const Winding winding, const bool usePointMode)
{
const CaseDefinition caseDef = {CASETYPE_ONE_MINUS_TESS_COORD, primitiveType, spacingMode, winding, usePointMode};
return createFunctionCaseWithPrograms(testCtx, tcu::NODETYPE_SELF_VALIDATE, name, description, checkSupportCase,
initPrograms, test, caseDef);
}
} // namespace TessCoordComponent
} // namespace
//! These tests correspond to dEQP-GLES31.functional.tessellation.invariance.*
//! Original OpenGL ES tests used transform feedback to get vertices in primitive order. To emulate this behavior we have to use geometry shader,
//! which allows us to intercept verticess of final output primitives. This can't be done with tessellation shaders alone as number and order of
//! invocation is undefined.
tcu::TestCaseGroup *createInvarianceTests(tcu::TestContext &testCtx)
{
de::MovePtr<tcu::TestCaseGroup> group(
new tcu::TestCaseGroup(testCtx, "invariance", "Test tessellation invariance rules"));
de::MovePtr<tcu::TestCaseGroup> invariantPrimitiveSetGroup(
new tcu::TestCaseGroup(testCtx, "primitive_set", "Test invariance rule #1"));
de::MovePtr<tcu::TestCaseGroup> invariantOuterEdgeGroup(
new tcu::TestCaseGroup(testCtx, "outer_edge_division", "Test invariance rule #2"));
de::MovePtr<tcu::TestCaseGroup> symmetricOuterEdgeGroup(
new tcu::TestCaseGroup(testCtx, "outer_edge_symmetry", "Test invariance rule #3"));
de::MovePtr<tcu::TestCaseGroup> outerEdgeVertexSetIndexIndependenceGroup(
new tcu::TestCaseGroup(testCtx, "outer_edge_index_independence", "Test invariance rule #4"));
de::MovePtr<tcu::TestCaseGroup> invariantTriangleSetGroup(
new tcu::TestCaseGroup(testCtx, "triangle_set", "Test invariance rule #5"));
de::MovePtr<tcu::TestCaseGroup> invariantInnerTriangleSetGroup(
new tcu::TestCaseGroup(testCtx, "inner_triangle_set", "Test invariance rule #6"));
de::MovePtr<tcu::TestCaseGroup> invariantOuterTriangleSetGroup(
new tcu::TestCaseGroup(testCtx, "outer_triangle_set", "Test invariance rule #7"));
de::MovePtr<tcu::TestCaseGroup> tessCoordComponentRangeGroup(
new tcu::TestCaseGroup(testCtx, "tess_coord_component_range", "Test invariance rule #8, first part"));
de::MovePtr<tcu::TestCaseGroup> oneMinusTessCoordComponentGroup(
new tcu::TestCaseGroup(testCtx, "one_minus_tess_coord_component", "Test invariance rule #8, second part"));
for (int primitiveTypeNdx = 0; primitiveTypeNdx < TESSPRIMITIVETYPE_LAST; ++primitiveTypeNdx)
for (int spacingModeNdx = 0; spacingModeNdx < SPACINGMODE_LAST; ++spacingModeNdx)
{
const TessPrimitiveType primitiveType = static_cast<TessPrimitiveType>(primitiveTypeNdx);
const SpacingMode spacingMode = static_cast<SpacingMode>(spacingModeNdx);
const bool triOrQuad =
primitiveType == TESSPRIMITIVETYPE_TRIANGLES || primitiveType == TESSPRIMITIVETYPE_QUADS;
const std::string primName = getTessPrimitiveTypeShaderName(primitiveType);
const std::string primSpacName = primName + "_" + getSpacingModeShaderName(spacingMode);
if (triOrQuad)
{
invariantOuterEdgeGroup->addChild(InvariantOuterEdge::makeOuterEdgeDivisionTest(
testCtx, primSpacName, "", primitiveType, spacingMode));
invariantTriangleSetGroup->addChild(PrimitiveSetInvariance::makeInvariantTriangleSetTest(
testCtx, primSpacName, "", primitiveType, spacingMode));
invariantInnerTriangleSetGroup->addChild(PrimitiveSetInvariance::makeInvariantInnerTriangleSetTest(
testCtx, primSpacName, "", primitiveType, spacingMode));
invariantOuterTriangleSetGroup->addChild(PrimitiveSetInvariance::makeInvariantOuterTriangleSetTest(
testCtx, primSpacName, "", primitiveType, spacingMode));
}
for (int windingNdx = 0; windingNdx < WINDING_LAST; ++windingNdx)
for (int usePointModeNdx = 0; usePointModeNdx <= 1; ++usePointModeNdx)
{
const Winding winding = static_cast<Winding>(windingNdx);
const bool usePointMode = (usePointModeNdx != 0);
const std::string primSpacWindPointName =
primSpacName + "_" + getWindingShaderName(winding) + (usePointMode ? "_point_mode" : "");
invariantPrimitiveSetGroup->addChild(PrimitiveSetInvariance::makeInvariantPrimitiveSetTest(
testCtx, primSpacWindPointName, "", primitiveType, spacingMode, winding, usePointMode));
tessCoordComponentRangeGroup->addChild(TessCoordComponent::makeTessCoordRangeTest(
testCtx, primSpacWindPointName, "", primitiveType, spacingMode, winding, usePointMode));
oneMinusTessCoordComponentGroup->addChild(TessCoordComponent::makeOneMinusTessCoordTest(
testCtx, primSpacWindPointName, "", primitiveType, spacingMode, winding, usePointMode));
symmetricOuterEdgeGroup->addChild(InvariantOuterEdge::makeSymmetricOuterEdgeTest(
testCtx, primSpacWindPointName, "", primitiveType, spacingMode, winding, usePointMode));
if (triOrQuad)
outerEdgeVertexSetIndexIndependenceGroup->addChild(
InvariantOuterEdge::makeOuterEdgeIndexIndependenceTest(
testCtx, primSpacWindPointName, "", primitiveType, spacingMode, winding, usePointMode));
}
}
group->addChild(invariantPrimitiveSetGroup.release());
group->addChild(invariantOuterEdgeGroup.release());
group->addChild(symmetricOuterEdgeGroup.release());
group->addChild(outerEdgeVertexSetIndexIndependenceGroup.release());
group->addChild(invariantTriangleSetGroup.release());
group->addChild(invariantInnerTriangleSetGroup.release());
group->addChild(invariantOuterTriangleSetGroup.release());
group->addChild(tessCoordComponentRangeGroup.release());
group->addChild(oneMinusTessCoordComponentGroup.release());
return group.release();
}
} // namespace tessellation
} // namespace vkt