1 /*------------------------------------------------------------------------
2 * Vulkan Conformance Tests
3 * ------------------------
4 *
5 * Copyright (c) 2022 The Khronos Group Inc.
6 * Copyright (c) 2022 Valve Corporation.
7 * Copyright (c) 2023 LunarG, Inc.
8 * Copyright (c) 2023 Nintendo
9 *
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
13 *
14 * http://www.apache.org/licenses/LICENSE-2.0
15 *
16 * Unless required by applicable law or agreed to in writing, software
17 * distributed under the License is distributed on an "AS IS" BASIS,
18 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19 * See the License for the specific language governing permissions and
20 * limitations under the License.
21 *
22 *//*
23 * \file
24 * \brief Tests involving dynamic patch control points
25 *//*--------------------------------------------------------------------*/
26
27 #include "vktPipelineDynamicControlPoints.hpp"
28 #include "vktTestCase.hpp"
29 #include "vkPrograms.hpp"
30 #include "vkRefUtil.hpp"
31 #include "vktTestCaseUtil.hpp"
32 #include "vkImageWithMemory.hpp"
33
34 #include "vktPipelineImageUtil.hpp"
35 #include "vktTestCase.hpp"
36
37 #include "vkDefs.hpp"
38 #include "vkTypeUtil.hpp"
39 #include "vkQueryUtil.hpp"
40 #include "vkObjUtil.hpp"
41 #include "vkBufferWithMemory.hpp"
42 #include "vkImageWithMemory.hpp"
43 #include "vkBuilderUtil.hpp"
44 #include "vkCmdUtil.hpp"
45 #include "vkImageUtil.hpp"
46
47 #include "tcuVector.hpp"
48 #include "tcuMaybe.hpp"
49 #include "tcuImageCompare.hpp"
50 #include "tcuDefs.hpp"
51 #include "tcuTextureUtil.hpp"
52 #include "tcuTestLog.hpp"
53 #include "tcuVectorUtil.hpp"
54 #include "tcuStringTemplate.hpp"
55
56 #include "deUniquePtr.hpp"
57 #include "deStringUtil.hpp"
58
59 #include <vector>
60 #include <sstream>
61 #include <algorithm>
62 #include <utility>
63 #include <iterator>
64 #include <string>
65 #include <limits>
66 #include <memory>
67 #include <functional>
68 #include <cstddef>
69 #include <set>
70
71 namespace vkt
72 {
73 namespace pipeline
74 {
75
76 struct TestConfig
77 {
78 vk::PipelineConstructionType constructionType;
79 bool changeOutput;
80 bool firstClockwise;
81 bool secondClockwise;
82 vk::VkCullModeFlags cullMode;
83 tcu::Vec4 expectedFirst;
84 tcu::Vec4 expectedSecond;
85 };
86
87 class DynamicControlPointsTestCase : public vkt::TestCase
88 {
89 public:
90 DynamicControlPointsTestCase(tcu::TestContext &context, const std::string &name, TestConfig config);
91 void initPrograms(vk::SourceCollections &programCollection) const override;
92 TestInstance *createInstance(Context &context) const override;
93 void checkSupport(Context &context) const override;
94
95 private:
96 TestConfig m_config;
97 };
98
99 class DynamicControlPointsTestInstance : public vkt::TestInstance
100 {
101 public:
102 DynamicControlPointsTestInstance(Context &context, TestConfig config);
103 virtual tcu::TestStatus iterate(void);
104
105 private:
106 TestConfig m_config;
107 };
108
DynamicControlPointsTestCase(tcu::TestContext & context,const std::string & name,TestConfig config)109 DynamicControlPointsTestCase::DynamicControlPointsTestCase(tcu::TestContext &context, const std::string &name,
110 TestConfig config)
111 : vkt::TestCase(context, name)
112 , m_config(config)
113 {
114 }
115
checkSupport(Context & context) const116 void DynamicControlPointsTestCase::checkSupport(Context &context) const
117 {
118 context.requireDeviceCoreFeature(DEVICE_CORE_FEATURE_TESSELLATION_SHADER);
119 checkPipelineConstructionRequirements(context.getInstanceInterface(), context.getPhysicalDevice(),
120 m_config.constructionType);
121 const auto &eds2Features = context.getExtendedDynamicState2FeaturesEXT();
122 if (!eds2Features.extendedDynamicState2PatchControlPoints)
123 {
124 TCU_THROW(NotSupportedError, "Dynamic patch control points aren't supported");
125 }
126 }
127
initPrograms(vk::SourceCollections & collection) const128 void DynamicControlPointsTestCase::initPrograms(vk::SourceCollections &collection) const
129 {
130 const std::string firstWinding = m_config.firstClockwise ? "cw" : "ccw";
131 const std::string secondWinding = m_config.secondClockwise ? "cw" : "ccw";
132
133 {
134 std::ostringstream src;
135 src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_450) << "\n"
136 << "vec2 positions[6] = vec2[](\n"
137 << " vec2(-1.0, -1.0),"
138 << " vec2(-1.0, 1.0),"
139 << " vec2(1.0, -1.0),"
140 << " vec2(1.0, -1.0),"
141 << " vec2(-1.0, 1.0),"
142 << " vec2(1.0, 1.0)"
143 << ");\n"
144 << "void main() {\n"
145 << " gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);\n"
146 << "}";
147 collection.glslSources.add("vert") << glu::VertexSource(src.str());
148 }
149
150 {
151 std::ostringstream src;
152 src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_450) << "\n"
153 << "layout(location = 0) out vec4 outColor;\n"
154 << "layout(location = 0) in vec3 fragColor;"
155 << "void main() {\n"
156 << " outColor = vec4(fragColor, 1.0);\n"
157 << "}";
158 collection.glslSources.add("frag") << glu::FragmentSource(src.str());
159 }
160
161 {
162 std::ostringstream src;
163 src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_450) << "\n"
164 << "layout(vertices = 3) out;\n"
165 << "void main (void)\n"
166 << "{\n"
167 << " gl_TessLevelInner[0] = 2.0;\n"
168 << " gl_TessLevelOuter[0] = 2.0;\n"
169 << " gl_TessLevelOuter[1] = 2.0;\n"
170 << " gl_TessLevelOuter[2] = 2.0;\n"
171 << " gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n"
172 << "}\n";
173 collection.glslSources.add("tesc") << glu::TessellationControlSource(src.str());
174 }
175
176 {
177 std::ostringstream src;
178 src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_450) << "\n"
179 << "layout(triangles, " << firstWinding << ") in;\n"
180 << "layout(location = 0) out vec3 fragColor;"
181 << "\n"
182 << "void main (void)\n"
183 << "{\n"
184 << " vec4 p0 = gl_TessCoord.x * gl_in[0].gl_Position;\n"
185 << " vec4 p1 = gl_TessCoord.y * gl_in[1].gl_Position;\n"
186 << " vec4 p2 = gl_TessCoord.z * gl_in[2].gl_Position;\n"
187 << " gl_Position = p0 + p1 + p2;\n"
188 << " fragColor = vec3(1.0, 0.0, 1.0); "
189 << "}\n";
190 collection.glslSources.add("tese") << glu::TessellationEvaluationSource(src.str());
191 }
192
193 {
194 std::ostringstream src;
195 src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_450) << "\n"
196 << "layout(vertices = " << (m_config.changeOutput ? 4 : 3) << ") out;\n"
197 << "void main (void)\n"
198 << "{\n"
199 << " gl_TessLevelInner[0] = 2;\n"
200 << " gl_TessLevelOuter[0] = 2.0;\n"
201 << " gl_TessLevelOuter[1] = 2.0;\n"
202 << " gl_TessLevelOuter[2] = 2.0;\n"
203 << "if (gl_InvocationID < 3) {"
204 << " gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n"
205 << "} else {"
206 << " gl_out[gl_InvocationID].gl_Position = vec4(1.0, 0.0, 1.0, 1.0);"
207 << "}"
208 << "}\n";
209 collection.glslSources.add("tesc2") << glu::TessellationControlSource(src.str());
210 }
211
212 {
213 std::ostringstream src;
214 src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_450) << "\n"
215 << "layout(triangles, " << secondWinding << ") in;\n"
216 << "layout(location = 0) out vec3 fragColor;"
217 << "\n"
218 << "void main (void)\n"
219 << "{\n"
220 << " vec4 p0 = gl_TessCoord.x * gl_in[0].gl_Position;\n"
221 << " vec4 p1 = gl_TessCoord.y * gl_in[1].gl_Position;\n"
222 << " vec4 p2 = gl_TessCoord.z * gl_in[2].gl_Position;\n"
223 << " gl_Position = p0 + p1 + p2;\n";
224 if (m_config.changeOutput)
225 {
226 src << " fragColor = vec3(gl_in[3].gl_Position.xyz);";
227 }
228 else
229 {
230 src << " fragColor = vec3(1.0, 0.0, 1.0);";
231 }
232 src << "}\n";
233 collection.glslSources.add("tese2") << glu::TessellationEvaluationSource(src.str());
234 }
235 }
236
createInstance(Context & context) const237 TestInstance *DynamicControlPointsTestCase::createInstance(Context &context) const
238 {
239 return new DynamicControlPointsTestInstance(context, m_config);
240 }
241
DynamicControlPointsTestInstance(Context & context,TestConfig config)242 DynamicControlPointsTestInstance::DynamicControlPointsTestInstance(Context &context, TestConfig config)
243 : vkt::TestInstance(context)
244 , m_config(config)
245 {
246 }
247
248 //make a buffer to read an image back after rendering
makeBufferForImage(const vk::DeviceInterface & vkd,const vk::VkDevice device,vk::Allocator & allocator,tcu::TextureFormat tcuFormat,vk::VkExtent3D imageExtent)249 std::unique_ptr<vk::BufferWithMemory> makeBufferForImage(const vk::DeviceInterface &vkd, const vk::VkDevice device,
250 vk::Allocator &allocator, tcu::TextureFormat tcuFormat,
251 vk::VkExtent3D imageExtent)
252 {
253 const auto outBufferSize = static_cast<vk::VkDeviceSize>(static_cast<uint32_t>(tcu::getPixelSize(tcuFormat)) *
254 imageExtent.width * imageExtent.height);
255 const auto outBufferUsage = vk::VK_BUFFER_USAGE_TRANSFER_DST_BIT;
256 const auto outBufferInfo = makeBufferCreateInfo(outBufferSize, outBufferUsage);
257 auto outBuffer = std::unique_ptr<vk::BufferWithMemory>(
258 new vk::BufferWithMemory(vkd, device, allocator, outBufferInfo, vk::MemoryRequirement::HostVisible));
259
260 return outBuffer;
261 }
262
iterate(void)263 tcu::TestStatus DynamicControlPointsTestInstance::iterate(void)
264 {
265 const auto &vki = m_context.getInstanceInterface();
266 const auto &vkd = m_context.getDeviceInterface();
267 const auto physicalDevice = m_context.getPhysicalDevice();
268 const auto device = m_context.getDevice();
269 auto &alloc = m_context.getDefaultAllocator();
270 auto imageFormat = vk::VK_FORMAT_R8G8B8A8_UNORM;
271 auto imageExtent = vk::makeExtent3D(4, 4, 1u);
272 auto mappedFormat = mapVkFormat(imageFormat);
273
274 const tcu::IVec3 imageDim(static_cast<int>(imageExtent.width), static_cast<int>(imageExtent.height),
275 static_cast<int>(imageExtent.depth));
276 const tcu::IVec2 imageSize(imageDim.x(), imageDim.y());
277
278 de::MovePtr<vk::ImageWithMemory> colorAttachment;
279
280 vk::GraphicsPipelineWrapper pipeline1(vki, vkd, physicalDevice, device, m_context.getDeviceExtensions(),
281 m_config.constructionType);
282 vk::GraphicsPipelineWrapper pipeline2(vki, vkd, physicalDevice, device, m_context.getDeviceExtensions(),
283 m_config.constructionType);
284 const auto qIndex = m_context.getUniversalQueueFamilyIndex();
285
286 const auto imageUsage = static_cast<vk::VkImageUsageFlags>(vk::VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
287 vk::VK_IMAGE_USAGE_TRANSFER_SRC_BIT);
288 const vk::VkImageCreateInfo imageCreateInfo = {
289 vk::VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, // VkStructureType sType;
290 nullptr, // const void* pNext;
291 0u, // VkImageCreateFlags flags;
292 vk::VK_IMAGE_TYPE_2D, // VkImageType imageType;
293 imageFormat, // VkFormat format;
294 imageExtent, // VkExtent3D extent;
295 1u, // uint32_t mipLevels;
296 1u, // uint32_t arrayLayers;
297 vk::VK_SAMPLE_COUNT_1_BIT, // VkSampleCountFlagBits samples;
298 vk::VK_IMAGE_TILING_OPTIMAL, // VkImageTiling tiling;
299 imageUsage, // VkImageUsageFlags usage;
300 vk::VK_SHARING_MODE_EXCLUSIVE, // VkSharingMode sharingMode;
301 0, // uint32_t queueFamilyIndexCount;
302 nullptr, // const uint32_t* pQueueFamilyIndices;
303 vk::VK_IMAGE_LAYOUT_UNDEFINED, // VkImageLayout initialLayout;
304 };
305
306 const auto subresourceRange = vk::makeImageSubresourceRange(vk::VK_IMAGE_ASPECT_COLOR_BIT, 0u, 1u, 0u, 1u);
307 colorAttachment = de::MovePtr<vk::ImageWithMemory>(
308 new vk::ImageWithMemory(vkd, device, alloc, imageCreateInfo, vk::MemoryRequirement::Any));
309 auto colorAttachmentView = vk::makeImageView(vkd, device, colorAttachment->get(), vk::VK_IMAGE_VIEW_TYPE_2D,
310 imageFormat, subresourceRange);
311
312 vk::RenderPassWrapper renderPass(m_config.constructionType, vkd, device, imageFormat);
313 renderPass.createFramebuffer(vkd, device, **colorAttachment, colorAttachmentView.get(), imageExtent.width,
314 imageExtent.height);
315
316 //buffer to read the output image
317 auto outBuffer = makeBufferForImage(vkd, device, alloc, mappedFormat, imageExtent);
318 auto &outBufferAlloc = outBuffer->getAllocation();
319 void *outBufferData = outBufferAlloc.getHostPtr();
320
321 const vk::VkPipelineVertexInputStateCreateInfo vertexInputState = vk::initVulkanStructure();
322
323 const std::vector<vk::VkViewport> viewport_left{
324 vk::makeViewport(0.0f, 0.0f, (float)imageExtent.width / 2, (float)imageExtent.height, 0.0f, 1.0f)};
325 const std::vector<vk::VkViewport> viewport_right{vk::makeViewport(
326 (float)imageExtent.width / 2, 0.0f, (float)imageExtent.width / 2, (float)imageExtent.height, 0.0f, 1.0f)};
327 const std::vector<vk::VkRect2D> scissors_left{vk::makeRect2D(0, 0, imageExtent.width / 2, imageExtent.height)};
328 const std::vector<vk::VkRect2D> scissors_right{
329 vk::makeRect2D(imageExtent.width / 2, 0, imageExtent.width / 2, imageExtent.height)};
330 const vk::PipelineLayoutWrapper graphicsPipelineLayout(m_config.constructionType, vkd, device);
331
332 auto vtxshader = vk::ShaderWrapper(vkd, device, m_context.getBinaryCollection().get("vert"));
333 auto frgshader = vk::ShaderWrapper(vkd, device, m_context.getBinaryCollection().get("frag"));
334 auto tscshader1 = vk::ShaderWrapper(vkd, device, m_context.getBinaryCollection().get("tesc"));
335 auto tscshader2 = vk::ShaderWrapper(vkd, device,
336 m_config.changeOutput ? m_context.getBinaryCollection().get("tesc2") :
337 m_context.getBinaryCollection().get("tesc"));
338 auto tseshader1 = vk::ShaderWrapper(vkd, device, m_context.getBinaryCollection().get("tese"));
339 auto tseshader2 = vk::ShaderWrapper(vkd, device, m_context.getBinaryCollection().get("tese2"));
340
341 vk::VkPipelineRasterizationStateCreateInfo raster = {
342 vk::VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, // VkStructureType sType
343 DE_NULL, // const void* pNext
344 0u, // VkPipelineRasterizationStateCreateFlags flags
345 VK_FALSE, // VkBool32 depthClampEnable
346 VK_FALSE, // VkBool32 rasterizerDiscardEnable
347 vk::VK_POLYGON_MODE_FILL, // VkPolygonMode polygonMode
348 m_config.cullMode, // VkCullModeFlags cullMode
349 vk::VK_FRONT_FACE_COUNTER_CLOCKWISE, // VkFrontFace frontFace
350 VK_FALSE, // VkBool32 depthBiasEnable
351 0.0f, // float depthBiasConstantFactor
352 0.0f, // float depthBiasClamp
353 0.0f, // float depthBiasSlopeFactor
354 1.0f // float lineWidth
355 };
356
357 vk::VkDynamicState dynamicStates = vk::VK_DYNAMIC_STATE_PATCH_CONTROL_POINTS_EXT;
358 vk::VkPipelineDynamicStateCreateInfo dynamicInfo = {vk::VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
359 nullptr, 0, 1, &dynamicStates};
360
361 pipeline1.setDefaultTopology(vk::VK_PRIMITIVE_TOPOLOGY_PATCH_LIST)
362 .setDynamicState(&dynamicInfo)
363 .setDefaultRasterizationState()
364 .setDefaultMultisampleState()
365 .setDefaultDepthStencilState()
366 .setDefaultColorBlendState()
367 .setupVertexInputState(&vertexInputState)
368 .setupPreRasterizationShaderState(viewport_left, scissors_left, graphicsPipelineLayout, *renderPass, 0u,
369 vtxshader, &raster, tscshader1, tseshader1)
370 .setupFragmentShaderState(graphicsPipelineLayout, *renderPass, 0u, frgshader, 0)
371 .setupFragmentOutputState(*renderPass, 0u)
372 .setMonolithicPipelineLayout(graphicsPipelineLayout)
373 .buildPipeline();
374
375 pipeline2.setDefaultTopology(vk::VK_PRIMITIVE_TOPOLOGY_PATCH_LIST)
376 .setDynamicState(&dynamicInfo)
377 .setDefaultRasterizationState()
378 .setDefaultMultisampleState()
379 .setDefaultDepthStencilState()
380 .setDefaultColorBlendState()
381 .setupVertexInputState(&vertexInputState)
382 .setupPreRasterizationShaderState(viewport_right, scissors_right, graphicsPipelineLayout, *renderPass, 0u,
383 vtxshader, &raster, tscshader2, tseshader2)
384 .setupFragmentShaderState(graphicsPipelineLayout, *renderPass, 0u, frgshader, 0)
385 .setupFragmentOutputState(*renderPass, 0u)
386 .setMonolithicPipelineLayout(graphicsPipelineLayout)
387 .buildPipeline();
388
389 auto commandPool = createCommandPool(vkd, device, vk::VK_COMMAND_POOL_CREATE_TRANSIENT_BIT, qIndex);
390 auto commandBuffer = vk::allocateCommandBuffer(vkd, device, commandPool.get(), vk::VK_COMMAND_BUFFER_LEVEL_PRIMARY);
391
392 const tcu::Vec4 clearColor(1.0f, 1.0f, 1.0f, 1.0f);
393
394 const vk::VkRect2D renderArea = {{0u, 0u}, {imageExtent.width, imageExtent.height}};
395
396 //render 2 triangles with each pipeline, covering the entire screen
397 //depending on the test settings one of them might be culled
398 vk::beginCommandBuffer(vkd, commandBuffer.get());
399 renderPass.begin(vkd, *commandBuffer, renderArea, clearColor);
400 vkd.cmdSetPatchControlPointsEXT(commandBuffer.get(), 3);
401 pipeline1.bind(commandBuffer.get());
402 vkd.cmdDraw(commandBuffer.get(), 6, 1, 0, 0);
403 pipeline2.bind(commandBuffer.get());
404 vkd.cmdDraw(commandBuffer.get(), 6, 1, 0, 0);
405 renderPass.end(vkd, commandBuffer.get());
406 vk::copyImageToBuffer(vkd, commandBuffer.get(), colorAttachment.get()->get(), (*outBuffer).get(), imageSize);
407 vk::endCommandBuffer(vkd, commandBuffer.get());
408 vk::submitCommandsAndWait(vkd, device, m_context.getUniversalQueue(), commandBuffer.get());
409
410 invalidateAlloc(vkd, device, outBufferAlloc);
411 tcu::ConstPixelBufferAccess outPixels(mappedFormat, imageDim, outBufferData);
412
413 auto expectedFirst = m_config.expectedFirst;
414 auto expectedSecond = m_config.expectedSecond;
415
416 tcu::TextureLevel referenceLevel(mappedFormat, imageExtent.height, imageExtent.height);
417 tcu::PixelBufferAccess reference = referenceLevel.getAccess();
418 tcu::clear(getSubregion(reference, 0, 0, imageExtent.width / 2, imageExtent.height), expectedFirst);
419 tcu::clear(getSubregion(reference, imageExtent.width / 2, 0, imageExtent.width / 2, imageExtent.height),
420 expectedSecond);
421
422 if (!tcu::floatThresholdCompare(m_context.getTestContext().getLog(), "Compare", "Result comparison", reference,
423 outPixels, tcu::Vec4(0.0), tcu::COMPARE_LOG_ON_ERROR))
424 return tcu::TestStatus::fail("Color output does not match reference, image added to log");
425
426 return tcu::TestStatus::pass("Pass");
427 }
428
createDynamicControlPointTests(tcu::TestContext & testCtx,vk::PipelineConstructionType pipelineConstructionType)429 tcu::TestCaseGroup *createDynamicControlPointTests(tcu::TestContext &testCtx,
430 vk::PipelineConstructionType pipelineConstructionType)
431 {
432 // Tests checking dynamic bind points and switching pipelines
433 de::MovePtr<tcu::TestCaseGroup> group(new tcu::TestCaseGroup(testCtx, "dynamic_control_points"));
434
435 // test switching pipelines with dynamic control points while changing the number of tcs invocations
436 group->addChild(new DynamicControlPointsTestCase(testCtx, "change_output",
437 TestConfig{
438 pipelineConstructionType,
439 true,
440 false,
441 false,
442 vk::VK_CULL_MODE_NONE,
443 tcu::Vec4(1.0, 0.0, 1.0, 1.0),
444 tcu::Vec4(1.0, 0.0, 1.0, 1.0),
445 }));
446
447 // test switching pipelines with dynamic control points while switching winding
448 group->addChild(new DynamicControlPointsTestCase(
449 testCtx, "change_winding",
450 TestConfig{pipelineConstructionType, false, true, false, vk::VK_CULL_MODE_FRONT_BIT,
451 tcu::Vec4(1.0, 1.0, 1.0, 1.0), tcu::Vec4(1.0, 0.0, 1.0, 1.0)}));
452
453 // test switching pipelines with dynamic control points while switching winding and number of tcs invocations
454 group->addChild(new DynamicControlPointsTestCase(
455 testCtx, "change_output_winding",
456 TestConfig{pipelineConstructionType, true, true, false, vk::VK_CULL_MODE_FRONT_BIT,
457 tcu::Vec4(1.0, 1.0, 1.0, 1.0), tcu::Vec4(1.0, 0.0, 1.0, 1.0)}));
458 return group.release();
459 }
460
461 } // namespace pipeline
462 } // namespace vkt
463