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