| // Copyright 2026 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 "dawn/tests/DawnTest.h" |
| #include "dawn/utils/WGPUHelpers.h" |
| |
| namespace dawn { |
| namespace { |
| |
| class TexelBufferTests : public DawnTest { |
| protected: |
| void SetUp() override { |
| DawnTest::SetUp(); |
| DAWN_TEST_UNSUPPORTED_IF( |
| !GetInstance().HasWGSLLanguageFeature(wgpu::WGSLLanguageFeatureName::TexelBuffers)); |
| } |
| }; |
| |
| // Verify that a read-only texel buffer binding can be read in a compute shader. |
| TEST_P(TexelBufferTests, ComputeReadOnly) { |
| uint32_t initValue = 42u; |
| wgpu::Buffer texelBuffer = utils::CreateBufferFromData( |
| device, |
| wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::TexelBuffer, |
| {initValue}); |
| |
| wgpu::TexelBufferViewDescriptor viewDesc = {}; |
| viewDesc.format = wgpu::TextureFormat::R32Uint; |
| wgpu::TexelBufferView view = texelBuffer.CreateTexelView(&viewDesc); |
| |
| wgpu::BufferDescriptor outDesc; |
| outDesc.size = sizeof(uint32_t); |
| outDesc.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::Storage; |
| wgpu::Buffer out = device.CreateBuffer(&outDesc); |
| |
| const char* shaderSrc = R"( |
| requires texel_buffers; |
| |
| @group(0) @binding(0) var<storage, read_write> result : array<u32, 1>; |
| @group(0) @binding(1) var t : texel_buffer<r32uint, read>; |
| |
| @compute @workgroup_size(1) |
| fn main() { |
| result[0] = textureLoad(t, 0u).x; |
| } |
| )"; |
| |
| wgpu::ComputePipelineDescriptor csDesc; |
| csDesc.compute.module = utils::CreateShaderModule(device, shaderSrc); |
| wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&csDesc); |
| |
| wgpu::BindGroup bindGroup = |
| utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), {{0, out}, {1, view}}); |
| |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); |
| pass.SetPipeline(pipeline); |
| pass.SetBindGroup(0, bindGroup); |
| pass.DispatchWorkgroups(1); |
| pass.End(); |
| |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| |
| EXPECT_BUFFER_U32_EQ(initValue, out, 0); |
| } |
| |
| // Verify that a read-write texel buffer binding can be written in a compute shader. |
| TEST_P(TexelBufferTests, ComputeReadWrite) { |
| uint32_t zero = 0u; |
| wgpu::Buffer texelBuffer = |
| utils::CreateBufferFromData(device, |
| wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst | |
| wgpu::BufferUsage::TexelBuffer | wgpu::BufferUsage::Storage, |
| {zero}); |
| |
| wgpu::TexelBufferViewDescriptor viewDesc = {}; |
| viewDesc.format = wgpu::TextureFormat::R32Uint; |
| wgpu::TexelBufferView view = texelBuffer.CreateTexelView(&viewDesc); |
| |
| const char* shaderSrc = R"( |
| requires texel_buffers; |
| |
| @group(0) @binding(0) var t : texel_buffer<r32uint, read_write>; |
| |
| @compute @workgroup_size(1) |
| fn main() { |
| textureStore(t, 0u, vec4<u32>(99u, 0u, 0u, 0u)); |
| } |
| )"; |
| |
| wgpu::ComputePipelineDescriptor csDesc; |
| csDesc.compute.module = utils::CreateShaderModule(device, shaderSrc); |
| wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&csDesc); |
| |
| wgpu::BindGroup bindGroup = |
| utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), {{0, view}}); |
| |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); |
| pass.SetPipeline(pipeline); |
| pass.SetBindGroup(0, bindGroup); |
| pass.DispatchWorkgroups(1); |
| pass.End(); |
| |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| |
| EXPECT_BUFFER_U32_EQ(99u, texelBuffer, 0); |
| } |
| |
| // Verify that texel buffers can supply vertex positions for a render pipeline. |
| TEST_P(TexelBufferTests, RenderVertexPositions) { |
| // Positions for a full screen triangle stored as RGBA32 floats. |
| wgpu::Buffer texelBuffer = utils::CreateBufferFromData( |
| device, |
| wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::TexelBuffer, |
| { |
| -1.f, -1.f, 0.f, 0.f, // Vertex 0 |
| 3.f, -1.f, 0.f, 0.f, // Vertex 1 |
| -1.f, 3.f, 0.f, 0.f // Vertex 2 |
| }); |
| |
| wgpu::TexelBufferViewDescriptor viewDesc = {}; |
| viewDesc.format = wgpu::TextureFormat::RGBA32Float; |
| wgpu::TexelBufferView view = texelBuffer.CreateTexelView(&viewDesc); |
| |
| const char* shaderSrc = R"( |
| requires texel_buffers; |
| |
| @group(0) @binding(0) var positions : texel_buffer<rgba32float, read>; |
| |
| struct VSOut { |
| @builtin(position) pos : vec4<f32> |
| }; |
| |
| @vertex |
| fn vs_main(@builtin(vertex_index) vi : u32) -> VSOut { |
| let xy = textureLoad(positions, vi).xy; |
| var out : VSOut; |
| out.pos = vec4<f32>(xy, 0.0, 1.0); |
| return out; |
| } |
| |
| @fragment |
| fn fs_main() -> @location(0) vec4<f32> { |
| return vec4<f32>(1.0, 0.0, 0.0, 1.0); |
| } |
| )"; |
| |
| utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, 1, 1); |
| |
| wgpu::RenderPipelineDescriptor desc; |
| desc.vertex.module = utils::CreateShaderModule(device, shaderSrc); |
| desc.vertex.entryPoint = "vs_main"; |
| |
| wgpu::FragmentState fragment = {}; |
| fragment.module = desc.vertex.module; |
| fragment.entryPoint = "fs_main"; |
| wgpu::ColorTargetState colorTarget = {}; |
| colorTarget.format = renderPass.colorFormat; |
| fragment.targetCount = 1; |
| fragment.targets = &colorTarget; |
| desc.fragment = &fragment; |
| desc.primitive.topology = wgpu::PrimitiveTopology::TriangleList; |
| |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&desc); |
| |
| wgpu::BindGroup bindGroup = |
| utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), {{0, view}}); |
| |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo); |
| pass.SetPipeline(pipeline); |
| pass.SetBindGroup(0, bindGroup); |
| pass.Draw(3); |
| pass.End(); |
| |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| |
| EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8::kRed, renderPass.color, 0, 0); |
| } |
| |
| // Verify that fragment shaders can read from texel buffers as color palettes. |
| TEST_P(TexelBufferTests, RenderFragmentColorPalette) { |
| // Two colors stored as RGBA8 values representing a simple palette. |
| wgpu::Buffer texelBuffer = utils::CreateBufferFromData( |
| device, |
| wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::TexelBuffer, |
| {utils::RGBA8::kRed, utils::RGBA8::kGreen}); |
| |
| wgpu::TexelBufferViewDescriptor viewDesc = {}; |
| viewDesc.format = wgpu::TextureFormat::RGBA8Unorm; |
| wgpu::TexelBufferView view = texelBuffer.CreateTexelView(&viewDesc); |
| |
| const char* shaderSrc = R"( |
| requires texel_buffers; |
| |
| @group(0) @binding(0) var colors : texel_buffer<rgba8unorm, read>; |
| |
| @vertex |
| fn vs_main(@builtin(vertex_index) vi : u32) -> @builtin(position) vec4<f32> { |
| var pos = array<vec2<f32>, 3>( |
| vec2<f32>(-1.0, -1.0), |
| vec2<f32>(3.0, -1.0), |
| vec2<f32>(-1.0, 3.0) |
| ); |
| return vec4<f32>(pos[vi], 0.0, 1.0); |
| } |
| |
| @fragment |
| fn fs_main(@builtin(position) coord : vec4<f32>) -> @location(0) vec4<f32> { |
| let idx = u32(floor(coord.x)); |
| return textureLoad(colors, idx); |
| } |
| )"; |
| |
| utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, 2, 1); |
| |
| wgpu::RenderPipelineDescriptor desc; |
| desc.vertex.module = utils::CreateShaderModule(device, shaderSrc); |
| desc.vertex.entryPoint = "vs_main"; |
| |
| wgpu::FragmentState fragment = {}; |
| fragment.module = desc.vertex.module; |
| fragment.entryPoint = "fs_main"; |
| wgpu::ColorTargetState colorTarget = {}; |
| colorTarget.format = renderPass.colorFormat; |
| fragment.targetCount = 1; |
| fragment.targets = &colorTarget; |
| desc.fragment = &fragment; |
| desc.primitive.topology = wgpu::PrimitiveTopology::TriangleList; |
| |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&desc); |
| |
| wgpu::BindGroup bindGroup = |
| utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), {{0, view}}); |
| |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo); |
| pass.SetPipeline(pipeline); |
| pass.SetBindGroup(0, bindGroup); |
| pass.Draw(3); |
| pass.End(); |
| |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| |
| EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8::kRed, renderPass.color, 0, 0); |
| EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8::kGreen, renderPass.color, 1, 0); |
| } |
| |
| // Verify that multiple elements can be incremented in a read-write texel buffer. |
| TEST_P(TexelBufferTests, ComputeIncrementValues) { |
| wgpu::Buffer texelBuffer = |
| utils::CreateBufferFromData(device, |
| wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst | |
| wgpu::BufferUsage::TexelBuffer | wgpu::BufferUsage::Storage, |
| {0u, 1u, 2u, 3u}); |
| |
| wgpu::TexelBufferViewDescriptor viewDesc = {}; |
| viewDesc.format = wgpu::TextureFormat::R32Uint; |
| wgpu::TexelBufferView view = texelBuffer.CreateTexelView(&viewDesc); |
| |
| const char* shaderSrc = R"( |
| requires texel_buffers; |
| |
| @group(0) @binding(0) var t : texel_buffer<r32uint, read_write>; |
| |
| @compute @workgroup_size(4) |
| fn main(@builtin(local_invocation_id) lid : vec3<u32>) { |
| let i = lid.x; |
| let value = textureLoad(t, i).x; |
| textureStore(t, i, vec4<u32>(value + 1u, 0u, 0u, 0u)); |
| } |
| )"; |
| |
| wgpu::ComputePipelineDescriptor csDesc; |
| csDesc.compute.module = utils::CreateShaderModule(device, shaderSrc); |
| wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&csDesc); |
| |
| wgpu::BindGroup bindGroup = |
| utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), {{0, view}}); |
| |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); |
| pass.SetPipeline(pipeline); |
| pass.SetBindGroup(0, bindGroup); |
| pass.DispatchWorkgroups(1); |
| pass.End(); |
| |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| |
| std::array<uint32_t, 4> expected = {1u, 2u, 3u, 4u}; |
| EXPECT_BUFFER_U32_RANGE_EQ(expected.data(), texelBuffer, 0, expected.size()); |
| } |
| |
| // Use a texel buffer as a lookup table for indices stored in a storage buffer. |
| TEST_P(TexelBufferTests, ComputePaletteLookup) { |
| // Palette of 4 colors stored in a texel buffer. |
| wgpu::Buffer palette = utils::CreateBufferFromData( |
| device, wgpu::BufferUsage::TexelBuffer | wgpu::BufferUsage::CopyDst, {0u, 1u, 2u, 3u}); |
| |
| wgpu::TexelBufferViewDescriptor paletteViewDesc = {}; |
| paletteViewDesc.format = wgpu::TextureFormat::R32Uint; |
| wgpu::TexelBufferView paletteView = palette.CreateTexelView(&paletteViewDesc); |
| |
| // Indices selecting colors in the palette. |
| wgpu::Buffer indices = |
| utils::CreateBufferFromData(device, wgpu::BufferUsage::Storage, {2u, 0u, 3u, 1u}); |
| |
| wgpu::BufferDescriptor resultDesc = {}; |
| resultDesc.size = sizeof(uint32_t) * 4; |
| resultDesc.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::Storage; |
| wgpu::Buffer result = device.CreateBuffer(&resultDesc); |
| |
| const char* shaderSrc = R"( |
| requires texel_buffers; |
| |
| @group(0) @binding(0) var<storage, read> indices : array<u32, 4>; |
| @group(0) @binding(1) var<storage, read_write> result : array<u32, 4>; |
| @group(0) @binding(2) var palette : texel_buffer<r32uint, read>; |
| |
| @compute @workgroup_size(4) |
| fn main(@builtin(local_invocation_id) lid : vec3<u32>) { |
| let i = lid.x; |
| let idx = indices[i]; |
| result[i] = textureLoad(palette, idx).x; |
| } |
| )"; |
| |
| wgpu::ComputePipelineDescriptor csDesc; |
| csDesc.compute.module = utils::CreateShaderModule(device, shaderSrc); |
| wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&csDesc); |
| |
| wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), |
| {{0, indices}, {1, result}, {2, paletteView}}); |
| |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); |
| pass.SetPipeline(pipeline); |
| pass.SetBindGroup(0, bindGroup); |
| pass.DispatchWorkgroups(1); |
| pass.End(); |
| |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| |
| std::array<uint32_t, 4> expected = {2u, 0u, 3u, 1u}; |
| EXPECT_BUFFER_U32_RANGE_EQ(expected.data(), result, 0, expected.size()); |
| } |
| |
| // Use a texel buffer as a source for colors in a vertex shader. |
| TEST_P(TexelBufferTests, RenderVertexShaderColorLookup) { |
| // Palette of three colors encoded as RGBA8Unorm texels. |
| wgpu::Buffer texelBuffer = utils::CreateBufferFromData( |
| device, |
| wgpu::BufferUsage::TexelBuffer | wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::CopySrc, |
| {utils::RGBA8::kRed, utils::RGBA8::kGreen, utils::RGBA8::kBlue}); |
| |
| wgpu::TexelBufferViewDescriptor viewDesc = {}; |
| viewDesc.format = wgpu::TextureFormat::RGBA8Unorm; |
| wgpu::TexelBufferView view = texelBuffer.CreateTexelView(&viewDesc); |
| |
| // Set up a 3x1 render target so each point maps to a single pixel. |
| utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, 3, 1); |
| |
| const char* shaderSrc = R"( |
| requires texel_buffers; |
| |
| @group(0) @binding(0) var palette : texel_buffer<rgba8unorm, read>; |
| |
| struct VSOut { |
| @builtin(position) position : vec4<f32>, |
| @location(0) color : vec4<f32>, |
| }; |
| |
| @vertex |
| fn vs_main(@builtin(vertex_index) vertexIndex : u32) -> VSOut { |
| var out : VSOut; |
| let x = -1.0 + (f32(vertexIndex) + 0.5) * 2.0 / 3.0; |
| out.position = vec4<f32>(x, 0.0, 0.0, 1.0); |
| out.color = textureLoad(palette, vertexIndex); |
| return out; |
| } |
| |
| @fragment |
| fn fs_main(@location(0) color : vec4<f32>) -> @location(0) vec4<f32> { |
| return color; |
| } |
| )"; |
| |
| wgpu::RenderPipelineDescriptor desc; |
| desc.vertex.module = utils::CreateShaderModule(device, shaderSrc); |
| desc.vertex.entryPoint = "vs_main"; |
| |
| wgpu::FragmentState fragment = {}; |
| fragment.module = desc.vertex.module; |
| fragment.entryPoint = "fs_main"; |
| wgpu::ColorTargetState colorTarget = {}; |
| colorTarget.format = renderPass.colorFormat; |
| fragment.targetCount = 1; |
| fragment.targets = &colorTarget; |
| desc.fragment = &fragment; |
| desc.primitive.topology = wgpu::PrimitiveTopology::PointList; |
| |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&desc); |
| |
| wgpu::BindGroup bindGroup = |
| utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), {{0, view}}); |
| |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo); |
| pass.SetPipeline(pipeline); |
| pass.SetBindGroup(0, bindGroup); |
| pass.Draw(3); |
| pass.End(); |
| |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| |
| EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8::kRed, renderPass.color, 0, 0); |
| EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8::kGreen, renderPass.color, 1, 0); |
| EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8::kBlue, renderPass.color, 2, 0); |
| } |
| |
| // Copy data from a texel buffer into a storage texture using a compute shader. |
| TEST_P(TexelBufferTests, CopyToStorageTexture) { |
| // Four RGBA colors. |
| wgpu::Buffer texelBuffer = utils::CreateBufferFromData( |
| device, |
| wgpu::BufferUsage::TexelBuffer | wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::CopySrc, |
| {utils::RGBA8::kRed, utils::RGBA8::kGreen, utils::RGBA8::kBlue, utils::RGBA8::kWhite}); |
| |
| wgpu::TexelBufferViewDescriptor viewDesc = {}; |
| viewDesc.format = wgpu::TextureFormat::RGBA8Unorm; |
| wgpu::TexelBufferView view = texelBuffer.CreateTexelView(&viewDesc); |
| |
| wgpu::TextureDescriptor texDesc; |
| texDesc.size = {4, 1, 1}; |
| texDesc.format = wgpu::TextureFormat::RGBA8Unorm; |
| texDesc.usage = wgpu::TextureUsage::StorageBinding | wgpu::TextureUsage::CopySrc; |
| wgpu::Texture texture = device.CreateTexture(&texDesc); |
| |
| const char* shaderSrc = R"( |
| requires texel_buffers; |
| |
| @group(0) @binding(0) var src : texel_buffer<rgba8unorm, read>; |
| @group(0) @binding(1) var dst : texture_storage_2d<rgba8unorm, write>; |
| |
| @compute @workgroup_size(1) |
| fn main(@builtin(global_invocation_id) gid : vec3<u32>) { |
| let c = textureLoad(src, gid.x); |
| textureStore(dst, vec2<u32>(gid.x, 0u), c); |
| } |
| )"; |
| |
| wgpu::ComputePipelineDescriptor csDesc; |
| csDesc.compute.module = utils::CreateShaderModule(device, shaderSrc); |
| wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&csDesc); |
| |
| wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), |
| {{0, view}, {1, texture.CreateView()}}); |
| |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); |
| pass.SetPipeline(pipeline); |
| pass.SetBindGroup(0, bindGroup); |
| pass.DispatchWorkgroups(4); |
| pass.End(); |
| |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| |
| EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8::kRed, texture, 0, 0); |
| EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8::kGreen, texture, 1, 0); |
| EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8::kBlue, texture, 2, 0); |
| EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8::kWhite, texture, 3, 0); |
| } |
| |
| // Use one texel buffer to index another and write results to a third texel buffer. |
| TEST_P(TexelBufferTests, ChainedLookup) { |
| wgpu::Buffer indices = utils::CreateBufferFromData( |
| device, wgpu::BufferUsage::TexelBuffer | wgpu::BufferUsage::CopyDst, {2u, 0u, 3u, 1u}); |
| |
| wgpu::Buffer palette = utils::CreateBufferFromData( |
| device, wgpu::BufferUsage::TexelBuffer | wgpu::BufferUsage::CopyDst, {0u, 1u, 2u, 3u}); |
| |
| wgpu::Buffer result = |
| utils::CreateBufferFromData(device, |
| wgpu::BufferUsage::TexelBuffer | wgpu::BufferUsage::Storage | |
| wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst, |
| {0u, 0u, 0u, 0u}); |
| |
| wgpu::TexelBufferViewDescriptor viewDesc = {}; |
| viewDesc.format = wgpu::TextureFormat::R32Uint; |
| wgpu::TexelBufferView indicesView = indices.CreateTexelView(&viewDesc); |
| wgpu::TexelBufferView paletteView = palette.CreateTexelView(&viewDesc); |
| wgpu::TexelBufferView resultView = result.CreateTexelView(&viewDesc); |
| |
| const char* shaderSrc = R"( |
| requires texel_buffers; |
| |
| @group(0) @binding(0) var indices : texel_buffer<r32uint, read>; |
| @group(0) @binding(1) var palette : texel_buffer<r32uint, read>; |
| @group(0) @binding(2) var output : texel_buffer<r32uint, read_write>; |
| |
| @compute @workgroup_size(4) |
| fn main(@builtin(local_invocation_id) lid : vec3<u32>) { |
| let i = lid.x; |
| let idx = textureLoad(indices, i).x; |
| let value = textureLoad(palette, idx).x; |
| textureStore(output, i, vec4<u32>(value, 0u, 0u, 0u)); |
| } |
| )"; |
| |
| wgpu::ComputePipelineDescriptor csDesc; |
| csDesc.compute.module = utils::CreateShaderModule(device, shaderSrc); |
| wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&csDesc); |
| |
| utils::BindingInitializationHelper indicesBinding(0, indicesView); |
| utils::BindingInitializationHelper paletteBinding(1, paletteView); |
| utils::BindingInitializationHelper resultBinding(2, resultView); |
| |
| wgpu::BindGroup bindGroup = utils::MakeBindGroup( |
| device, pipeline.GetBindGroupLayout(0), {indicesBinding, paletteBinding, resultBinding}); |
| |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); |
| pass.SetPipeline(pipeline); |
| pass.SetBindGroup(0, bindGroup); |
| pass.DispatchWorkgroups(1); |
| pass.End(); |
| |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| |
| std::array<uint32_t, 4> expected = {2u, 0u, 3u, 1u}; |
| EXPECT_BUFFER_U32_RANGE_EQ(expected.data(), result, 0, expected.size()); |
| } |
| |
| // Read from two texel buffers, add their values and write the results to a third |
| // texel buffer. |
| TEST_P(TexelBufferTests, AddTexelBuffers) { |
| wgpu::Buffer a = utils::CreateBufferFromData( |
| device, wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::TexelBuffer, {1u, 2u, 3u, 4u}); |
| wgpu::Buffer b = utils::CreateBufferFromData( |
| device, wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::TexelBuffer, {10u, 20u, 30u, 40u}); |
| wgpu::Buffer c = |
| utils::CreateBufferFromData(device, |
| wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst | |
| wgpu::BufferUsage::TexelBuffer | wgpu::BufferUsage::Storage, |
| {0u, 0u, 0u, 0u}); |
| |
| wgpu::TexelBufferViewDescriptor viewDesc = {}; |
| viewDesc.format = wgpu::TextureFormat::R32Uint; |
| wgpu::TexelBufferView aView = a.CreateTexelView(&viewDesc); |
| wgpu::TexelBufferView bView = b.CreateTexelView(&viewDesc); |
| wgpu::TexelBufferView cView = c.CreateTexelView(&viewDesc); |
| |
| const char* shaderSrc = R"( |
| requires texel_buffers; |
| |
| @group(0) @binding(0) var srcA : texel_buffer<r32uint, read>; |
| @group(0) @binding(1) var srcB : texel_buffer<r32uint, read>; |
| @group(0) @binding(2) var dst : texel_buffer<r32uint, read_write>; |
| |
| @compute @workgroup_size(4) |
| fn main(@builtin(local_invocation_id) lid : vec3<u32>) { |
| let i = lid.x; |
| let av = textureLoad(srcA, i).x; |
| let bv = textureLoad(srcB, i).x; |
| textureStore(dst, i, vec4<u32>(av + bv, 0u, 0u, 0u)); |
| } |
| )"; |
| |
| wgpu::TexelBufferBindingLayout layoutA = {}; |
| layoutA.format = wgpu::TextureFormat::R32Uint; |
| layoutA.access = wgpu::TexelBufferAccess::ReadOnly; |
| wgpu::TexelBufferBindingLayout layoutB = layoutA; |
| wgpu::TexelBufferBindingLayout layoutC = layoutA; |
| layoutC.access = wgpu::TexelBufferAccess::ReadWrite; |
| |
| wgpu::BindGroupLayout bgl = |
| utils::MakeBindGroupLayout(device, {{0, wgpu::ShaderStage::Compute, &layoutA}, |
| {1, wgpu::ShaderStage::Compute, &layoutB}, |
| {2, wgpu::ShaderStage::Compute, &layoutC}}); |
| wgpu::PipelineLayout pl = utils::MakePipelineLayout(device, {bgl}); |
| |
| wgpu::ComputePipelineDescriptor csDesc; |
| csDesc.layout = pl; |
| csDesc.compute.module = utils::CreateShaderModule(device, shaderSrc); |
| wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&csDesc); |
| |
| wgpu::BindGroup bindGroup = |
| utils::MakeBindGroup(device, bgl, {{0, aView}, {1, bView}, {2, cView}}); |
| |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); |
| pass.SetPipeline(pipeline); |
| pass.SetBindGroup(0, bindGroup); |
| pass.DispatchWorkgroups(1); |
| pass.End(); |
| |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| |
| std::array<uint32_t, 4> expected = {11u, 22u, 33u, 44u}; |
| EXPECT_BUFFER_U32_RANGE_EQ(expected.data(), c, 0, expected.size()); |
| } |
| |
| // Copy a range of texels between two texel buffer views using a buffer copy. |
| TEST_P(TexelBufferTests, CopyBetweenViews) { |
| wgpu::Buffer src = |
| utils::CreateBufferFromData(device, |
| wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst | |
| wgpu::BufferUsage::TexelBuffer | wgpu::BufferUsage::Storage, |
| {1u, 2u, 3u}); |
| |
| // dst is large enough to hold 256 bytes of padding followed by 3 uint32 values. |
| // The view uses offset=256 to satisfy kTexelBufferOffsetAlignment. |
| constexpr uint64_t kDstViewOffset = 256; |
| constexpr uint64_t kViewSize = 3 * sizeof(uint32_t); |
| wgpu::BufferDescriptor dstBufDesc = {}; |
| dstBufDesc.size = kDstViewOffset + kViewSize; |
| dstBufDesc.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst | |
| wgpu::BufferUsage::TexelBuffer | wgpu::BufferUsage::Storage; |
| wgpu::Buffer dst = device.CreateBuffer(&dstBufDesc); |
| |
| wgpu::TexelBufferViewDescriptor srcDesc = {}; |
| srcDesc.format = wgpu::TextureFormat::R32Uint; |
| srcDesc.offset = 0; |
| srcDesc.size = kViewSize; |
| wgpu::TexelBufferView srcView = src.CreateTexelView(&srcDesc); |
| |
| wgpu::TexelBufferViewDescriptor dstDesc = {}; |
| dstDesc.format = wgpu::TextureFormat::R32Uint; |
| dstDesc.offset = kDstViewOffset; |
| dstDesc.size = kViewSize; |
| wgpu::TexelBufferView dstView = dst.CreateTexelView(&dstDesc); |
| |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| encoder.CopyBufferToBuffer(src, srcDesc.offset, dst, dstDesc.offset, srcDesc.size); |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| |
| std::array<uint32_t, 3> expected = {1u, 2u, 3u}; |
| EXPECT_BUFFER_U32_RANGE_EQ(expected.data(), dst, dstDesc.offset, expected.size()); |
| } |
| |
| DAWN_INSTANTIATE_TEST(TexelBufferTests, VulkanBackend()); |
| |
| } // anonymous namespace |
| } // namespace dawn |