[dawn][metal] Add texture component swizzle feature support
Bug: 414312052
Change-Id: I4fe036c99f0eb965f53ff21562ac9daccc934fd3
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/249434
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Zhaoming Jiang <zhaoming.jiang@microsoft.com>
Commit-Queue: Fr <beaufort.francois@gmail.com>
diff --git a/src/dawn/native/Texture.cpp b/src/dawn/native/Texture.cpp
index 248fe5b..148ba87 100644
--- a/src/dawn/native/Texture.cpp
+++ b/src/dawn/native/Texture.cpp
@@ -1577,6 +1577,14 @@
texture->GetSampleCount(),
texture->GetNumMipLevels(),
texture->GetArrayLayers())) {
+ if (auto* swizzleDesc = descriptor.Get<TextureComponentSwizzleDescriptor>()) {
+ auto swizzle = swizzleDesc->swizzle.WithTrivialFrontendDefaults();
+ mSwizzleRed = swizzle.r;
+ mSwizzleGreen = swizzle.g;
+ mSwizzleBlue = swizzle.b;
+ mSwizzleAlpha = swizzle.a;
+ }
+
GetObjectTrackingList()->Track(this);
// Emit a warning if invalid usages were removed for this view.
@@ -1691,6 +1699,32 @@
return mInternalUsage;
}
+wgpu::ComponentSwizzle TextureViewBase::GetSwizzleRed() const {
+ return mSwizzleRed;
+}
+
+wgpu::ComponentSwizzle TextureViewBase::GetSwizzleGreen() const {
+ return mSwizzleGreen;
+}
+
+wgpu::ComponentSwizzle TextureViewBase::GetSwizzleBlue() const {
+ return mSwizzleBlue;
+}
+
+wgpu::ComponentSwizzle TextureViewBase::GetSwizzleAlpha() const {
+ return mSwizzleAlpha;
+}
+
+bool TextureViewBase::UsesNonDefaultSwizzle() const {
+ // TODO(414312052): Refine this condition. A view might not be strictly necessary
+ // in case of the given swizzle works identically to default with the original
+ // format, e.g. a R8Unorm texture with swizzle.r set to R and swizzle.g set to One.
+ // This current check provides a correct, though potentially overly broad,
+ // first approximation.
+ return mSwizzleRed != wgpu::ComponentSwizzle::R || mSwizzleGreen != wgpu::ComponentSwizzle::G ||
+ mSwizzleBlue != wgpu::ComponentSwizzle::B || mSwizzleAlpha != wgpu::ComponentSwizzle::A;
+}
+
bool TextureViewBase::IsYCbCr() const {
return false;
}
diff --git a/src/dawn/native/Texture.h b/src/dawn/native/Texture.h
index 96d2903..a4f8c90 100644
--- a/src/dawn/native/Texture.h
+++ b/src/dawn/native/Texture.h
@@ -315,6 +315,12 @@
wgpu::TextureUsage GetUsage() const;
wgpu::TextureUsage GetInternalUsage() const;
+ wgpu::ComponentSwizzle GetSwizzleRed() const;
+ wgpu::ComponentSwizzle GetSwizzleGreen() const;
+ wgpu::ComponentSwizzle GetSwizzleBlue() const;
+ wgpu::ComponentSwizzle GetSwizzleAlpha() const;
+ bool UsesNonDefaultSwizzle() const;
+
virtual bool IsYCbCr() const;
// Valid to call only if `IsYCbCr()` is true.
virtual YCbCrVkDescriptor GetYCbCrVkDescriptor() const;
@@ -334,6 +340,10 @@
SubresourceRange mRange;
const wgpu::TextureUsage mUsage = wgpu::TextureUsage::None;
const wgpu::TextureUsage mInternalUsage = wgpu::TextureUsage::None;
+ wgpu::ComponentSwizzle mSwizzleRed = wgpu::ComponentSwizzle::R;
+ wgpu::ComponentSwizzle mSwizzleGreen = wgpu::ComponentSwizzle::G;
+ wgpu::ComponentSwizzle mSwizzleBlue = wgpu::ComponentSwizzle::B;
+ wgpu::ComponentSwizzle mSwizzleAlpha = wgpu::ComponentSwizzle::A;
};
} // namespace dawn::native
diff --git a/src/dawn/native/metal/PhysicalDeviceMTL.mm b/src/dawn/native/metal/PhysicalDeviceMTL.mm
index 73c9fef..aed4563 100644
--- a/src/dawn/native/metal/PhysicalDeviceMTL.mm
+++ b/src/dawn/native/metal/PhysicalDeviceMTL.mm
@@ -717,6 +717,10 @@
EnableFeature(Feature::BufferMapExtendedUsages);
}
#endif
+
+ if ([*mDevice supportsFamily:MTLGPUFamilyMac2]) {
+ EnableFeature(Feature::TextureComponentSwizzle);
+ }
}
void PhysicalDevice::InitializeVendorArchitectureImpl() {
diff --git a/src/dawn/native/metal/TextureMTL.mm b/src/dawn/native/metal/TextureMTL.mm
index 8a14036..591ee01 100644
--- a/src/dawn/native/metal/TextureMTL.mm
+++ b/src/dawn/native/metal/TextureMTL.mm
@@ -101,6 +101,26 @@
}
}
+MTLTextureSwizzle MetalTextureSwizzle(wgpu::ComponentSwizzle swizzle) {
+ switch (swizzle) {
+ case wgpu::ComponentSwizzle::Zero:
+ return MTLTextureSwizzleZero;
+ case wgpu::ComponentSwizzle::One:
+ return MTLTextureSwizzleOne;
+ case wgpu::ComponentSwizzle::R:
+ return MTLTextureSwizzleRed;
+ case wgpu::ComponentSwizzle::G:
+ return MTLTextureSwizzleGreen;
+ case wgpu::ComponentSwizzle::B:
+ return MTLTextureSwizzleBlue;
+ case wgpu::ComponentSwizzle::A:
+ return MTLTextureSwizzleAlpha;
+
+ case wgpu::ComponentSwizzle::Undefined:
+ DAWN_UNREACHABLE();
+ }
+}
+
bool RequiresCreatingNewTextureView(
const TextureBase* texture,
wgpu::TextureUsage internalViewUsage,
@@ -156,6 +176,16 @@
break;
}
+ // TODO(414312052): Use TextureViewBase::UsesNonDefaultSwizzle() instead of
+ // textureViewDescriptor.
+ if (auto* swizzleDesc = textureViewDescriptor.Get<TextureComponentSwizzleDescriptor>()) {
+ auto swizzle = swizzleDesc->swizzle.WithTrivialFrontendDefaults();
+ if (swizzle.r != wgpu::ComponentSwizzle::R || swizzle.g != wgpu::ComponentSwizzle::G ||
+ swizzle.b != wgpu::ComponentSwizzle::B || swizzle.a != wgpu::ComponentSwizzle::A) {
+ return true;
+ }
+ }
+
return false;
}
@@ -808,10 +838,26 @@
auto mipLevelRange = NSMakeRange(descriptor->baseMipLevel, descriptor->mipLevelCount);
auto arrayLayerRange = NSMakeRange(descriptor->baseArrayLayer, descriptor->arrayLayerCount);
- mMtlTextureView = AcquireNSPRef([mtlTexture newTextureViewWithPixelFormat:viewFormat
- textureType:textureViewType
- levels:mipLevelRange
- slices:arrayLayerRange]);
+ if (UsesNonDefaultSwizzle()) {
+ MTLTextureSwizzleChannels swizzle;
+ swizzle.red = MetalTextureSwizzle(GetSwizzleRed());
+ swizzle.green = MetalTextureSwizzle(GetSwizzleGreen());
+ swizzle.blue = MetalTextureSwizzle(GetSwizzleBlue());
+ swizzle.alpha = MetalTextureSwizzle(GetSwizzleAlpha());
+ mMtlTextureView =
+ AcquireNSPRef([mtlTexture newTextureViewWithPixelFormat:viewFormat
+ textureType:textureViewType
+ levels:mipLevelRange
+ slices:arrayLayerRange
+ swizzle:swizzle]);
+ } else {
+ mMtlTextureView =
+ AcquireNSPRef([mtlTexture newTextureViewWithPixelFormat:viewFormat
+ textureType:textureViewType
+ levels:mipLevelRange
+ slices:arrayLayerRange]);
+ }
+
if (mMtlTextureView == nil) {
return DAWN_INTERNAL_ERROR("Failed to create MTLTexture view.");
}
diff --git a/src/dawn/tests/BUILD.gn b/src/dawn/tests/BUILD.gn
index f580f26..85a4642 100644
--- a/src/dawn/tests/BUILD.gn
+++ b/src/dawn/tests/BUILD.gn
@@ -688,6 +688,7 @@
"end2end/SubresourceRenderAttachmentTests.cpp",
"end2end/SupportedFeatureArch.cpp",
"end2end/Texture3DTests.cpp",
+ "end2end/TextureComponentSwizzleTests.cpp",
"end2end/TextureCorruptionTests.cpp",
"end2end/TextureFormatTests.cpp",
"end2end/TextureFormatsTier1Tests.cpp",
diff --git a/src/dawn/tests/end2end/TextureComponentSwizzleTests.cpp b/src/dawn/tests/end2end/TextureComponentSwizzleTests.cpp
new file mode 100644
index 0000000..2a7272e
--- /dev/null
+++ b/src/dawn/tests/end2end/TextureComponentSwizzleTests.cpp
@@ -0,0 +1,236 @@
+// 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 <array>
+#include <vector>
+
+#include "dawn/tests/DawnTest.h"
+#include "dawn/utils/ComboRenderPipelineDescriptor.h"
+#include "dawn/utils/TextureUtils.h"
+#include "dawn/utils/WGPUHelpers.h"
+
+namespace dawn {
+namespace {
+
+// Define all component swizzle options to test.
+constexpr std::array<wgpu::ComponentSwizzle, 6> kComponentSwizzles = {
+ wgpu::ComponentSwizzle::Zero, wgpu::ComponentSwizzle::One, wgpu::ComponentSwizzle::R,
+ wgpu::ComponentSwizzle::G, wgpu::ComponentSwizzle::B, wgpu::ComponentSwizzle::A};
+
+class TextureComponentSwizzleTest : public DawnTest {
+ protected:
+ void SetUp() override {
+ DawnTest::SetUp();
+ DAWN_TEST_UNSUPPORTED_IF(!device.HasFeature(wgpu::FeatureName::TextureComponentSwizzle));
+ }
+
+ std::vector<wgpu::FeatureName> GetRequiredFeatures() override {
+ std::vector<wgpu::FeatureName> requiredFeatures = {};
+ if (SupportsFeatures({wgpu::FeatureName::TextureComponentSwizzle})) {
+ requiredFeatures.push_back(wgpu::FeatureName::TextureComponentSwizzle);
+ }
+ return requiredFeatures;
+ }
+
+ struct TestParams {
+ wgpu::TextureFormat format;
+ // Input data for the source texture. Max 4 bytes for convenience.
+ std::array<uint8_t, 4> inputData;
+ // The expected values (R, G, B, A) that `textureLoad` would produce
+ // for this format *before* any swizzling. This accounts for default values
+ // for missing channels (e.g., G=0, B=0, A=255 for R8Unorm).
+ std::array<uint8_t, 4> baseLoadValues;
+ };
+
+ // Calculates the expected value after swizzling and normalization.
+ uint8_t GetExpectedValue(wgpu::ComponentSwizzle swizzle,
+ const std::array<uint8_t, 4>& rgbaData) {
+ switch (swizzle) {
+ case wgpu::ComponentSwizzle::Zero:
+ return 0;
+ case wgpu::ComponentSwizzle::One:
+ return 255;
+ case wgpu::ComponentSwizzle::R:
+ return rgbaData[0];
+ case wgpu::ComponentSwizzle::G:
+ return rgbaData[1];
+ case wgpu::ComponentSwizzle::B:
+ return rgbaData[2];
+ case wgpu::ComponentSwizzle::A:
+ return rgbaData[3];
+ case wgpu::ComponentSwizzle::Undefined:
+ default:
+ DAWN_UNREACHABLE();
+ return 0;
+ }
+ }
+
+ void RunTest(TestParams params) {
+ wgpu::ShaderModule module = utils::CreateShaderModule(device, R"(
+ @group(0) @binding(0) var texture : texture_2d<f32>;
+
+ @vertex fn vs_main(@builtin(vertex_index) vertexIndex : u32) -> @builtin(position) vec4f {
+ var pos = array<vec2f, 3>(
+ vec2f(-1.0, -1.0),
+ vec2f(-1.0, 3.0),
+ vec2f(3.0, -1.0)
+ );
+ return vec4f(pos[vertexIndex], 0.0, 1.0);
+ }
+
+ @fragment fn fs_main() -> @location(0) vec4f {
+ // textureLoad samples at an integer coordinate and mip level 0.
+ return textureLoad(texture, vec2i(0, 0), 0);
+ })");
+
+ utils::ComboRenderPipelineDescriptor pipelineDesc;
+ pipelineDesc.vertex.module = module;
+ pipelineDesc.cFragment.module = module;
+ wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&pipelineDesc);
+
+ wgpu::TextureDescriptor outputTextureDesc = {};
+ outputTextureDesc.usage =
+ wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc;
+ outputTextureDesc.size = {1, 1, 1};
+ outputTextureDesc.format = wgpu::TextureFormat::RGBA8Unorm;
+ wgpu::Texture outputTexture = device.CreateTexture(&outputTextureDesc);
+
+ auto RunSwizzleTest = [this, params, pipeline, outputTexture](
+ wgpu::ComponentSwizzle swizzleRed,
+ wgpu::ComponentSwizzle swizzleGreen,
+ wgpu::ComponentSwizzle swizzleBlue,
+ wgpu::ComponentSwizzle swizzleAlpha) {
+ // Create source texture.
+ wgpu::TextureDescriptor textureDesc = {};
+ textureDesc.usage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::TextureBinding;
+ textureDesc.size = {1, 1, 1};
+ textureDesc.format = params.format;
+ wgpu::Texture texture = device.CreateTexture(&textureDesc);
+
+ wgpu::TexelCopyTextureInfo dest = {.texture = texture};
+ wgpu::TexelCopyBufferLayout dataLayout = {.bytesPerRow = 256, .rowsPerImage = 1};
+ wgpu::Extent3D writeSize = {1, 1, 1};
+ const uint32_t bytesPerTexel = utils::GetTexelBlockSizeInBytes(params.format);
+ device.GetQueue().WriteTexture(&dest, params.inputData.data(), bytesPerTexel,
+ &dataLayout, &writeSize);
+
+ // Create the TextureView for the source texture with the specified swizzle.
+ wgpu::TextureViewDescriptor viewDesc = {};
+ wgpu::TextureComponentSwizzleDescriptor swizzleDesc = {};
+ swizzleDesc.swizzle.r = swizzleRed;
+ swizzleDesc.swizzle.g = swizzleGreen;
+ swizzleDesc.swizzle.b = swizzleBlue;
+ swizzleDesc.swizzle.a = swizzleAlpha;
+ viewDesc.nextInChain = &swizzleDesc;
+ wgpu::TextureView textureView = texture.CreateView(&viewDesc);
+
+ // Set up bind group using the swizzled texture view.
+ wgpu::BindGroup bindGroup =
+ utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), {{0, textureView}});
+
+ // Issue render commands.
+ wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+ wgpu::RenderPassDescriptor renderPassDesc = {};
+ wgpu::RenderPassColorAttachment colorAttachment = {};
+ colorAttachment.view = outputTexture.CreateView();
+ colorAttachment.loadOp = wgpu::LoadOp::Clear;
+ colorAttachment.storeOp = wgpu::StoreOp::Store;
+ colorAttachment.clearValue = {0.0f, 0.0f, 0.0f, 0.0f};
+ renderPassDesc.colorAttachmentCount = 1;
+ renderPassDesc.colorAttachments = &colorAttachment;
+
+ wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDesc);
+ pass.SetPipeline(pipeline);
+ pass.SetBindGroup(0, bindGroup);
+ pass.Draw(3);
+ pass.End();
+
+ // Submit commands to the queue.
+ wgpu::CommandBuffer commands = encoder.Finish();
+ queue.Submit(1, &commands);
+
+ utils::RGBA8 expectedColor =
+ utils::RGBA8(GetExpectedValue(swizzleRed, params.baseLoadValues),
+ GetExpectedValue(swizzleGreen, params.baseLoadValues),
+ GetExpectedValue(swizzleBlue, params.baseLoadValues),
+ GetExpectedValue(swizzleAlpha, params.baseLoadValues));
+ EXPECT_PIXEL_RGBA8_EQ(expectedColor, outputTexture, 0, 0);
+ };
+
+ for (auto swizzleRed : kComponentSwizzles) {
+ RunSwizzleTest(swizzleRed, wgpu::ComponentSwizzle::G, wgpu::ComponentSwizzle::B,
+ wgpu::ComponentSwizzle::A);
+ }
+
+ for (auto swizzleGreen : kComponentSwizzles) {
+ RunSwizzleTest(wgpu::ComponentSwizzle::R, swizzleGreen, wgpu::ComponentSwizzle::B,
+ wgpu::ComponentSwizzle::A);
+ }
+
+ for (auto swizzleBlue : kComponentSwizzles) {
+ RunSwizzleTest(wgpu::ComponentSwizzle::R, wgpu::ComponentSwizzle::G, swizzleBlue,
+ wgpu::ComponentSwizzle::A);
+ }
+
+ for (auto swizzleAlpha : kComponentSwizzles) {
+ RunSwizzleTest(wgpu::ComponentSwizzle::R, wgpu::ComponentSwizzle::G,
+ wgpu::ComponentSwizzle::B, swizzleAlpha);
+ }
+ }
+};
+
+// Test that texture component swizzle works as expected when the 'texture-component-swizzle'
+// feature is enabled. These tests verify each component's swizzle mapping independently
+// by using a render shader to read from a swizzled texture and render to a texture.
+// Those covers formats with all channels, and formats where some channels are implicitly
+// generated (0.0 or 1.0) by `textureLoad` due to the base format missing components.
+TEST_P(TextureComponentSwizzleTest, AllChannelsPresent) {
+ TestParams params;
+ params.format = wgpu::TextureFormat::RGBA8Unorm;
+ params.inputData = {255, 128, 64, 0};
+ params.baseLoadValues = {255, 128, 64, 0};
+ RunTest(params);
+}
+TEST_P(TextureComponentSwizzleTest, OnlyRedChannelPresent) {
+ TestParams params;
+ params.format = wgpu::TextureFormat::R8Unorm;
+ params.inputData = {128, 0, 0, 0};
+ params.baseLoadValues = {128, 0, 0, 255}; // G, B default to 0, A defaults to 255.
+ RunTest(params);
+}
+TEST_P(TextureComponentSwizzleTest, OnlyRedAndGreenChannelPresent) {
+ TestParams params;
+ params.format = wgpu::TextureFormat::RG8Unorm;
+ params.inputData = {128, 64, 0, 0};
+ params.baseLoadValues = {128, 64, 0, 255}; // B defaults to 0, A defaults to 255.
+ RunTest(params);
+}
+
+DAWN_INSTANTIATE_TEST(TextureComponentSwizzleTest, MetalBackend());
+
+} // anonymous namespace
+} // namespace dawn