| // 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)); |
| |
| 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; |
| 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; |
| outputTexture = device.CreateTexture(&outputTextureDesc); |
| } |
| |
| 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 RunSwizzleTest(TestParams params, |
| wgpu::Texture inputTexture, |
| wgpu::ComponentSwizzle swizzleRed, |
| wgpu::ComponentSwizzle swizzleGreen, |
| wgpu::ComponentSwizzle swizzleBlue, |
| wgpu::ComponentSwizzle swizzleAlpha) { |
| wgpu::TexelCopyTextureInfo dest = {.texture = inputTexture}; |
| 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 input 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 = inputTexture.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); |
| } |
| |
| void RunTest(TestParams params) { |
| wgpu::TextureDescriptor textureDesc = {}; |
| textureDesc.usage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::TextureBinding; |
| textureDesc.size = {1, 1, 1}; |
| textureDesc.format = params.format; |
| wgpu::Texture inputTexture = device.CreateTexture(&textureDesc); |
| |
| for (auto swizzleRed : kComponentSwizzles) { |
| RunSwizzleTest(params, inputTexture, swizzleRed, wgpu::ComponentSwizzle::G, |
| wgpu::ComponentSwizzle::B, wgpu::ComponentSwizzle::A); |
| } |
| |
| for (auto swizzleGreen : kComponentSwizzles) { |
| RunSwizzleTest(params, inputTexture, wgpu::ComponentSwizzle::R, swizzleGreen, |
| wgpu::ComponentSwizzle::B, wgpu::ComponentSwizzle::A); |
| } |
| |
| for (auto swizzleBlue : kComponentSwizzles) { |
| RunSwizzleTest(params, inputTexture, wgpu::ComponentSwizzle::R, |
| wgpu::ComponentSwizzle::G, swizzleBlue, wgpu::ComponentSwizzle::A); |
| } |
| |
| for (auto swizzleAlpha : kComponentSwizzles) { |
| RunSwizzleTest(params, inputTexture, wgpu::ComponentSwizzle::R, |
| wgpu::ComponentSwizzle::G, wgpu::ComponentSwizzle::B, swizzleAlpha); |
| } |
| } |
| |
| wgpu::RenderPipeline pipeline; |
| wgpu::Texture outputTexture; |
| }; |
| |
| // 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); |
| } |
| |
| // Test that custom swizzle correctly reorders texture components and that a subsequent use of the |
| // default swizzle correctly reverts to the standard component mapping. |
| TEST_P(TextureComponentSwizzleTest, UseDefaultSwizzleAfterNonDefaultSwizzle) { |
| TestParams params; |
| params.format = wgpu::TextureFormat::RGBA8Unorm; |
| params.inputData = {255, 128, 64, 0}; |
| params.baseLoadValues = {255, 128, 64, 0}; |
| |
| wgpu::TextureDescriptor textureDesc = {}; |
| textureDesc.usage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::TextureBinding; |
| textureDesc.size = {1, 1, 1}; |
| textureDesc.format = params.format; |
| wgpu::Texture inputTexture = device.CreateTexture(&textureDesc); |
| |
| // First, use a non-default swizzle. |
| RunSwizzleTest(params, inputTexture, wgpu::ComponentSwizzle::One, wgpu::ComponentSwizzle::One, |
| wgpu::ComponentSwizzle::One, wgpu::ComponentSwizzle::One); |
| |
| // Then, use a default swizzle. |
| RunSwizzleTest(params, inputTexture, wgpu::ComponentSwizzle::R, wgpu::ComponentSwizzle::G, |
| wgpu::ComponentSwizzle::B, wgpu::ComponentSwizzle::A); |
| } |
| |
| DAWN_INSTANTIATE_TEST(TextureComponentSwizzleTest, |
| D3D12Backend(), |
| MetalBackend(), |
| OpenGLBackend(), |
| OpenGLESBackend(), |
| VulkanBackend(), |
| WebGPUBackend()); |
| |
| } // anonymous namespace |
| } // namespace dawn |