| // 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 "src/dawn/tests/DawnTest.h" | |
| #include "src/dawn/utils/ComboRenderPipelineDescriptor.h" | |
| #include "src/dawn/utils/TextureUtils.h" | |
| #include "src/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(); | |
| // TODO(crbug.com/523272960): Produces incorrect result on Pixel 10. | |
| DAWN_SUPPRESS_TEST_IF(IsAndroid() && IsImgTec() && IsVulkan()); | |
| 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 |