Dawn: Add the r8unorm-storage feature.
This is a feature for use in Graphite to make compute shaders render
coverage masks. It will be superseeded by some storage texture tier in
the future.
Fixed: dawn:2101
Change-Id: I6fa127bd1d2db59816a2e7013c7ca11cc3ff0b7f
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/177543
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/docs/dawn/features/r8unorm_storage.md b/docs/dawn/features/r8unorm_storage.md
new file mode 100644
index 0000000..11123fe
--- /dev/null
+++ b/docs/dawn/features/r8unorm_storage.md
@@ -0,0 +1,7 @@
+# R8UnormStorage
+
+Adds the ability to read or write to r8unorm textures as storage for Graphite (but not both at the same time).
+
+Usage in WGSL requires the use of the `chromium_internal_graphite` enable.
+
+Note that in the future this will be merged in some form of storage texture support tier.
diff --git a/src/dawn/dawn.json b/src/dawn/dawn.json
index 5864f39..fe7c864 100644
--- a/src/dawn/dawn.json
+++ b/src/dawn/dawn.json
@@ -2189,6 +2189,7 @@
{"value": 1026, "name": "buffer map extended usages", "tags": ["dawn"]},
{"value": 1027, "name": "adapter properties memory heaps", "tags": ["dawn"]},
{"value": 1028, "name": "adapter properties D3D", "tags": ["dawn"]},
+ {"value": 1029, "name": "r8 unorm storage", "tags": ["dawn"]},
{"value": 1100, "name": "shared texture memory vk dedicated allocation", "tags": ["dawn", "native"]},
{"value": 1101, "name": "shared texture memory a hardware buffer", "tags": ["dawn", "native"]},
diff --git a/src/dawn/native/Features.cpp b/src/dawn/native/Features.cpp
index d2b9b06..74a7216 100644
--- a/src/dawn/native/Features.cpp
+++ b/src/dawn/native/Features.cpp
@@ -299,6 +299,11 @@
{"Support importing ID3D12Resource as shared buffer memory.",
"https://dawn.googlesource.com/dawn/+/refs/heads/main/docs/dawn/features/shared_buffer.md",
FeatureInfo::FeatureState::Experimental}},
+ {Feature::R8UnormStorage,
+ {"Supports using r8unorm texture as storage texture.",
+ "https://dawn.googlesource.com/dawn/+/refs/heads/main/docs/dawn/features/"
+ "r8unorm_storage.md",
+ FeatureInfo::FeatureState::Experimental}},
};
} // anonymous namespace
diff --git a/src/dawn/native/Format.cpp b/src/dawn/native/Format.cpp
index 6ce2515..91705d5 100644
--- a/src/dawn/native/Format.cpp
+++ b/src/dawn/native/Format.cpp
@@ -427,7 +427,8 @@
// clang-format off
// 1 byte color formats
- AddColorFormat(wgpu::TextureFormat::R8Unorm, Cap::Renderable | Cap::Multisample | Cap::Resolve, ByteSize(1), kAnyFloat, ComponentCount(1), RenderTargetPixelByteCost(1), RenderTargetComponentAlignment(1));
+ auto r8unormSupportsStorage = device->HasFeature(Feature::R8UnormStorage) ? Cap::StorageW : Cap::None;
+ AddColorFormat(wgpu::TextureFormat::R8Unorm, Cap::Renderable | Cap::Multisample | Cap::Resolve | r8unormSupportsStorage, ByteSize(1), kAnyFloat, ComponentCount(1), RenderTargetPixelByteCost(1), RenderTargetComponentAlignment(1));
AddColorFormat(wgpu::TextureFormat::R8Snorm, Cap::None, ByteSize(1), kAnyFloat, ComponentCount(1));
AddColorFormat(wgpu::TextureFormat::R8Uint, Cap::Renderable | Cap::Multisample, ByteSize(1), SampleTypeBit::Uint, ComponentCount(1), RenderTargetPixelByteCost(1), RenderTargetComponentAlignment(1));
AddColorFormat(wgpu::TextureFormat::R8Sint, Cap::Renderable | Cap::Multisample, ByteSize(1), SampleTypeBit::Sint, ComponentCount(1), RenderTargetPixelByteCost(1), RenderTargetComponentAlignment(1));
diff --git a/src/dawn/native/ShaderModule.cpp b/src/dawn/native/ShaderModule.cpp
index b71516c..d0857a5 100644
--- a/src/dawn/native/ShaderModule.cpp
+++ b/src/dawn/native/ShaderModule.cpp
@@ -132,6 +132,8 @@
return wgpu::TextureFormat::RGBA32Sint;
case tint::inspector::ResourceBinding::TexelFormat::kRgba32Float:
return wgpu::TextureFormat::RGBA32Float;
+ case tint::inspector::ResourceBinding::TexelFormat::kR8Unorm:
+ return wgpu::TextureFormat::R8Unorm;
case tint::inspector::ResourceBinding::TexelFormat::kNone:
return wgpu::TextureFormat::Undefined;
diff --git a/src/dawn/native/d3d11/PhysicalDeviceD3D11.cpp b/src/dawn/native/d3d11/PhysicalDeviceD3D11.cpp
index 8480dd7..f6f0863 100644
--- a/src/dawn/native/d3d11/PhysicalDeviceD3D11.cpp
+++ b/src/dawn/native/d3d11/PhysicalDeviceD3D11.cpp
@@ -191,6 +191,7 @@
EnableFeature(Feature::Norm16TextureFormats);
EnableFeature(Feature::AdapterPropertiesMemoryHeaps);
EnableFeature(Feature::AdapterPropertiesD3D);
+ EnableFeature(Feature::R8UnormStorage);
// Multi planar formats are always supported since Feature Level 11.0
// https://learn.microsoft.com/en-us/windows/win32/direct3ddxgi/format-support-for-direct3d-11-0-feature-level-hardware
diff --git a/src/dawn/native/d3d12/PhysicalDeviceD3D12.cpp b/src/dawn/native/d3d12/PhysicalDeviceD3D12.cpp
index f17def9..fb6cb21 100644
--- a/src/dawn/native/d3d12/PhysicalDeviceD3D12.cpp
+++ b/src/dawn/native/d3d12/PhysicalDeviceD3D12.cpp
@@ -146,6 +146,7 @@
EnableFeature(Feature::AdapterPropertiesMemoryHeaps);
EnableFeature(Feature::AdapterPropertiesD3D);
EnableFeature(Feature::MultiPlanarRenderTargets);
+ EnableFeature(Feature::R8UnormStorage);
if (AreTimestampQueriesSupported()) {
EnableFeature(Feature::TimestampQuery);
diff --git a/src/dawn/native/metal/BackendMTL.mm b/src/dawn/native/metal/BackendMTL.mm
index 398c40a..c4c8314 100644
--- a/src/dawn/native/metal/BackendMTL.mm
+++ b/src/dawn/native/metal/BackendMTL.mm
@@ -616,6 +616,7 @@
EnableFeature(Feature::SurfaceCapabilities);
EnableFeature(Feature::MSAARenderToSingleSampled);
EnableFeature(Feature::DualSourceBlending);
+ EnableFeature(Feature::R8UnormStorage);
// SIMD-scoped permute operations is supported by GPU family Metal3, Apple6, Apple7, Apple8,
// and Mac2.
diff --git a/src/dawn/native/vulkan/DeviceVk.cpp b/src/dawn/native/vulkan/DeviceVk.cpp
index e404a84..9d3218b 100644
--- a/src/dawn/native/vulkan/DeviceVk.cpp
+++ b/src/dawn/native/vulkan/DeviceVk.cpp
@@ -474,6 +474,10 @@
usedKnobs.features.dualSrcBlend = VK_TRUE;
}
+ if (HasFeature(Feature::R8UnormStorage)) {
+ usedKnobs.features.shaderStorageImageExtendedFormats = VK_TRUE;
+ }
+
if (IsRobustnessEnabled() && mDeviceInfo.HasExt(DeviceExt::Robustness2)) {
DAWN_ASSERT(usedKnobs.HasExt(DeviceExt::Robustness2));
diff --git a/src/dawn/native/vulkan/PhysicalDeviceVk.cpp b/src/dawn/native/vulkan/PhysicalDeviceVk.cpp
index 5e4fb35..1d59695 100644
--- a/src/dawn/native/vulkan/PhysicalDeviceVk.cpp
+++ b/src/dawn/native/vulkan/PhysicalDeviceVk.cpp
@@ -240,6 +240,10 @@
EnableFeature(Feature::DualSourceBlending);
}
+ if (mDeviceInfo.features.shaderStorageImageExtendedFormats == VK_TRUE) {
+ EnableFeature(Feature::R8UnormStorage);
+ }
+
if (mDeviceInfo.HasExt(DeviceExt::ShaderFloat16Int8) &&
mDeviceInfo.HasExt(DeviceExt::_16BitStorage) &&
mDeviceInfo.shaderFloat16Int8Features.shaderFloat16 == VK_TRUE &&
diff --git a/src/dawn/node/binding/Converter.cpp b/src/dawn/node/binding/Converter.cpp
index b91a63c..4ef2d00 100644
--- a/src/dawn/node/binding/Converter.cpp
+++ b/src/dawn/node/binding/Converter.cpp
@@ -1529,6 +1529,8 @@
case wgpu::FeatureName::FramebufferFetch:
case wgpu::FeatureName::BufferMapExtendedUsages:
case wgpu::FeatureName::AdapterPropertiesMemoryHeaps:
+ case wgpu::FeatureName::SharedBufferMemoryD3D12Resource:
+ case wgpu::FeatureName::R8UnormStorage:
case wgpu::FeatureName::Undefined:
return false;
}
diff --git a/src/dawn/tests/end2end/StorageTextureTests.cpp b/src/dawn/tests/end2end/StorageTextureTests.cpp
index 2ca3f9b..2660dc2 100644
--- a/src/dawn/tests/end2end/StorageTextureTests.cpp
+++ b/src/dawn/tests/end2end/StorageTextureTests.cpp
@@ -173,6 +173,12 @@
break;
}
+ case wgpu::TextureFormat::R8Unorm: {
+ uint8_t* valuePtr = static_cast<uint8_t*>(pixelValuePtr);
+ *valuePtr = pixelValue;
+ break;
+ }
+
default:
DAWN_UNREACHABLE();
break;
@@ -260,6 +266,9 @@
return "vec4f(f32(value) / 127.0, -f32(value) / 127.0, "
"f32(value) * 2.0 / 127.0, -f32(value) * 2.0 / 127.0)";
+ case wgpu::TextureFormat::R8Unorm:
+ return "vec4f(f32(value) / 255.0, 0.0, 0.0, 1.0)";
+
default:
DAWN_UNREACHABLE();
break;
@@ -303,6 +312,7 @@
// normalized signed/unsigned integer formats
case wgpu::TextureFormat::RGBA8Unorm:
case wgpu::TextureFormat::RGBA8Snorm:
+ case wgpu::TextureFormat::R8Unorm:
// On Windows Intel drivers the tests will fail if tolerance <= 0.00000001f.
return R"(
fn IsEqualTo(pixel : vec4f, expected : vec4f) -> bool {
@@ -318,6 +328,13 @@
return "";
}
+ const char* GetEnable(wgpu::TextureFormat format) {
+ if (format == wgpu::TextureFormat::R8Unorm) {
+ return "enable chromium_internal_graphite;";
+ }
+ return "";
+ }
+
std::string CommonWriteOnlyTestCode(
const char* stage,
wgpu::TextureFormat format,
@@ -353,6 +370,7 @@
const bool isFragment = strcmp(stage, "fragment") == 0;
std::ostringstream ostream;
+ ostream << GetEnable(format) << "\n";
ostream << GetImageDeclaration(format, "write", dimension, 0) << "\n";
ostream << "@" << stage << workgroupSize << "\n";
ostream << "fn main() ";
@@ -948,6 +966,71 @@
OpenGLESBackend(),
VulkanBackend());
+class R8UnormStorageTextureTests : public StorageTextureTests {
+ public:
+ std::vector<wgpu::FeatureName> GetRequiredFeatures() override {
+ if (SupportsFeatures({wgpu::FeatureName::R8UnormStorage})) {
+ mIsR8UnormStorageSupported = true;
+ return {wgpu::FeatureName::R8UnormStorage};
+ } else {
+ mIsR8UnormStorageSupported = false;
+ return {};
+ }
+ }
+
+ bool IsR8UnormStorageSupported() { return mIsR8UnormStorageSupported; }
+
+ private:
+ bool mIsR8UnormStorageSupported = false;
+};
+
+// Test that R8Unorm is supported to be used as storage texture in compute shaders when the
+// optional feature 'r8unorm-storage' is supported.
+TEST_P(R8UnormStorageTextureTests, WriteonlyStorageTextureInComputeShader) {
+ DAWN_TEST_UNSUPPORTED_IF(!IsR8UnormStorageSupported());
+
+ constexpr wgpu::TextureFormat kFormat = wgpu::TextureFormat::R8Unorm;
+ wgpu::Texture writeonlyStorageTexture =
+ CreateTexture(kFormat, wgpu::TextureUsage::StorageBinding | wgpu::TextureUsage::CopySrc,
+ {kWidth, kHeight});
+
+ // Write the expected pixel values into the write-only storage texture.
+ const std::string computeShader = CommonWriteOnlyTestCode("compute", kFormat);
+ WriteIntoStorageTextureInComputePass(writeonlyStorageTexture, computeShader.c_str());
+
+ // Verify the pixel data in the write-only storage texture is expected.
+ CheckOutputStorageTexture(writeonlyStorageTexture, kFormat, {kWidth, kHeight});
+}
+
+// Test that R8Unorm is supported to be used as storage texture in fragment shaders when the
+// optional feature 'r8unorm-storage' is supported.
+TEST_P(R8UnormStorageTextureTests, WriteonlyStorageTextureInFragmentShader) {
+ DAWN_TEST_UNSUPPORTED_IF(!IsR8UnormStorageSupported());
+
+ constexpr wgpu::TextureFormat kFormat = wgpu::TextureFormat::R8Unorm;
+
+ // Prepare the write-only storage texture.
+ wgpu::Texture writeonlyStorageTexture =
+ CreateTexture(kFormat, wgpu::TextureUsage::StorageBinding | wgpu::TextureUsage::CopySrc,
+ {kWidth, kHeight});
+
+ // Write the expected pixel values into the write-only storage texture.
+ const std::string fragmentShader = CommonWriteOnlyTestCode("fragment", kFormat);
+ WriteIntoStorageTextureInRenderPass(writeonlyStorageTexture, kSimpleVertexShader,
+ fragmentShader.c_str());
+
+ // Verify the pixel data in the write-only storage texture is expected.
+ CheckOutputStorageTexture(writeonlyStorageTexture, kFormat, {kWidth, kHeight});
+}
+
+DAWN_INSTANTIATE_TEST(R8UnormStorageTextureTests,
+ D3D11Backend(),
+ D3D12Backend(),
+ MetalBackend(),
+ OpenGLBackend(),
+ OpenGLESBackend(),
+ VulkanBackend());
+
class StorageTextureZeroInitTests : public StorageTextureTests {
public:
static std::vector<uint8_t> GetExpectedData() {
diff --git a/src/dawn/tests/unittests/validation/StorageTextureValidationTests.cpp b/src/dawn/tests/unittests/validation/StorageTextureValidationTests.cpp
index cfd9bcd..97e8423 100644
--- a/src/dawn/tests/unittests/validation/StorageTextureValidationTests.cpp
+++ b/src/dawn/tests/unittests/validation/StorageTextureValidationTests.cpp
@@ -1216,5 +1216,89 @@
}
}
+class R8UnormStorageValidationTests : public StorageTextureValidationTests {
+ WGPUDevice CreateTestDevice(native::Adapter dawnAdapter,
+ wgpu::DeviceDescriptor descriptor) override {
+ wgpu::FeatureName requiredFeatures[1] = {wgpu::FeatureName::R8UnormStorage};
+ descriptor.requiredFeatures = requiredFeatures;
+ descriptor.requiredFeatureCount = 1;
+ return dawnAdapter.CreateDevice(&descriptor);
+ }
+};
+
+// Check that it is allowed to create an R8Unorm texture with the storage usage.
+TEST_F(R8UnormStorageValidationTests, TextureCreation) {
+ wgpu::TextureDescriptor desc;
+ desc.format = wgpu::TextureFormat::R8Unorm;
+ desc.usage = wgpu::TextureUsage::StorageBinding;
+ desc.size = {1, 1};
+ device.CreateTexture(&desc);
+}
+
+// Check that it is allowed to create a BGL with a read-only or write-only R8unorm storage texture
+// entry.
+TEST_F(R8UnormStorageValidationTests, BGLEntry) {
+ // Control case: read-only or write-only are allowed.
+ utils::MakeBindGroupLayout(
+ device, {{0, wgpu::ShaderStage::Fragment, wgpu::StorageTextureAccess::ReadOnly,
+ wgpu::TextureFormat::R8Unorm}});
+ utils::MakeBindGroupLayout(
+ device, {{0, wgpu::ShaderStage::Fragment, wgpu::StorageTextureAccess::WriteOnly,
+ wgpu::TextureFormat::R8Unorm}});
+
+ // Error cases: read-write is disallowed.
+ ASSERT_DEVICE_ERROR(utils::MakeBindGroupLayout(
+ device, {{0, wgpu::ShaderStage::Fragment, wgpu::StorageTextureAccess::ReadWrite,
+ wgpu::TextureFormat::R8Unorm}}));
+}
+
+// Check that using the `r8unorm` to create a WGSL compute shader is allowed.
+TEST_F(R8UnormStorageValidationTests, ShaderModule) {
+ utils::CreateShaderModule(device, R"(
+ enable chromium_internal_graphite;
+ @group(0) @binding(0) var t : texture_storage_2d<r8unorm, write>;
+ )");
+}
+
+// Check that using an r8unorm storage texture read-only or write-only with implicit layout is
+// valid.
+TEST_F(R8UnormStorageValidationTests, End2endUsage) {
+ wgpu::ComputePipelineDescriptor cDesc;
+ cDesc.compute.module = utils::CreateShaderModule(device, R"(
+ enable chromium_internal_graphite;
+ @group(0) @binding(0) var input : texture_storage_2d<r8unorm, read>;
+ @group(0) @binding(1) var output : texture_storage_2d<r8unorm, write>;
+
+ @workgroup_size(4, 4) @compute fn main(@builtin(local_invocation_id) id : vec3<u32>) {
+ textureStore(output, id.xy, 2 * textureLoad(input, id.xy));
+ }
+ )");
+ wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&cDesc);
+
+ wgpu::TextureDescriptor tDesc;
+ tDesc.format = wgpu::TextureFormat::R8Unorm;
+ tDesc.usage = wgpu::TextureUsage::StorageBinding | wgpu::TextureUsage::CopySrc |
+ wgpu::TextureUsage::CopyDst;
+ tDesc.size = {4, 4};
+ wgpu::Texture input = device.CreateTexture(&tDesc);
+ wgpu::Texture output = device.CreateTexture(&tDesc);
+
+ wgpu::BindGroup bg = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0),
+ {
+ {0, input.CreateView()},
+ {1, output.CreateView()},
+ });
+
+ wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+ wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
+ pass.SetPipeline(pipeline);
+ pass.SetBindGroup(0, bg);
+ pass.DispatchWorkgroups(1);
+ pass.End();
+
+ wgpu::CommandBuffer commands = encoder.Finish();
+ device.GetQueue().Submit(1, &commands);
+}
+
} // anonymous namespace
} // namespace dawn
diff --git a/src/dawn/wire/SupportedFeatures.cpp b/src/dawn/wire/SupportedFeatures.cpp
index 6f72263..5447227 100644
--- a/src/dawn/wire/SupportedFeatures.cpp
+++ b/src/dawn/wire/SupportedFeatures.cpp
@@ -95,6 +95,7 @@
case WGPUFeatureName_FramebufferFetch:
case WGPUFeatureName_AdapterPropertiesMemoryHeaps:
case WGPUFeatureName_AdapterPropertiesD3D:
+ case WGPUFeatureName_R8UnormStorage:
return true;
}