1 /*------------------------------------------------------------------------
2  * Vulkan Conformance Tests
3  * ------------------------
4  *
5  * Copyright (c) 2014 The Android Open Source Project
6  * Copyright (c) 2016 The Khronos Group Inc.
7  *
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  *
20  *//*!
21  * \file
22  * \brief Tessellation Primitive Discard Tests
23  *//*--------------------------------------------------------------------*/
24 
25 #include "vktTessellationPrimitiveDiscardTests.hpp"
26 #include "vktTestCaseUtil.hpp"
27 #include "vktTessellationUtil.hpp"
28 
29 #include "tcuTestLog.hpp"
30 
31 #include "vkDefs.hpp"
32 #include "vkQueryUtil.hpp"
33 #include "vkBuilderUtil.hpp"
34 #include "vkImageUtil.hpp"
35 #include "vkTypeUtil.hpp"
36 #include "vkCmdUtil.hpp"
37 #include "vkObjUtil.hpp"
38 #include "vkBarrierUtil.hpp"
39 #include "vkBufferWithMemory.hpp"
40 #include "vkImageWithMemory.hpp"
41 
42 #include "deUniquePtr.hpp"
43 #include "deStringUtil.hpp"
44 
45 #include <string>
46 #include <vector>
47 
48 namespace vkt
49 {
50 namespace tessellation
51 {
52 
53 using namespace vk;
54 
55 namespace
56 {
57 
58 struct CaseDefinition
59 {
60     TessPrimitiveType primitiveType;
61     SpacingMode spacingMode;
62     Winding winding;
63     bool usePointMode;
64     bool useLessThanOneInnerLevels;
65 };
66 
lessThanOneInnerLevelsDefined(const CaseDefinition & caseDef)67 bool lessThanOneInnerLevelsDefined(const CaseDefinition &caseDef)
68 {
69     // From Vulkan API specification:
70     // >> When tessellating triangles or quads (with/without point mode) with fractional odd spacing, the tessellator
71     // >> ***may*** produce interior vertices that are positioned on the edge of the patch if an inner
72     // >> tessellation level is less than or equal to one.
73     return !((caseDef.primitiveType == vkt::tessellation::TESSPRIMITIVETYPE_QUADS ||
74               caseDef.primitiveType == vkt::tessellation::TESSPRIMITIVETYPE_TRIANGLES) &&
75              caseDef.spacingMode == vkt::tessellation::SPACINGMODE_FRACTIONAL_ODD);
76 }
77 
intPow(int base,int exp)78 int intPow(int base, int exp)
79 {
80     DE_ASSERT(exp >= 0);
81     if (exp == 0)
82         return 1;
83     else
84     {
85         const int sub = intPow(base, exp / 2);
86         if (exp % 2 == 0)
87             return sub * sub;
88         else
89             return sub * sub * base;
90     }
91 }
92 
genAttributes(bool useLessThanOneInnerLevels)93 std::vector<float> genAttributes(bool useLessThanOneInnerLevels)
94 {
95     // Generate input attributes (tessellation levels, and position scale and
96     // offset) for a number of primitives. Each primitive has a different
97     // combination of tessellatio levels; each level is either a valid
98     // value or an "invalid" value (negative or zero, chosen from
99     // invalidTessLevelChoices).
100 
101     // \note The attributes are generated in such an order that all of the
102     //         valid attribute tuples come before the first invalid one both
103     //         in the result vector, and when scanning the resulting 2d grid
104     //         of primitives is scanned in y-major order. This makes
105     //         verification somewhat simpler.
106 
107     static const float baseTessLevels[6]         = {3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f};
108     static const float invalidTessLevelChoices[] = {-0.42f, 0.0f};
109     const int numChoices                         = 1 + DE_LENGTH_OF_ARRAY(invalidTessLevelChoices);
110     float choices[6][numChoices];
111     std::vector<float> result;
112 
113     for (int levelNdx = 0; levelNdx < 6; levelNdx++)
114         for (int choiceNdx = 0; choiceNdx < numChoices; choiceNdx++)
115             choices[levelNdx][choiceNdx] = (choiceNdx == 0 || !useLessThanOneInnerLevels) ?
116                                                baseTessLevels[levelNdx] :
117                                                invalidTessLevelChoices[choiceNdx - 1];
118 
119     {
120         const int numCols = intPow(numChoices, 6 / 2); // sqrt(numChoices**6) == sqrt(number of primitives)
121         const int numRows = numCols;
122         int index         = 0;
123         int i[6];
124         // We could do this with some generic combination-generation function, but meh, it's not that bad.
125         for (i[2] = 0; i[2] < numChoices; i[2]++)                     // First  outer
126             for (i[3] = 0; i[3] < numChoices; i[3]++)                 // Second outer
127                 for (i[4] = 0; i[4] < numChoices; i[4]++)             // Third  outer
128                     for (i[5] = 0; i[5] < numChoices; i[5]++)         // Fourth outer
129                         for (i[0] = 0; i[0] < numChoices; i[0]++)     // First  inner
130                             for (i[1] = 0; i[1] < numChoices; i[1]++) // Second inner
131                             {
132                                 for (int j = 0; j < 6; j++)
133                                     result.push_back(choices[j][i[j]]);
134 
135                                 {
136                                     const int col = index % numCols;
137                                     const int row = index / numCols;
138                                     // Position scale.
139                                     result.push_back((float)2.0f / (float)numCols);
140                                     result.push_back((float)2.0f / (float)numRows);
141                                     // Position offset.
142                                     result.push_back((float)col / (float)numCols * 2.0f - 1.0f);
143                                     result.push_back((float)row / (float)numRows * 2.0f - 1.0f);
144                                 }
145 
146                                 index++;
147                             }
148     }
149 
150     return result;
151 }
152 
153 //! Check that white pixels are found around every non-discarded patch,
154 //! and that only black pixels are found after the last non-discarded patch.
155 //! Returns true on successful comparison.
verifyResultImage(tcu::TestLog & log,const int numPrimitives,const int numAttribsPerPrimitive,const TessPrimitiveType primitiveType,const std::vector<float> & attributes,const tcu::ConstPixelBufferAccess pixels)156 bool verifyResultImage(tcu::TestLog &log, const int numPrimitives, const int numAttribsPerPrimitive,
157                        const TessPrimitiveType primitiveType, const std::vector<float> &attributes,
158                        const tcu::ConstPixelBufferAccess pixels)
159 {
160     const tcu::Vec4 black(0.0f, 0.0f, 0.0f, 1.0f);
161     const tcu::Vec4 white(1.0f, 1.0f, 1.0f, 1.0f);
162 
163     int lastWhitePixelRow                               = 0;
164     int secondToLastWhitePixelRow                       = 0;
165     int lastWhitePixelColumnOnSecondToLastWhitePixelRow = 0;
166 
167     for (int patchNdx = 0; patchNdx < numPrimitives; ++patchNdx)
168     {
169         const float *const attr = &attributes[numAttribsPerPrimitive * patchNdx];
170         const bool validLevels  = !isPatchDiscarded(primitiveType, &attr[2]);
171 
172         if (validLevels)
173         {
174             // Not a discarded patch; check that at least one white pixel is found in its area.
175 
176             const float *const scale  = &attr[6];
177             const float *const offset = &attr[8];
178             const int x0              = (int)((offset[0] + 1.0f) * 0.5f * (float)pixels.getWidth()) - 1;
179             const int x1              = (int)((scale[0] + offset[0] + 1.0f) * 0.5f * (float)pixels.getWidth()) + 1;
180             const int y0              = (int)((offset[1] + 1.0f) * 0.5f * (float)pixels.getHeight()) - 1;
181             const int y1              = (int)((scale[1] + offset[1] + 1.0f) * 0.5f * (float)pixels.getHeight()) + 1;
182             bool pixelOk              = false;
183 
184             if (y1 > lastWhitePixelRow)
185             {
186                 secondToLastWhitePixelRow = lastWhitePixelRow;
187                 lastWhitePixelRow         = y1;
188             }
189             lastWhitePixelColumnOnSecondToLastWhitePixelRow = x1;
190 
191             for (int y = y0; y <= y1 && !pixelOk; y++)
192                 for (int x = x0; x <= x1 && !pixelOk; x++)
193                 {
194                     if (!de::inBounds(x, 0, pixels.getWidth()) || !de::inBounds(y, 0, pixels.getHeight()))
195                         continue;
196 
197                     if (pixels.getPixel(x, y) == white)
198                         pixelOk = true;
199                 }
200 
201             if (!pixelOk)
202             {
203                 log << tcu::TestLog::Message << "Failure: expected at least one white pixel in the rectangle "
204                     << "[x0=" << x0 << ", y0=" << y0 << ", x1=" << x1 << ", y1=" << y1 << "]"
205                     << tcu::TestLog::EndMessage << tcu::TestLog::Message
206                     << "Note: the rectangle approximately corresponds to the patch with these tessellation levels: "
207                     << getTessellationLevelsString(&attr[0], &attr[1]) << tcu::TestLog::EndMessage;
208 
209                 return false;
210             }
211         }
212         else
213         {
214             // First discarded primitive patch; the remaining are guaranteed to be discarded ones as well.
215 
216             for (int y = 0; y < pixels.getHeight(); y++)
217                 for (int x = 0; x < pixels.getWidth(); x++)
218                 {
219                     if (y > lastWhitePixelRow ||
220                         (y > secondToLastWhitePixelRow && x > lastWhitePixelColumnOnSecondToLastWhitePixelRow))
221                     {
222                         if (pixels.getPixel(x, y) != black)
223                         {
224                             log << tcu::TestLog::Message << "Failure: expected all pixels to be black in the area "
225                                 << (lastWhitePixelColumnOnSecondToLastWhitePixelRow < pixels.getWidth() - 1 ?
226                                         std::string() + "y > " + de::toString(lastWhitePixelRow) + " || (y > " +
227                                             de::toString(secondToLastWhitePixelRow) + " && x > " +
228                                             de::toString(lastWhitePixelColumnOnSecondToLastWhitePixelRow) + ")" :
229                                         std::string() + "y > " + de::toString(lastWhitePixelRow))
230                                 << " (they all correspond to patches that should be discarded)"
231                                 << tcu::TestLog::EndMessage << tcu::TestLog::Message << "Note: pixel "
232                                 << tcu::IVec2(x, y) << " isn't black" << tcu::TestLog::EndMessage;
233 
234                             return false;
235                         }
236                     }
237                 }
238             break;
239         }
240     }
241     return true;
242 }
243 
expectedVertexCount(const int numPrimitives,const int numAttribsPerPrimitive,const TessPrimitiveType primitiveType,const SpacingMode spacingMode,const std::vector<float> & attributes)244 int expectedVertexCount(const int numPrimitives, const int numAttribsPerPrimitive,
245                         const TessPrimitiveType primitiveType, const SpacingMode spacingMode,
246                         const std::vector<float> &attributes)
247 {
248     int count = 0;
249     for (int patchNdx = 0; patchNdx < numPrimitives; ++patchNdx)
250         count +=
251             referenceVertexCount(primitiveType, spacingMode, true, &attributes[numAttribsPerPrimitive * patchNdx + 0],
252                                  &attributes[numAttribsPerPrimitive * patchNdx + 2]);
253     return count;
254 }
255 
initPrograms(vk::SourceCollections & programCollection,const CaseDefinition caseDef)256 void initPrograms(vk::SourceCollections &programCollection, const CaseDefinition caseDef)
257 {
258     // Vertex shader
259     {
260         std::ostringstream src;
261         src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES) << "\n"
262             << "\n"
263             << "layout(location = 0) in  highp float in_v_attr;\n"
264             << "layout(location = 0) out highp float in_tc_attr;\n"
265             << "\n"
266             << "void main (void)\n"
267             << "{\n"
268             << "    in_tc_attr = in_v_attr;\n"
269             << "}\n";
270 
271         programCollection.glslSources.add("vert") << glu::VertexSource(src.str());
272     }
273 
274     // Tessellation control shader
275     {
276         std::ostringstream src;
277         src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES) << "\n"
278             << "#extension GL_EXT_tessellation_shader : require\n"
279             << "\n"
280             << "layout(vertices = 1) out;\n"
281             << "\n"
282             << "layout(location = 0) in highp float in_tc_attr[];\n"
283             << "\n"
284             << "layout(location = 0) patch out highp vec2 in_te_positionScale;\n"
285             << "layout(location = 1) patch out highp vec2 in_te_positionOffset;\n"
286             << "\n"
287             << "void main (void)\n"
288             << "{\n"
289             << "    in_te_positionScale  = vec2(in_tc_attr[6], in_tc_attr[7]);\n"
290             << "    in_te_positionOffset = vec2(in_tc_attr[8], in_tc_attr[9]);\n"
291             << "\n"
292             << "    gl_TessLevelInner[0] = in_tc_attr[0];\n"
293             << "    gl_TessLevelInner[1] = in_tc_attr[1];\n"
294             << "\n"
295             << "    gl_TessLevelOuter[0] = in_tc_attr[2];\n"
296             << "    gl_TessLevelOuter[1] = in_tc_attr[3];\n"
297             << "    gl_TessLevelOuter[2] = in_tc_attr[4];\n"
298             << "    gl_TessLevelOuter[3] = in_tc_attr[5];\n"
299             << "}\n";
300 
301         programCollection.glslSources.add("tesc") << glu::TessellationControlSource(src.str());
302     }
303 
304     // Tessellation evaluation shader
305     // When using point mode we need two variants of the shader, one for the case where
306     // shaderTessellationAndGeometryPointSize is enabled (in which the tessellation evaluation
307     // shader needs to write to gl_PointSize for it to be defined) and one for the case where
308     // it is disabled, in which we can't write to gl_PointSize but it has a default value
309     // of 1.0
310     {
311         const uint32_t numVariants = caseDef.usePointMode ? 2 : 1;
312         for (uint32_t variant = 0; variant < numVariants; variant++)
313         {
314             const bool needPointSizeWrite = caseDef.usePointMode && variant == 1;
315 
316             std::ostringstream src;
317             src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES) << "\n"
318                 << "#extension GL_EXT_tessellation_shader : require\n";
319             if (needPointSizeWrite)
320             {
321                 src << "#extension GL_EXT_tessellation_point_size : require\n";
322             }
323             src << "\n"
324                 << "layout(" << getTessPrimitiveTypeShaderName(caseDef.primitiveType) << ", "
325                 << getSpacingModeShaderName(caseDef.spacingMode) << ", " << getWindingShaderName(caseDef.winding)
326                 << (caseDef.usePointMode ? ", point_mode" : "") << ") in;\n"
327                 << "\n"
328                 << "layout(location = 0) patch in highp vec2 in_te_positionScale;\n"
329                 << "layout(location = 1) patch in highp vec2 in_te_positionOffset;\n"
330                 << "\n"
331                 << "layout(set = 0, binding = 0, std430) coherent restrict buffer Output {\n"
332                 << "    int  numInvocations;\n"
333                 << "} sb_out;\n"
334                 << "\n"
335                 << "void main (void)\n"
336                 << "{\n"
337                 << "    atomicAdd(sb_out.numInvocations, 1);\n"
338                 << "\n"
339                 << "    gl_Position = vec4(gl_TessCoord.xy*in_te_positionScale + in_te_positionOffset, 0.0, 1.0);\n";
340             if (needPointSizeWrite)
341             {
342                 src << "    gl_PointSize = 1.0;\n";
343             }
344             src << "}\n";
345 
346             programCollection.glslSources.add(needPointSizeWrite ? "tese_psw" : "tese")
347                 << glu::TessellationEvaluationSource(src.str());
348         }
349     }
350 
351     // Fragment shader
352     {
353         std::ostringstream src;
354         src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES) << "\n"
355             << "\n"
356             << "layout(location = 0) out mediump vec4 o_color;\n"
357             << "\n"
358             << "void main (void)\n"
359             << "{\n"
360             << "    o_color = vec4(1.0);\n"
361             << "}\n";
362 
363         programCollection.glslSources.add("frag") << glu::FragmentSource(src.str());
364     }
365 }
366 
367 /*--------------------------------------------------------------------*//*!
368  * \brief Test that patch is discarded if relevant outer level <= 0.0
369  *
370  * Draws patches with different combinations of tessellation levels,
371  * varying which levels are negative. Verifies by checking that white
372  * pixels exist inside the area of valid primitives, and only black pixels
373  * exist inside the area of discarded primitives. An additional sanity
374  * test is done, checking that the number of primitives written by shader is
375  * correct.
376  *//*--------------------------------------------------------------------*/
test(Context & context,const CaseDefinition caseDef)377 tcu::TestStatus test(Context &context, const CaseDefinition caseDef)
378 {
379     requireFeatures(context.getInstanceInterface(), context.getPhysicalDevice(),
380                     FEATURE_TESSELLATION_SHADER | FEATURE_VERTEX_PIPELINE_STORES_AND_ATOMICS);
381 
382     const DeviceInterface &vk       = context.getDeviceInterface();
383     const VkDevice device           = context.getDevice();
384     const VkQueue queue             = context.getUniversalQueue();
385     const uint32_t queueFamilyIndex = context.getUniversalQueueFamilyIndex();
386     Allocator &allocator            = context.getDefaultAllocator();
387 
388     const std::vector<float> attributes = genAttributes(caseDef.useLessThanOneInnerLevels);
389     const int numAttribsPerPrimitive    = 6 + 2 + 2; // Tess levels, scale, offset.
390     const int numPrimitives             = static_cast<int>(attributes.size() / numAttribsPerPrimitive);
391     const int numExpectedVertices = expectedVertexCount(numPrimitives, numAttribsPerPrimitive, caseDef.primitiveType,
392                                                         caseDef.spacingMode, attributes);
393 
394     // Check the convenience assertion that all discarded patches come after the last non-discarded patch.
395     {
396         bool discardedPatchEncountered = false;
397         for (int patchNdx = 0; patchNdx < numPrimitives; ++patchNdx)
398         {
399             const bool discard =
400                 isPatchDiscarded(caseDef.primitiveType, &attributes[numAttribsPerPrimitive * patchNdx + 2]);
401             DE_ASSERT(discard || !discardedPatchEncountered);
402             discardedPatchEncountered = discard;
403         }
404         DE_UNREF(discardedPatchEncountered);
405     }
406 
407     // Vertex input attributes buffer
408 
409     const VkFormat vertexFormat            = VK_FORMAT_R32_SFLOAT;
410     const uint32_t vertexStride            = tcu::getPixelSize(mapVkFormat(vertexFormat));
411     const VkDeviceSize vertexDataSizeBytes = sizeInBytes(attributes);
412     const BufferWithMemory vertexBuffer(vk, device, allocator,
413                                         makeBufferCreateInfo(vertexDataSizeBytes, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT),
414                                         MemoryRequirement::HostVisible);
415 
416     DE_ASSERT(static_cast<int>(attributes.size()) == numPrimitives * numAttribsPerPrimitive);
417     DE_ASSERT(sizeof(attributes[0]) == vertexStride);
418 
419     {
420         const Allocation &alloc = vertexBuffer.getAllocation();
421 
422         deMemcpy(alloc.getHostPtr(), &attributes[0], static_cast<std::size_t>(vertexDataSizeBytes));
423         flushAlloc(vk, device, alloc);
424         // No barrier needed, flushed memory is automatically visible
425     }
426 
427     // Output buffer: number of invocations
428 
429     const VkDeviceSize resultBufferSizeBytes = sizeof(int32_t);
430     const BufferWithMemory resultBuffer(vk, device, allocator,
431                                         makeBufferCreateInfo(resultBufferSizeBytes, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT),
432                                         MemoryRequirement::HostVisible);
433 
434     {
435         const Allocation &alloc = resultBuffer.getAllocation();
436 
437         deMemset(alloc.getHostPtr(), 0, static_cast<std::size_t>(resultBufferSizeBytes));
438         flushAlloc(vk, device, alloc);
439     }
440 
441     // Color attachment
442 
443     const tcu::IVec2 renderSize = tcu::IVec2(256, 256);
444     const VkFormat colorFormat  = VK_FORMAT_R8G8B8A8_UNORM;
445     const VkImageSubresourceRange colorImageSubresourceRange =
446         makeImageSubresourceRange(VK_IMAGE_ASPECT_COLOR_BIT, 0u, 1u, 0u, 1u);
447     const ImageWithMemory colorAttachmentImage(
448         vk, device, allocator,
449         makeImageCreateInfo(renderSize, colorFormat,
450                             VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT, 1u),
451         MemoryRequirement::Any);
452 
453     // Color output buffer: image will be copied here for verification
454 
455     const VkDeviceSize colorBufferSizeBytes =
456         renderSize.x() * renderSize.y() * tcu::getPixelSize(mapVkFormat(colorFormat));
457     const BufferWithMemory colorBuffer(vk, device, allocator,
458                                        makeBufferCreateInfo(colorBufferSizeBytes, VK_BUFFER_USAGE_TRANSFER_DST_BIT),
459                                        MemoryRequirement::HostVisible);
460 
461     // Descriptors
462 
463     const Unique<VkDescriptorSetLayout> descriptorSetLayout(
464         DescriptorSetLayoutBuilder()
465             .addSingleBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT)
466             .build(vk, device));
467 
468     const Unique<VkDescriptorPool> descriptorPool(
469         DescriptorPoolBuilder()
470             .addType(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER)
471             .build(vk, device, VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, 1u));
472 
473     const Unique<VkDescriptorSet> descriptorSet(makeDescriptorSet(vk, device, *descriptorPool, *descriptorSetLayout));
474     const VkDescriptorBufferInfo resultBufferInfo =
475         makeDescriptorBufferInfo(resultBuffer.get(), 0ull, resultBufferSizeBytes);
476 
477     DescriptorSetUpdateBuilder()
478         .writeSingle(*descriptorSet, DescriptorSetUpdateBuilder::Location::binding(0u),
479                      VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, &resultBufferInfo)
480         .update(vk, device);
481 
482     // Pipeline
483 
484     const Unique<VkImageView> colorAttachmentView(makeImageView(
485         vk, device, *colorAttachmentImage, VK_IMAGE_VIEW_TYPE_2D, colorFormat, colorImageSubresourceRange));
486     const Unique<VkRenderPass> renderPass(makeRenderPass(vk, device, colorFormat));
487     const Unique<VkFramebuffer> framebuffer(
488         makeFramebuffer(vk, device, *renderPass, *colorAttachmentView, renderSize.x(), renderSize.y()));
489     const Unique<VkPipelineLayout> pipelineLayout(makePipelineLayout(vk, device, *descriptorSetLayout));
490     const Unique<VkCommandPool> cmdPool(makeCommandPool(vk, device, queueFamilyIndex));
491     const Unique<VkCommandBuffer> cmdBuffer(
492         allocateCommandBuffer(vk, device, *cmdPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY));
493     const bool needPointSizeWrite =
494         getPhysicalDeviceFeatures(context.getInstanceInterface(), context.getPhysicalDevice())
495             .shaderTessellationAndGeometryPointSize &&
496         caseDef.usePointMode;
497 
498     const Unique<VkPipeline> pipeline(
499         GraphicsPipelineBuilder()
500             .setRenderSize(renderSize)
501             .setPatchControlPoints(numAttribsPerPrimitive)
502             .setVertexInputSingleAttribute(vertexFormat, vertexStride)
503             .setShader(vk, device, VK_SHADER_STAGE_VERTEX_BIT, context.getBinaryCollection().get("vert"), DE_NULL)
504             .setShader(vk, device, VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT, context.getBinaryCollection().get("tesc"),
505                        DE_NULL)
506             .setShader(vk, device, VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT,
507                        context.getBinaryCollection().get(needPointSizeWrite ? "tese_psw" : "tese"), DE_NULL)
508             .setShader(vk, device, VK_SHADER_STAGE_FRAGMENT_BIT, context.getBinaryCollection().get("frag"), DE_NULL)
509             .build(vk, device, *pipelineLayout, *renderPass));
510 
511     context.getTestContext().getLog()
512         << tcu::TestLog::Message << "Note: rendering " << numPrimitives
513         << " patches; first patches have valid relevant outer levels, "
514         << "but later patches have one or more invalid (i.e. less than or equal to 0.0) relevant outer levels"
515         << tcu::TestLog::EndMessage;
516 
517     // Draw commands
518 
519     beginCommandBuffer(vk, *cmdBuffer);
520 
521     // Change color attachment image layout
522     {
523         const VkImageMemoryBarrier colorAttachmentLayoutBarrier = makeImageMemoryBarrier(
524             (VkAccessFlags)0, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED,
525             VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, *colorAttachmentImage, colorImageSubresourceRange);
526 
527         vk.cmdPipelineBarrier(*cmdBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
528                               VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0u, 0u, DE_NULL, 0u, DE_NULL, 1u,
529                               &colorAttachmentLayoutBarrier);
530     }
531 
532     // Begin render pass
533     {
534         const VkRect2D renderArea  = makeRect2D(renderSize);
535         const tcu::Vec4 clearColor = tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f);
536 
537         beginRenderPass(vk, *cmdBuffer, *renderPass, *framebuffer, renderArea, clearColor);
538     }
539 
540     vk.cmdBindPipeline(*cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline);
541     vk.cmdBindDescriptorSets(*cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *pipelineLayout, 0u, 1u, &descriptorSet.get(),
542                              0u, DE_NULL);
543     {
544         const VkDeviceSize vertexBufferOffset = 0ull;
545         vk.cmdBindVertexBuffers(*cmdBuffer, 0u, 1u, &vertexBuffer.get(), &vertexBufferOffset);
546     }
547 
548     vk.cmdDraw(*cmdBuffer, static_cast<uint32_t>(attributes.size()), 1u, 0u, 0u);
549     endRenderPass(vk, *cmdBuffer);
550 
551     // Copy render result to a host-visible buffer
552     copyImageToBuffer(vk, *cmdBuffer, *colorAttachmentImage, *colorBuffer, renderSize);
553     {
554         const VkBufferMemoryBarrier shaderWriteBarrier = makeBufferMemoryBarrier(
555             VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT, *resultBuffer, 0ull, resultBufferSizeBytes);
556 
557         vk.cmdPipelineBarrier(*cmdBuffer, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0u, 0u,
558                               DE_NULL, 1u, &shaderWriteBarrier, 0u, DE_NULL);
559     }
560 
561     endCommandBuffer(vk, *cmdBuffer);
562     submitCommandsAndWait(vk, device, queue, *cmdBuffer);
563 
564     {
565         // Log rendered image
566         const Allocation &colorBufferAlloc = colorBuffer.getAllocation();
567 
568         invalidateAlloc(vk, device, colorBufferAlloc);
569 
570         const tcu::ConstPixelBufferAccess imagePixelAccess(mapVkFormat(colorFormat), renderSize.x(), renderSize.y(), 1,
571                                                            colorBufferAlloc.getHostPtr());
572         tcu::TestLog &log = context.getTestContext().getLog();
573 
574         log << tcu::TestLog::Image("color0", "Rendered image", imagePixelAccess);
575 
576         // Verify case result
577         const Allocation &resultAlloc = resultBuffer.getAllocation();
578 
579         invalidateAlloc(vk, device, resultAlloc);
580 
581         const int32_t numResultVertices = *static_cast<int32_t *>(resultAlloc.getHostPtr());
582 
583         if (!lessThanOneInnerLevelsDefined(caseDef) && caseDef.useLessThanOneInnerLevels)
584         {
585             // Since we cannot explicitly determine whether or not such interior vertices are going to be
586             // generated, we will not verify the number of generated vertices for fractional odd + quads/triangles
587             // tessellation configurations.
588             log << tcu::TestLog::Message << "Note: shader invocations generated " << numResultVertices
589                 << " vertices (not verified as number of vertices is implementation-dependent)"
590                 << tcu::TestLog::EndMessage;
591         }
592         else if (numResultVertices < numExpectedVertices)
593         {
594             log << tcu::TestLog::Message << "Failure: expected " << numExpectedVertices
595                 << " vertices from shader invocations, but got only " << numResultVertices << tcu::TestLog::EndMessage;
596             return tcu::TestStatus::fail("Wrong number of tessellation coordinates");
597         }
598         else if (numResultVertices == numExpectedVertices)
599         {
600             log << tcu::TestLog::Message << "Note: shader invocations generated " << numResultVertices << " vertices"
601                 << tcu::TestLog::EndMessage;
602         }
603         else
604         {
605             log << tcu::TestLog::Message << "Note: shader invocations generated " << numResultVertices
606                 << " vertices (expected " << numExpectedVertices << ", got "
607                 << (numResultVertices - numExpectedVertices) << " extra)" << tcu::TestLog::EndMessage;
608         }
609 
610         return (verifyResultImage(log, numPrimitives, numAttribsPerPrimitive, caseDef.primitiveType, attributes,
611                                   imagePixelAccess) ?
612                     tcu::TestStatus::pass("OK") :
613                     tcu::TestStatus::fail("Image verification failed"));
614     }
615 }
616 
617 } // namespace
618 
619 //! These tests correspond to dEQP-GLES31.functional.tessellation.primitive_discard.*
620 //! \note Original test used transform feedback (TF) to capture the number of output vertices. The behavior of TF differs significantly from SSBO approach,
621 //!       especially for non-point_mode rendering. TF returned all coordinates, while SSBO computes the count based on the number of shader invocations
622 //!       which yields a much smaller number because invocations for duplicate coordinates are often eliminated.
623 //!       Because of this, the test was changed to:
624 //!       - always compute the number of expected coordinates as if point_mode was enabled
625 //!       - not fail if implementation returned more coordinates than expected
createPrimitiveDiscardTests(tcu::TestContext & testCtx)626 tcu::TestCaseGroup *createPrimitiveDiscardTests(tcu::TestContext &testCtx)
627 {
628     // Test primitive discard with relevant outer tessellation level <= 0.0
629     de::MovePtr<tcu::TestCaseGroup> group(new tcu::TestCaseGroup(testCtx, "primitive_discard"));
630 
631     for (int primitiveTypeNdx = 0; primitiveTypeNdx < TESSPRIMITIVETYPE_LAST; primitiveTypeNdx++)
632         for (int spacingModeNdx = 0; spacingModeNdx < SPACINGMODE_LAST; spacingModeNdx++)
633             for (int windingNdx = 0; windingNdx < WINDING_LAST; windingNdx++)
634                 for (int usePointModeNdx = 0; usePointModeNdx <= 1; usePointModeNdx++)
635                     for (int lessThanOneInnerLevelsNdx = 0; lessThanOneInnerLevelsNdx <= 1; lessThanOneInnerLevelsNdx++)
636                     {
637                         const CaseDefinition caseDef = {(TessPrimitiveType)primitiveTypeNdx,
638                                                         (SpacingMode)spacingModeNdx, (Winding)windingNdx,
639                                                         (usePointModeNdx != 0), (lessThanOneInnerLevelsNdx != 0)};
640 
641                         if (lessThanOneInnerLevelsDefined(caseDef) && !caseDef.useLessThanOneInnerLevels)
642                             continue; // No point generating a separate case as <= 1 inner level behavior is well-defined
643 
644                         const std::string caseName =
645                             std::string() + getTessPrimitiveTypeShaderName(caseDef.primitiveType) + "_" +
646                             getSpacingModeShaderName(caseDef.spacingMode) + "_" +
647                             getWindingShaderName(caseDef.winding) + (caseDef.usePointMode ? "_point_mode" : "") +
648                             (caseDef.useLessThanOneInnerLevels ? "" : "_valid_levels");
649 
650                         addFunctionCaseWithPrograms(group.get(), caseName, checkSupportCase, initPrograms, test,
651                                                     caseDef);
652                     }
653 
654     return group.release();
655 }
656 
657 } // namespace tessellation
658 } // namespace vkt
659