/*------------------------------------------------------------------------ * Vulkan Conformance Tests * ------------------------ * * Copyright (c) 2021 The Khronos Group Inc. * Copyright (c) 2021 Valve Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * *//*! * \file * \brief Mesh Shader API Tests *//*--------------------------------------------------------------------*/ #include "vktMeshShaderApiTests.hpp" #include "vktTestCase.hpp" #include "vkTypeUtil.hpp" #include "vkImageWithMemory.hpp" #include "vkBufferWithMemory.hpp" #include "vkObjUtil.hpp" #include "vkBuilderUtil.hpp" #include "vkCmdUtil.hpp" #include "vkImageUtil.hpp" #include "tcuMaybe.hpp" #include "tcuTestLog.hpp" #include "tcuImageCompare.hpp" #include "deRandom.hpp" #include #include #include #include #include #include namespace vkt { namespace MeshShader { namespace { using namespace vk; using GroupPtr = de::MovePtr; using ImageWithMemoryPtr = de::MovePtr; using BufferWithMemoryPtr = de::MovePtr; enum class DrawType { DRAW = 0, DRAW_INDIRECT, DRAW_INDIRECT_COUNT, }; std::ostream& operator<< (std::ostream& stream, DrawType drawType) { switch (drawType) { case DrawType::DRAW: stream << "draw"; break; case DrawType::DRAW_INDIRECT: stream << "draw_indirect"; break; case DrawType::DRAW_INDIRECT_COUNT: stream << "draw_indirect_count"; break; default: DE_ASSERT(false); break; } return stream; } // This helps test the maxDrawCount rule for the DRAW_INDIRECT_COUNT case. enum class IndirectCountLimitType { BUFFER_VALUE = 0, // The actual count will be given by the count buffer. MAX_COUNT, // The actual count will be given by the maxDrawCount argument passed to the draw command. }; struct IndirectArgs { uint32_t offset; uint32_t stride; }; struct TestParams { DrawType drawType; uint32_t seed; uint32_t drawCount; // Equivalent to taskCount or drawCount. uint32_t firstTask; // Equivalent to firstTask in every call. tcu::Maybe indirectArgs; // Only used for DRAW_INDIRECT*. tcu::Maybe indirectCountLimit; // Only used for DRAW_INDIRECT_COUNT. tcu::Maybe indirectCountOffset; // Only used for DRAW_INDIRECT_COUNT. bool useTask; }; // The framebuffer will have a number of rows and 32 columns. Each mesh shader workgroup will generate geometry to fill a single // framebuffer row, using a triangle list with 32 triangles of different colors, each covering a framebuffer pixel. // // Note: the total framebuffer rows is called "full" below (e.g. 64). When using a task shader to generate work, each workgroup will // generate a single mesh workgroup using a push constant instead of a compile-time constant. // // When using DRAW, the task count will tell us how many rows of pixels will be filled in the framebuffer. // // When using indirect draws, the full framebuffer will always be drawn into by using multiple draw command structures, except in // the case of drawCount==0. Each draw will spawn the needed number of tasks to fill the whole framebuffer. In addition, in order to // make all argument structures different, the number of tasks in each draw count will be slightly different and assigned // pseudorandomly. // // DRAW: taskCount=0, taskCount=1, taskCount=2, taskCount=half, taskCount=full // // DRAW_INDIRECT: drawCount=0, drawCount=1, drawCount=2, drawCount=half, drawCount=full. // * With offset 0 and pseudorandom (multiples of 4). // * With stride adding a padding of 0 and pseudorandom (multiples of 4). // // DRAW_INDIRECT_COUNT: same as indirect in two variants: // 1. Passing the count in a buffer with a large maximum. // 2. Passing a large value in the buffer and limiting it with the maximum. class MeshApiCase : public vkt::TestCase { public: MeshApiCase (tcu::TestContext& testCtx, const std::string& name, const std::string& description, const TestParams& params) : vkt::TestCase (testCtx, name, description) , m_params (params) {} virtual ~MeshApiCase (void) {} void initPrograms (vk::SourceCollections& programCollection) const override; void checkSupport (Context& context) const override; TestInstance* createInstance (Context& context) const override; protected: TestParams m_params; }; class MeshApiInstance : public vkt::TestInstance { public: MeshApiInstance (Context& context, const TestParams& params) : vkt::TestInstance (context) , m_params (params) {} virtual ~MeshApiInstance (void) {} tcu::TestStatus iterate (void) override; protected: TestParams m_params; }; TestInstance* MeshApiCase::createInstance (Context& context) const { return new MeshApiInstance(context, m_params); } struct PushConstantData { uint32_t width; uint32_t height; uint32_t firstTaskMesh; uint32_t one; uint32_t firstTaskTask; std::vector getRanges (bool includeTask) const { constexpr uint32_t offsetMesh = 0u; constexpr uint32_t offsetTask = static_cast(offsetof(PushConstantData, one)); constexpr uint32_t sizeMesh = offsetTask; constexpr uint32_t sizeTask = static_cast(sizeof(PushConstantData)) - offsetTask; const VkPushConstantRange meshRange = { VK_SHADER_STAGE_MESH_BIT_NV, // VkShaderStageFlags stageFlags; offsetMesh, // uint32_t offset; sizeMesh, // uint32_t size; }; const VkPushConstantRange taskRange = { VK_SHADER_STAGE_TASK_BIT_NV, // VkShaderStageFlags stageFlags; offsetTask, // uint32_t offset; sizeTask, // uint32_t size; }; std::vector ranges (1u, meshRange); if (includeTask) ranges.push_back(taskRange); return ranges; } }; void MeshApiCase::initPrograms (vk::SourceCollections& programCollection) const { const std::string taskDataDecl = "taskNV TaskData {\n" " uint blockNumber;\n" " uint blockRow;\n" "} td;\n" ; // Task shader if needed. if (m_params.useTask) { std::ostringstream task; task << "#version 460\n" << "#extension GL_NV_mesh_shader : enable\n" << "\n" << "layout (local_size_x=1) in;\n" << "\n" << "layout (push_constant, std430) uniform TaskPushConstantBlock {\n" << " layout (offset=12) uint one;\n" << " layout (offset=16) uint firstTask;\n" << "} pc;\n" << "\n" << "out " << taskDataDecl << "\n" << "void main ()\n" << "{\n" << " gl_TaskCountNV = pc.one;\n" << " td.blockNumber = uint(gl_DrawID);\n" << " td.blockRow = gl_WorkGroupID.x - pc.firstTask;\n" << "}\n" ; programCollection.glslSources.add("task") << glu::TaskSource(task.str()); } // Mesh shader. { std::ostringstream mesh; mesh << "#version 460\n" << "#extension GL_NV_mesh_shader : enable\n" << "\n" << "layout (local_size_x=32) in;\n" << "layout (triangles) out;\n" << "layout (max_vertices=96, max_primitives=32) out;\n" << "\n" << "layout (push_constant, std430) uniform MeshPushConstantBlock {\n" << " uint width;\n" << " uint height;\n" << " uint firstTask;\n" << "} pc;\n" << "\n" << "layout (location=0) perprimitiveNV out vec4 primitiveColor[];\n" << "\n" << (m_params.useTask ? ("in " + taskDataDecl): "") << "\n" << "layout (set=0, binding=0, std430) readonly buffer BlockSizes {\n" << " uint blockSize[];\n" << "} bsz;\n" << "\n" << "uint startOfBlock (uint blockNumber)\n" << "{\n" << " uint start = 0;\n" << " for (uint i = 0; i < blockNumber; i++)\n" << " start += bsz.blockSize[i];\n" << " return start;\n" << "}\n" << "\n" << "void main ()\n" << "{\n" << " const uint blockNumber = " << (m_params.useTask ? "td.blockNumber" : "uint(gl_DrawID)") << ";\n" << " const uint blockRow = " << (m_params.useTask ? "td.blockRow" : "(gl_WorkGroupID.x - pc.firstTask)") << ";\n" << "\n" << " // Each workgroup will fill one row, and each invocation will generate a\n" << " // triangle around the pixel center in each column.\n" << " const uint row = startOfBlock(blockNumber) + blockRow;\n" << " const uint col = gl_LocalInvocationID.x;\n" << "\n" << " const float fHeight = float(pc.height);\n" << " const float fWidth = float(pc.width);\n" << "\n" << " // Pixel coordinates, normalized.\n" << " const float rowNorm = (float(row) + 0.5) / fHeight;\n" << " const float colNorm = (float(col) + 0.5) / fWidth;\n" << "\n" << " // Framebuffer coordinates.\n" << " const float coordX = (colNorm * 2.0) - 1.0;\n" << " const float coordY = (rowNorm * 2.0) - 1.0;\n" << "\n" << " const float pixelWidth = 2.0 / fWidth;\n" << " const float pixelHeight = 2.0 / fHeight;\n" << "\n" << " const float offsetX = pixelWidth / 2.0;\n" << " const float offsetY = pixelHeight / 2.0;\n" << "\n" << " const uint baseIndex = col*3;\n" << " const uvec3 indices = uvec3(baseIndex, baseIndex + 1, baseIndex + 2);\n" << "\n" << " gl_PrimitiveCountNV = 32u;\n" << " primitiveColor[col] = vec4(rowNorm, colNorm, 0.0, 1.0);\n" << "\n" << " gl_PrimitiveIndicesNV[indices.x] = indices.x;\n" << " gl_PrimitiveIndicesNV[indices.y] = indices.y;\n" << " gl_PrimitiveIndicesNV[indices.z] = indices.z;\n" << "\n" << " gl_MeshVerticesNV[indices.x].gl_Position = vec4(coordX - offsetX, coordY + offsetY, 0.0, 1.0);\n" << " gl_MeshVerticesNV[indices.y].gl_Position = vec4(coordX + offsetX, coordY + offsetY, 0.0, 1.0);\n" << " gl_MeshVerticesNV[indices.z].gl_Position = vec4(coordX, coordY - offsetY, 0.0, 1.0);\n" << "}\n" ; programCollection.glslSources.add("mesh") << glu::MeshSource(mesh.str()); } // Frag shader. { std::ostringstream frag; frag << "#version 460\n" << "#extension GL_NV_mesh_shader : enable\n" << "\n" << "layout (location=0) perprimitiveNV in vec4 primitiveColor;\n" << "layout (location=0) out vec4 outColor;\n" << "\n" << "void main ()\n" << "{\n" << " outColor = primitiveColor;\n" << "}\n" ; programCollection.glslSources.add("frag") << glu::FragmentSource(frag.str()); } } void MeshApiCase::checkSupport (Context& context) const { context.requireDeviceFunctionality("VK_NV_mesh_shader"); const auto& meshFeatures = context.getMeshShaderFeatures(); if (!meshFeatures.meshShader) TCU_THROW(NotSupportedError, "Mesh shaders not supported"); if (m_params.useTask && !meshFeatures.taskShader) TCU_THROW(NotSupportedError, "Task shaders not supported"); // VUID-vkCmdDrawMeshTasksIndirectNV-drawCount-02718 if (m_params.drawType == DrawType::DRAW_INDIRECT && m_params.drawCount > 1u) { const auto& features = context.getDeviceFeatures(); if (!features.multiDrawIndirect) TCU_THROW(NotSupportedError, "Indirect multi-draws not supported"); } // VUID-vkCmdDrawMeshTasksIndirectCountNV-None-04445 if (m_params.drawType == DrawType::DRAW_INDIRECT_COUNT) context.requireDeviceFunctionality("VK_KHR_draw_indirect_count"); } template BufferWithMemoryPtr makeStridedBuffer(const DeviceInterface& vkd, VkDevice device, Allocator& alloc, const std::vector& elements, uint32_t offset, uint32_t stride, VkBufferUsageFlags usage, uint32_t endPadding) { const auto elementSize = static_cast(sizeof(T)); const auto actualStride = std::max(elementSize, stride); const auto bufferSize = static_cast(offset) + static_cast(actualStride) * elements.size() + static_cast(endPadding); const auto bufferInfo = makeBufferCreateInfo(static_cast(bufferSize), usage); BufferWithMemoryPtr buffer(new BufferWithMemory(vkd, device, alloc, bufferInfo, MemoryRequirement::HostVisible)); auto& bufferAlloc = buffer->getAllocation(); char* bufferDataPtr = reinterpret_cast(bufferAlloc.getHostPtr()); char* itr = bufferDataPtr + offset; for (const auto& elem : elements) { deMemcpy(itr, &elem, sizeof(elem)); itr += actualStride; } if (endPadding > 0u) deMemset(itr, 0xFF, endPadding); flushAlloc(vkd, device, bufferAlloc); return buffer; } VkExtent3D getExtent () { return makeExtent3D(32u, 64u, 1u); } tcu::TestStatus MeshApiInstance::iterate (void) { const auto& vkd = m_context.getDeviceInterface(); const auto device = m_context.getDevice(); auto& alloc = m_context.getDefaultAllocator(); const auto queueIndex = m_context.getUniversalQueueFamilyIndex(); const auto queue = m_context.getUniversalQueue(); const auto extent = getExtent(); const auto iExtent3D = tcu::IVec3(static_cast(extent.width), static_cast(extent.height), static_cast(extent.depth)); const auto iExtent2D = tcu::IVec2(iExtent3D.x(), iExtent3D.y()); const auto format = VK_FORMAT_R8G8B8A8_UNORM; const auto tcuFormat = mapVkFormat(format); const auto colorUsage = (VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT); const auto colorSRR = makeImageSubresourceRange(VK_IMAGE_ASPECT_COLOR_BIT, 0u, 1u, 0u, 1u); const tcu::Vec4 clearColor (0.0f, 0.0f, 0.0f, 1.0f); const float colorThres = 0.005f; // 1/255 < 0.005 < 2/255 const tcu::Vec4 threshold (colorThres, colorThres, 0.0f, 0.0f); ImageWithMemoryPtr colorBuffer; Move colorBufferView; { const VkImageCreateInfo colorBufferInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, // VkStructureType sType; nullptr, // const void* pNext; 0u, // VkImageCreateFlags flags; VK_IMAGE_TYPE_2D, // VkImageType imageType; format, // VkFormat format; extent, // VkExtent3D extent; 1u, // uint32_t mipLevels; 1u, // uint32_t arrayLayers; VK_SAMPLE_COUNT_1_BIT, // VkSampleCountFlagBits samples; VK_IMAGE_TILING_OPTIMAL, // VkImageTiling tiling; colorUsage, // VkImageUsageFlags usage; VK_SHARING_MODE_EXCLUSIVE, // VkSharingMode sharingMode; 0u, // uint32_t queueFamilyIndexCount; nullptr, // const uint32_t* pQueueFamilyIndices; VK_IMAGE_LAYOUT_UNDEFINED, // VkImageLayout initialLayout; }; colorBuffer = ImageWithMemoryPtr(new ImageWithMemory(vkd, device, alloc, colorBufferInfo, MemoryRequirement::Any)); colorBufferView = makeImageView(vkd, device, colorBuffer->get(), VK_IMAGE_VIEW_TYPE_2D, format, colorSRR); } // Prepare buffer containing the array of block sizes. de::Random rnd (m_params.seed); std::vector blockSizes; const uint32_t vectorSize = std::max(1u, m_params.drawCount); const uint32_t largeDrawCount = vectorSize + 1u; // The indirect buffer needs to have some padding at the end. See below. const uint32_t evenBlockSize = extent.height / vectorSize; uint32_t remainingRows = extent.height; blockSizes.reserve(vectorSize); for (uint32_t i = 0; i < vectorSize - 1u; ++i) { const auto blockSize = static_cast(rnd.getInt(1, evenBlockSize)); remainingRows -= blockSize; blockSizes.push_back(blockSize); } blockSizes.push_back(remainingRows); const auto blockSizesBufferSize = static_cast(de::dataSize(blockSizes)); BufferWithMemoryPtr blockSizesBuffer = makeStridedBuffer(vkd, device, alloc, blockSizes, 0u, 0u, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, 0u); // Descriptor set layout, pool and set. DescriptorSetLayoutBuilder layoutBuilder; layoutBuilder.addSingleBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_MESH_BIT_NV); const auto setLayout = layoutBuilder.build(vkd, device); DescriptorPoolBuilder poolBuilder; poolBuilder.addType(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER); const auto descriptorPool = poolBuilder.build(vkd, device, VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, 1u); const auto descriptorSet = makeDescriptorSet(vkd, device, descriptorPool.get(), setLayout.get()); // Update descriptor set. { DescriptorSetUpdateBuilder updateBuilder; const auto location = DescriptorSetUpdateBuilder::Location::binding(0u); const auto descriptorBufferInfo = makeDescriptorBufferInfo(blockSizesBuffer->get(), 0ull, blockSizesBufferSize); updateBuilder.writeSingle(descriptorSet.get(), location, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, &descriptorBufferInfo); updateBuilder.update(vkd, device); } // Pipeline layout. PushConstantData pcData; const auto pcRanges = pcData.getRanges(m_params.useTask); const auto pipelineLayout = makePipelineLayout(vkd, device, 1u, &setLayout.get(), static_cast(pcRanges.size()), de::dataOrNull(pcRanges)); // Push constants. pcData.width = extent.width; pcData.height = extent.height; pcData.firstTaskMesh = m_params.firstTask; pcData.one = 1u; pcData.firstTaskTask = m_params.firstTask; // Render pass and framebuffer. const auto renderPass = makeRenderPass(vkd, device, format); const auto framebuffer = makeFramebuffer(vkd, device, renderPass.get(), colorBufferView.get(), extent.width, extent.height); // Pipeline. Move taskModule; Move meshModule; Move fragModule; const auto& binaries = m_context.getBinaryCollection(); if (m_params.useTask) taskModule = createShaderModule(vkd, device, binaries.get("task")); meshModule = createShaderModule(vkd, device, binaries.get("mesh")); fragModule = createShaderModule(vkd, device, binaries.get("frag")); const std::vector viewports (1u, makeViewport(extent)); const std::vector scissors (1u, makeRect2D(extent)); const auto pipeline = makeGraphicsPipeline(vkd, device, pipelineLayout.get(), taskModule.get(), meshModule.get(), fragModule.get(), renderPass.get(), viewports, scissors); // Command pool and buffer. const auto cmdPool = makeCommandPool(vkd, device, queueIndex); const auto cmdBufferPtr = allocateCommandBuffer(vkd, device, cmdPool.get(), VK_COMMAND_BUFFER_LEVEL_PRIMARY); const auto cmdBuffer = cmdBufferPtr.get(); // Indirect and count buffers if needed. BufferWithMemoryPtr indirectBuffer; BufferWithMemoryPtr countBuffer; if (m_params.drawType != DrawType::DRAW) { // Indirect draws. DE_ASSERT(static_cast(m_params.indirectArgs)); const auto& indirectArgs = m_params.indirectArgs.get(); // Check stride and offset validity. DE_ASSERT(indirectArgs.offset % 4u == 0u); DE_ASSERT(indirectArgs.stride % 4u == 0u && (indirectArgs.stride == 0u || indirectArgs.stride >= static_cast(sizeof(VkDrawMeshTasksIndirectCommandNV)))); // Prepare struct vector, which will be converted to a buffer with the proper stride and offset later. std::vector commands; commands.reserve(blockSizes.size()); std::transform(begin(blockSizes), end(blockSizes), std::back_inserter(commands), [this](uint32_t blockSize) { return VkDrawMeshTasksIndirectCommandNV{blockSize, this->m_params.firstTask}; }); const auto padding = static_cast(sizeof(VkDrawMeshTasksIndirectCommandNV)); indirectBuffer = makeStridedBuffer(vkd, device, alloc, commands, indirectArgs.offset, indirectArgs.stride, VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT, padding); // Prepare count buffer if needed. if (m_params.drawType == DrawType::DRAW_INDIRECT_COUNT) { DE_ASSERT(static_cast(m_params.indirectCountLimit)); DE_ASSERT(static_cast(m_params.indirectCountOffset)); const auto countBufferValue = ((m_params.indirectCountLimit.get() == IndirectCountLimitType::BUFFER_VALUE) ? m_params.drawCount : largeDrawCount); const std::vector singleCount (1u, countBufferValue); countBuffer = makeStridedBuffer(vkd, device, alloc, singleCount, m_params.indirectCountOffset.get(), static_cast(sizeof(uint32_t)), VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT, 0u); } } // Submit commands. beginCommandBuffer(vkd, cmdBuffer); beginRenderPass(vkd, cmdBuffer, renderPass.get(), framebuffer.get(), scissors.at(0), clearColor); vkd.cmdBindDescriptorSets(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout.get(), 0u, 1u, &descriptorSet.get(), 0u, nullptr); { const char* pcDataPtr = reinterpret_cast(&pcData); for (const auto& range : pcRanges) vkd.cmdPushConstants(cmdBuffer, pipelineLayout.get(), range.stageFlags, range.offset, range.size, pcDataPtr + range.offset); } vkd.cmdBindPipeline(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.get()); if (m_params.drawType == DrawType::DRAW) { vkd.cmdDrawMeshTasksNV(cmdBuffer, m_params.drawCount, m_params.firstTask); } else if (m_params.drawType == DrawType::DRAW_INDIRECT) { const auto& indirectArgs = m_params.indirectArgs.get(); vkd.cmdDrawMeshTasksIndirectNV(cmdBuffer, indirectBuffer->get(), indirectArgs.offset, m_params.drawCount, indirectArgs.stride); } else if (m_params.drawType == DrawType::DRAW_INDIRECT_COUNT) { const auto& indirectArgs = m_params.indirectArgs.get(); const auto& indirectCountOffset = m_params.indirectCountOffset.get(); const auto& indirectCountLimit = m_params.indirectCountLimit.get(); const auto maxCount = ((indirectCountLimit == IndirectCountLimitType::MAX_COUNT) ? m_params.drawCount : largeDrawCount); vkd.cmdDrawMeshTasksIndirectCountNV(cmdBuffer, indirectBuffer->get(), indirectArgs.offset, countBuffer->get(), indirectCountOffset, maxCount, indirectArgs.stride); } else DE_ASSERT(false); endRenderPass(vkd, cmdBuffer); // Output buffer to extract the color buffer. BufferWithMemoryPtr outBuffer; void* outBufferData = nullptr; { const auto outBufferSize = static_cast(static_cast(tcu::getPixelSize(tcuFormat)) * extent.width * extent.height); const auto outBufferUsage = VK_BUFFER_USAGE_TRANSFER_DST_BIT; const auto outBufferInfo = makeBufferCreateInfo(outBufferSize, outBufferUsage); outBuffer = BufferWithMemoryPtr(new BufferWithMemory(vkd, device, alloc, outBufferInfo, MemoryRequirement::HostVisible)); outBufferData = outBuffer->getAllocation().getHostPtr(); } copyImageToBuffer(vkd, cmdBuffer, colorBuffer->get(), outBuffer->get(), iExtent2D); endCommandBuffer(vkd, cmdBuffer); submitCommandsAndWait(vkd, device, queue, cmdBuffer); // Generate reference image and compare. { auto& log = m_context.getTestContext().getLog(); auto& outBufferAlloc = outBuffer->getAllocation(); tcu::ConstPixelBufferAccess result (tcuFormat, iExtent3D, outBufferData); tcu::TextureLevel referenceLevel (tcuFormat, iExtent3D.x(), iExtent3D.y()); const auto reference = referenceLevel.getAccess(); const auto setName = de::toString(m_params.drawType) + "_draw_count_" + de::toString(m_params.drawCount) + (m_params.useTask ? "_with_task" : "_no_task"); const auto fHeight = static_cast(extent.height); const auto fWidth = static_cast(extent.width); invalidateAlloc(vkd, device, outBufferAlloc); for (int y = 0; y < iExtent3D.y(); ++y) for (int x = 0; x < iExtent3D.x(); ++x) { const tcu::Vec4 refColor = ((m_params.drawCount == 0u || (m_params.drawType == DrawType::DRAW && y >= static_cast(m_params.drawCount))) ? clearColor : tcu::Vec4( // These match the per-primitive color set by the mesh shader. (static_cast(y) + 0.5f) / fHeight, (static_cast(x) + 0.5f) / fWidth, 0.0f, 1.0f)); reference.setPixel(refColor, x, y); } if (!tcu::floatThresholdCompare(log, setName.c_str(), "", reference, result, threshold, tcu::COMPARE_LOG_ON_ERROR)) return tcu::TestStatus::fail("Image comparison failed; check log for details"); } return tcu::TestStatus::pass("Pass"); } } // anonymous tcu::TestCaseGroup* createMeshShaderApiTests (tcu::TestContext& testCtx) { GroupPtr mainGroup (new tcu::TestCaseGroup(testCtx, "api", "Mesh Shader API tests")); const DrawType drawCases[] = { DrawType::DRAW, DrawType::DRAW_INDIRECT, DrawType::DRAW_INDIRECT_COUNT, }; const auto extent = getExtent(); const uint32_t drawCountCases[] = { 0u, 1u, 2u, extent.height / 2u, extent.height }; const uint32_t normalStride = static_cast(sizeof(VkDrawMeshTasksIndirectCommandNV)); const uint32_t largeStride = 2u * normalStride + 4u; const uint32_t altOffset = 20u; const struct { tcu::Maybe indirectArgs; const char* name; } indirectArgsCases[] = { { tcu::nothing(), "no_indirect_args" }, // Offset 0, varying strides. { tcu::just(IndirectArgs{ 0u, 0u }), "offset_0_stride_0" }, { tcu::just(IndirectArgs{ 0u, normalStride }), "offset_0_stride_normal" }, { tcu::just(IndirectArgs{ 0u, largeStride }), "offset_0_stride_large" }, // Nonzero offset, varying strides. { tcu::just(IndirectArgs{ altOffset, 0u }), "offset_alt_stride_0" }, { tcu::just(IndirectArgs{ altOffset, normalStride }), "offset_alt_stride_normal" }, { tcu::just(IndirectArgs{ altOffset, largeStride }), "offset_alt_stride_large" }, }; const struct { tcu::Maybe limitType; const char* name; } countLimitCases[] = { { tcu::nothing(), "no_count_limit" }, { tcu::just(IndirectCountLimitType::BUFFER_VALUE), "count_limit_buffer" }, { tcu::just(IndirectCountLimitType::MAX_COUNT), "count_limit_max_count" }, }; const struct { tcu::Maybe countOffset; const char* name; } countOffsetCases[] = { { tcu::nothing(), "no_count_offset" }, { tcu::just(uint32_t{0u}), "count_offset_0" }, { tcu::just(altOffset), "count_offset_alt" }, }; const struct { bool useTask; const char* name; } taskCases[] = { { false, "no_task_shader" }, { true, "with_task_shader" }, }; const struct { uint32_t firstTask; const char* name; } firstTaskCases[] = { { 0u, "first_task_zero" }, { 1001u, "first_task_nonzero" }, }; uint32_t seed = 1628678795u; for (const auto& drawCase : drawCases) { const auto drawCaseName = de::toString(drawCase); const bool isIndirect = (drawCase != DrawType::DRAW); const bool isIndirectNoCount = (drawCase == DrawType::DRAW_INDIRECT); const bool isIndirectCount = (drawCase == DrawType::DRAW_INDIRECT_COUNT); GroupPtr drawGroup(new tcu::TestCaseGroup(testCtx, drawCaseName.c_str(), "")); for (const auto& drawCountCase : drawCountCases) { const auto drawCountName = "draw_count_" + de::toString(drawCountCase); GroupPtr drawCountGroup(new tcu::TestCaseGroup(testCtx, drawCountName.c_str(), "")); for (const auto& indirectArgsCase : indirectArgsCases) { const bool hasIndirectArgs = static_cast(indirectArgsCase.indirectArgs); const bool strideZero = (hasIndirectArgs && indirectArgsCase.indirectArgs.get().stride == 0u); if (isIndirect != hasIndirectArgs) continue; // VUID-vkCmdDrawMeshTasksIndirectNV-drawCount-02146 and VUID-vkCmdDrawMeshTasksIndirectCountNV-stride-02182. if (((isIndirectNoCount && drawCountCase > 1u) || isIndirectCount) && strideZero) continue; GroupPtr indirectArgsGroup(new tcu::TestCaseGroup(testCtx, indirectArgsCase.name, "")); for (const auto& countLimitCase : countLimitCases) { const bool hasCountLimit = static_cast(countLimitCase.limitType); if (isIndirectCount != hasCountLimit) continue; GroupPtr countLimitGroup(new tcu::TestCaseGroup(testCtx, countLimitCase.name, "")); for (const auto& countOffsetCase : countOffsetCases) { const bool hasCountOffsetType = static_cast(countOffsetCase.countOffset); if (isIndirectCount != hasCountOffsetType) continue; GroupPtr countOffsetGroup(new tcu::TestCaseGroup(testCtx, countOffsetCase.name, "")); for (const auto& taskCase : taskCases) { GroupPtr taskCaseGrp(new tcu::TestCaseGroup(testCtx, taskCase.name, "")); for (const auto& firstTaskCase : firstTaskCases) { const TestParams params = { drawCase, // DrawType drawType; seed++, // uint32_t seed; drawCountCase, // uint32_t drawCount; firstTaskCase.firstTask, // uint32_t firstTask; indirectArgsCase.indirectArgs, // tcu::Maybe indirectArgs; countLimitCase.limitType, // tcu::Maybe indirectCountLimit; countOffsetCase.countOffset, // tcu::Maybe indirectCountOffset; taskCase.useTask, // bool useTask; }; taskCaseGrp->addChild(new MeshApiCase(testCtx, firstTaskCase.name, "", params)); } countOffsetGroup->addChild(taskCaseGrp.release()); } countLimitGroup->addChild(countOffsetGroup.release()); } indirectArgsGroup->addChild(countLimitGroup.release()); } drawCountGroup->addChild(indirectArgsGroup.release()); } drawGroup->addChild(drawCountGroup.release()); } mainGroup->addChild(drawGroup.release()); } return mainGroup.release(); } } // MeshShader } // vkt