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 vk::PipelineConstructionType constructionType;
78 bool changeOutput;
79 bool firstClockwise;
80 bool secondClockwise;
81 vk::VkCullModeFlags cullMode;
82 tcu::Vec4 expectedFirst;
83 tcu::Vec4 expectedSecond;
84 };
85
86 class DynamicControlPointsTestCase : public vkt::TestCase
87 {
88 public:
89 DynamicControlPointsTestCase(tcu::TestContext& context, const std::string& name, TestConfig config);
90 void initPrograms (vk::SourceCollections& programCollection) const override;
91 TestInstance* createInstance (Context& context) const override;
92 void checkSupport (Context& context) const override;
93
94 private:
95 TestConfig m_config;
96 };
97
98
99 class DynamicControlPointsTestInstance : public vkt::TestInstance
100 {
101 public:
102 DynamicControlPointsTestInstance(Context& context, TestConfig config);
103 virtual tcu::TestStatus iterate (void);
104 private:
105 TestConfig m_config;
106 };
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) : vkt::TestCase (context, name), m_config(config)
111 {
112 }
113
checkSupport(Context& context) const114 void DynamicControlPointsTestCase::checkSupport(Context& context) const
115 {
116 context.requireDeviceCoreFeature(DEVICE_CORE_FEATURE_TESSELLATION_SHADER);
117 checkPipelineConstructionRequirements(context.getInstanceInterface(), context.getPhysicalDevice(), m_config.constructionType);
118 const auto& eds2Features = context.getExtendedDynamicState2FeaturesEXT();
119 if (!eds2Features.extendedDynamicState2PatchControlPoints) {
120 TCU_THROW(NotSupportedError, "Dynamic patch control points aren't supported");
121 }
122 }
123
initPrograms(vk::SourceCollections& collection) const124 void DynamicControlPointsTestCase::initPrograms(vk::SourceCollections& collection) const
125 {
126 const std::string firstWinding = m_config.firstClockwise ? "cw" : "ccw";
127 const std::string secondWinding = m_config.secondClockwise ? "cw" : "ccw";
128
129 {
130 std::ostringstream src;
131 src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_450) << "\n"
132 << "vec2 positions[6] = vec2[](\n"
133 << " vec2(-1.0, -1.0),"
134 << " vec2(-1.0, 1.0),"
135 << " vec2(1.0, -1.0),"
136 << " vec2(1.0, -1.0),"
137 << " vec2(-1.0, 1.0),"
138 << " vec2(1.0, 1.0)"
139 << ");\n"
140 << "void main() {\n"
141 << " gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);\n"
142 << "}";
143 collection.glslSources.add("vert") << glu::VertexSource(src.str());
144 }
145
146 {
147 std::ostringstream src;
148 src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_450) << "\n"
149 << "layout(location = 0) out vec4 outColor;\n"
150 << "layout(location = 0) in vec3 fragColor;"
151 << "void main() {\n"
152 << " outColor = vec4(fragColor, 1.0);\n"
153 << "}";
154 collection.glslSources.add("frag") << glu::FragmentSource(src.str());
155 }
156
157 {
158 std::ostringstream src;
159 src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_450) << "\n"
160 << "layout(vertices = 3) out;\n"
161 << "void main (void)\n"
162 << "{\n"
163 << " gl_TessLevelInner[0] = 2.0;\n"
164 << " gl_TessLevelOuter[0] = 2.0;\n"
165 << " gl_TessLevelOuter[1] = 2.0;\n"
166 << " gl_TessLevelOuter[2] = 2.0;\n"
167 << " gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n"
168 << "}\n";
169 collection.glslSources.add("tesc") << glu::TessellationControlSource(src.str());
170 }
171
172 {
173 std::ostringstream src;
174 src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_450) << "\n"
175 << "layout(triangles, " << firstWinding << ") in;\n"
176 << "layout(location = 0) out vec3 fragColor;"
177 << "\n"
178 << "void main (void)\n"
179 << "{\n"
180 << " vec4 p0 = gl_TessCoord.x * gl_in[0].gl_Position;\n"
181 << " vec4 p1 = gl_TessCoord.y * gl_in[1].gl_Position;\n"
182 << " vec4 p2 = gl_TessCoord.z * gl_in[2].gl_Position;\n"
183 << " gl_Position = p0 + p1 + p2;\n"
184 << " fragColor = vec3(1.0, 0.0, 1.0); "
185 << "}\n";
186 collection.glslSources.add("tese") << glu::TessellationEvaluationSource(src.str());
187 }
188
189 {
190 std::ostringstream src;
191 src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_450) << "\n"
192 << "layout(vertices = " << (m_config.changeOutput ? 4 : 3) << ") out;\n"
193 << "void main (void)\n"
194 << "{\n"
195 << " gl_TessLevelInner[0] = 2;\n"
196 << " gl_TessLevelOuter[0] = 2.0;\n"
197 << " gl_TessLevelOuter[1] = 2.0;\n"
198 << " gl_TessLevelOuter[2] = 2.0;\n"
199 << "if (gl_InvocationID < 3) {"
200 << " gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n"
201 << "} else {"
202 << " gl_out[gl_InvocationID].gl_Position = vec4(1.0, 0.0, 1.0, 1.0);"
203 << "}"
204 << "}\n";
205 collection.glslSources.add("tesc2") << glu::TessellationControlSource(src.str());
206 }
207
208 {
209 std::ostringstream src;
210 src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_450) << "\n"
211 << "layout(triangles, " << secondWinding << ") in;\n"
212 << "layout(location = 0) out vec3 fragColor;"
213 << "\n"
214 << "void main (void)\n"
215 << "{\n"
216 << " vec4 p0 = gl_TessCoord.x * gl_in[0].gl_Position;\n"
217 << " vec4 p1 = gl_TessCoord.y * gl_in[1].gl_Position;\n"
218 << " vec4 p2 = gl_TessCoord.z * gl_in[2].gl_Position;\n"
219 << " gl_Position = p0 + p1 + p2;\n";
220 if (m_config.changeOutput) {
221 src << " fragColor = vec3(gl_in[3].gl_Position.xyz);";
222 } else {
223 src << " fragColor = vec3(1.0, 0.0, 1.0);";
224 }
225 src << "}\n";
226 collection.glslSources.add("tese2") << glu::TessellationEvaluationSource(src.str());
227 }
228 }
229
createInstance(Context& context) const230 TestInstance* DynamicControlPointsTestCase::createInstance(Context& context) const
231 {
232 return new DynamicControlPointsTestInstance(context, m_config);
233 }
234
DynamicControlPointsTestInstance(Context& context, TestConfig config)235 DynamicControlPointsTestInstance::DynamicControlPointsTestInstance(Context& context, TestConfig config) :
236 vkt::TestInstance (context), m_config (config) { }
237
238 //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)239 std::unique_ptr<vk::BufferWithMemory> makeBufferForImage(const vk::DeviceInterface& vkd, const vk::VkDevice device, vk::Allocator& allocator, tcu::TextureFormat tcuFormat, vk::VkExtent3D imageExtent)
240 {
241 const auto outBufferSize = static_cast<vk::VkDeviceSize>(static_cast<uint32_t>(tcu::getPixelSize(tcuFormat)) * imageExtent.width * imageExtent.height);
242 const auto outBufferUsage = vk::VK_BUFFER_USAGE_TRANSFER_DST_BIT;
243 const auto outBufferInfo = makeBufferCreateInfo(outBufferSize, outBufferUsage);
244 auto outBuffer = std::unique_ptr<vk::BufferWithMemory>(new vk::BufferWithMemory(vkd, device, allocator, outBufferInfo, vk::MemoryRequirement::HostVisible));
245
246 return outBuffer;
247 }
248
iterate(void)249 tcu::TestStatus DynamicControlPointsTestInstance::iterate(void)
250 {
251 const auto& vki = m_context.getInstanceInterface();
252 const auto& vkd = m_context.getDeviceInterface();
253 const auto physicalDevice = m_context.getPhysicalDevice();
254 const auto device = m_context.getDevice();
255 auto& alloc = m_context.getDefaultAllocator();
256 auto imageFormat = vk::VK_FORMAT_R8G8B8A8_UNORM;
257 auto imageExtent = vk::makeExtent3D(4, 4, 1u);
258 auto mappedFormat = mapVkFormat(imageFormat);
259
260 const tcu::IVec3 imageDim (static_cast<int>(imageExtent.width), static_cast<int>(imageExtent.height), static_cast<int>(imageExtent.depth));
261 const tcu::IVec2 imageSize (imageDim.x(), imageDim.y());
262
263 de::MovePtr<vk::ImageWithMemory> colorAttachment;
264
265 vk::GraphicsPipelineWrapper pipeline1(vki, vkd, physicalDevice, device, m_context.getDeviceExtensions(), m_config.constructionType);
266 vk::GraphicsPipelineWrapper pipeline2(vki, vkd, physicalDevice, device, m_context.getDeviceExtensions(), m_config.constructionType);
267 const auto qIndex = m_context.getUniversalQueueFamilyIndex();
268
269 const auto imageUsage = static_cast<vk::VkImageUsageFlags>(vk::VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | vk::VK_IMAGE_USAGE_TRANSFER_SRC_BIT);
270 const vk::VkImageCreateInfo imageCreateInfo =
271 {
272 vk::VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, // VkStructureType sType;
273 nullptr, // const void* pNext;
274 0u, // VkImageCreateFlags flags;
275 vk::VK_IMAGE_TYPE_2D, // VkImageType imageType;
276 imageFormat, // VkFormat format;
277 imageExtent, // VkExtent3D extent;
278 1u, // deUint32 mipLevels;
279 1u, // deUint32 arrayLayers;
280 vk::VK_SAMPLE_COUNT_1_BIT, // VkSampleCountFlagBits samples;
281 vk::VK_IMAGE_TILING_OPTIMAL, // VkImageTiling tiling;
282 imageUsage, // VkImageUsageFlags usage;
283 vk::VK_SHARING_MODE_EXCLUSIVE, // VkSharingMode sharingMode;
284 0, // deUint32 queueFamilyIndexCount;
285 nullptr, // const deUint32* pQueueFamilyIndices;
286 vk::VK_IMAGE_LAYOUT_UNDEFINED, // VkImageLayout initialLayout;
287 };
288
289 const auto subresourceRange = vk::makeImageSubresourceRange(vk::VK_IMAGE_ASPECT_COLOR_BIT, 0u, 1u, 0u, 1u);
290 colorAttachment = de::MovePtr<vk::ImageWithMemory>(new vk::ImageWithMemory(vkd, device, alloc, imageCreateInfo, vk::MemoryRequirement::Any));
291 auto colorAttachmentView = vk::makeImageView(vkd, device, colorAttachment->get(), vk::VK_IMAGE_VIEW_TYPE_2D, imageFormat, subresourceRange);
292
293 vk::RenderPassWrapper renderPass (m_config.constructionType, vkd, device, imageFormat);
294 renderPass.createFramebuffer(vkd, device, **colorAttachment, colorAttachmentView.get(), imageExtent.width, imageExtent.height);
295
296 //buffer to read the output image
297 auto outBuffer = makeBufferForImage(vkd, device, alloc, mappedFormat, imageExtent);
298 auto& outBufferAlloc = outBuffer->getAllocation();
299 void* outBufferData = outBufferAlloc.getHostPtr();
300
301 const vk::VkPipelineVertexInputStateCreateInfo vertexInputState = vk::initVulkanStructure();
302
303 const std::vector<vk::VkViewport> viewport_left { vk::makeViewport(0.0f, 0.0f, (float)imageExtent.width / 2, (float)imageExtent.height, 0.0f, 1.0f) };
304 const std::vector<vk::VkViewport> viewport_right { vk::makeViewport((float)imageExtent.width / 2, 0.0f, (float)imageExtent.width / 2, (float)imageExtent.height, 0.0f, 1.0f) };
305 const std::vector<vk::VkRect2D> scissors_left { vk::makeRect2D(0.0f, 0.0f, imageExtent.width / 2, imageExtent.height) };
306 const std::vector<vk::VkRect2D> scissors_right { vk::makeRect2D(imageExtent.width / 2, 0.0, imageExtent.width / 2, imageExtent.height) };
307 const vk::PipelineLayoutWrapper graphicsPipelineLayout (m_config.constructionType, vkd, device);
308
309 auto vtxshader = vk::ShaderWrapper(vkd, device, m_context.getBinaryCollection().get("vert"));
310 auto frgshader = vk::ShaderWrapper(vkd, device, m_context.getBinaryCollection().get("frag"));
311 auto tscshader1 = vk::ShaderWrapper(vkd, device, m_context.getBinaryCollection().get("tesc"));
312 auto tscshader2 = vk::ShaderWrapper(vkd, device,
313 m_config.changeOutput ? m_context.getBinaryCollection().get("tesc2") : m_context.getBinaryCollection().get("tesc"));
314 auto tseshader1 = vk::ShaderWrapper(vkd, device, m_context.getBinaryCollection().get("tese"));
315 auto tseshader2 = vk::ShaderWrapper(vkd, device, m_context.getBinaryCollection().get("tese2"));
316
317 vk::VkPipelineRasterizationStateCreateInfo raster = {
318 vk::VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, // VkStructureType sType
319 DE_NULL, // const void* pNext
320 0u, // VkPipelineRasterizationStateCreateFlags flags
321 VK_FALSE, // VkBool32 depthClampEnable
322 VK_FALSE, // VkBool32 rasterizerDiscardEnable
323 vk::VK_POLYGON_MODE_FILL, // VkPolygonMode polygonMode
324 m_config.cullMode, // VkCullModeFlags cullMode
325 vk::VK_FRONT_FACE_COUNTER_CLOCKWISE, // VkFrontFace frontFace
326 VK_FALSE, // VkBool32 depthBiasEnable
327 0.0f, // float depthBiasConstantFactor
328 0.0f, // float depthBiasClamp
329 0.0f, // float depthBiasSlopeFactor
330 1.0f // float lineWidth
331 };
332
333 vk::VkDynamicState dynamicStates = vk::VK_DYNAMIC_STATE_PATCH_CONTROL_POINTS_EXT;
334 vk::VkPipelineDynamicStateCreateInfo dynamicInfo = {
335 vk::VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
336 nullptr,
337 0,
338 1,
339 &dynamicStates
340 };
341
342 pipeline1.setDefaultTopology(vk::VK_PRIMITIVE_TOPOLOGY_PATCH_LIST)
343 .setDynamicState(&dynamicInfo)
344 .setDefaultRasterizationState()
345 .setDefaultMultisampleState()
346 .setDefaultDepthStencilState()
347 .setDefaultColorBlendState()
348 .setupVertexInputState(&vertexInputState)
349 .setupPreRasterizationShaderState(
350 viewport_left,
351 scissors_left,
352 graphicsPipelineLayout,
353 *renderPass,
354 0u,
355 vtxshader, &raster,
356 tscshader1, tseshader1)
357 .setupFragmentShaderState(graphicsPipelineLayout, *renderPass, 0u,
358 frgshader, 0)
359 .setupFragmentOutputState(*renderPass, 0u)
360 .setMonolithicPipelineLayout(graphicsPipelineLayout).buildPipeline();
361
362 pipeline2.setDefaultTopology(vk::VK_PRIMITIVE_TOPOLOGY_PATCH_LIST)
363 .setDynamicState(&dynamicInfo)
364 .setDefaultRasterizationState()
365 .setDefaultMultisampleState()
366 .setDefaultDepthStencilState()
367 .setDefaultColorBlendState()
368 .setupVertexInputState(&vertexInputState)
369 .setupPreRasterizationShaderState(
370 viewport_right,
371 scissors_right,
372 graphicsPipelineLayout,
373 *renderPass,
374 0u,
375 vtxshader, &raster,
376 tscshader2, tseshader2)
377 .setupFragmentShaderState(graphicsPipelineLayout, *renderPass, 0u,
378 frgshader, 0)
379 .setupFragmentOutputState(*renderPass, 0u)
380 .setMonolithicPipelineLayout(graphicsPipelineLayout).buildPipeline();
381
382 auto commandPool = createCommandPool(vkd, device, vk::VK_COMMAND_POOL_CREATE_TRANSIENT_BIT, qIndex);
383 auto commandBuffer = vk::allocateCommandBuffer(vkd, device, commandPool.get(), vk::VK_COMMAND_BUFFER_LEVEL_PRIMARY);
384
385 const tcu::Vec4 clearColor(1.0f, 1.0f, 1.0f, 1.0f);
386
387 const vk::VkRect2D renderArea =
388 {
389 { 0u, 0u },
390 { imageExtent.width, imageExtent.height }
391 };
392
393 //render 2 triangles with each pipeline, covering the entire screen
394 //depending on the test settings one of them might be culled
395 vk::beginCommandBuffer(vkd, commandBuffer.get());
396 renderPass.begin(vkd, *commandBuffer, renderArea, clearColor);
397 vkd.cmdSetPatchControlPointsEXT(commandBuffer.get(), 3);
398 pipeline1.bind(commandBuffer.get());
399 vkd.cmdDraw(commandBuffer.get(), 6, 1, 0, 0);
400 pipeline2.bind(commandBuffer.get());
401 vkd.cmdDraw(commandBuffer.get(), 6, 1, 0, 0);
402 renderPass.end(vkd, commandBuffer.get());
403 vk::copyImageToBuffer(vkd, commandBuffer.get(), colorAttachment.get()->get(), (*outBuffer).get(), imageSize);
404 vk::endCommandBuffer(vkd, commandBuffer.get());
405 vk::submitCommandsAndWait(vkd, device, m_context.getUniversalQueue(), commandBuffer.get());
406
407 invalidateAlloc(vkd, device, outBufferAlloc);
408 tcu::ConstPixelBufferAccess outPixels(mappedFormat, imageDim, outBufferData);
409
410 auto expectedFirst = m_config.expectedFirst;
411 auto expectedSecond = m_config.expectedSecond;
412
413 tcu::TextureLevel referenceLevel(mappedFormat, imageExtent.height, imageExtent.height);
414 tcu::PixelBufferAccess reference = referenceLevel.getAccess();
415 tcu::clear(getSubregion(reference, 0, 0, imageExtent.width / 2, imageExtent.height), expectedFirst);
416 tcu::clear(getSubregion(reference, imageExtent.width / 2, 0, imageExtent.width / 2, imageExtent.height), expectedSecond);
417
418 if (!tcu::floatThresholdCompare(m_context.getTestContext().getLog(), "Compare", "Result comparison", reference, outPixels, tcu::Vec4(0.0), tcu::COMPARE_LOG_ON_ERROR))
419 return tcu::TestStatus::fail("Color output does not match reference, image added to log");
420
421 return tcu::TestStatus::pass("Pass");
422 }
423
createDynamicControlPointTests(tcu::TestContext& testCtx, vk::PipelineConstructionType pipelineConstructionType)424 tcu::TestCaseGroup* createDynamicControlPointTests (tcu::TestContext& testCtx, vk::PipelineConstructionType pipelineConstructionType)
425 {
426 // Tests checking dynamic bind points and switching pipelines
427 de::MovePtr<tcu::TestCaseGroup> group(new tcu::TestCaseGroup(testCtx, "dynamic_control_points"));
428
429 // test switching pipelines with dynamic control points while changing the number of tcs invocations
430 group->addChild(new DynamicControlPointsTestCase(testCtx, "change_output", TestConfig {
431 pipelineConstructionType,
432 true,
433 false,
434 false,
435 vk::VK_CULL_MODE_NONE,
436 tcu::Vec4(1.0, 0.0, 1.0, 1.0),
437 tcu::Vec4(1.0, 0.0, 1.0, 1.0),
438 }));
439
440 // test switching pipelines with dynamic control points while switching winding
441 group->addChild(new DynamicControlPointsTestCase(testCtx, "change_winding", TestConfig {
442 pipelineConstructionType,
443 false,
444 true,
445 false,
446 vk::VK_CULL_MODE_FRONT_BIT,
447 tcu::Vec4(1.0, 1.0, 1.0, 1.0),
448 tcu::Vec4(1.0, 0.0, 1.0, 1.0)
449 }));
450
451 // test switching pipelines with dynamic control points while switching winding and number of tcs invocations
452 group->addChild(new DynamicControlPointsTestCase(testCtx, "change_output_winding", TestConfig {
453 pipelineConstructionType,
454 true,
455 true,
456 false,
457 vk::VK_CULL_MODE_FRONT_BIT,
458 tcu::Vec4(1.0, 1.0, 1.0, 1.0),
459 tcu::Vec4(1.0, 0.0, 1.0, 1.0)
460 }));
461 return group.release();
462 }
463
464 } // pipeline
465 } // vkt
466