Expose PrimitiveID feature in Dawn
Adds the necessary plumbing to expose @builtin(primitive_id)
in Tint with a feature in Dawn, currently chromium-experimental-primitive-id
Bug: 342172182
Change-Id: I5c6d32afc504042bac0f76adbd0a714e265f5b6b
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/254394
Reviewed-by: dan sinclair <dsinclair@chromium.org>
Commit-Queue: Brandon Jones <bajones@chromium.org>
diff --git a/src/dawn/dawn.json b/src/dawn/dawn.json
index d1134ac..9f37766 100644
--- a/src/dawn/dawn.json
+++ b/src/dawn/dawn.json
@@ -2343,7 +2343,8 @@
{"value": 55, "name": "chromium experimental subgroup matrix", "tags": ["dawn"]},
{"value": 56, "name": "shared fence EGL sync", "tags": ["dawn", "native"]},
{"value": 57, "name": "dawn device allocator control", "tags": ["dawn"]},
- {"value": 58, "name": "texture component swizzle", "tags": ["dawn"]}
+ {"value": 58, "name": "texture component swizzle", "tags": ["dawn"]},
+ {"value": 59, "name": "chromium experimental primitive id", "tags": ["dawn"]}
]
},
"filter mode": {
diff --git a/src/dawn/native/Device.cpp b/src/dawn/native/Device.cpp
index f603992..f6d97d5 100644
--- a/src/dawn/native/Device.cpp
+++ b/src/dawn/native/Device.cpp
@@ -1720,6 +1720,10 @@
mWGSLAllowedFeatures.extensions.insert(
tint::wgsl::Extension::kChromiumExperimentalSubgroupMatrix);
}
+ if (mEnabledFeatures.IsEnabled(Feature::ChromiumExperimentalPrimitiveId)) {
+ mWGSLAllowedFeatures.extensions.insert(
+ tint::wgsl::Extension::kChromiumExperimentalPrimitiveId);
+ }
// Language features are enabled instance-wide.
const auto& allowedFeatures = GetInstance()->GetAllowedWGSLLanguageFeatures();
diff --git a/src/dawn/native/Features.cpp b/src/dawn/native/Features.cpp
index 4116433..be3baad 100644
--- a/src/dawn/native/Features.cpp
+++ b/src/dawn/native/Features.cpp
@@ -428,6 +428,11 @@
{"Supports configuring device allocator via DawnDeviceAllocatorControl",
"https://dawn.googlesource.com/dawn/+/refs/heads/main/docs/dawn/features/"
"dawn_device_allocator_control.md",
+ FeatureInfo::FeatureState::Experimental}},
+ {Feature::ChromiumExperimentalPrimitiveId,
+ {"Supports the \"enable chromium_experimental_primitive_id;\" directive in WGSL",
+ "https://dawn.googlesource.com/dawn/+/refs/heads/main/docs/tint/extensions/"
+ "chromium_experimental_primitive_id.md",
FeatureInfo::FeatureState::Experimental}}};
} // anonymous namespace
diff --git a/src/dawn/native/d3d11/PhysicalDeviceD3D11.cpp b/src/dawn/native/d3d11/PhysicalDeviceD3D11.cpp
index 7f0201b..2801402 100644
--- a/src/dawn/native/d3d11/PhysicalDeviceD3D11.cpp
+++ b/src/dawn/native/d3d11/PhysicalDeviceD3D11.cpp
@@ -162,6 +162,8 @@
EnableFeature(Feature::DawnPartialLoadResolveTexture);
EnableFeature(Feature::RG11B10UfloatRenderable);
EnableFeature(Feature::TextureFormatsTier1);
+ EnableFeature(Feature::ChromiumExperimentalPrimitiveId);
+
if (mDeviceInfo.isUMA && mDeviceInfo.supportsMapNoOverwriteDynamicBuffers) {
// With UMA we should allow mapping usages on more type of buffers.
EnableFeature(Feature::BufferMapExtendedUsages);
diff --git a/src/dawn/native/d3d12/PhysicalDeviceD3D12.cpp b/src/dawn/native/d3d12/PhysicalDeviceD3D12.cpp
index cad0945..8910b14 100644
--- a/src/dawn/native/d3d12/PhysicalDeviceD3D12.cpp
+++ b/src/dawn/native/d3d12/PhysicalDeviceD3D12.cpp
@@ -174,6 +174,7 @@
EnableFeature(Feature::FlexibleTextureViews);
EnableFeature(Feature::TextureFormatsTier1);
EnableFeature(Feature::TextureComponentSwizzle);
+ EnableFeature(Feature::ChromiumExperimentalPrimitiveId);
if (AreTimestampQueriesSupported()) {
EnableFeature(Feature::TimestampQuery);
diff --git a/src/dawn/native/metal/PhysicalDeviceMTL.mm b/src/dawn/native/metal/PhysicalDeviceMTL.mm
index 9ad5f4f..b60836c 100644
--- a/src/dawn/native/metal/PhysicalDeviceMTL.mm
+++ b/src/dawn/native/metal/PhysicalDeviceMTL.mm
@@ -691,6 +691,9 @@
if ([*mDevice supportsFamily:MTLGPUFamilyApple7]) {
EnableFeature(Feature::ChromiumExperimentalSubgroupMatrix);
+ // TODO(342172182): This may be available in more places?
+ // (mwyrzykowski says "Apple7 and all Macs")
+ EnableFeature(Feature::ChromiumExperimentalPrimitiveId);
}
EnableFeature(Feature::SharedTextureMemoryIOSurface);
diff --git a/src/dawn/native/vulkan/DeviceVk.cpp b/src/dawn/native/vulkan/DeviceVk.cpp
index ca1b540..08a4070 100644
--- a/src/dawn/native/vulkan/DeviceVk.cpp
+++ b/src/dawn/native/vulkan/DeviceVk.cpp
@@ -516,6 +516,11 @@
usedKnobs.features.depthClamp = VK_TRUE;
}
+ if (HasFeature(Feature::ChromiumExperimentalPrimitiveId)) {
+ DAWN_ASSERT(mDeviceInfo.features.geometryShader == VK_TRUE);
+ usedKnobs.features.geometryShader = VK_TRUE;
+ }
+
bool shaderFloat16Int8FeaturesAdded = false;
if (HasFeature(Feature::ShaderF16)) {
DAWN_ASSERT(usedKnobs.HasExt(DeviceExt::ShaderFloat16Int8) &&
diff --git a/src/dawn/native/vulkan/PhysicalDeviceVk.cpp b/src/dawn/native/vulkan/PhysicalDeviceVk.cpp
index f3c3af8..bfbdfde 100644
--- a/src/dawn/native/vulkan/PhysicalDeviceVk.cpp
+++ b/src/dawn/native/vulkan/PhysicalDeviceVk.cpp
@@ -333,6 +333,12 @@
EnableFeature(Feature::ClipDistances);
}
+ // primitive_id is currently exposed if we support geometry shaders.
+ // TODO(342172182): We could also potentially use tessellation or mesh shaders.
+ if (mDeviceInfo.features.geometryShader == VK_TRUE) {
+ EnableFeature(Feature::ChromiumExperimentalPrimitiveId);
+ }
+
bool shaderF16Enabled = false;
if (mDeviceInfo.HasExt(DeviceExt::ShaderFloat16Int8) &&
mDeviceInfo.HasExt(DeviceExt::_16BitStorage) &&
diff --git a/src/dawn/node/binding/Converter.cpp b/src/dawn/node/binding/Converter.cpp
index e274b45..59abac4 100644
--- a/src/dawn/node/binding/Converter.cpp
+++ b/src/dawn/node/binding/Converter.cpp
@@ -1627,6 +1627,9 @@
case interop::GPUFeatureName::kTextureComponentSwizzle:
out = wgpu::FeatureName::TextureComponentSwizzle;
return true;
+ case interop::GPUFeatureName::kChromiumExperimentalPrimitiveId:
+ out = wgpu::FeatureName::ChromiumExperimentalPrimitiveId;
+ return true;
}
return false;
}
@@ -1661,6 +1664,7 @@
CASE(TextureFormatsTier1, kTextureFormatsTier1);
CASE(TextureFormatsTier2, kTextureFormatsTier2);
CASE(TextureComponentSwizzle, kTextureComponentSwizzle);
+ CASE(ChromiumExperimentalPrimitiveId, kChromiumExperimentalPrimitiveId);
#undef CASE
diff --git a/src/dawn/node/interop/DawnExtensions.idl b/src/dawn/node/interop/DawnExtensions.idl
index 22b2c1a..3e69eea 100644
--- a/src/dawn/node/interop/DawnExtensions.idl
+++ b/src/dawn/node/interop/DawnExtensions.idl
@@ -32,6 +32,7 @@
"multi-draw-indirect",
"chromium-experimental-subgroup-matrix",
"texture-component-swizzle",
+ "chromium-experimental-primitive-id",
};
enum GPUSubgroupMatrixComponentType {
diff --git a/src/dawn/tests/BUILD.gn b/src/dawn/tests/BUILD.gn
index a9a9e25..6437389 100644
--- a/src/dawn/tests/BUILD.gn
+++ b/src/dawn/tests/BUILD.gn
@@ -657,6 +657,7 @@
"end2end/PipelineLayoutTests.cpp",
"end2end/PixelLocalStorageTests.cpp",
"end2end/PolyfillBuiltinSimpleTests.cpp",
+ "end2end/PrimitiveIdTests.cpp",
"end2end/PrimitiveStateTests.cpp",
"end2end/PrimitiveTopologyTests.cpp",
"end2end/QueryTests.cpp",
diff --git a/src/dawn/tests/end2end/PrimitiveIdTests.cpp b/src/dawn/tests/end2end/PrimitiveIdTests.cpp
new file mode 100644
index 0000000..904c7ca
--- /dev/null
+++ b/src/dawn/tests/end2end/PrimitiveIdTests.cpp
@@ -0,0 +1,177 @@
+// Copyright 2025 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <vector>
+
+#include "dawn/tests/DawnTest.h"
+#include "dawn/utils/ComboRenderPipelineDescriptor.h"
+#include "dawn/utils/WGPUHelpers.h"
+
+namespace dawn {
+namespace {
+
+constexpr uint32_t kRTSize = 16;
+constexpr wgpu::TextureFormat kFormat = wgpu::TextureFormat::RGBA8Unorm;
+
+constexpr wgpu::FeatureName kPrimitiveIdFeature =
+ wgpu::FeatureName::ChromiumExperimentalPrimitiveId;
+
+using RequirePrimitiveIdFeature = bool;
+DAWN_TEST_PARAM_STRUCT(PrimitiveIdTestsParams, RequirePrimitiveIdFeature);
+
+class PrimitiveIdTests : public DawnTestWithParams<PrimitiveIdTestsParams> {
+ public:
+ wgpu::Texture CreateDefault2DTexture() {
+ wgpu::TextureDescriptor descriptor;
+ descriptor.dimension = wgpu::TextureDimension::e2D;
+ descriptor.size.width = kRTSize;
+ descriptor.size.height = kRTSize;
+ descriptor.size.depthOrArrayLayers = 1;
+ descriptor.sampleCount = 1;
+ descriptor.format = kFormat;
+ descriptor.mipLevelCount = 1;
+ descriptor.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc;
+ return device.CreateTexture(&descriptor);
+ }
+
+ protected:
+ std::vector<wgpu::FeatureName> GetRequiredFeatures() override {
+ mIsPrimitiveIdSupportedOnAdapter = SupportsFeatures({kPrimitiveIdFeature});
+ if (!mIsPrimitiveIdSupportedOnAdapter) {
+ return {};
+ }
+
+ if (GetParam().mRequirePrimitiveIdFeature) {
+ return {kPrimitiveIdFeature};
+ }
+
+ return {};
+ }
+
+ bool IsPrimitiveIdSupportedOnAdapter() const { return mIsPrimitiveIdSupportedOnAdapter; }
+
+ private:
+ bool mIsPrimitiveIdSupportedOnAdapter = false;
+};
+
+// Test simple primitive ID within shader with enable directive. The result should be as expected if
+// the device enables the extension, otherwise a shader creation error should be caught.
+TEST_P(PrimitiveIdTests, BasicPrimitiveIdFeaturesTest) {
+ // Skip if device doesn't support the extension.
+ DAWN_TEST_UNSUPPORTED_IF(!device.HasFeature(kPrimitiveIdFeature));
+
+ const char* shader = R"(
+enable chromium_experimental_primitive_id;
+
+@vertex
+fn VSMain(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4f {
+ var pos = array(
+ vec2f(-1, -1), vec2f(1, -1), vec2f(-1, 1),
+ vec2f(1, 1), vec2f(1, -1), vec2f(-1, 1));
+
+ return vec4f(pos[VertexIndex % 6], 0.0, 1.0);
+}
+
+var<private> colorId : array<vec3f, 5> = array<vec3f, 5>(
+ vec3f(1, 0, 0),
+ vec3f(0, 1, 0),
+ vec3f(0, 0, 1),
+ vec3f(1, 1, 0),
+ vec3f(1, 1, 1)
+);
+
+@fragment
+fn FSMain(@builtin(primitive_id) pid : u32) -> @location(0) vec4f {
+ // Select a color based on the primitive ID
+ return vec4f(colorId[pid%5], 1.0);
+})";
+
+ wgpu::ShaderModule shaderModule = utils::CreateShaderModule(device, shader);
+
+ // Create render pipeline.
+ wgpu::RenderPipeline pipeline;
+ {
+ utils::ComboRenderPipelineDescriptor descriptor;
+
+ descriptor.vertex.module = shaderModule;
+
+ descriptor.cFragment.module = shaderModule;
+ descriptor.primitive.topology = wgpu::PrimitiveTopology::TriangleList;
+ descriptor.cTargets[0].format = kFormat;
+
+ pipeline = device.CreateRenderPipeline(&descriptor);
+ }
+
+ wgpu::Texture renderTarget = CreateDefault2DTexture();
+
+ auto drawPrimitives = [&](uint32_t count) {
+ wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+
+ {
+ // In the render pass we clear renderTarget to black, then draw a colored triangles
+ // based on the primitive ID to the top left and bottom right of the rnederTarget.
+ utils::ComboRenderPassDescriptor renderPass({renderTarget.CreateView()});
+ renderPass.cColorAttachments[0].clearValue = {0.0f, 0.0f, 0.0f, 1.0f};
+
+ wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass);
+ pass.SetPipeline(pipeline);
+ pass.Draw(count * 3);
+ pass.End();
+ }
+
+ wgpu::CommandBuffer commands = encoder.Finish();
+ queue.Submit(1, &commands);
+ };
+
+ // Draw N primitives and ensure that the color written corresponds to the given primitive ID.
+ drawPrimitives(2);
+ EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8::kRed, renderTarget, 1, kRTSize - 1);
+ EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8::kGreen, renderTarget, kRTSize - 1, 1);
+
+ drawPrimitives(4);
+ EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8::kBlue, renderTarget, 1, kRTSize - 1);
+ EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8::kYellow, renderTarget, kRTSize - 1, 1);
+
+ drawPrimitives(6);
+ EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8::kWhite, renderTarget, 1, kRTSize - 1);
+ EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8::kRed, renderTarget, kRTSize - 1, 1);
+}
+
+// DawnTestBase::CreateDeviceImpl always enables allow_unsafe_apis toggle.
+DAWN_INSTANTIATE_TEST_P(PrimitiveIdTests,
+ {
+ D3D11Backend(),
+ D3D12Backend(),
+ VulkanBackend(),
+ MetalBackend(),
+ OpenGLBackend(),
+ OpenGLESBackend(),
+ },
+ {true, false});
+
+} // anonymous namespace
+} // namespace dawn
diff --git a/src/dawn/tests/unittests/validation/ShaderModuleValidationTests.cpp b/src/dawn/tests/unittests/validation/ShaderModuleValidationTests.cpp
index ee7bc9d..b6458d4 100644
--- a/src/dawn/tests/unittests/validation/ShaderModuleValidationTests.cpp
+++ b/src/dawn/tests/unittests/validation/ShaderModuleValidationTests.cpp
@@ -882,6 +882,7 @@
{"chromium_internal_graphite", true, {}, {}},
{"chromium_experimental_framebuffer_fetch", true, {"framebuffer-fetch"}, {}},
{"chromium_experimental_subgroup_matrix", true, {"chromium-experimental-subgroup-matrix"}, {}},
+ {"chromium_experimental_primitive_id", true, {"chromium-experimental-primitive-id"}, {}},
// Currently the following WGSL extensions are not enabled under any situation.
/*
diff --git a/src/dawn/wire/SupportedFeatures.cpp b/src/dawn/wire/SupportedFeatures.cpp
index de716ab..04e50aa 100644
--- a/src/dawn/wire/SupportedFeatures.cpp
+++ b/src/dawn/wire/SupportedFeatures.cpp
@@ -121,6 +121,7 @@
case WGPUFeatureName_TextureFormatsTier1:
case WGPUFeatureName_TextureFormatsTier2:
case WGPUFeatureName_TextureComponentSwizzle:
+ case WGPUFeatureName_ChromiumExperimentalPrimitiveId:
return true;
}