blob: 19c7eee9b72c213e594941c5e3cd76005260a78f [file] [log] [blame]
/*------------------------------------------------------------------------
* 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 Builtin Tests
*//*--------------------------------------------------------------------*/
#include "vktMeshShaderBuiltinTests.hpp"
#include "vktTestCase.hpp"
#include "vkTypeUtil.hpp"
#include "vkImageUtil.hpp"
#include "vkObjUtil.hpp"
#include "vkBuilderUtil.hpp"
#include "vkImageWithMemory.hpp"
#include "vkBufferWithMemory.hpp"
#include "vkCmdUtil.hpp"
#include "vkBarrierUtil.hpp"
#include "tcuTexture.hpp"
#include "tcuTestLog.hpp"
#include <vector>
#include <algorithm>
#include <sstream>
#include <map>
#include <utility>
#include <sstream>
namespace tcu
{
// Needed for PixelMap below.
bool operator<(const IVec2& a, const IVec2& b)
{
return (a.x() < b.x() || (a.x() == b.x() && a.y() < b.y()));
}
}
namespace vkt
{
namespace MeshShader
{
namespace
{
using namespace vk;
using GroupPtr = de::MovePtr<tcu::TestCaseGroup>;
using DrawCommandVec = std::vector<VkDrawMeshTasksIndirectCommandNV>;
using ImageWithMemoryPtr = de::MovePtr<ImageWithMemory>;
using BufferWithMemoryPtr = de::MovePtr<BufferWithMemory>;
using ViewportVec = std::vector<VkViewport>;
using ColorVec = std::vector<tcu::Vec4>;
using PixelMap = std::map<tcu::IVec2, tcu::Vec4>; // Coordinates to color.
VkExtent2D getDefaultExtent ()
{
return makeExtent2D(8u, 8u);
}
VkExtent2D getLinearExtent ()
{
return makeExtent2D(8u, 1u);
}
struct JobSize
{
uint32_t numTasks;
uint32_t localSize;
};
JobSize getLargeJobSize ()
{
return JobSize{8u, 8u};
}
// Single draw command with the given number of tasks, 1 by default.
DrawCommandVec getDefaultDrawCommands (uint32_t taskCount = 1u)
{
return DrawCommandVec(1u, makeDrawMeshTasksIndirectCommandNV(taskCount, 0u));
}
// Basic fragment shader that draws fragments in blue.
std::string getBasicFragShader ()
{
return
"#version 460\n"
"#extension GL_NV_mesh_shader : enable\n"
"\n"
"layout (location=0) out vec4 outColor;\n"
"\n"
"void main ()\n"
"{\n"
" outColor = vec4(0.0, 0.0, 1.0, 1.0);\n"
"}\n"
;
}
struct IterationParams
{
VkExtent2D colorExtent;
uint32_t numLayers;
DrawCommandVec drawArgs;
bool indirect;
ViewportVec viewports; // If empty, a single default viewport is used.
};
class MeshShaderBuiltinInstance : public vkt::TestInstance
{
public:
MeshShaderBuiltinInstance (Context& context, const IterationParams& params)
: vkt::TestInstance (context)
, m_params (params)
{}
virtual ~MeshShaderBuiltinInstance (void) {}
tcu::TestStatus iterate () override;
virtual void verifyResults (const tcu::ConstPixelBufferAccess& result) = 0;
protected:
IterationParams m_params;
};
tcu::TestStatus MeshShaderBuiltinInstance::iterate ()
{
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& binaries = m_context.getBinaryCollection();
const auto useTask = binaries.contains("task");
const auto useFrag = binaries.contains("frag");
const auto extent = makeExtent3D(m_params.colorExtent.width, m_params.colorExtent.height, 1u);
const auto iExtent3D = tcu::IVec3(static_cast<int>(extent.width), static_cast<int>(extent.height), static_cast<int>(m_params.numLayers));
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 viewType = ((m_params.numLayers > 1u) ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D);
const auto colorSRR = makeImageSubresourceRange(VK_IMAGE_ASPECT_COLOR_BIT, 0u, 1u, 0u, m_params.numLayers);
const auto colorSRL = makeImageSubresourceLayers(VK_IMAGE_ASPECT_COLOR_BIT, 0u, 0u, m_params.numLayers);
const tcu::Vec4 clearColor (0.0f, 0.0f, 0.0f, 1.0f);
ImageWithMemoryPtr colorBuffer;
Move<VkImageView> 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;
m_params.numLayers, // 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(), viewType, format, colorSRR);
}
// Empty descriptor set layout.
DescriptorSetLayoutBuilder layoutBuilder;
const auto setLayout = layoutBuilder.build(vkd, device);
// Pipeline layout.
const auto pipelineLayout = makePipelineLayout(vkd, device, setLayout.get());
// 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, m_params.numLayers);
// Pipeline.
Move<VkShaderModule> taskModule;
Move<VkShaderModule> meshModule;
Move<VkShaderModule> fragModule;
if (useTask)
taskModule = createShaderModule(vkd, device, binaries.get("task"));
if (useFrag)
fragModule = createShaderModule(vkd, device, binaries.get("frag"));
meshModule = createShaderModule(vkd, device, binaries.get("mesh"));
std::vector<VkViewport> viewports;
std::vector<VkRect2D> scissors;
if (m_params.viewports.empty())
{
// Default ones.
viewports.push_back(makeViewport(extent));
scissors.push_back(makeRect2D(extent));
}
else
{
// The desired viewports and the same number of default scissors.
viewports.reserve(m_params.viewports.size());
std::copy(begin(m_params.viewports), end(m_params.viewports), std::back_inserter(viewports));
scissors.resize(viewports.size(), 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 buffer if needed.
BufferWithMemoryPtr indirectBuffer;
DE_ASSERT(!m_params.drawArgs.empty());
if (m_params.indirect)
{
// Indirect draws.
const auto indirectBufferSize = static_cast<VkDeviceSize>(de::dataSize(m_params.drawArgs));
const auto indirectBufferUsage = (VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT);
const auto indirectBufferInfo = makeBufferCreateInfo(indirectBufferSize, indirectBufferUsage);
indirectBuffer = BufferWithMemoryPtr(new BufferWithMemory(vkd, device, alloc, indirectBufferInfo, MemoryRequirement::HostVisible));
auto& indirectBufferAlloc = indirectBuffer->getAllocation();
void* indirectBufferData = indirectBufferAlloc.getHostPtr();
deMemcpy(indirectBufferData, m_params.drawArgs.data(), static_cast<size_t>(indirectBufferSize));
flushAlloc(vkd, device, indirectBufferAlloc);
}
// Submit commands.
beginCommandBuffer(vkd, cmdBuffer);
beginRenderPass(vkd, cmdBuffer, renderPass.get(), framebuffer.get(), scissors.at(0), clearColor);
vkd.cmdBindPipeline(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.get());
if (!m_params.indirect)
{
for (const auto& command : m_params.drawArgs)
vkd.cmdDrawMeshTasksNV(cmdBuffer, command.taskCount, command.firstTask);
}
else
{
const auto numDraws = static_cast<uint32_t>(m_params.drawArgs.size());
const auto stride = static_cast<uint32_t>(sizeof(decltype(m_params.drawArgs)::value_type));
vkd.cmdDrawMeshTasksIndirectNV(cmdBuffer, indirectBuffer->get(), 0ull, numDraws, stride);
}
endRenderPass(vkd, cmdBuffer);
// Output buffer to extract the color buffer contents.
BufferWithMemoryPtr outBuffer;
void* outBufferData = nullptr;
{
const auto layerSize = static_cast<VkDeviceSize>(static_cast<uint32_t>(tcu::getPixelSize(tcuFormat)) * extent.width * extent.height);
const auto outBufferSize = layerSize * m_params.numLayers;
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();
}
// Transition image layout.
const auto preTransferBarrier = makeImageMemoryBarrier(
(VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT), VK_ACCESS_TRANSFER_READ_BIT,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
colorBuffer->get(), colorSRR);
vkd.cmdPipelineBarrier(cmdBuffer, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0u, 0u, nullptr, 0u, nullptr, 1u, &preTransferBarrier);
// Copy image to output buffer.
const std::vector<VkBufferImageCopy> regions (1u, makeBufferImageCopy(extent, colorSRL));
vkd.cmdCopyImageToBuffer(cmdBuffer, colorBuffer->get(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, outBuffer->get(), static_cast<uint32_t>(regions.size()), de::dataOrNull(regions));
// Transfer to host barrier.
const auto postTransferBarrier = makeMemoryBarrier(VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT);
vkd.cmdPipelineBarrier(cmdBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0u, 1u, &postTransferBarrier, 0u, nullptr, 0u, nullptr);
endCommandBuffer(vkd, cmdBuffer);
submitCommandsAndWait(vkd, device, queue, cmdBuffer);
// Invalidate alloc and verify result.
{
auto& outBufferAlloc = outBuffer->getAllocation();
invalidateAlloc(vkd, device, outBufferAlloc);
tcu::ConstPixelBufferAccess result (tcuFormat, iExtent3D, outBufferData);
verifyResults(result);
}
return tcu::TestStatus::pass("Pass");
}
// Abstract case that implements the generic checkSupport method.
class MeshShaderBuiltinCase : public vkt::TestCase
{
public:
MeshShaderBuiltinCase (tcu::TestContext& testCtx, const std::string& name, const std::string& description, bool taskNeeded)
: vkt::TestCase (testCtx, name, description)
, m_taskNeeded (taskNeeded)
{}
virtual ~MeshShaderBuiltinCase (void) {}
void checkSupport (Context& context) const override;
protected:
const bool m_taskNeeded;
};
void MeshShaderBuiltinCase::checkSupport (Context& context) const
{
context.requireDeviceFunctionality("VK_NV_mesh_shader");
const auto& meshFeatures = context.getMeshShaderFeatures();
if (!meshFeatures.meshShader)
TCU_THROW(NotSupportedError, "Mesh shader not supported");
if (m_taskNeeded && !meshFeatures.taskShader)
TCU_THROW(NotSupportedError, "Task shader not supported");
}
// Instance that verifies color layers.
class FullScreenColorInstance : public MeshShaderBuiltinInstance
{
public:
FullScreenColorInstance (Context& context, const IterationParams& params, const ColorVec& expectedColors)
: MeshShaderBuiltinInstance (context, params)
, m_expectedColors (expectedColors)
{}
virtual ~FullScreenColorInstance (void) {}
void verifyResults (const tcu::ConstPixelBufferAccess& result) override;
protected:
const ColorVec m_expectedColors;
};
void FullScreenColorInstance::verifyResults (const tcu::ConstPixelBufferAccess& result)
{
auto& log = m_context.getTestContext().getLog();
bool fail = false;
const auto width = result.getWidth();
const auto height = result.getHeight();
const auto depth = result.getDepth();
for (int z = 0; z < depth; ++z)
{
const auto& expected = m_expectedColors.at(z);
for (int y = 0; y < height; ++y)
for (int x = 0; x < width; ++x)
{
const auto resultColor = result.getPixel(x, y, z);
if (resultColor != expected)
{
std::ostringstream msg;
msg << "Pixel (" << x << ", " << y << ", " << z << ") failed: expected " << expected << " and found " << resultColor;
log << tcu::TestLog::Message << msg.str() << tcu::TestLog::EndMessage;
fail = true;
}
}
}
if (fail)
{
log << tcu::TestLog::Image("Result", "", result);
TCU_FAIL("Check log for details");
}
}
// Instance that verifies single-layer framebuffers divided into 4 quadrants.
class QuadrantsInstance : public MeshShaderBuiltinInstance
{
public:
QuadrantsInstance (Context& context, const IterationParams& params,
const tcu::Vec4 topLeft,
const tcu::Vec4 topRight,
const tcu::Vec4 bottomLeft,
const tcu::Vec4 bottomRight)
: MeshShaderBuiltinInstance (context, params)
, m_topLeft (topLeft)
, m_topRight (topRight)
, m_bottomLeft (bottomLeft)
, m_bottomRight (bottomRight)
{}
virtual ~QuadrantsInstance (void) {}
void verifyResults (const tcu::ConstPixelBufferAccess& result) override;
protected:
const tcu::Vec4 m_topLeft;
const tcu::Vec4 m_topRight;
const tcu::Vec4 m_bottomLeft;
const tcu::Vec4 m_bottomRight;
};
void QuadrantsInstance::verifyResults (const tcu::ConstPixelBufferAccess& result)
{
const auto width = result.getWidth();
const auto height = result.getHeight();
const auto depth = result.getDepth();
DE_ASSERT(depth == 1);
DE_ASSERT(width > 0 && width % 2 == 0);
DE_ASSERT(height > 0 && height % 2 == 0);
DE_UNREF(depth); // For release builds.
const auto halfWidth = width / 2;
const auto halfHeight = height / 2;
tcu::Vec4 expected;
for (int y = 0; y < height; ++y)
for (int x = 0; x < width; ++x)
{
// Choose the right quadrant
if (y < halfHeight)
expected = ((x < halfWidth) ? m_topLeft : m_topRight);
else
expected = ((x < halfWidth) ? m_bottomLeft : m_bottomRight);
const auto resultColor = result.getPixel(x, y);
if (resultColor != expected)
{
std::ostringstream msg;
msg << "Pixel (" << x << ", " << y << ") failed: expected " << expected << " and found " << resultColor;
TCU_FAIL(msg.str());
}
}
}
// Instance that verifies single-layer framebuffers with specific pixels set to some color.
struct PixelVerifierParams
{
const tcu::Vec4 background;
const PixelMap pixelMap;
};
class PixelsInstance : public MeshShaderBuiltinInstance
{
public:
PixelsInstance (Context& context, const IterationParams& params, const PixelVerifierParams& pixelParams)
: MeshShaderBuiltinInstance (context, params)
, m_pixelParams (pixelParams)
{}
virtual ~PixelsInstance (void) {}
void verifyResults (const tcu::ConstPixelBufferAccess& result) override;
protected:
const PixelVerifierParams m_pixelParams;
};
void PixelsInstance::verifyResults (const tcu::ConstPixelBufferAccess& result)
{
const auto width = result.getWidth();
const auto height = result.getHeight();
const auto depth = result.getDepth();
DE_ASSERT(depth == 1);
DE_UNREF(depth); // For release builds.
for (int y = 0; y < height; ++y)
for (int x = 0; x < width; ++x)
{
const tcu::IVec2 coords (x, y);
const auto iter = m_pixelParams.pixelMap.find(coords);
const auto expected = ((iter == m_pixelParams.pixelMap.end()) ? m_pixelParams.background : iter->second);
const auto resultColor = result.getPixel(x, y);
if (resultColor != expected)
{
std::ostringstream msg;
msg << "Pixel (" << x << ", " << y << ") failed: expected " << expected << " and found " << resultColor;
TCU_FAIL(msg.str());
}
}
}
// Primitive ID cases.
class PrimitiveIdCase : public MeshShaderBuiltinCase
{
public:
PrimitiveIdCase (tcu::TestContext& testCtx, const std::string& name, const std::string& description, bool glslFrag)
: MeshShaderBuiltinCase (testCtx, name, description, false/*taskNeeded*/)
, m_glslFrag (glslFrag)
{}
virtual ~PrimitiveIdCase (void) {}
void initPrograms (vk::SourceCollections& programCollection) const override;
void checkSupport (Context& context) const override;
TestInstance* createInstance (Context& context) const override;
protected:
// Fragment shader in GLSL means glslang will use the Geometry capability due to gl_PrimitiveID.
const bool m_glslFrag;
};
void PrimitiveIdCase::initPrograms (vk::SourceCollections& programCollection) const
{
// Mesh shader.
{
std::ostringstream mesh;
mesh
<< "#version 460\n"
<< "#extension GL_NV_mesh_shader : enable\n"
<< "\n"
<< "layout (local_size_x=1) in;\n"
<< "layout (triangles) out;\n"
<< "layout (max_vertices=3, max_primitives=1) out;\n"
<< "\n"
<< "perprimitiveNV out gl_MeshPerPrimitiveNV {\n"
<< " int gl_PrimitiveID;\n"
<< "} gl_MeshPrimitivesNV[];\n"
<< "\n"
<< "void main ()\n"
<< "{\n"
<< " gl_PrimitiveCountNV = 1u;\n"
<< "\n"
<< " gl_PrimitiveIndicesNV[0] = 0;\n"
<< " gl_PrimitiveIndicesNV[1] = 1;\n"
<< " gl_PrimitiveIndicesNV[2] = 2;\n"
<< "\n"
<< " gl_MeshVerticesNV[0].gl_Position = vec4(-1.0, -1.0, 0.0, 1.0);\n"
<< " gl_MeshVerticesNV[1].gl_Position = vec4(-1.0, 3.0, 0.0, 1.0);\n"
<< " gl_MeshVerticesNV[2].gl_Position = vec4( 3.0, -1.0, 0.0, 1.0);\n"
<< "\n"
// Sets an arbitrary primitive id.
<< " gl_MeshPrimitivesNV[0].gl_PrimitiveID = 1629198956;\n"
<< "}\n"
;
programCollection.glslSources.add("mesh") << glu::MeshSource(mesh.str());
}
// Frag shader.
if (m_glslFrag)
{
std::ostringstream frag;
frag
<< "#version 460\n"
<< "#extension GL_NV_mesh_shader : enable\n"
<< "\n"
<< "layout (location=0) out vec4 outColor;\n"
<< "\n"
<< "void main ()\n"
<< "{\n"
// Checks the primitive id matches.
<< " outColor = ((gl_PrimitiveID == 1629198956) ? vec4(0.0, 0.0, 1.0, 1.0) : vec4(0.0, 0.0, 0.0, 1.0));\n"
<< "}\n"
;
programCollection.glslSources.add("frag") << glu::FragmentSource(frag.str());
}
else
{
// This is the same shader as above, but OpCapability Geometry has been replaced by OpCapability MeshShadingNV in order to
// access gl_PrimitiveID. This also needs the SPV_NV_mesh_shader extension.
std::ostringstream frag;
frag
<< "; Version: 1.0\n"
<< "; Generator: Khronos Glslang Reference Front End; 10\n"
<< "; Bound: 24\n"
<< "; Schema: 0\n"
<< " OpCapability Shader\n"
// Manual change in these lines.
//<< " OpCapability Geometry\n"
<< " OpCapability MeshShadingNV\n"
<< " OpExtension \"SPV_NV_mesh_shader\"\n"
<< " %1 = OpExtInstImport \"GLSL.std.450\"\n"
<< " OpMemoryModel Logical GLSL450\n"
<< " OpEntryPoint Fragment %4 \"main\" %9 %12\n"
<< " OpExecutionMode %4 OriginUpperLeft\n"
<< " OpDecorate %9 Location 0\n"
<< " OpDecorate %12 Flat\n"
<< " OpDecorate %12 BuiltIn PrimitiveId\n"
<< " %2 = OpTypeVoid\n"
<< " %3 = OpTypeFunction %2\n"
<< " %6 = OpTypeFloat 32\n"
<< " %7 = OpTypeVector %6 4\n"
<< " %8 = OpTypePointer Output %7\n"
<< " %9 = OpVariable %8 Output\n"
<< "%10 = OpTypeInt 32 1\n"
<< "%11 = OpTypePointer Input %10\n"
<< "%12 = OpVariable %11 Input\n"
<< "%14 = OpConstant %10 1629198956\n"
<< "%15 = OpTypeBool\n"
<< "%17 = OpConstant %6 0\n"
<< "%18 = OpConstant %6 1\n"
<< "%19 = OpConstantComposite %7 %17 %17 %18 %18\n"
<< "%20 = OpConstantComposite %7 %17 %17 %17 %18\n"
<< "%21 = OpTypeVector %15 4\n"
<< " %4 = OpFunction %2 None %3\n"
<< " %5 = OpLabel\n"
<< "%13 = OpLoad %10 %12\n"
<< "%16 = OpIEqual %15 %13 %14\n"
<< "%22 = OpCompositeConstruct %21 %16 %16 %16 %16\n"
<< "%23 = OpSelect %7 %22 %19 %20\n"
<< " OpStore %9 %23\n"
<< " OpReturn\n"
<< " OpFunctionEnd\n"
;
programCollection.spirvAsmSources.add("frag") << frag.str();
}
}
void PrimitiveIdCase::checkSupport (Context& context) const
{
MeshShaderBuiltinCase::checkSupport(context);
// Fragment shader in GLSL means glslang will use the Geometry capability due to gl_PrimitiveID.
if (m_glslFrag)
context.requireDeviceCoreFeature(DEVICE_CORE_FEATURE_GEOMETRY_SHADER);
}
TestInstance* PrimitiveIdCase::createInstance (Context& context) const
{
const ColorVec expectedColors (1u, tcu::Vec4(0.0f, 0.0f, 1.0f, 1.0f));
const IterationParams iterationParams =
{
getDefaultExtent(), // VkExtent2D colorExtent;
1u, // uint32_t numLayers;
getDefaultDrawCommands(), // DrawCommandVec drawArgs;
false, // bool indirect;
{}, // ViewportVec viewports; // If empty, a single default viewport is used.
};
return new FullScreenColorInstance(context, iterationParams, expectedColors);
}
// Layer builtin case.
class LayerCase : public MeshShaderBuiltinCase
{
public:
LayerCase (tcu::TestContext& testCtx, const std::string& name, const std::string& description, bool shareVertices)
: MeshShaderBuiltinCase (testCtx, name, description, false/*taskNeeded*/)
, m_shareVertices (shareVertices)
{}
virtual ~LayerCase (void) {}
void initPrograms (vk::SourceCollections& programCollection) const override;
void checkSupport (Context& context) const override;
TestInstance* createInstance (Context& context) const override;
static constexpr uint32_t kNumLayers = 4u;
protected:
const bool m_shareVertices;
};
void LayerCase::initPrograms (vk::SourceCollections& programCollection) const
{
const auto localSize = (m_shareVertices ? kNumLayers : 1u);
const auto numPrimitives = (m_shareVertices ? kNumLayers : 1u);
const auto layerNumber = (m_shareVertices ? "gl_LocalInvocationIndex" : "gl_WorkGroupID.x");
// One layer per local invocation or work group (shared vertices or not, respectively).
{
std::ostringstream mesh;
mesh
<< "#version 460\n"
<< "#extension GL_NV_mesh_shader : enable\n"
<< "\n"
<< "layout (local_size_x=" << localSize << ") in;\n"
<< "layout (triangles) out;\n"
<< "layout (max_vertices=3, max_primitives=" << numPrimitives << ") out;\n"
<< "\n"
<< "perprimitiveNV out gl_MeshPerPrimitiveNV {\n"
<< " int gl_Layer;\n"
<< "} gl_MeshPrimitivesNV[];\n"
<< "\n"
<< "void main ()\n"
<< "{\n"
<< " gl_PrimitiveCountNV = " << numPrimitives << ";\n"
<< "\n"
<< " if (gl_LocalInvocationIndex == 0u)\n"
<< " {\n"
<< " gl_MeshVerticesNV[0].gl_Position = vec4(-1.0, -1.0, 0.0, 1.0);\n"
<< " gl_MeshVerticesNV[1].gl_Position = vec4(-1.0, 3.0, 0.0, 1.0);\n"
<< " gl_MeshVerticesNV[2].gl_Position = vec4( 3.0, -1.0, 0.0, 1.0);\n"
<< " }\n"
<< "\n"
<< " const uint baseIndex = gl_LocalInvocationIndex * 3u;\n"
<< " gl_PrimitiveIndicesNV[baseIndex + 0] = 0;\n"
<< " gl_PrimitiveIndicesNV[baseIndex + 1] = 1;\n"
<< " gl_PrimitiveIndicesNV[baseIndex + 2] = 2;\n"
<< "\n"
<< " gl_MeshPrimitivesNV[gl_LocalInvocationIndex].gl_Layer = int(" << layerNumber << ");\n"
<< "}\n"
;
programCollection.glslSources.add("mesh") << glu::MeshSource(mesh.str());
}
// Fragment shader chooses one color per layer.
{
std::ostringstream frag;
frag
<< "#version 460\n"
<< "#extension GL_NV_mesh_shader : enable\n"
<< "\n"
<< "layout (location=0) out vec4 outColor;\n"
<< "\n"
<< "vec4 colors[" << kNumLayers << "] = vec4[](\n"
<< " vec4(0.0, 0.0, 1.0, 1.0),\n"
<< " vec4(1.0, 0.0, 1.0, 1.0),\n"
<< " vec4(0.0, 1.0, 1.0, 1.0),\n"
<< " vec4(1.0, 1.0, 0.0, 1.0)\n"
<< ");\n"
<< "\n"
<< "void main ()\n"
<< "{\n"
<< " outColor = colors[gl_Layer];\n"
<< "}\n"
;
programCollection.glslSources.add("frag") << glu::FragmentSource(frag.str());
}
}
void LayerCase::checkSupport (Context& context) const
{
MeshShaderBuiltinCase::checkSupport(context);
if (!context.contextSupports(vk::ApiVersion(1u, 2u, 0u)))
context.requireDeviceFunctionality("VK_EXT_shader_viewport_index_layer");
else
{
const auto& features = context.getDeviceVulkan12Features();
if (!features.shaderOutputLayer)
TCU_THROW(NotSupportedError, "shaderOutputLayer feature not supported");
}
}
TestInstance* LayerCase::createInstance (Context& context) const
{
ColorVec expectedColors;
expectedColors.reserve(kNumLayers);
expectedColors.push_back(tcu::Vec4(0.0, 0.0, 1.0, 1.0));
expectedColors.push_back(tcu::Vec4(1.0, 0.0, 1.0, 1.0));
expectedColors.push_back(tcu::Vec4(0.0, 1.0, 1.0, 1.0));
expectedColors.push_back(tcu::Vec4(1.0, 1.0, 0.0, 1.0));
const auto numWorkGroups = (m_shareVertices ? 1u : kNumLayers);
const IterationParams iterationParams =
{
getDefaultExtent(), // VkExtent2D colorExtent;
kNumLayers, // uint32_t numLayers;
getDefaultDrawCommands(numWorkGroups), // DrawCommandVec drawArgs;
false, // bool indirect;
{}, // ViewportVec viewports; // If empty, a single default viewport is used.
};
return new FullScreenColorInstance(context, iterationParams, expectedColors);
}
// ViewportIndex builtin case.
class ViewportIndexCase : public MeshShaderBuiltinCase
{
public:
ViewportIndexCase (tcu::TestContext& testCtx, const std::string& name, const std::string& description, bool shareVertices)
: MeshShaderBuiltinCase (testCtx, name, description, false/*taskNeeded*/)
, m_shareVertices (shareVertices)
{}
virtual ~ViewportIndexCase (void) {}
void initPrograms (vk::SourceCollections& programCollection) const override;
void checkSupport (Context& context) const override;
TestInstance* createInstance (Context& context) const override;
static constexpr uint32_t kQuadrants = 4u;
protected:
const bool m_shareVertices;
};
void ViewportIndexCase::initPrograms (vk::SourceCollections& programCollection) const
{
const auto localSize = (m_shareVertices ? kQuadrants : 1u);
const auto numPrimitives = (m_shareVertices ? kQuadrants : 1u);
const auto viewportIndex = (m_shareVertices ? "gl_LocalInvocationIndex" : "gl_WorkGroupID.x");
// One viewport per local invocation or work group (sharing vertices or not, respectively).
{
std::ostringstream mesh;
mesh
<< "#version 460\n"
<< "#extension GL_NV_mesh_shader : enable\n"
<< "\n"
<< "layout (local_size_x=" << localSize << ") in;\n"
<< "layout (triangles) out;\n"
<< "layout (max_vertices=3, max_primitives=" << numPrimitives << ") out;\n"
<< "\n"
<< "perprimitiveNV out gl_MeshPerPrimitiveNV {\n"
<< " int gl_ViewportIndex;\n"
<< "} gl_MeshPrimitivesNV[];\n"
<< "\n"
<< "void main ()\n"
<< "{\n"
<< " gl_PrimitiveCountNV = " << numPrimitives << ";\n"
<< "\n"
<< " if (gl_LocalInvocationIndex == 0u)\n"
<< " {\n"
<< " gl_MeshVerticesNV[0].gl_Position = vec4(-1.0, -1.0, 0.0, 1.0);\n"
<< " gl_MeshVerticesNV[1].gl_Position = vec4(-1.0, 3.0, 0.0, 1.0);\n"
<< " gl_MeshVerticesNV[2].gl_Position = vec4( 3.0, -1.0, 0.0, 1.0);\n"
<< " }\n"
<< "\n"
<< " const uint baseIndex = gl_LocalInvocationIndex * 3u;\n"
<< " gl_PrimitiveIndicesNV[baseIndex + 0] = 0;\n"
<< " gl_PrimitiveIndicesNV[baseIndex + 1] = 1;\n"
<< " gl_PrimitiveIndicesNV[baseIndex + 2] = 2;\n"
<< "\n"
<< " gl_MeshPrimitivesNV[gl_LocalInvocationIndex].gl_ViewportIndex = int(" << viewportIndex << ");\n"
<< "}\n"
;
programCollection.glslSources.add("mesh") << glu::MeshSource(mesh.str());
}
// Fragment shader chooses one color per viewport.
{
std::ostringstream frag;
frag
<< "#version 460\n"
<< "#extension GL_NV_mesh_shader : enable\n"
<< "\n"
<< "layout (location=0) out vec4 outColor;\n"
<< "\n"
<< "vec4 colors[" << kQuadrants << "] = vec4[](\n"
<< " vec4(0.0, 0.0, 1.0, 1.0),\n"
<< " vec4(1.0, 0.0, 1.0, 1.0),\n"
<< " vec4(0.0, 1.0, 1.0, 1.0),\n"
<< " vec4(1.0, 1.0, 0.0, 1.0)\n"
<< ");\n"
<< "\n"
<< "void main ()\n"
<< "{\n"
<< " outColor = colors[gl_ViewportIndex];\n"
<< "}\n"
;
programCollection.glslSources.add("frag") << glu::FragmentSource(frag.str());
}
}
void ViewportIndexCase::checkSupport (Context& context) const
{
MeshShaderBuiltinCase::checkSupport(context);
context.requireDeviceCoreFeature(DEVICE_CORE_FEATURE_MULTI_VIEWPORT);
if (!context.contextSupports(vk::ApiVersion(1u, 2u, 0u)))
context.requireDeviceFunctionality("VK_EXT_shader_viewport_index_layer");
else
{
const auto& features = context.getDeviceVulkan12Features();
if (!features.shaderOutputViewportIndex)
TCU_THROW(NotSupportedError, "shaderOutputViewportIndex feature not supported");
}
}
TestInstance* ViewportIndexCase::createInstance (Context& context) const
{
const auto extent = getDefaultExtent();
DE_ASSERT(extent.width > 0u && extent.width % 2u == 0u);
DE_ASSERT(extent.height > 0u && extent.height % 2u == 0u);
const auto halfWidth = static_cast<float>(extent.width / 2u);
const auto halfHeight = static_cast<float>(extent.height / 2u);
const auto topLeft = tcu::Vec4(0.0, 0.0, 1.0, 1.0);
const auto topRight = tcu::Vec4(1.0, 0.0, 1.0, 1.0);
const auto bottomLeft = tcu::Vec4(0.0, 1.0, 1.0, 1.0);
const auto bottomRight = tcu::Vec4(1.0, 1.0, 0.0, 1.0);
ViewportVec viewports;
viewports.reserve(kQuadrants);
viewports.emplace_back(makeViewport(0.0f, 0.0f, halfWidth, halfHeight, 0.0f, 1.0f));
viewports.emplace_back(makeViewport(halfWidth, 0.0f, halfWidth, halfHeight, 0.0f, 1.0f));
viewports.emplace_back(makeViewport(0.0f, halfHeight, halfWidth, halfHeight, 0.0f, 1.0f));
viewports.emplace_back(makeViewport(halfWidth, halfHeight, halfWidth, halfHeight, 0.0f, 1.0f));
const auto numWorkGroups = (m_shareVertices ? 1u : kQuadrants);
const IterationParams iterationParams =
{
getDefaultExtent(), // VkExtent2D colorExtent;
1u, // uint32_t numLayers;
getDefaultDrawCommands(numWorkGroups), // DrawCommandVec drawArgs;
false, // bool indirect;
std::move(viewports), // ViewportVec viewports;
};
return new QuadrantsInstance(context, iterationParams, topLeft, topRight, bottomLeft, bottomRight);
}
// Position builtin case.
class PositionCase : public MeshShaderBuiltinCase
{
public:
PositionCase (tcu::TestContext& testCtx, const std::string& name, const std::string& description)
: MeshShaderBuiltinCase (testCtx, name, description, false/*taskNeeded*/)
{}
virtual ~PositionCase (void) {}
void initPrograms (vk::SourceCollections& programCollection) const override;
TestInstance* createInstance (Context& context) const override;
};
void PositionCase::initPrograms (vk::SourceCollections& programCollection) const
{
// Mesh shader: emit single triangle around the center of the top left pixel.
{
const auto extent = getDefaultExtent();
const auto fWidth = static_cast<float>(extent.width);
const auto fHeight = static_cast<float>(extent.height);
const auto pxWidth = 2.0f / fWidth;
const auto pxHeight = 2.0f / fHeight;
const auto halfXPix = pxWidth / 2.0f;
const auto halfYPix = pxHeight / 2.0f;
// Center of top left pixel.
const auto x = -1.0f + halfXPix;
const auto y = -1.0f + halfYPix;
std::ostringstream mesh;
mesh
<< "#version 460\n"
<< "#extension GL_NV_mesh_shader : enable\n"
<< "\n"
<< "layout (local_size_x=1) in;\n"
<< "layout (triangles) out;\n"
<< "layout (max_vertices=3, max_primitives=1) out;\n"
<< "\n"
<< "void main ()\n"
<< "{\n"
<< " gl_PrimitiveCountNV = 1u;\n"
<< "\n"
<< " gl_PrimitiveIndicesNV[0] = 0;\n"
<< " gl_PrimitiveIndicesNV[1] = 1;\n"
<< " gl_PrimitiveIndicesNV[2] = 2;\n"
<< "\n"
<< " gl_MeshVerticesNV[0].gl_Position = vec4(" << (x - halfXPix) << ", " << (y + halfYPix) << ", 0.0, 1.0);\n"
<< " gl_MeshVerticesNV[1].gl_Position = vec4(" << (x + halfXPix) << ", " << (y + halfYPix) << ", 0.0, 1.0);\n"
<< " gl_MeshVerticesNV[2].gl_Position = vec4(" << x << ", " << (y - halfYPix) << ", 0.0, 1.0);\n"
<< "}\n"
;
programCollection.glslSources.add("mesh") << glu::MeshSource(mesh.str());
}
// Basic fragment shader.
{
const auto frag = getBasicFragShader();
programCollection.glslSources.add("frag") << glu::FragmentSource(frag);
}
}
TestInstance* PositionCase::createInstance (Context& context) const
{
const IterationParams iterationParams =
{
getDefaultExtent(), // VkExtent2D colorExtent;
1u, // uint32_t numLayers;
getDefaultDrawCommands(), // DrawCommandVec drawArgs;
false, // bool indirect;
{}, // ViewportVec viewports; // If empty, a single default viewport is used.
};
// Must match the shader.
PixelMap pixelMap;
pixelMap[tcu::IVec2(0, 0)] = tcu::Vec4(0.0f, 0.0f, 1.0f, 1.0f);
const PixelVerifierParams verifierParams =
{
tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f), // const tcu::Vec4 background;
std::move(pixelMap), // const PixelMap pixelMap;
};
return new PixelsInstance(context, iterationParams, verifierParams);
}
// PointSize builtin case.
class PointSizeCase : public MeshShaderBuiltinCase
{
public:
PointSizeCase (tcu::TestContext& testCtx, const std::string& name, const std::string& description)
: MeshShaderBuiltinCase (testCtx, name, description, false/*taskNeeded*/)
{}
virtual ~PointSizeCase (void) {}
void initPrograms (vk::SourceCollections& programCollection) const override;
TestInstance* createInstance (Context& context) const override;
void checkSupport (Context& context) const override;
static constexpr float kPointSize = 4.0f;
};
void PointSizeCase::initPrograms (vk::SourceCollections& programCollection) const
{
// Mesh shader: large point covering the top left quadrant.
{
std::ostringstream mesh;
mesh
<< "#version 460\n"
<< "#extension GL_NV_mesh_shader : enable\n"
<< "\n"
<< "layout (local_size_x=1) in;\n"
<< "layout (points) out;\n"
<< "layout (max_vertices=1, max_primitives=1) out;\n"
<< "\n"
<< "void main ()\n"
<< "{\n"
<< " gl_PrimitiveCountNV = 1u;\n"
<< " gl_PrimitiveIndicesNV[0] = 0;\n"
<< " gl_MeshVerticesNV[0].gl_Position = vec4(-0.5, -0.5, 0.0, 1.0);\n"
<< " gl_MeshVerticesNV[0].gl_PointSize = " << kPointSize << ";\n"
<< "}\n"
;
programCollection.glslSources.add("mesh") << glu::MeshSource(mesh.str());
}
// Basic fragment shader.
{
const auto frag = getBasicFragShader();
programCollection.glslSources.add("frag") << glu::FragmentSource(frag);
}
}
TestInstance* PointSizeCase::createInstance (Context& context) const
{
const IterationParams iterationParams =
{
getDefaultExtent(), // VkExtent2D colorExtent;
1u, // uint32_t numLayers;
getDefaultDrawCommands(), // DrawCommandVec drawArgs;
false, // bool indirect;
{}, // ViewportVec viewports; // If empty, a single default viewport is used.
};
// Must match the shader.
const tcu::Vec4 black (0.0f, 0.0f, 0.0f, 1.0f);
const tcu::Vec4 blue (0.0f, 0.0f, 1.0f, 1.0f);
return new QuadrantsInstance(context, iterationParams, blue, black, black, black);
}
void PointSizeCase::checkSupport (Context& context) const
{
MeshShaderBuiltinCase::checkSupport(context);
context.requireDeviceCoreFeature(DEVICE_CORE_FEATURE_LARGE_POINTS);
const auto& properties = context.getDeviceProperties();
if (kPointSize < properties.limits.pointSizeRange[0] || kPointSize > properties.limits.pointSizeRange[1])
TCU_THROW(NotSupportedError, "Required point size outside point size range");
}
// ClipDistance builtin case.
class ClipDistanceCase : public MeshShaderBuiltinCase
{
public:
ClipDistanceCase (tcu::TestContext& testCtx, const std::string& name, const std::string& description)
: MeshShaderBuiltinCase (testCtx, name, description, false/*taskNeeded*/)
{}
virtual ~ClipDistanceCase (void) {}
void initPrograms (vk::SourceCollections& programCollection) const override;
TestInstance* createInstance (Context& context) const override;
void checkSupport (Context& context) const override;
};
void ClipDistanceCase::initPrograms (vk::SourceCollections& programCollection) const
{
// Mesh shader: full-screen quad using different clip distances.
{
std::ostringstream mesh;
mesh
<< "#version 460\n"
<< "#extension GL_NV_mesh_shader : enable\n"
<< "\n"
<< "layout (local_size_x=1) in;\n"
<< "layout (triangles) out;\n"
<< "layout (max_vertices=4, max_primitives=2) out;\n"
<< "\n"
<< "out gl_MeshPerVertexNV {\n"
<< " vec4 gl_Position;\n"
<< " float gl_ClipDistance[2];\n"
<< "} gl_MeshVerticesNV[];\n"
<< "\n"
<< "void main ()\n"
<< "{\n"
<< " gl_PrimitiveCountNV = 2u;\n"
<< "\n"
<< " gl_PrimitiveIndicesNV[0] = 0;\n"
<< " gl_PrimitiveIndicesNV[1] = 1;\n"
<< " gl_PrimitiveIndicesNV[2] = 2;\n"
<< " gl_PrimitiveIndicesNV[3] = 1;\n"
<< " gl_PrimitiveIndicesNV[4] = 3;\n"
<< " gl_PrimitiveIndicesNV[5] = 2;\n"
<< "\n"
<< " gl_MeshVerticesNV[0].gl_Position = vec4(-1.0, -1.0, 0.0, 1.0);\n"
<< " gl_MeshVerticesNV[1].gl_Position = vec4(-1.0, 1.0, 0.0, 1.0);\n"
<< " gl_MeshVerticesNV[2].gl_Position = vec4( 1.0, -1.0, 0.0, 1.0);\n"
<< " gl_MeshVerticesNV[3].gl_Position = vec4( 1.0, 1.0, 0.0, 1.0);\n"
<< "\n"
// The first clip plane keeps the left half of the frame buffer.
<< " gl_MeshVerticesNV[0].gl_ClipDistance[0] = 1.0;\n"
<< " gl_MeshVerticesNV[1].gl_ClipDistance[0] = 1.0;\n"
<< " gl_MeshVerticesNV[2].gl_ClipDistance[0] = -1.0;\n"
<< " gl_MeshVerticesNV[3].gl_ClipDistance[0] = -1.0;\n"
<< "\n"
// The second clip plane keeps the top half of the frame buffer.
<< " gl_MeshVerticesNV[0].gl_ClipDistance[1] = 1.0;\n"
<< " gl_MeshVerticesNV[1].gl_ClipDistance[1] = -1.0;\n"
<< " gl_MeshVerticesNV[2].gl_ClipDistance[1] = 1.0;\n"
<< " gl_MeshVerticesNV[3].gl_ClipDistance[1] = -1.0;\n"
<< "}\n"
;
programCollection.glslSources.add("mesh") << glu::MeshSource(mesh.str());
}
// Fragment shader chooses a constant color.
{
std::ostringstream frag;
frag
<< "#version 460\n"
<< "#extension GL_NV_mesh_shader : enable\n"
<< "\n"
<< "layout (location=0) out vec4 outColor;\n"
<< "\n"
<< "void main ()\n"
<< "{\n"
// White color should not actually be used, as those fragments are supposed to be discarded.
<< " outColor = ((gl_ClipDistance[0] >= 0.0 && gl_ClipDistance[1] >= 0.0) ? vec4(0.0, 0.0, 1.0, 1.0) : vec4(1.0, 1.0, 1.0, 1.0));\n"
<< "}\n"
;
programCollection.glslSources.add("frag") << glu::FragmentSource(frag.str());
}
}
TestInstance* ClipDistanceCase::createInstance (Context& context) const
{
const IterationParams iterationParams =
{
getDefaultExtent(), // VkExtent2D colorExtent;
1u, // uint32_t numLayers;
getDefaultDrawCommands(), // DrawCommandVec drawArgs;
false, // bool indirect;
{}, // ViewportVec viewports; // If empty, a single default viewport is used.
};
// Must match the shader.
const tcu::Vec4 black (0.0f, 0.0f, 0.0f, 1.0f);
const tcu::Vec4 blue (0.0f, 0.0f, 1.0f, 1.0f);
return new QuadrantsInstance(context, iterationParams, blue, black, black, black);
}
void ClipDistanceCase::checkSupport (Context& context) const
{
MeshShaderBuiltinCase::checkSupport(context);
context.requireDeviceCoreFeature(DEVICE_CORE_FEATURE_SHADER_CLIP_DISTANCE);
}
// CullDistance builtin case.
class CullDistanceCase : public MeshShaderBuiltinCase
{
public:
CullDistanceCase (tcu::TestContext& testCtx, const std::string& name, const std::string& description)
: MeshShaderBuiltinCase (testCtx, name, description, false/*taskNeeded*/)
{}
virtual ~CullDistanceCase (void) {}
void initPrograms (vk::SourceCollections& programCollection) const override;
TestInstance* createInstance (Context& context) const override;
void checkSupport (Context& context) const override;
};
void CullDistanceCase::initPrograms (vk::SourceCollections& programCollection) const
{
// Mesh shader: two quads covering the whole screen, one on top of the other.
// Use cull distances to discard the bottom quad.
// Use cull distances to paint the top one in two colors: blue on the left, white on the right.
{
std::ostringstream mesh;
mesh
<< "#version 460\n"
<< "#extension GL_NV_mesh_shader : enable\n"
<< "\n"
<< "layout (local_size_x=1) in;\n"
<< "layout (triangles) out;\n"
<< "layout (max_vertices=6, max_primitives=4) out;\n"
<< "\n"
<< "out gl_MeshPerVertexNV {\n"
<< " vec4 gl_Position;\n"
<< " float gl_CullDistance[2];\n"
<< "} gl_MeshVerticesNV[];\n"
<< "\n"
<< "void main ()\n"
<< "{\n"
<< " gl_PrimitiveCountNV = 4u;\n"
<< "\n"
<< " gl_PrimitiveIndicesNV[0] = 0;\n"
<< " gl_PrimitiveIndicesNV[1] = 1;\n"
<< " gl_PrimitiveIndicesNV[2] = 3;\n"
<< " gl_PrimitiveIndicesNV[3] = 1;\n"
<< " gl_PrimitiveIndicesNV[4] = 4;\n"
<< " gl_PrimitiveIndicesNV[5] = 3;\n"
<< " gl_PrimitiveIndicesNV[6] = 1;\n"
<< " gl_PrimitiveIndicesNV[7] = 2;\n"
<< " gl_PrimitiveIndicesNV[8] = 4;\n"
<< " gl_PrimitiveIndicesNV[9] = 2;\n"
<< " gl_PrimitiveIndicesNV[10] = 5;\n"
<< " gl_PrimitiveIndicesNV[11] = 4;\n"
<< "\n"
<< " gl_MeshVerticesNV[0].gl_Position = vec4(-1.0, -1.0, 0.0, 1.0);\n"
<< " gl_MeshVerticesNV[1].gl_Position = vec4(-1.0, 0.0, 0.0, 1.0);\n"
<< " gl_MeshVerticesNV[2].gl_Position = vec4(-1.0, 1.0, 0.0, 1.0);\n"
<< " gl_MeshVerticesNV[3].gl_Position = vec4( 1.0, -1.0, 0.0, 1.0);\n"
<< " gl_MeshVerticesNV[4].gl_Position = vec4( 1.0, 0.0, 0.0, 1.0);\n"
<< " gl_MeshVerticesNV[5].gl_Position = vec4( 1.0, 1.0, 0.0, 1.0);\n"
<< "\n"
// The first cull plane discards the bottom quad
<< " gl_MeshVerticesNV[0].gl_CullDistance[0] = 1.0;\n"
<< " gl_MeshVerticesNV[1].gl_CullDistance[0] = -1.0;\n"
<< " gl_MeshVerticesNV[2].gl_CullDistance[0] = -2.0;\n"
<< " gl_MeshVerticesNV[3].gl_CullDistance[0] = 1.0;\n"
<< " gl_MeshVerticesNV[4].gl_CullDistance[0] = -1.0;\n"
<< " gl_MeshVerticesNV[5].gl_CullDistance[0] = -2.0;\n"
<< "\n"
// The second cull plane helps paint left and right different.
<< " gl_MeshVerticesNV[0].gl_CullDistance[1] = 1.0;\n"
<< " gl_MeshVerticesNV[1].gl_CullDistance[1] = 1.0;\n"
<< " gl_MeshVerticesNV[2].gl_CullDistance[1] = 1.0;\n"
<< " gl_MeshVerticesNV[3].gl_CullDistance[1] = -1.0;\n"
<< " gl_MeshVerticesNV[4].gl_CullDistance[1] = -1.0;\n"
<< " gl_MeshVerticesNV[5].gl_CullDistance[1] = -1.0;\n"
<< "}\n"
;
programCollection.glslSources.add("mesh") << glu::MeshSource(mesh.str());
}
// Fragment shader chooses color based on the second cull distance.
{
std::ostringstream frag;
frag
<< "#version 460\n"
<< "#extension GL_NV_mesh_shader : enable\n"
<< "\n"
<< "layout (location=0) out vec4 outColor;\n"
<< "\n"
<< "void main ()\n"
<< "{\n"
<< " outColor = ((gl_CullDistance[1] >= 0.0) ? vec4(0.0, 0.0, 1.0, 1.0) : vec4(1.0, 1.0, 1.0, 1.0));\n"
<< "}\n"
;
programCollection.glslSources.add("frag") << glu::FragmentSource(frag.str());
}
}
TestInstance* CullDistanceCase::createInstance (Context& context) const
{
const IterationParams iterationParams =
{
getDefaultExtent(), // VkExtent2D colorExtent;
1u, // uint32_t numLayers;
getDefaultDrawCommands(), // DrawCommandVec drawArgs;
false, // bool indirect;
{}, // ViewportVec viewports; // If empty, a single default viewport is used.
};
// Must match the shader.
const tcu::Vec4 black (0.0f, 0.0f, 0.0f, 1.0f);
const tcu::Vec4 blue (0.0f, 0.0f, 1.0f, 1.0f);
const tcu::Vec4 white (1.0f, 1.0f, 1.0f, 1.0f);
return new QuadrantsInstance(context, iterationParams, blue, white, black, black);
}
void CullDistanceCase::checkSupport (Context& context) const
{
MeshShaderBuiltinCase::checkSupport(context);
context.requireDeviceCoreFeature(DEVICE_CORE_FEATURE_SHADER_CULL_DISTANCE);
}
// Generates statements to draw a triangle around the given pixel number, knowing the framebuffer width (len).
// Supposes the height of the framebuffer is 1.
std::string triangleForPixel(const std::string& pixel, const std::string& len, const std::string& baseIndex)
{
std::ostringstream statements;
statements
<< " const float imgWidth = float(" << len << ");\n"
<< " const float pixWidth = (2.0 / imgWidth);\n"
<< " const float halfPix = (pixWidth / 2.0);\n"
<< " const float xCenter = (((float(" << pixel << ") + 0.5) / imgWidth) * 2.0 - 1.0);\n"
<< " const float xLeft = (xCenter - halfPix);\n"
<< " const float xRight = (xCenter + halfPix);\n"
<< " const uvec3 indices = uvec3(" << baseIndex << " + 0, " << baseIndex << " + 1, " << baseIndex << " + 2);\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(xLeft, 0.5, 0.0, 1.0);\n"
<< " gl_MeshVerticesNV[indices.y].gl_Position = vec4(xRight, 0.5, 0.0, 1.0);\n"
<< " gl_MeshVerticesNV[indices.z].gl_Position = vec4(xCenter, -0.5, 0.0, 1.0);\n"
;
return statements.str();
}
// WorkGroupID builtin case.
class WorkGroupIdCase : public MeshShaderBuiltinCase
{
public:
WorkGroupIdCase (tcu::TestContext& testCtx, const std::string& name, const std::string& description, bool taskNeeded)
: MeshShaderBuiltinCase (testCtx, name, description, taskNeeded)
, m_extent (getLinearExtent())
{}
virtual ~WorkGroupIdCase (void) {}
void initPrograms (vk::SourceCollections& programCollection) const override;
TestInstance* createInstance (Context& context) const override;
protected:
const VkExtent2D m_extent;
};
void WorkGroupIdCase::initPrograms (vk::SourceCollections& programCollection) const
{
const std::string taskDataDecl =
"taskNV TaskData {\n"
" uint id;\n"
" uint size;\n"
"} td;\n"
;
// Mesh shader: each work group fills one pixel.
{
const std::string pixel = (m_taskNeeded ? "td.id" : "gl_WorkGroupID.x" );
const std::string len = (m_taskNeeded ? "td.size" : de::toString(m_extent.width) );
std::ostringstream mesh;
mesh
<< "#version 460\n"
<< "#extension GL_NV_mesh_shader : enable\n"
<< "\n"
<< "layout (local_size_x=1) in;\n"
<< "layout (triangles) out;\n"
<< "layout (max_vertices=3, max_primitives=1) out;\n"
<< "\n"
<< (m_taskNeeded ? ("in " + taskDataDecl) : "")
<< "\n"
<< "void main ()\n"
<< "{\n"
<< " gl_PrimitiveCountNV = 1u;\n"
<< "\n"
<< triangleForPixel(pixel, len, "0")
<< "}\n"
;
programCollection.glslSources.add("mesh") << glu::MeshSource(mesh.str());
}
if (m_taskNeeded)
{
std::ostringstream task;
task
<< "#version 460\n"
<< "#extension GL_NV_mesh_shader : enable\n"
<< "\n"
<< "layout (local_size_x=1) in;\n"
<< "\n"
<< "out " << taskDataDecl
<< "\n"
<< "void main ()\n"
<< "{\n"
<< " gl_TaskCountNV = 1u;\n"
<< " td.id = gl_WorkGroupID.x;\n"
<< " td.size = " << m_extent.width << ";\n"
<< "}\n"
;
programCollection.glslSources.add("task") << glu::TaskSource(task.str());
}
// Basic fragment shader.
{
const auto frag = getBasicFragShader();
programCollection.glslSources.add("frag") << glu::FragmentSource(frag);
}
}
TestInstance* WorkGroupIdCase::createInstance (Context& context) const
{
// Must match the shader.
const ColorVec expectedColors (1u, tcu::Vec4(0.0, 0.0, 1.0, 1.0));
const IterationParams iterationParams =
{
m_extent, // VkExtent2D colorExtent;
1u, // uint32_t numLayers;
getDefaultDrawCommands(m_extent.width), // DrawCommandVec drawArgs;
false, // bool indirect;
{}, // ViewportVec viewports; // If empty, a single default viewport is used.
};
return new FullScreenColorInstance(context, iterationParams, expectedColors);
}
// Variable to use.
enum class LocalInvocation { ID=0, INDEX };
// LocalInvocationId and LocalInvocationIndex builtin cases. These are also used to test WorkGroupSize.
class LocalInvocationCase : public MeshShaderBuiltinCase
{
public:
LocalInvocationCase (tcu::TestContext& testCtx, const std::string& name, const std::string& description, bool taskNeeded, LocalInvocation variable)
: MeshShaderBuiltinCase (testCtx, name, description, taskNeeded)
, m_extent (getLinearExtent())
, m_variable (variable)
{}
virtual ~LocalInvocationCase (void) {}
void initPrograms (vk::SourceCollections& programCollection) const override;
TestInstance* createInstance (Context& context) const override;
protected:
const VkExtent2D m_extent;
const LocalInvocation m_variable;
};
void LocalInvocationCase::initPrograms (vk::SourceCollections& programCollection) const
{
// Invocation index to use.
const std::string localIndex = ((m_variable == LocalInvocation::ID) ? "gl_LocalInvocationID.x" : "gl_LocalInvocationIndex");
// Task data.
std::ostringstream taskDataDecl;
taskDataDecl
<< "taskNV TaskData {\n"
// indexNumber[x] == x
<< " uint indexNumber[" << m_extent.width << "];\n"
<< " uint size;\n"
<< "} td;\n"
;
const auto taskDataDeclStr = taskDataDecl.str();
// Mesh shader: each work group fills one pixel.
{
const std::string pixel = (m_taskNeeded ? "td.indexNumber[gl_WorkGroupID.x]" : localIndex);
const std::string len = (m_taskNeeded ? "td.size" : "gl_WorkGroupSize.x");
const auto localSize = (m_taskNeeded ? 1u : m_extent.width);
const auto maxVert = localSize * 3u;
const std::string baseIndex = (m_taskNeeded ? "0" : "(" + localIndex + " * 3u)");
std::ostringstream mesh;
mesh
<< "#version 460\n"
<< "#extension GL_NV_mesh_shader : enable\n"
<< "\n"
<< "layout (local_size_x=" << localSize << ") in;\n"
<< "layout (triangles) out;\n"
<< "layout (max_vertices=" << maxVert << ", max_primitives=" << localSize << ") out;\n"
<< "\n"
<< (m_taskNeeded ? ("in " + taskDataDeclStr) : "")
<< "\n"
<< "void main ()\n"
<< "{\n"
<< " gl_PrimitiveCountNV = " << localSize << ";\n"
<< "\n"
<< triangleForPixel(pixel, len, baseIndex)
<< "}\n"
;
programCollection.glslSources.add("mesh") << glu::MeshSource(mesh.str());
}
if (m_taskNeeded)
{
std::ostringstream task;
task
<< "#version 460\n"
<< "#extension GL_NV_mesh_shader : enable\n"
<< "\n"
<< "layout (local_size_x=" << m_extent.width << ") in;\n"
<< "\n"
<< "out " << taskDataDeclStr
<< "\n"
<< "void main ()\n"
<< "{\n"
<< " gl_TaskCountNV = " << m_extent.width << ";\n"
<< " td.indexNumber[" << localIndex << "] = " << localIndex << ";\n"
<< " td.size = gl_WorkGroupSize.x;\n"
<< "}\n"
;
programCollection.glslSources.add("task") << glu::TaskSource(task.str());
}
// Basic fragment shader.
{
const auto frag = getBasicFragShader();
programCollection.glslSources.add("frag") << glu::FragmentSource(frag);
}
}
TestInstance* LocalInvocationCase::createInstance (Context& context) const
{
// Must match the shader.
const ColorVec expectedColors (1u, tcu::Vec4(0.0, 0.0, 1.0, 1.0));
const IterationParams iterationParams =
{
m_extent, // VkExtent2D colorExtent;
1u, // uint32_t numLayers;
getDefaultDrawCommands(), // DrawCommandVec drawArgs;
false, // bool indirect;
{}, // ViewportVec viewports; // If empty, a single default viewport is used.
};
return new FullScreenColorInstance(context, iterationParams, expectedColors);
}
// GlobalInvocationId builtin case.
class GlobalInvocationIdCase : public MeshShaderBuiltinCase
{
public:
GlobalInvocationIdCase (tcu::TestContext& testCtx, const std::string& name, const std::string& description, bool taskNeeded)
: MeshShaderBuiltinCase (testCtx, name, description, taskNeeded)
, m_jobSize (getLargeJobSize())
, m_extent {m_jobSize.numTasks * m_jobSize.localSize, 1u}
{}
virtual ~GlobalInvocationIdCase (void) {}
void initPrograms (vk::SourceCollections& programCollection) const override;
TestInstance* createInstance (Context& context) const override;
protected:
const JobSize m_jobSize;
const VkExtent2D m_extent;
};
void GlobalInvocationIdCase::initPrograms (vk::SourceCollections& programCollection) const
{
const auto& localSize = m_jobSize.localSize;
// Task data.
std::ostringstream taskDataDecl;
taskDataDecl
<< "taskNV TaskData {\n"
<< " uint pixelId[" << localSize << "];\n"
<< " uint size;\n"
<< "} td;\n"
;
const auto taskDataDeclStr = taskDataDecl.str();
// Mesh shader: each work group fills one pixel.
{
const std::string pixel = (m_taskNeeded ? "td.pixelId[gl_LocalInvocationIndex]" : "gl_GlobalInvocationID.x");
const std::string len = (m_taskNeeded ? "td.size" : de::toString(m_extent.width));
const std::string baseIndex = "(gl_LocalInvocationIndex * 3u)";
const auto maxVert = localSize * 3u;
std::ostringstream mesh;
mesh
<< "#version 460\n"
<< "#extension GL_NV_mesh_shader : enable\n"
<< "\n"
<< "layout (local_size_x=" << localSize << ") in;\n"
<< "layout (triangles) out;\n"
<< "layout (max_vertices=" << maxVert << ", max_primitives=" << localSize << ") out;\n"
<< "\n"
<< (m_taskNeeded ? ("in " + taskDataDeclStr) : "")
<< "\n"
<< "void main ()\n"
<< "{\n"
<< " gl_PrimitiveCountNV = " << localSize << ";\n"
<< "\n"
<< triangleForPixel(pixel, len, baseIndex)
<< "}\n"
;
programCollection.glslSources.add("mesh") << glu::MeshSource(mesh.str());
}
if (m_taskNeeded)
{
std::ostringstream task;
task
<< "#version 460\n"
<< "#extension GL_NV_mesh_shader : enable\n"
<< "\n"
<< "layout (local_size_x=" << localSize << ") in;\n"
<< "\n"
<< "out " << taskDataDeclStr
<< "\n"
<< "void main ()\n"
<< "{\n"
<< " gl_TaskCountNV = 1;\n"
<< " td.pixelId[gl_LocalInvocationIndex] = gl_GlobalInvocationID.x;\n"
<< " td.size = " << m_extent.width << ";\n"
<< "}\n"
;
programCollection.glslSources.add("task") << glu::TaskSource(task.str());
}
// Basic fragment shader.
{
const auto frag = getBasicFragShader();
programCollection.glslSources.add("frag") << glu::FragmentSource(frag);
}
}
TestInstance* GlobalInvocationIdCase::createInstance (Context& context) const
{
// Must match the shader.
const ColorVec expectedColors (1u, tcu::Vec4(0.0, 0.0, 1.0, 1.0));
const IterationParams iterationParams =
{
m_extent, // VkExtent2D colorExtent;
1u, // uint32_t numLayers;
getDefaultDrawCommands(m_jobSize.numTasks), // DrawCommandVec drawArgs;
false, // bool indirect;
{}, // ViewportVec viewports; // If empty, a single default viewport is used.
};
return new FullScreenColorInstance(context, iterationParams, expectedColors);
}
// DrawIndex builtin case.
class DrawIndexCase : public MeshShaderBuiltinCase
{
public:
DrawIndexCase (tcu::TestContext& testCtx, const std::string& name, const std::string& description, bool taskNeeded)
: MeshShaderBuiltinCase (testCtx, name, description, taskNeeded)
, m_extent (getLinearExtent())
{}
virtual ~DrawIndexCase (void) {}
void initPrograms (vk::SourceCollections& programCollection) const override;
TestInstance* createInstance (Context& context) const override;
protected:
const VkExtent2D m_extent;
};
void DrawIndexCase::initPrograms (vk::SourceCollections& programCollection) const
{
const std::string taskDataDecl =
"taskNV TaskData {\n"
" uint id;\n"
" uint size;\n"
"} td;\n"
;
const auto drawIndex = "uint(gl_DrawID)";
// Mesh shader: each work group fills one pixel.
{
const std::string pixel = (m_taskNeeded ? "td.id" : drawIndex);
const std::string len = (m_taskNeeded ? "td.size" : de::toString(m_extent.width));
std::ostringstream mesh;
mesh
<< "#version 460\n"
<< "#extension GL_NV_mesh_shader : enable\n"
<< "\n"
<< "layout (local_size_x=1) in;\n"
<< "layout (triangles) out;\n"
<< "layout (max_vertices=3, max_primitives=1) out;\n"
<< "\n"
<< (m_taskNeeded ? ("in " + taskDataDecl) : "")
<< "\n"
<< "void main ()\n"
<< "{\n"
<< " gl_PrimitiveCountNV = 1u;\n"
<< "\n"
<< triangleForPixel(pixel, len, "0")
<< "}\n"
;
programCollection.glslSources.add("mesh") << glu::MeshSource(mesh.str());
}
if (m_taskNeeded)
{
std::ostringstream task;
task
<< "#version 460\n"
<< "#extension GL_NV_mesh_shader : enable\n"
<< "\n"
<< "layout (local_size_x=1) in;\n"
<< "\n"
<< "out " << taskDataDecl
<< "\n"
<< "void main ()\n"
<< "{\n"
<< " gl_TaskCountNV = 1u;\n"
<< " td.id = " << drawIndex << ";\n"
<< " td.size = " << m_extent.width << ";\n"
<< "}\n"
;
programCollection.glslSources.add("task") << glu::TaskSource(task.str());
}
// Basic fragment shader.
{
const auto frag = getBasicFragShader();
programCollection.glslSources.add("frag") << glu::FragmentSource(frag);
}
}
TestInstance* DrawIndexCase::createInstance (Context& context) const
{
// Must match the shader.
const ColorVec expectedColors (1u, tcu::Vec4(0.0, 0.0, 1.0, 1.0));
const DrawCommandVec commands (m_extent.width, makeDrawMeshTasksIndirectCommandNV(1u, 0u));
const IterationParams iterationParams =
{
m_extent, // VkExtent2D colorExtent;
1u, // uint32_t numLayers;
commands, // DrawCommandVec drawArgs;
true, // bool indirect;
{}, // ViewportVec viewports; // If empty, a single default viewport is used.
};
return new FullScreenColorInstance(context, iterationParams, expectedColors);
}
} // anonymous
tcu::TestCaseGroup* createMeshShaderBuiltinTests (tcu::TestContext& testCtx)
{
GroupPtr mainGroup (new tcu::TestCaseGroup(testCtx, "builtin", "Mesh Shader Builtin Tests"));
mainGroup->addChild(new PositionCase (testCtx, "position", ""));
mainGroup->addChild(new PointSizeCase (testCtx, "point_size", ""));
mainGroup->addChild(new ClipDistanceCase (testCtx, "clip_distance", ""));
mainGroup->addChild(new CullDistanceCase (testCtx, "cull_distance", ""));
mainGroup->addChild(new PrimitiveIdCase (testCtx, "primitive_id_glsl", "", true/*glslFrag*/));
mainGroup->addChild(new PrimitiveIdCase (testCtx, "primitive_id_spirv", "", false/*glslFrag*/));
mainGroup->addChild(new LayerCase (testCtx, "layer", "", false/*shareVertices*/));
mainGroup->addChild(new LayerCase (testCtx, "layer_shared", "", true/*shareVertices*/));
mainGroup->addChild(new ViewportIndexCase (testCtx, "viewport_index", "", false/*shareVertices*/));
mainGroup->addChild(new ViewportIndexCase (testCtx, "viewport_index_shared", "", true/*shareVertices*/));
mainGroup->addChild(new WorkGroupIdCase (testCtx, "work_group_id_in_mesh", "", false/*taskNeeded*/));
mainGroup->addChild(new WorkGroupIdCase (testCtx, "work_group_id_in_task", "", true/*taskNeeded*/));
mainGroup->addChild(new LocalInvocationCase (testCtx, "local_invocation_id_in_mesh", "", false/*taskNeeded*/, LocalInvocation::ID));
mainGroup->addChild(new LocalInvocationCase (testCtx, "local_invocation_id_in_task", "", true/*taskNeeded*/, LocalInvocation::ID));
mainGroup->addChild(new LocalInvocationCase (testCtx, "local_invocation_index_in_task", "", true/*taskNeeded*/, LocalInvocation::INDEX));
mainGroup->addChild(new LocalInvocationCase (testCtx, "local_invocation_index_in_mesh", "", false/*taskNeeded*/, LocalInvocation::INDEX));
mainGroup->addChild(new GlobalInvocationIdCase (testCtx, "global_invocation_id_in_mesh", "", false/*taskNeeded*/));
mainGroup->addChild(new GlobalInvocationIdCase (testCtx, "global_invocation_id_in_task", "", true/*taskNeeded*/));
mainGroup->addChild(new DrawIndexCase (testCtx, "draw_index_in_mesh", "", false/*taskNeeded*/));
mainGroup->addChild(new DrawIndexCase (testCtx, "draw_index_in_task", "", true/*taskNeeded*/));
return mainGroup.release();
}
} // MeshShader
} // vkt