1 /*------------------------------------------------------------------------
2 * Vulkan Conformance Tests
3 * ------------------------
4 *
5 * Copyright (c) 2016 The Khronos Group Inc.
6 * Copyright (c) 2014 The Android Open Source Project
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 Geometry shader instanced rendering tests
23 *//*--------------------------------------------------------------------*/
24
25 #include "vktGeometryInstancedRenderingTests.hpp"
26 #include "vktTestCase.hpp"
27 #include "vktTestCaseUtil.hpp"
28 #include "vktGeometryTestsUtil.hpp"
29
30 #include "vkPrograms.hpp"
31 #include "vkQueryUtil.hpp"
32 #include "vkMemUtil.hpp"
33 #include "vkRefUtil.hpp"
34 #include "vkTypeUtil.hpp"
35 #include "vkImageUtil.hpp"
36 #include "vkCmdUtil.hpp"
37 #include "vkObjUtil.hpp"
38
39 #include "tcuTextureUtil.hpp"
40 #include "tcuImageCompare.hpp"
41 #include "tcuTestLog.hpp"
42
43 #include "deRandom.hpp"
44 #include "deMath.h"
45
46 namespace vkt
47 {
48 namespace geometry
49 {
50 namespace
51 {
52 using namespace vk;
53 using de::MovePtr;
54 using de::UniquePtr;
55 using tcu::UVec2;
56 using tcu::Vec4;
57
58 struct TestParams
59 {
60 int numDrawInstances;
61 int numInvocations;
62 };
63
makeImageCreateInfo(const VkFormat format,const VkExtent3D size,const VkImageUsageFlags usage)64 VkImageCreateInfo makeImageCreateInfo(const VkFormat format, const VkExtent3D size, const VkImageUsageFlags usage)
65 {
66 const VkImageCreateInfo imageParams = {
67 VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, // VkStructureType sType;
68 DE_NULL, // const void* pNext;
69 (VkImageCreateFlags)0, // VkImageCreateFlags flags;
70 VK_IMAGE_TYPE_2D, // VkImageType imageType;
71 format, // VkFormat format;
72 size, // VkExtent3D extent;
73 1u, // uint32_t mipLevels;
74 1u, // uint32_t arrayLayers;
75 VK_SAMPLE_COUNT_1_BIT, // VkSampleCountFlagBits samples;
76 VK_IMAGE_TILING_OPTIMAL, // VkImageTiling tiling;
77 usage, // VkImageUsageFlags usage;
78 VK_SHARING_MODE_EXCLUSIVE, // VkSharingMode sharingMode;
79 0u, // uint32_t queueFamilyIndexCount;
80 DE_NULL, // const uint32_t* pQueueFamilyIndices;
81 VK_IMAGE_LAYOUT_UNDEFINED, // VkImageLayout initialLayout;
82 };
83 return imageParams;
84 }
85
makeGraphicsPipeline(const DeviceInterface & vk,const VkDevice device,const VkPipelineLayout pipelineLayout,const VkRenderPass renderPass,const VkShaderModule vertexModule,const VkShaderModule geometryModule,const VkShaderModule fragmentModule,const VkExtent2D renderSize)86 Move<VkPipeline> makeGraphicsPipeline(const DeviceInterface &vk, const VkDevice device,
87 const VkPipelineLayout pipelineLayout, const VkRenderPass renderPass,
88 const VkShaderModule vertexModule, const VkShaderModule geometryModule,
89 const VkShaderModule fragmentModule, const VkExtent2D renderSize)
90 {
91 const std::vector<VkViewport> viewports(1, makeViewport(renderSize));
92 const std::vector<VkRect2D> scissors(1, makeRect2D(renderSize));
93
94 const VkVertexInputBindingDescription vertexInputBindingDescription = {
95 0u, // uint32_t binding;
96 sizeof(Vec4), // uint32_t stride;
97 VK_VERTEX_INPUT_RATE_INSTANCE // VkVertexInputRate inputRate;
98 };
99
100 const VkVertexInputAttributeDescription vertexInputAttributeDescription = {
101 0u, // uint32_t location;
102 0u, // uint32_t binding;
103 VK_FORMAT_R32G32B32A32_SFLOAT, // VkFormat format;
104 0u // uint32_t offset;
105 };
106
107 const VkPipelineVertexInputStateCreateInfo vertexInputStateCreateInfo = {
108 VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, // VkStructureType sType;
109 DE_NULL, // const void* pNext;
110 (VkPipelineVertexInputStateCreateFlags)0, // VkPipelineVertexInputStateCreateFlags flags;
111 1u, // uint32_t vertexBindingDescriptionCount;
112 &vertexInputBindingDescription, // const VkVertexInputBindingDescription* pVertexBindingDescriptions;
113 1u, // uint32_t vertexAttributeDescriptionCount;
114 &vertexInputAttributeDescription // const VkVertexInputAttributeDescription* pVertexAttributeDescriptions;
115 };
116
117 return vk::makeGraphicsPipeline(
118 vk, // const DeviceInterface& vk
119 device, // const VkDevice device
120 pipelineLayout, // const VkPipelineLayout pipelineLayout
121 vertexModule, // const VkShaderModule vertexShaderModule
122 DE_NULL, // const VkShaderModule tessellationControlModule
123 DE_NULL, // const VkShaderModule tessellationEvalModule
124 geometryModule, // const VkShaderModule geometryShaderModule
125 fragmentModule, // const VkShaderModule fragmentShaderModule
126 renderPass, // const VkRenderPass renderPass
127 viewports, // const std::vector<VkViewport>& viewports
128 scissors, // const std::vector<VkRect2D>& scissors
129 VK_PRIMITIVE_TOPOLOGY_POINT_LIST, // const VkPrimitiveTopology topology
130 0u, // const uint32_t subpass
131 0u, // const uint32_t patchControlPoints
132 &vertexInputStateCreateInfo); // const VkPipelineVertexInputStateCreateInfo* vertexInputStateCreateInfo
133 }
134
draw(Context & context,const UVec2 & renderSize,const VkFormat colorFormat,const Vec4 & clearColor,const VkBuffer colorBuffer,const int numDrawInstances,const std::vector<Vec4> & perInstanceAttribute)135 void draw(Context &context, const UVec2 &renderSize, const VkFormat colorFormat, const Vec4 &clearColor,
136 const VkBuffer colorBuffer, const int numDrawInstances, const std::vector<Vec4> &perInstanceAttribute)
137 {
138 const DeviceInterface &vk = context.getDeviceInterface();
139 const VkDevice device = context.getDevice();
140 const uint32_t queueFamilyIndex = context.getUniversalQueueFamilyIndex();
141 const VkQueue queue = context.getUniversalQueue();
142 Allocator &allocator = context.getDefaultAllocator();
143
144 const VkImageSubresourceRange colorSubresourceRange(
145 makeImageSubresourceRange(VK_IMAGE_ASPECT_COLOR_BIT, 0u, 1u, 0u, 1u));
146 const VkExtent3D colorImageExtent(makeExtent3D(renderSize.x(), renderSize.y(), 1u));
147 const VkExtent2D renderExtent(makeExtent2D(renderSize.x(), renderSize.y()));
148
149 const Unique<VkImage> colorImage(
150 makeImage(vk, device,
151 makeImageCreateInfo(colorFormat, colorImageExtent,
152 VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT)));
153 const UniquePtr<Allocation> colorImageAlloc(bindImage(vk, device, allocator, *colorImage, MemoryRequirement::Any));
154 const Unique<VkImageView> colorAttachment(
155 makeImageView(vk, device, *colorImage, VK_IMAGE_VIEW_TYPE_2D, colorFormat, colorSubresourceRange));
156
157 const VkDeviceSize vertexBufferSize = sizeInBytes(perInstanceAttribute);
158 const Unique<VkBuffer> vertexBuffer(makeBuffer(vk, device, vertexBufferSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT));
159 const UniquePtr<Allocation> vertexBufferAlloc(
160 bindBuffer(vk, device, allocator, *vertexBuffer, MemoryRequirement::HostVisible));
161
162 const Unique<VkShaderModule> vertexModule(
163 createShaderModule(vk, device, context.getBinaryCollection().get("vert"), 0u));
164 const Unique<VkShaderModule> geometryModule(
165 createShaderModule(vk, device, context.getBinaryCollection().get("geom"), 0u));
166 const Unique<VkShaderModule> fragmentModule(
167 createShaderModule(vk, device, context.getBinaryCollection().get("frag"), 0u));
168
169 const Unique<VkRenderPass> renderPass(vk::makeRenderPass(vk, device, colorFormat));
170 const Unique<VkFramebuffer> framebuffer(
171 makeFramebuffer(vk, device, *renderPass, *colorAttachment, renderSize.x(), renderSize.y()));
172 const Unique<VkPipelineLayout> pipelineLayout(makePipelineLayout(vk, device));
173 const Unique<VkPipeline> pipeline(makeGraphicsPipeline(vk, device, *pipelineLayout, *renderPass, *vertexModule,
174 *geometryModule, *fragmentModule, renderExtent));
175
176 const Unique<VkCommandPool> cmdPool(
177 createCommandPool(vk, device, VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, queueFamilyIndex));
178 const Unique<VkCommandBuffer> cmdBuffer(
179 allocateCommandBuffer(vk, device, *cmdPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY));
180
181 // Initialize vertex data
182 {
183 deMemcpy(vertexBufferAlloc->getHostPtr(), &perInstanceAttribute[0], (size_t)vertexBufferSize);
184 flushAlloc(vk, device, *vertexBufferAlloc);
185 }
186
187 beginCommandBuffer(vk, *cmdBuffer);
188
189 beginRenderPass(vk, *cmdBuffer, *renderPass, *framebuffer, makeRect2D(renderExtent), clearColor);
190
191 vk.cmdBindPipeline(*cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline);
192 {
193 const VkDeviceSize offset = 0ull;
194 vk.cmdBindVertexBuffers(*cmdBuffer, 0u, 1u, &vertexBuffer.get(), &offset);
195 }
196 vk.cmdDraw(*cmdBuffer, 1u, static_cast<uint32_t>(numDrawInstances), 0u, 0u);
197 endRenderPass(vk, *cmdBuffer);
198
199 copyImageToBuffer(vk, *cmdBuffer, *colorImage, colorBuffer, tcu::IVec2(renderSize.x(), renderSize.y()));
200
201 endCommandBuffer(vk, *cmdBuffer);
202 submitCommandsAndWait(vk, device, queue, *cmdBuffer);
203 }
204
generatePerInstancePosition(const int numInstances)205 std::vector<Vec4> generatePerInstancePosition(const int numInstances)
206 {
207 de::Random rng(1234);
208 std::vector<Vec4> positions;
209
210 for (int i = 0; i < numInstances; ++i)
211 {
212 const float flipX = rng.getBool() ? 1.0f : -1.0f;
213 const float flipY = rng.getBool() ? 1.0f : -1.0f;
214 const float x = flipX * rng.getFloat(0.1f, 0.9f); // x mustn't be 0.0, because we are using sign() in the shader
215 const float y = flipY * rng.getFloat(0.0f, 0.7f);
216
217 positions.push_back(Vec4(x, y, 0.0f, 1.0f));
218 }
219
220 return positions;
221 }
222
223 //! Get a rectangle region of an image, using NDC coordinates (i.e. [-1, 1] range).
224 //! Result rect is cropped in either dimension to be inside the bounds of the image.
getSubregion(tcu::PixelBufferAccess image,const float x,const float y,const float size)225 tcu::PixelBufferAccess getSubregion(tcu::PixelBufferAccess image, const float x, const float y, const float size)
226 {
227 const float w = static_cast<float>(image.getWidth());
228 const float h = static_cast<float>(image.getHeight());
229 const float x1 = w * (x + 1.0f) * 0.5f;
230 const float y1 = h * (y + 1.0f) * 0.5f;
231 const float sx = w * size * 0.5f;
232 const float sy = h * size * 0.5f;
233 const float x2 = x1 + sx;
234 const float y2 = y1 + sy;
235
236 // Round and clamp only after all of the above.
237 const int ix1 = std::max(deRoundFloatToInt32(x1), 0);
238 const int ix2 = std::min(deRoundFloatToInt32(x2), image.getWidth());
239 const int iy1 = std::max(deRoundFloatToInt32(y1), 0);
240 const int iy2 = std::min(deRoundFloatToInt32(y2), image.getHeight());
241
242 return tcu::getSubregion(image, ix1, iy1, ix2 - ix1, iy2 - iy1);
243 }
244
245 //! Must be in sync with the geometry shader code.
generateReferenceImage(tcu::PixelBufferAccess image,const Vec4 & clearColor,const std::vector<Vec4> & perInstancePosition,const int numInvocations)246 void generateReferenceImage(tcu::PixelBufferAccess image, const Vec4 &clearColor,
247 const std::vector<Vec4> &perInstancePosition, const int numInvocations)
248 {
249 tcu::clear(image, clearColor);
250
251 for (std::vector<Vec4>::const_iterator iterPosition = perInstancePosition.begin();
252 iterPosition != perInstancePosition.end(); ++iterPosition)
253 for (int invocationNdx = 0; invocationNdx < numInvocations; ++invocationNdx)
254 {
255 const float x = iterPosition->x();
256 const float y = iterPosition->y();
257 const float modifier =
258 (numInvocations > 1 ? static_cast<float>(invocationNdx) / static_cast<float>(numInvocations - 1) :
259 0.0f);
260 const Vec4 color(deFloatAbs(x), deFloatAbs(y), 0.2f + 0.8f * modifier, 1.0f);
261 const float size = 0.05f + 0.03f * modifier;
262 const float dx = (deFloatSign(-x) - x) / static_cast<float>(numInvocations);
263 const float xOffset = static_cast<float>(invocationNdx) * dx;
264 const float yOffset = 0.3f * deFloatSin(12.0f * modifier);
265
266 tcu::PixelBufferAccess rect = getSubregion(image, x + xOffset - size, y + yOffset - size, size + size);
267 tcu::clear(rect, color);
268 }
269 }
270
initPrograms(SourceCollections & programCollection,const TestParams params)271 void initPrograms(SourceCollections &programCollection, const TestParams params)
272 {
273 // Vertex shader
274 {
275 std::ostringstream src;
276 src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_450) << "\n"
277 << "\n"
278 << "layout(location = 0) in vec4 in_position;\n"
279 << "\n"
280 << "out gl_PerVertex {\n"
281 << " vec4 gl_Position;\n"
282 << "};\n"
283 << "\n"
284 << "void main(void)\n"
285 << "{\n"
286 << " gl_Position = in_position;\n"
287 << "}\n";
288
289 programCollection.glslSources.add("vert") << glu::VertexSource(src.str());
290 }
291
292 // Geometry shader
293 {
294 // The shader must be in sync with reference image rendering routine.
295
296 std::ostringstream src;
297 src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_450) << "\n"
298 << "\n"
299 << "layout(points, invocations = " << params.numInvocations << ") in;\n"
300 << "layout(triangle_strip, max_vertices = 4) out;\n"
301 << "\n"
302 << "layout(location = 0) out vec4 out_color;\n"
303 << "\n"
304 << "in gl_PerVertex {\n"
305 << " vec4 gl_Position;\n"
306 << "} gl_in[];\n"
307 << "\n"
308 << "out gl_PerVertex {\n"
309 << " vec4 gl_Position;\n"
310 << "};\n"
311 << "\n"
312 << "void main(void)\n"
313 << "{\n"
314 << " const vec4 pos = gl_in[0].gl_Position;\n"
315 << " const float modifier = "
316 << (params.numInvocations > 1 ?
317 "float(gl_InvocationID) / float(" + de::toString(params.numInvocations - 1) + ")" :
318 "0.0")
319 << ";\n"
320 << " const vec4 color = vec4(abs(pos.x), abs(pos.y), 0.2 + 0.8 * modifier, 1.0);\n"
321 << " const float size = 0.05 + 0.03 * modifier;\n"
322 << " const float dx = (sign(-pos.x) - pos.x) / float(" << params.numInvocations << ");\n"
323 << " const vec4 offsetPos = pos + vec4(float(gl_InvocationID) * dx,\n"
324 << " 0.3 * sin(12.0 * modifier),\n"
325 << " 0.0,\n"
326 << " 0.0);\n"
327 << "\n"
328 << " gl_Position = offsetPos + vec4(-size, -size, 0.0, 0.0);\n"
329 << " out_color = color;\n"
330 << " EmitVertex();\n"
331 << "\n"
332 << " gl_Position = offsetPos + vec4(-size, size, 0.0, 0.0);\n"
333 << " out_color = color;\n"
334 << " EmitVertex();\n"
335 << "\n"
336 << " gl_Position = offsetPos + vec4( size, -size, 0.0, 0.0);\n"
337 << " out_color = color;\n"
338 << " EmitVertex();\n"
339 << "\n"
340 << " gl_Position = offsetPos + vec4( size, size, 0.0, 0.0);\n"
341 << " out_color = color;\n"
342 << " EmitVertex();\n"
343 << "}\n";
344
345 programCollection.glslSources.add("geom") << glu::GeometrySource(src.str());
346 }
347
348 // Fragment shader
349 {
350 std::ostringstream src;
351 src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_450) << "\n"
352 << "\n"
353 << "layout(location = 0) in vec4 in_color;\n"
354 << "layout(location = 0) out vec4 o_color;\n"
355 << "\n"
356 << "void main(void)\n"
357 << "{\n"
358 << " o_color = in_color;\n"
359 << "}\n";
360
361 programCollection.glslSources.add("frag") << glu::FragmentSource(src.str());
362 }
363 }
364
test(Context & context,const TestParams params)365 tcu::TestStatus test(Context &context, const TestParams params)
366 {
367 const DeviceInterface &vk = context.getDeviceInterface();
368 const VkDevice device = context.getDevice();
369 Allocator &allocator = context.getDefaultAllocator();
370
371 const UVec2 renderSize(128u, 128u);
372 const VkFormat colorFormat = VK_FORMAT_R8G8B8A8_UNORM;
373 const Vec4 clearColor = Vec4(0.0f, 0.0f, 0.0f, 1.0f);
374
375 const VkDeviceSize colorBufferSize = renderSize.x() * renderSize.y() * tcu::getPixelSize(mapVkFormat(colorFormat));
376 const Unique<VkBuffer> colorBuffer(makeBuffer(vk, device, colorBufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT));
377 const UniquePtr<Allocation> colorBufferAlloc(
378 bindBuffer(vk, device, allocator, *colorBuffer, MemoryRequirement::HostVisible));
379
380 const std::vector<Vec4> perInstancePosition = generatePerInstancePosition(params.numDrawInstances);
381
382 {
383 context.getTestContext().getLog()
384 << tcu::TestLog::Message << "Rendering " << params.numDrawInstances << " instance(s) of colorful quads."
385 << tcu::TestLog::EndMessage << tcu::TestLog::Message << "Drawing " << params.numInvocations
386 << " quad(s), each drawn by a geometry shader invocation." << tcu::TestLog::EndMessage;
387 }
388
389 zeroBuffer(vk, device, *colorBufferAlloc, colorBufferSize);
390 draw(context, renderSize, colorFormat, clearColor, *colorBuffer, params.numDrawInstances, perInstancePosition);
391
392 // Compare result
393 {
394 invalidateAlloc(vk, device, *colorBufferAlloc);
395 const tcu::ConstPixelBufferAccess result(mapVkFormat(colorFormat), renderSize.x(), renderSize.y(), 1u,
396 colorBufferAlloc->getHostPtr());
397
398 tcu::TextureLevel reference(mapVkFormat(colorFormat), renderSize.x(), renderSize.y());
399 generateReferenceImage(reference.getAccess(), clearColor, perInstancePosition, params.numInvocations);
400
401 if (!tcu::fuzzyCompare(context.getTestContext().getLog(), "Image Compare", "Image Compare",
402 reference.getAccess(), result, 0.01f, tcu::COMPARE_LOG_RESULT))
403 return tcu::TestStatus::fail("Rendered image is incorrect");
404 else
405 return tcu::TestStatus::pass("OK");
406 }
407 }
408
checkSupport(Context & context,TestParams params)409 void checkSupport(Context &context, TestParams params)
410 {
411 context.requireDeviceCoreFeature(DEVICE_CORE_FEATURE_GEOMETRY_SHADER);
412
413 if (context.getDeviceProperties().limits.maxGeometryShaderInvocations < (uint32_t)params.numInvocations)
414 TCU_THROW(NotSupportedError, (std::string("Unsupported limit: maxGeometryShaderInvocations < ") +
415 de::toString(params.numInvocations))
416 .c_str());
417 }
418
419 } // namespace
420
421 //! \note CTS requires shaders to be known ahead of time (some platforms use precompiled shaders), so we can't query a limit at runtime and generate
422 //! a shader based on that. This applies to number of GS invocations which can't be injected into the shader.
createInstancedRenderingTests(tcu::TestContext & testCtx)423 tcu::TestCaseGroup *createInstancedRenderingTests(tcu::TestContext &testCtx)
424 {
425 MovePtr<tcu::TestCaseGroup> group(new tcu::TestCaseGroup(testCtx, "instanced"));
426
427 const int drawInstanceCases[] = {
428 1,
429 2,
430 4,
431 8,
432 };
433 const int invocationCases[] = {
434 1, 2, 8, 32, // required by the Vulkan spec
435 64, 127, // larger than the minimum, but perhaps some implementations support it, so we'll try
436 };
437
438 for (const int *pNumDrawInstances = drawInstanceCases;
439 pNumDrawInstances != drawInstanceCases + DE_LENGTH_OF_ARRAY(drawInstanceCases); ++pNumDrawInstances)
440 for (const int *pNumInvocations = invocationCases;
441 pNumInvocations != invocationCases + DE_LENGTH_OF_ARRAY(invocationCases); ++pNumInvocations)
442 {
443 std::ostringstream caseName;
444 caseName << "draw_" << *pNumDrawInstances << "_instances_" << *pNumInvocations << "_geometry_invocations";
445
446 const TestParams params = {
447 *pNumDrawInstances,
448 *pNumInvocations,
449 };
450
451 addFunctionCaseWithPrograms(group.get(), caseName.str(), checkSupport, initPrograms, test, params);
452 }
453
454 return group.release();
455 }
456
457 } // namespace geometry
458 } // namespace vkt
459