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