| /*------------------------------------------------------------------------ |
| * 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 Geometry Interaction - Grid render (limits, scatter) |
| *//*--------------------------------------------------------------------*/ |
| |
| #include "vktTessellationGeometryGridRenderTests.hpp" |
| #include "vktTestCaseUtil.hpp" |
| #include "vktTessellationUtil.hpp" |
| |
| #include "tcuTestLog.hpp" |
| #include "tcuTextureUtil.hpp" |
| #include "tcuSurface.hpp" |
| #include "tcuRGBA.hpp" |
| |
| #include "vkDefs.hpp" |
| #include "vkBarrierUtil.hpp" |
| #include "vkQueryUtil.hpp" |
| #include "vkBuilderUtil.hpp" |
| #include "vkTypeUtil.hpp" |
| #include "vkImageUtil.hpp" |
| #include "vkCmdUtil.hpp" |
| #include "vkObjUtil.hpp" |
| |
| #include "deUniquePtr.hpp" |
| |
| #include <string> |
| #include <vector> |
| |
| namespace vkt |
| { |
| namespace tessellation |
| { |
| |
| using namespace vk; |
| |
| namespace |
| { |
| |
| enum Constants |
| { |
| RENDER_SIZE = 256, |
| }; |
| |
| enum FlagBits |
| { |
| FLAG_TESSELLATION_MAX_SPEC = 1u << 0, |
| FLAG_GEOMETRY_MAX_SPEC = 1u << 1, |
| FLAG_GEOMETRY_INVOCATIONS_MAX_SPEC = 1u << 2, |
| |
| FLAG_GEOMETRY_SCATTER_INSTANCES = 1u << 3, |
| FLAG_GEOMETRY_SCATTER_PRIMITIVES = 1u << 4, |
| FLAG_GEOMETRY_SEPARATE_PRIMITIVES = 1u << 5, //!< if set, geometry shader outputs separate grid cells and not continuous slices |
| FLAG_GEOMETRY_SCATTER_LAYERS = 1u << 6, |
| }; |
| typedef deUint32 Flags; |
| |
| class GridRenderTestCase : public TestCase |
| { |
| public: |
| void initPrograms (vk::SourceCollections& programCollection) const; |
| TestInstance* createInstance (Context& context) const; |
| |
| GridRenderTestCase (tcu::TestContext& testCtx, const std::string& name, const std::string& description, const Flags flags); |
| |
| private: |
| const Flags m_flags; |
| const int m_tessGenLevel; |
| const int m_numGeometryInvocations; |
| const int m_numLayers; |
| int m_numGeometryPrimitivesPerInvocation; |
| }; |
| |
| GridRenderTestCase::GridRenderTestCase (tcu::TestContext& testCtx, const std::string& name, const std::string& description, const Flags flags) |
| : TestCase (testCtx, name, description) |
| , m_flags (flags) |
| , m_tessGenLevel ((m_flags & FLAG_TESSELLATION_MAX_SPEC) ? 64 : 5) |
| , m_numGeometryInvocations ((m_flags & FLAG_GEOMETRY_INVOCATIONS_MAX_SPEC) ? 32 : 4) |
| , m_numLayers ((m_flags & FLAG_GEOMETRY_SCATTER_LAYERS) ? 8 : 1) |
| { |
| DE_ASSERT(((flags & (FLAG_GEOMETRY_SCATTER_PRIMITIVES | FLAG_GEOMETRY_SCATTER_LAYERS)) != 0) == ((flags & FLAG_GEOMETRY_SEPARATE_PRIMITIVES) != 0)); |
| |
| int geometryOutputVertices = 0; |
| int geometryTotalOutputComponents = 0; |
| |
| if (m_flags & FLAG_GEOMETRY_MAX_SPEC) |
| { |
| geometryOutputVertices = 256; |
| geometryTotalOutputComponents = 1024; |
| } |
| else |
| { |
| geometryOutputVertices = 16; |
| geometryTotalOutputComponents = 1024; |
| } |
| |
| const bool separatePrimitives = (m_flags & FLAG_GEOMETRY_SEPARATE_PRIMITIVES) != 0; |
| const int numComponentsPerVertex = 8; // vec4 pos, vec4 color |
| |
| if (separatePrimitives) |
| { |
| const int numComponentLimit = geometryTotalOutputComponents / (4 * numComponentsPerVertex); |
| const int numOutputLimit = geometryOutputVertices / 4; |
| |
| m_numGeometryPrimitivesPerInvocation = de::min(numComponentLimit, numOutputLimit); |
| } |
| 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 + 2); // each node 2 vertices |
| const int numSliceNodesOutputLimit = geometryOutputVertices / 2; // each node 2 vertices |
| const int numSliceNodes = de::min(numSliceNodesComponentLimit, numSliceNodesOutputLimit); |
| |
| m_numGeometryPrimitivesPerInvocation = (numSliceNodes - 1) * 2; |
| } |
| |
| } |
| |
| void GridRenderTestCase::initPrograms (SourceCollections& programCollection) const |
| { |
| // Vertex shader |
| { |
| std::ostringstream src; |
| src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES) << "\n" |
| << "\n" |
| << "void main (void)\n" |
| << "{\n" |
| << " gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n" |
| << "}\n"; |
| |
| programCollection.glslSources.add("vert") << glu::VertexSource(src.str()); |
| } |
| |
| // Fragment shader |
| { |
| std::ostringstream src; |
| src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES) << "\n" |
| << "layout(location = 0) flat in highp vec4 v_color;\n" |
| << "layout(location = 0) out mediump vec4 fragColor;\n" |
| << "\n" |
| << "void main (void)\n" |
| << "{\n" |
| << " fragColor = v_color;\n" |
| << "}\n"; |
| |
| programCollection.glslSources.add("frag") << glu::FragmentSource(src.str()); |
| } |
| |
| // Tessellation control |
| { |
| std::ostringstream src; |
| src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES) << "\n" |
| "#extension GL_EXT_tessellation_shader : require\n" |
| "layout(vertices = 1) out;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n" |
| " gl_TessLevelInner[0] = float(" << m_tessGenLevel << ");\n" |
| " gl_TessLevelInner[1] = float(" << m_tessGenLevel << ");\n" |
| " gl_TessLevelOuter[0] = float(" << m_tessGenLevel << ");\n" |
| " gl_TessLevelOuter[1] = float(" << m_tessGenLevel << ");\n" |
| " gl_TessLevelOuter[2] = float(" << m_tessGenLevel << ");\n" |
| " gl_TessLevelOuter[3] = float(" << m_tessGenLevel << ");\n" |
| "}\n"; |
| |
| programCollection.glslSources.add("tesc") << glu::TessellationControlSource(src.str()); |
| } |
| |
| // Tessellation evaluation |
| { |
| std::ostringstream src; |
| src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES) << "\n" |
| << "#extension GL_EXT_tessellation_shader : require\n" |
| << "layout(quads) in;\n" |
| << "\n" |
| << "layout(location = 0) 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)) |
| src << " // 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 |
| src << " // 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"; |
| |
| src << " // Calculate position in tessellation grid\n" |
| << " v_tessellationGridPosition = ivec2(round(gl_TessCoord.xy * float(" << m_tessGenLevel << ")));\n" |
| << "}\n"; |
| |
| programCollection.glslSources.add("tese") << glu::TessellationEvaluationSource(src.str()); |
| } |
| |
| // Geometry shader |
| { |
| const int numInvocations = m_numGeometryInvocations; |
| const int numPrimitives = m_numGeometryPrimitivesPerInvocation; |
| |
| std::ostringstream src; |
| |
| src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES) << "\n" |
| << "#extension GL_EXT_geometry_shader : require\n" |
| << "layout(triangles, invocations = " << numInvocations << ") in;\n" |
| << "layout(triangle_strip, max_vertices = " << ((m_flags & FLAG_GEOMETRY_SEPARATE_PRIMITIVES) ? (4 * numPrimitives) : (numPrimitives + 2)) << ") out;\n" |
| << "\n" |
| << "layout(location = 0) in mediump ivec2 v_tessellationGridPosition[];\n" |
| << "layout(location = 0) flat out highp vec4 v_color;\n" |
| << "\n" |
| << "void main (void)\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 |
| src << " // 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(" << m_tessGenLevel << " * " << numPrimitives << ", 2 * " << m_tessGenLevel << " * " << numInvocations << ");\n" |
| << " ivec2 dstGridNdx = ivec2(" << m_tessGenLevel << " * ndx + gridPosition.x, " << m_tessGenLevel << " * 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 == numInvocations * 2); |
| |
| src << " // 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(" << m_tessGenLevel << " * " << numPrimitives << ", " << m_tessGenLevel << ");\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) |
| { |
| src << " // Scatter slices\n" |
| << " int inputTriangleNdx = gl_InvocationID * 2 + ((isBottomTriangle) ? (1) : (0));\n" |
| << " ivec2 srcSliceNdx = ivec2(gridPosition.x, gridPosition.y * " << (numInvocations*2) << " + inputTriangleNdx);\n" |
| << " ivec2 dstSliceNdx = ivec2(7 * srcSliceNdx.x, 127 * srcSliceNdx.y) % ivec2(" << m_tessGenLevel << ", " << m_tessGenLevel << " * " << (numInvocations*2) << ");\n" |
| << "\n" |
| << " // Draw slice to the dstSlice slot\n" |
| << " vec4 outputSliceArea;\n" |
| << " outputSliceArea.x = float(dstSliceNdx.x) / float(" << m_tessGenLevel << ") * 2.0 - 1.0 - gapOffset;\n" |
| << " outputSliceArea.y = float(dstSliceNdx.y) / float(" << (m_tessGenLevel * numInvocations * 2) << ") * 2.0 - 1.0 - gapOffset;\n" |
| << " outputSliceArea.z = float(dstSliceNdx.x+1) / float(" << m_tessGenLevel << ") * 2.0 - 1.0 + gapOffset;\n" |
| << " outputSliceArea.w = float(dstSliceNdx.y+1) / float(" << (m_tessGenLevel * numInvocations * 2) << ") * 2.0 - 1.0 + gapOffset;\n"; |
| } |
| else |
| { |
| src << " // 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 * " << numInvocations << ");\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"; |
| } |
| |
| src << "\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"; |
| } |
| |
| src << "}\n"; |
| |
| programCollection.glslSources.add("geom") << glu::GeometrySource(src.str()); |
| } |
| } |
| |
| class GridRenderTestInstance : public TestInstance |
| { |
| public: |
| struct Params |
| { |
| tcu::TestContext& testCtx; |
| Flags flags; |
| const char* description; |
| int tessGenLevel; |
| int numGeometryInvocations; |
| int numLayers; |
| int numGeometryPrimitivesPerInvocation; |
| |
| Params (tcu::TestContext& testContext) : testCtx(testContext), flags(), description(), tessGenLevel(), numGeometryInvocations(), numLayers(), numGeometryPrimitivesPerInvocation() {} |
| }; |
| GridRenderTestInstance (Context& context, const Params& params); |
| tcu::TestStatus iterate (void); |
| |
| private: |
| Params m_params; |
| }; |
| |
| GridRenderTestInstance::GridRenderTestInstance (Context& context, const Params& params) : TestInstance(context), m_params(params) |
| { |
| tcu::TestContext& testCtx = m_params.testCtx; |
| testCtx.getLog() |
| << tcu::TestLog::Message |
| << "Testing tessellation and geometry shaders that output a large number of primitives.\n" |
| << m_params.description |
| << tcu::TestLog::EndMessage; |
| |
| if (m_params.flags & FLAG_GEOMETRY_SCATTER_LAYERS) |
| testCtx.getLog() << tcu::TestLog::Message << "Rendering to 2d texture array, numLayers = " << m_params.numLayers << tcu::TestLog::EndMessage; |
| |
| testCtx.getLog() |
| << tcu::TestLog::Message |
| << "Tessellation level: " << m_params.tessGenLevel << ", mode = quad.\n" |
| << "\tEach input patch produces " << (m_params.tessGenLevel * m_params.tessGenLevel) << " (" << (m_params.tessGenLevel * m_params.tessGenLevel * 2) << " triangles)\n" |
| << tcu::TestLog::EndMessage; |
| |
| int geometryOutputComponents = 0; |
| int geometryOutputVertices = 0; |
| int geometryTotalOutputComponents = 0; |
| |
| if (m_params.flags & FLAG_GEOMETRY_MAX_SPEC) |
| { |
| testCtx.getLog() << tcu::TestLog::Message << "Using geometry shader minimum maximum output limits." << tcu::TestLog::EndMessage; |
| |
| geometryOutputComponents = 64; |
| geometryOutputVertices = 256; |
| geometryTotalOutputComponents = 1024; |
| } |
| else |
| { |
| geometryOutputComponents = 64; |
| geometryOutputVertices = 16; |
| geometryTotalOutputComponents = 1024; |
| } |
| |
| if ((m_params.flags & FLAG_GEOMETRY_MAX_SPEC) || (m_params.flags & FLAG_GEOMETRY_INVOCATIONS_MAX_SPEC)) |
| { |
| tcu::MessageBuilder msg(&testCtx.getLog()); |
| |
| msg << "Geometry shader, targeting following limits:\n"; |
| |
| if (m_params.flags & FLAG_GEOMETRY_MAX_SPEC) |
| msg << "\tmaxGeometryOutputComponents = " << geometryOutputComponents << "\n" |
| << "\tmaxGeometryOutputVertices = " << geometryOutputVertices << "\n" |
| << "\tmaxGeometryTotalOutputComponents = " << geometryTotalOutputComponents << "\n"; |
| |
| if (m_params.flags & FLAG_GEOMETRY_INVOCATIONS_MAX_SPEC) |
| msg << "\tmaxGeometryShaderInvocations = " << m_params.numGeometryInvocations; |
| |
| msg << tcu::TestLog::EndMessage; |
| } |
| |
| const bool separatePrimitives = (m_params.flags & FLAG_GEOMETRY_SEPARATE_PRIMITIVES) != 0; |
| const int numComponentsPerVertex = 8; // vec4 pos, vec4 color |
| int numVerticesPerInvocation = 0; |
| int geometryVerticesPerPrimitive = 0; |
| int geometryPrimitivesOutPerPrimitive = 0; |
| |
| if (separatePrimitives) |
| { |
| numVerticesPerInvocation = m_params.numGeometryPrimitivesPerInvocation * 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; |
| } |
| |
| geometryVerticesPerPrimitive = numVerticesPerInvocation * m_params.numGeometryInvocations; |
| geometryPrimitivesOutPerPrimitive = m_params.numGeometryPrimitivesPerInvocation * m_params.numGeometryInvocations; |
| |
| testCtx.getLog() |
| << tcu::TestLog::Message |
| << "Geometry shader:\n" |
| << "\tTotal output vertex count per invocation: " << numVerticesPerInvocation << "\n" |
| << "\tTotal output primitive count per invocation: " << m_params.numGeometryPrimitivesPerInvocation << "\n" |
| << "\tNumber of invocations per primitive: " << m_params.numGeometryInvocations << "\n" |
| << "\tTotal output vertex count per input primitive: " << geometryVerticesPerPrimitive << "\n" |
| << "\tTotal output primitive count per input primitive: " << geometryPrimitivesOutPerPrimitive << "\n" |
| << tcu::TestLog::EndMessage; |
| |
| testCtx.getLog() |
| << tcu::TestLog::Message |
| << "Program:\n" |
| << "\tTotal program output vertices count per input patch: " << (m_params.tessGenLevel * m_params.tessGenLevel * 2 * geometryVerticesPerPrimitive) << "\n" |
| << "\tTotal program output primitive count per input patch: " << (m_params.tessGenLevel * m_params.tessGenLevel * 2 * geometryPrimitivesOutPerPrimitive) << "\n" |
| << tcu::TestLog::EndMessage; |
| } |
| |
| TestInstance* GridRenderTestCase::createInstance (Context& context) const |
| { |
| GridRenderTestInstance::Params params(m_testCtx); |
| |
| params.flags = m_flags; |
| params.description = getDescription(); |
| params.tessGenLevel = m_tessGenLevel; |
| params.numGeometryInvocations = m_numGeometryInvocations; |
| params.numLayers = m_numLayers; |
| params.numGeometryPrimitivesPerInvocation = m_numGeometryPrimitivesPerInvocation; |
| |
| return new GridRenderTestInstance(context, params); |
| } |
| |
| bool verifyResultLayer (tcu::TestLog& log, const tcu::ConstPixelBufferAccess& image, const int layerNdx) |
| { |
| tcu::Surface errorMask (image.getWidth(), image.getHeight()); |
| bool foundError = false; |
| |
| tcu::clear(errorMask.getAccess(), tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f)); |
| |
| log << 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) |
| { |
| log << tcu::TestLog::Message << "Image valid." << tcu::TestLog::EndMessage |
| << tcu::TestLog::ImageSet("ImageVerification", "Image verification") |
| << tcu::TestLog::Image("Result", "Rendered result", image) |
| << tcu::TestLog::EndImageSet; |
| return true; |
| } |
| else |
| { |
| log << 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) |
| << tcu::TestLog::Image("ErrorMask", "Error mask", errorMask.getAccess()) |
| << tcu::TestLog::EndImageSet; |
| return false; |
| } |
| } |
| |
| tcu::TestStatus GridRenderTestInstance::iterate (void) |
| { |
| requireFeatures(m_context.getInstanceInterface(), m_context.getPhysicalDevice(), FEATURE_TESSELLATION_SHADER | FEATURE_GEOMETRY_SHADER); |
| |
| m_context.getTestContext().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; |
| |
| const DeviceInterface& vk = m_context.getDeviceInterface(); |
| const VkDevice device = m_context.getDevice(); |
| const VkQueue queue = m_context.getUniversalQueue(); |
| const deUint32 queueFamilyIndex = m_context.getUniversalQueueFamilyIndex(); |
| Allocator& allocator = m_context.getDefaultAllocator(); |
| |
| // Color attachment |
| |
| const tcu::IVec2 renderSize = tcu::IVec2(RENDER_SIZE, RENDER_SIZE); |
| const VkFormat colorFormat = VK_FORMAT_R8G8B8A8_UNORM; |
| const VkImageSubresourceRange colorImageAllLayersRange = makeImageSubresourceRange(VK_IMAGE_ASPECT_COLOR_BIT, 0u, 1u, 0u, m_params.numLayers); |
| const VkImageCreateInfo colorImageCreateInfo = makeImageCreateInfo(renderSize, colorFormat, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT, m_params.numLayers); |
| const VkImageViewType colorAttachmentViewType = (m_params.numLayers == 1 ? VK_IMAGE_VIEW_TYPE_2D : VK_IMAGE_VIEW_TYPE_2D_ARRAY); |
| const Image colorAttachmentImage (vk, device, allocator, colorImageCreateInfo, MemoryRequirement::Any); |
| |
| // Color output buffer: image will be copied here for verification (big enough for all layers). |
| |
| const VkDeviceSize colorBufferSizeBytes = renderSize.x()*renderSize.y() * m_params.numLayers * tcu::getPixelSize(mapVkFormat(colorFormat)); |
| const Buffer colorBuffer (vk, device, allocator, makeBufferCreateInfo(colorBufferSizeBytes, VK_BUFFER_USAGE_TRANSFER_DST_BIT), MemoryRequirement::HostVisible); |
| |
| // Pipeline: no vertex input attributes nor descriptors. |
| |
| const Unique<VkImageView> colorAttachmentView (makeImageView (vk, device, *colorAttachmentImage, colorAttachmentViewType, colorFormat, colorImageAllLayersRange)); |
| const Unique<VkRenderPass> renderPass (makeRenderPass (vk, device, colorFormat)); |
| const Unique<VkFramebuffer> framebuffer (makeFramebuffer (vk, device, *renderPass, *colorAttachmentView, renderSize.x(), renderSize.y(), m_params.numLayers)); |
| const Unique<VkPipelineLayout> pipelineLayout (makePipelineLayout (vk, device)); |
| 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() |
| .setRenderSize (renderSize) |
| .setShader (vk, device, VK_SHADER_STAGE_VERTEX_BIT, m_context.getBinaryCollection().get("vert"), DE_NULL) |
| .setShader (vk, device, VK_SHADER_STAGE_FRAGMENT_BIT, m_context.getBinaryCollection().get("frag"), 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("tese"), DE_NULL) |
| .setShader (vk, device, VK_SHADER_STAGE_GEOMETRY_BIT, m_context.getBinaryCollection().get("geom"), DE_NULL) |
| .build (vk, device, *pipelineLayout, *renderPass)); |
| |
| beginCommandBuffer(vk, *cmdBuffer); |
| |
| // Change color attachment image layout |
| { |
| const VkImageMemoryBarrier colorAttachmentLayoutBarrier = makeImageMemoryBarrier( |
| (VkAccessFlags)0, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, |
| VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, |
| *colorAttachmentImage, colorImageAllLayersRange); |
| |
| vk.cmdPipelineBarrier(*cmdBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0u, |
| 0u, DE_NULL, 0u, DE_NULL, 1u, &colorAttachmentLayoutBarrier); |
| } |
| |
| // Begin render pass |
| { |
| const VkRect2D renderArea = makeRect2D(renderSize); |
| const tcu::Vec4 clearColor (0.0f, 0.0f, 0.0f, 1.0f); |
| |
| beginRenderPass(vk, *cmdBuffer, *renderPass, *framebuffer, renderArea, clearColor); |
| } |
| |
| vk.cmdBindPipeline(*cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline); |
| |
| vk.cmdDraw(*cmdBuffer, 1u, 1u, 0u, 0u); |
| endRenderPass(vk, *cmdBuffer); |
| |
| // Copy render result to a host-visible buffer |
| copyImageToBuffer(vk, *cmdBuffer, *colorAttachmentImage, *colorBuffer, renderSize, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, m_params.numLayers); |
| |
| endCommandBuffer(vk, *cmdBuffer); |
| submitCommandsAndWait(vk, device, queue, *cmdBuffer); |
| |
| // Verify results |
| { |
| const Allocation& alloc (colorBuffer.getAllocation()); |
| |
| invalidateAlloc(vk, device, alloc); |
| |
| const tcu::ConstPixelBufferAccess imageAllLayers (mapVkFormat(colorFormat), renderSize.x(), renderSize.y(), m_params.numLayers, alloc.getHostPtr()); |
| bool allOk (true); |
| |
| for (int ndx = 0; ndx < m_params.numLayers; ++ndx) |
| allOk = allOk && verifyResultLayer(m_context.getTestContext().getLog(), |
| tcu::getSubregion(imageAllLayers, 0, 0, ndx, renderSize.x(), renderSize.y(), 1), |
| ndx); |
| |
| return (allOk ? tcu::TestStatus::pass("OK") : tcu::TestStatus::fail("Image comparison failed")); |
| } |
| } |
| |
| struct TestCaseDescription |
| { |
| const char* name; |
| const char* desc; |
| Flags flags; |
| }; |
| |
| } // anonymous |
| |
| //! Ported from dEQP-GLES31.functional.tessellation_geometry_interaction.render.limits.* |
| //! \note Tests that check implementation defined limits were omitted, because they rely on runtime shader source generation |
| //! (e.g. changing the number of vertices output from geometry shader). CTS currently doesn't support that, |
| //! because some platforms require precompiled shaders. |
| tcu::TestCaseGroup* createGeometryGridRenderLimitsTests (tcu::TestContext& testCtx) |
| { |
| de::MovePtr<tcu::TestCaseGroup> group (new tcu::TestCaseGroup(testCtx, "limits", "Render with properties near their limits")); |
| |
| static const TestCaseDescription cases[] = |
| { |
| { |
| "output_required_max_tessellation", |
| "Minimum maximum tessellation level", |
| FLAG_TESSELLATION_MAX_SPEC |
| }, |
| { |
| "output_required_max_geometry", |
| "Output minimum maximum number of vertices the geometry shader", |
| FLAG_GEOMETRY_MAX_SPEC |
| }, |
| { |
| "output_required_max_invocations", |
| "Minimum maximum number of geometry shader invocations", |
| FLAG_GEOMETRY_INVOCATIONS_MAX_SPEC |
| }, |
| }; |
| |
| for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(cases); ++ndx) |
| group->addChild(new GridRenderTestCase(testCtx, cases[ndx].name, cases[ndx].desc, cases[ndx].flags)); |
| |
| return group.release(); |
| } |
| |
| //! Ported from dEQP-GLES31.functional.tessellation_geometry_interaction.render.scatter.* |
| tcu::TestCaseGroup* createGeometryGridRenderScatterTests (tcu::TestContext& testCtx) |
| { |
| de::MovePtr<tcu::TestCaseGroup> group (new tcu::TestCaseGroup(testCtx, "scatter", "Scatter output primitives")); |
| |
| static const TestCaseDescription cases[] = |
| { |
| { |
| "geometry_scatter_instances", |
| "Each geometry shader instance outputs its primitives far from other instances of the same execution", |
| FLAG_GEOMETRY_SCATTER_INSTANCES |
| }, |
| { |
| "geometry_scatter_primitives", |
| "Each geometry shader instance outputs its primitives far from other primitives of the same instance", |
| FLAG_GEOMETRY_SCATTER_PRIMITIVES | FLAG_GEOMETRY_SEPARATE_PRIMITIVES |
| }, |
| { |
| "geometry_scatter_layers", |
| "Each geometry shader instance outputs its primitives to multiple layers and far from other primitives of the same instance", |
| FLAG_GEOMETRY_SCATTER_LAYERS | FLAG_GEOMETRY_SEPARATE_PRIMITIVES |
| }, |
| }; |
| |
| for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(cases); ++ndx) |
| group->addChild(new GridRenderTestCase(testCtx, cases[ndx].name, cases[ndx].desc, cases[ndx].flags)); |
| |
| return group.release(); |
| } |
| |
| } // tessellation |
| } // vkt |