Add tests about max inter-stage shader variables and components
This patch adds tests about maxInterStageShaderVariables and
maxInterStageShaderComponents to verify that we can create such
render pipelines with the values of maximum inter-stage shader
variables and components queried from the adapter.
Bug: dawn:685, dawn:1448
Test: dawn_end2end_tests
Change-Id: I2799c75857f1e04775ac509429f7cb3bc0db8e50
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/155361
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Austin Eng <enga@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Austin Eng <enga@chromium.org>
diff --git a/src/dawn/native/Limits.cpp b/src/dawn/native/Limits.cpp
index bbf60cd..7f0e402 100644
--- a/src/dawn/native/Limits.cpp
+++ b/src/dawn/native/Limits.cpp
@@ -305,6 +305,8 @@
limits->maxVertexBuffers = std::min(limits->maxVertexBuffers, uint32_t(kMaxVertexBuffers));
limits->maxInterStageShaderComponents =
std::min(limits->maxInterStageShaderComponents, kMaxInterStageShaderComponents);
+ limits->maxInterStageShaderVariables =
+ std::min(limits->maxInterStageShaderVariables, kMaxInterStageShaderVariables);
limits->maxSampledTexturesPerShaderStage =
std::min(limits->maxSampledTexturesPerShaderStage, kMaxSampledTexturesPerShaderStage);
limits->maxSamplersPerShaderStage =
diff --git a/src/dawn/tests/end2end/MaxLimitTests.cpp b/src/dawn/tests/end2end/MaxLimitTests.cpp
index 43e4327..97d9e97 100644
--- a/src/dawn/tests/end2end/MaxLimitTests.cpp
+++ b/src/dawn/tests/end2end/MaxLimitTests.cpp
@@ -719,5 +719,319 @@
OpenGLESBackend(),
VulkanBackend());
+// Verifies the limits maxInterStageShaderVariables and maxInterStageShaderComponents work correctly
+class MaxInterStageLimitTests : public MaxLimitTests {
+ public:
+ struct MaxInterStageLimitTestsSpec {
+ bool renderPointLists;
+ bool hasSampleMask;
+ bool hasSampleIndex;
+ bool hasFrontFacing;
+ };
+
+ void DoTest(const MaxInterStageLimitTestsSpec& spec) {
+ wgpu::RenderPipeline pipeline = CreateRenderPipeline(spec);
+ EXPECT_NE(nullptr, pipeline.Get());
+ }
+
+ private:
+ struct InterStageVariableAllocation {
+ uint32_t variableSize;
+ uint32_t variableCount;
+ };
+ using InterStageVariableAllocations = std::array<InterStageVariableAllocation, 2>;
+
+ // Allocate the inter-stage shader variables that consume all inter-stage shader variables and
+ // components
+ InterStageVariableAllocations AllocateInterStageVariables(
+ const MaxInterStageLimitTestsSpec& spec) {
+ wgpu::Limits baseLimits = GetAdapterLimits().limits;
+
+ uint32_t interStageVariableCount = baseLimits.maxInterStageShaderVariables;
+
+ uint32_t builtinCount = static_cast<uint32_t>(spec.renderPointLists) +
+ static_cast<uint32_t>(spec.hasFrontFacing) +
+ static_cast<uint32_t>(spec.hasSampleIndex) +
+ static_cast<uint32_t>(spec.hasSampleMask);
+ uint32_t userDefinedInterStageComponents =
+ baseLimits.maxInterStageShaderComponents - builtinCount;
+
+ InterStageVariableAllocations allocation;
+
+ // Allocate `userDefinedInterStageComponents` inter-stage shader components to fill
+ // `interStageVariableCount` inter-stage shader variables.
+ // For example, assuming we are neither rendering point lists and nor using built-ins other
+ // than @builtin(position):
+ // 1. on D3D12, maxInterStageShaderVariables == 30 and maxInterStageShaderComponents == 120,
+ // so allocation[1].variableCount = 0 (allocation[1].variableSize == 5 is not used) and
+ // allocation[0].variableCount = 30.
+ // 2. on Vulkan. maxInterStageShaderVariables can be 16 and maxInterStageShaderComponents
+ // can be 60, so the inter-stage variables are 12 vec4fs and 4 vec3fs.
+ allocation[0].variableSize = userDefinedInterStageComponents / interStageVariableCount;
+ allocation[1].variableSize = allocation[0].variableSize + 1;
+ allocation[1].variableCount =
+ userDefinedInterStageComponents - interStageVariableCount * allocation[0].variableSize;
+ allocation[0].variableCount = interStageVariableCount - allocation[1].variableCount;
+ DAWN_ASSERT(userDefinedInterStageComponents ==
+ allocation[0].variableSize * allocation[0].variableCount +
+ allocation[1].variableSize * allocation[1].variableCount);
+
+ return allocation;
+ }
+
+ std::string GetWGSLTypeFromVariableSize(uint32_t variableSize) {
+ switch (variableSize) {
+ case 1:
+ return "f32";
+ case 2:
+ return "vec2f";
+ case 3:
+ return "vec3f";
+ case 4:
+ return "vec4f";
+ // It's OK to return an empty string for variableSize == 5 as when variableSizes[i] ==
+ // 0, variableSizesCount[i] must be 5, thus the returned empty string won't be used.
+ case 5:
+ return "";
+ default:
+ DAWN_UNREACHABLE();
+ return "";
+ }
+ }
+
+ std::string GetInterStageVariableDeclarations(const InterStageVariableAllocations& allocation) {
+ std::stringstream stream;
+
+ stream << "struct VertexOut {" << std::endl;
+
+ uint32_t interStageVariableCount =
+ allocation[0].variableCount + allocation[1].variableCount;
+ for (uint32_t location = 0; location < interStageVariableCount; ++location) {
+ uint32_t variableIndexInAllocation = (location < allocation[0].variableCount) ? 0 : 1;
+ std::string wgslType =
+ GetWGSLTypeFromVariableSize(allocation[variableIndexInAllocation].variableSize);
+ stream << "@location(" << location << ") color" << location << " : " << wgslType << ", "
+ << std::endl;
+ }
+
+ stream << "@builtin(position) pos : vec4f" << std::endl << "}" << std::endl;
+
+ return stream.str();
+ }
+
+ wgpu::ShaderModule GetShaderModuleForTest(const MaxInterStageLimitTestsSpec& spec) {
+ std::stringstream stream;
+
+ InterStageVariableAllocations allocation = AllocateInterStageVariables(spec);
+ stream << GetInterStageVariableDeclarations(allocation) << std::endl
+ << GetVertexShaderForTest(allocation) << std::endl
+ << GetFragmentShaderForTest(allocation, spec) << std::endl;
+ return utils::CreateShaderModule(device, stream.str().c_str());
+ }
+
+ std::string GetVertexShaderForTest(const InterStageVariableAllocations& allocation) {
+ std::stringstream stream;
+ stream << R"(
+ @vertex
+ fn vs_main(@builtin(vertex_index) vertexIndex : u32) -> VertexOut {
+ var pos = array<vec2f, 3>(
+ vec2f(-1.0, -1.0),
+ vec2f( 2.0, 0.0),
+ vec2f( 0.0, 2.0));
+ var output : VertexOut;
+ output.pos = vec4f(pos[vertexIndex], 0.0, 1.0);
+ var vertexIndexFloat = f32(vertexIndex);
+)";
+ // Ensure every inter-stage shader variable is used instead of being optimized out.
+ uint32_t interStageVariableCount =
+ allocation[0].variableCount + allocation[1].variableCount;
+ for (uint32_t location = 0; location < interStageVariableCount; ++location) {
+ uint32_t variableIndexInAllocation = (location < allocation[0].variableCount) ? 0 : 1;
+ std::string wgslType =
+ GetWGSLTypeFromVariableSize(allocation[variableIndexInAllocation].variableSize);
+ stream << "output.color" << location << " = " << wgslType << "(";
+ for (uint32_t index = 0; index < allocation[variableIndexInAllocation].variableSize;
+ ++index) {
+ stream << "vertexIndexFloat / "
+ << location * allocation[variableIndexInAllocation].variableCount + index +
+ 1;
+ if (index != allocation[variableIndexInAllocation].variableSize - 1) {
+ stream << ", ";
+ }
+ }
+ stream << ");" << std::endl;
+ }
+ stream << "return output;" << std::endl << "}" << std::endl;
+ return stream.str();
+ }
+
+ std::string GetInterStageVariableToVec4f(uint32_t location, uint32_t variableSize) {
+ std::stringstream stream;
+
+ switch (variableSize) {
+ case 1:
+ stream << "vec4f(input.color" << location << ", 0, 0, 1)";
+ break;
+ case 2:
+ stream << "vec4f(input.color" << location << ", 0, 1)";
+ break;
+ case 3:
+ stream << "vec4f(input.color" << location << ", 1)";
+ break;
+ case 4:
+ stream << "input.color" << location;
+ break;
+ default:
+ DAWN_UNREACHABLE();
+ break;
+ }
+
+ return stream.str();
+ }
+
+ std::string GetFragmentShaderForTest(const InterStageVariableAllocations& allocation,
+ const MaxInterStageLimitTestsSpec& spec) {
+ std::stringstream stream;
+
+ stream << "@fragment fn fs_main(input: VertexOut";
+ if (spec.hasFrontFacing) {
+ stream << ", @builtin(front_facing) isFront : bool";
+ }
+ if (spec.hasSampleIndex) {
+ stream << ", @builtin(sample_index) sampleIndex : u32";
+ }
+ if (spec.hasSampleMask) {
+ stream << ", @builtin(sample_mask) sampleMask : u32";
+ }
+ // Ensure every inter-stage shader variable and built-in variable is used instead of being
+ // optimized out.
+ stream << ") -> @location(0) vec4f {" << std::endl << "return input.pos";
+ if (spec.hasFrontFacing) {
+ stream << " + vec4f(f32(isFront), 0, 0, 1)";
+ }
+ if (spec.hasSampleIndex) {
+ stream << " + vec4f(f32(sampleIndex), 0, 0, 1)";
+ }
+ if (spec.hasSampleMask) {
+ stream << " + vec4f(f32(sampleMask), 0, 0, 1)";
+ }
+ uint32_t interStageVariableCount =
+ allocation[0].variableCount + allocation[1].variableCount;
+ for (uint32_t location = 0; location < interStageVariableCount; ++location) {
+ uint32_t variableIndexInAllocation = (location < allocation[0].variableCount) ? 0 : 1;
+ stream << " + "
+ << GetInterStageVariableToVec4f(
+ location, allocation[variableIndexInAllocation].variableSize);
+ }
+ stream << ";}";
+ return stream.str();
+ }
+
+ wgpu::RenderPipeline CreateRenderPipeline(const MaxInterStageLimitTestsSpec& spec) {
+ wgpu::ShaderModule shaderModule = GetShaderModuleForTest(spec);
+ utils::ComboRenderPipelineDescriptor descriptor;
+ descriptor.vertex.module = shaderModule;
+ descriptor.vertex.entryPoint = "vs_main";
+ descriptor.cFragment.module = shaderModule;
+ descriptor.cFragment.entryPoint = "fs_main";
+ descriptor.vertex.bufferCount = 0;
+ descriptor.cBuffers[0].attributeCount = 0;
+ descriptor.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm;
+ descriptor.primitive.topology = spec.renderPointLists
+ ? wgpu::PrimitiveTopology::PointList
+ : wgpu::PrimitiveTopology::TriangleList;
+ if (spec.hasSampleIndex || spec.hasSampleMask) {
+ descriptor.multisample.count = 4;
+ }
+ return device.CreateRenderPipeline(&descriptor);
+ }
+};
+
+// Tests that both maxInterStageShaderComponents and maxInterStageShaderVariables work for a render
+// pipeline with no built-in variables.
+TEST_P(MaxInterStageLimitTests, NoBuiltins) {
+ MaxInterStageLimitTestsSpec spec = {};
+ DoTest(spec);
+}
+
+// Tests that both maxInterStageShaderComponents and maxInterStageShaderVariables work for a render
+// pipeline with @builtin(sample_mask). On D3D SV_Coverage doesn't consume an independent float4
+// register.
+TEST_P(MaxInterStageLimitTests, SampleMask) {
+ MaxInterStageLimitTestsSpec spec = {};
+ spec.hasSampleMask = true;
+ DoTest(spec);
+}
+
+// Tests that both maxInterStageShaderComponents and maxInterStageShaderVariables work for a render
+// pipeline with @builtin(sample_index). On D3D SV_SampleIndex consumes an independent float4
+// register.
+TEST_P(MaxInterStageLimitTests, SampleIndex) {
+ MaxInterStageLimitTestsSpec spec = {};
+ spec.hasSampleIndex = true;
+ DoTest(spec);
+}
+
+// Tests that both maxInterStageShaderComponents and maxInterStageShaderVariables work for a render
+// pipeline with @builtin(front_facing). On D3D SV_IsFrontFace consumes an independent float4
+// register.
+TEST_P(MaxInterStageLimitTests, FrontFacing) {
+ MaxInterStageLimitTestsSpec spec = {};
+ spec.hasFrontFacing = true;
+ DoTest(spec);
+}
+
+// Tests that both maxInterStageShaderComponents and maxInterStageShaderVariables work for a render
+// pipeline with @builtin(front_facing). On D3D SV_IsFrontFace and SV_SampleIndex consume one
+// independent float4 register.
+TEST_P(MaxInterStageLimitTests, SampleIndex_FrontFacing) {
+ MaxInterStageLimitTestsSpec spec = {};
+ spec.hasSampleIndex = true;
+ spec.hasFrontFacing = true;
+ DoTest(spec);
+}
+
+// Tests that both maxInterStageShaderComponents and maxInterStageShaderVariables work for a render
+// pipeline with @builtin(sample_mask),
+// @builtin(sample_index) and @builtin(front_facing).
+TEST_P(MaxInterStageLimitTests, SampleMask_SampleIndex_FrontFacing) {
+ MaxInterStageLimitTestsSpec spec = {};
+ spec.hasSampleMask = true;
+ spec.hasSampleIndex = true;
+ spec.hasFrontFacing = true;
+ DoTest(spec);
+}
+
+// Tests that both maxInterStageShaderComponents and maxInterStageShaderVariables work for a render
+// pipeline with PointList primitive topology. On Vulkan when the primitive topology is PointList,
+// the SPIR-V builtin PointSize must be declared in vertex shader, which will consume 1 inter-stage
+// shader component.
+TEST_P(MaxInterStageLimitTests, RenderPointList) {
+ MaxInterStageLimitTestsSpec spec = {};
+ spec.renderPointLists = true;
+ DoTest(spec);
+}
+
+// Tests that both maxInterStageShaderComponents and maxInterStageShaderVariables work for a render
+// pipeline with PointList primitive topology, @builtin(sample_mask),
+// @builtin(sample_index) and @builtin(front_facing).
+TEST_P(MaxInterStageLimitTests, RenderPointList_SampleMask_SampleIndex_FrontFacing) {
+ MaxInterStageLimitTestsSpec spec = {};
+ spec.renderPointLists = true;
+ spec.hasSampleMask = true;
+ spec.hasSampleIndex = true;
+ spec.hasFrontFacing = true;
+ DoTest(spec);
+}
+
+DAWN_INSTANTIATE_TEST(MaxInterStageLimitTests,
+ D3D11Backend(),
+ D3D12Backend({}, {"use_dxc"}),
+ D3D12Backend({"use_dxc"}),
+ MetalBackend(),
+ OpenGLBackend(),
+ OpenGLESBackend(),
+ VulkanBackend());
+
} // anonymous namespace
} // namespace dawn