| // 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 <vector> |
| |
| #include "dawn/tests/unittests/validation/ValidationTest.h" |
| #include "dawn/utils/WGPUHelpers.h" |
| |
| namespace dawn { |
| namespace { |
| |
| class TexelBufferValidationTest : public ValidationTest { |
| protected: |
| wgpu::Buffer CreateTexelBuffer(uint64_t size, wgpu::BufferUsage usage) { |
| wgpu::BufferDescriptor desc; |
| desc.size = size; |
| desc.usage = usage; |
| return device.CreateBuffer(&desc); |
| } |
| |
| struct TexelBufferLayoutDescriptor { |
| wgpu::TexelBufferBindingLayout layout; |
| wgpu::BindGroupLayoutEntry entry; |
| wgpu::BindGroupLayoutDescriptor desc; |
| |
| TexelBufferLayoutDescriptor(wgpu::TexelBufferAccess access, |
| wgpu::ShaderStage visibility, |
| wgpu::TextureFormat format, |
| uint32_t bindingArraySize = 0) { |
| layout = {}; |
| layout.access = access; |
| layout.format = format; |
| |
| entry = {}; |
| entry.binding = 0; |
| entry.visibility = visibility; |
| entry.bindingArraySize = bindingArraySize; |
| entry.nextInChain = &layout; |
| |
| desc = {}; |
| desc.entryCount = 1; |
| desc.entries = &entry; |
| } |
| }; |
| }; |
| |
| // Creation succeeds when the feature is enabled. |
| TEST_F(TexelBufferValidationTest, CreationSuccess) { |
| wgpu::BufferDescriptor desc; |
| desc.size = 4; |
| desc.usage = wgpu::BufferUsage::TexelBuffer; |
| device.CreateBuffer(&desc); |
| } |
| |
| // Mappable usages require BufferMapExtendedUsages when combined with TexelBuffer. |
| TEST_F(TexelBufferValidationTest, MappableUsageRequiresExtendedFeature) { |
| wgpu::BufferDescriptor desc; |
| desc.size = 4; |
| desc.usage = wgpu::BufferUsage::TexelBuffer | wgpu::BufferUsage::MapRead; |
| ASSERT_DEVICE_ERROR(device.CreateBuffer(&desc)); |
| |
| desc.usage = wgpu::BufferUsage::TexelBuffer | wgpu::BufferUsage::MapWrite; |
| ASSERT_DEVICE_ERROR(device.CreateBuffer(&desc)); |
| } |
| |
| // bindingArraySize > 1 is invalid. |
| TEST_F(TexelBufferValidationTest, BindingArraySize) { |
| TexelBufferLayoutDescriptor helper(wgpu::TexelBufferAccess::ReadOnly, |
| wgpu::ShaderStage::Fragment, wgpu::TextureFormat::RGBA8Uint, |
| 2); |
| ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&helper.desc)); |
| } |
| |
| // Vertex visibility requires read-only access. |
| TEST_F(TexelBufferValidationTest, VertexVisibilityRequiresReadOnly) { |
| TexelBufferLayoutDescriptor helper(wgpu::TexelBufferAccess::ReadWrite, |
| wgpu::ShaderStage::Vertex, wgpu::TextureFormat::RGBA8Uint); |
| ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&helper.desc)); |
| } |
| |
| // Access must not be undefined. |
| TEST_F(TexelBufferValidationTest, UndefinedLayoutAccess) { |
| TexelBufferLayoutDescriptor helper(wgpu::TexelBufferAccess::Undefined, |
| wgpu::ShaderStage::Fragment, wgpu::TextureFormat::RGBA8Uint); |
| ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&helper.desc)); |
| } |
| |
| // Format must not be undefined. |
| TEST_F(TexelBufferValidationTest, UndefinedLayoutFormat) { |
| TexelBufferLayoutDescriptor helper(wgpu::TexelBufferAccess::ReadOnly, |
| wgpu::ShaderStage::Fragment, wgpu::TextureFormat::Undefined); |
| ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&helper.desc)); |
| } |
| |
| // BindingInitializationHelper should chain a TexelBufferBindingEntry when initialized |
| // with a TexelBufferView. |
| TEST_F(TexelBufferValidationTest, BindingHelperChainsTexelBufferBindingEntry) { |
| constexpr uint64_t kSize = 4 * 4; |
| wgpu::Buffer buffer = CreateTexelBuffer(kSize, wgpu::BufferUsage::TexelBuffer); |
| |
| wgpu::TexelBufferViewDescriptor viewDesc; |
| viewDesc.format = wgpu::TextureFormat::RGBA8Uint; |
| viewDesc.offset = 0; |
| viewDesc.size = kSize; |
| wgpu::TexelBufferView view = buffer.CreateTexelView(&viewDesc); |
| |
| utils::BindingInitializationHelper helper(0, view); |
| wgpu::BindGroupEntry entry = helper.GetAsBinding(); |
| |
| EXPECT_EQ(entry.buffer, nullptr); |
| EXPECT_EQ(entry.textureView, nullptr); |
| |
| ASSERT_NE(entry.nextInChain, nullptr); |
| EXPECT_EQ(entry.nextInChain->sType, wgpu::SType::TexelBufferBindingEntry); |
| |
| const auto* texelEntry = |
| reinterpret_cast<const wgpu::TexelBufferBindingEntry*>(entry.nextInChain); |
| EXPECT_EQ(texelEntry->texelBufferView.Get(), view.Get()); |
| } |
| |
| // The format of a bound texel buffer view must match the layout. |
| TEST_F(TexelBufferValidationTest, ViewFormatMustMatchLayout) { |
| wgpu::BufferDescriptor desc; |
| desc.size = 256; |
| desc.usage = wgpu::BufferUsage::TexelBuffer; |
| wgpu::Buffer buffer = device.CreateBuffer(&desc); |
| |
| wgpu::TexelBufferViewDescriptor viewDesc = {}; |
| viewDesc.format = wgpu::TextureFormat::RGBA8Uint; |
| viewDesc.offset = 0; |
| viewDesc.size = 256; |
| wgpu::TexelBufferView view = buffer.CreateTexelView(&viewDesc); |
| |
| wgpu::TexelBufferBindingLayout layout = {}; |
| layout.access = wgpu::TexelBufferAccess::ReadOnly; |
| layout.format = wgpu::TextureFormat::R32Uint; |
| |
| wgpu::BindGroupLayout bgl = |
| utils::MakeBindGroupLayout(device, {{0, wgpu::ShaderStage::Compute, &layout}}); |
| |
| ASSERT_DEVICE_ERROR(utils::MakeBindGroup(device, bgl, {{0, view}})); |
| } |
| |
| // Read-write texel buffer bindings require the buffer to also have STORAGE usage. |
| TEST_F(TexelBufferValidationTest, ReadWriteBindingRequiresStorageUsage) { |
| wgpu::BufferDescriptor desc; |
| desc.size = 256; |
| desc.usage = wgpu::BufferUsage::TexelBuffer; |
| wgpu::Buffer buffer = device.CreateBuffer(&desc); |
| |
| wgpu::TexelBufferViewDescriptor viewDesc = {}; |
| viewDesc.format = wgpu::TextureFormat::R32Uint; |
| viewDesc.offset = 0; |
| viewDesc.size = 4; |
| wgpu::TexelBufferView view = buffer.CreateTexelView(&viewDesc); |
| |
| wgpu::TexelBufferBindingLayout layout = {}; |
| layout.access = wgpu::TexelBufferAccess::ReadWrite; |
| layout.format = wgpu::TextureFormat::R32Uint; |
| |
| wgpu::BindGroupLayout bgl = |
| utils::MakeBindGroupLayout(device, {{0, wgpu::ShaderStage::Compute, &layout}}); |
| |
| ASSERT_DEVICE_ERROR(utils::MakeBindGroup(device, bgl, {{0, view}})); |
| } |
| |
| // Binding a TexelBuffer to a texture binding slot fails. |
| TEST_F(TexelBufferValidationTest, TexelBufferCannotBindToTextureSlot) { |
| wgpu::BindGroupLayoutEntry textureEntry = {}; |
| textureEntry.binding = 0; |
| textureEntry.visibility = wgpu::ShaderStage::Compute; |
| textureEntry.texture.sampleType = wgpu::TextureSampleType::Float; |
| textureEntry.texture.viewDimension = wgpu::TextureViewDimension::e2D; |
| |
| wgpu::BindGroupLayoutDescriptor bglDesc = {}; |
| bglDesc.entryCount = 1; |
| bglDesc.entries = &textureEntry; |
| wgpu::BindGroupLayout bgl = device.CreateBindGroupLayout(&bglDesc); |
| |
| wgpu::Buffer buffer = CreateTexelBuffer(4, wgpu::BufferUsage::TexelBuffer); |
| wgpu::TexelBufferViewDescriptor viewDesc = {}; |
| viewDesc.format = wgpu::TextureFormat::R32Uint; |
| wgpu::TexelBufferView view = buffer.CreateTexelView(&viewDesc); |
| |
| wgpu::TexelBufferBindingEntry texelEntry = {}; |
| texelEntry.texelBufferView = view; |
| |
| wgpu::BindGroupEntry bgEntry = {}; |
| bgEntry.binding = 0; |
| bgEntry.nextInChain = &texelEntry; |
| |
| wgpu::BindGroupDescriptor bgDesc = {}; |
| bgDesc.layout = bgl; |
| bgDesc.entryCount = 1; |
| bgDesc.entries = &bgEntry; |
| |
| ASSERT_DEVICE_ERROR(device.CreateBindGroup(&bgDesc)); |
| } |
| |
| // Check that TexelBufferBindingEntry cannot be chained with something else. |
| TEST_F(TexelBufferValidationTest, AdditionalChain) { |
| // Make the BGL using a texel buffer. |
| wgpu::TexelBufferBindingLayout layout = {}; |
| layout.access = wgpu::TexelBufferAccess::ReadWrite; |
| layout.format = wgpu::TextureFormat::R32Uint; |
| |
| wgpu::BindGroupLayout bgl = |
| utils::MakeBindGroupLayout(device, {{0, wgpu::ShaderStage::Compute, &layout}}); |
| |
| // Make the texel buffer |
| wgpu::BufferDescriptor bufferDesc; |
| bufferDesc.size = 256; |
| bufferDesc.usage = wgpu::BufferUsage::TexelBuffer | wgpu::BufferUsage::Storage; |
| wgpu::Buffer buffer = device.CreateBuffer(&bufferDesc); |
| |
| wgpu::TexelBufferViewDescriptor viewDesc = {}; |
| viewDesc.format = wgpu::TextureFormat::R32Uint; |
| viewDesc.offset = 0; |
| viewDesc.size = 4; |
| wgpu::TexelBufferView view = buffer.CreateTexelView(&viewDesc); |
| |
| // Make the texel buffer binding. |
| wgpu::TexelBufferBindingEntry texelEntry = {}; |
| texelEntry.texelBufferView = view; |
| |
| wgpu::BindGroupEntry bgEntry = {}; |
| bgEntry.binding = 0; |
| bgEntry.nextInChain = &texelEntry; |
| |
| wgpu::BindGroupDescriptor bgDesc = {}; |
| bgDesc.layout = bgl; |
| bgDesc.entryCount = 1; |
| bgDesc.entries = &bgEntry; |
| |
| // Success case, having only a texel buffer chained is valid. |
| device.CreateBindGroup(&bgDesc); |
| |
| // Error case, an external texture is also chained. |
| wgpu::ExternalTexture externalTexture; |
| { |
| wgpu::TextureDescriptor plane0Desc; |
| plane0Desc.size = {1, 1}; |
| plane0Desc.format = wgpu::TextureFormat::RGBA8Unorm; |
| plane0Desc.usage = wgpu::TextureUsage::TextureBinding; |
| wgpu::Texture plane0 = device.CreateTexture(&plane0Desc); |
| |
| std::array<float, 12> placeholderConstants; |
| |
| wgpu::ExternalTextureDescriptor desc; |
| desc.yuvToRgbConversionMatrix = placeholderConstants.data(); |
| desc.gamutConversionMatrix = placeholderConstants.data(); |
| desc.srcTransferFunctionParameters = placeholderConstants.data(); |
| desc.dstTransferFunctionParameters = placeholderConstants.data(); |
| desc.cropSize = {1, 1}; |
| desc.apparentSize = {1, 1}; |
| desc.plane0 = plane0.CreateView(); |
| externalTexture = device.CreateExternalTexture(&desc); |
| } |
| |
| wgpu::ExternalTextureBindingEntry externalTextureEntry; |
| externalTextureEntry.externalTexture = externalTexture; |
| |
| texelEntry.nextInChain = &externalTextureEntry; |
| ASSERT_DEVICE_ERROR(device.CreateBindGroup(&bgDesc)); |
| } |
| |
| class TexelBufferValidationWithExtendedMapTest : public TexelBufferValidationTest { |
| protected: |
| void SetUp() override { |
| DAWN_SKIP_TEST_IF(UsesWire()); |
| TexelBufferValidationTest::SetUp(); |
| DAWN_SKIP_TEST_IF(!adapter.HasFeature(wgpu::FeatureName::BufferMapExtendedUsages)); |
| } |
| std::vector<wgpu::FeatureName> GetRequiredFeatures() override { |
| return {wgpu::FeatureName::BufferMapExtendedUsages}; |
| } |
| }; |
| |
| // When BufferMapExtendedUsages is enabled, MapRead and MapWrite can be combined |
| // with TexelBuffer usage. |
| TEST_F(TexelBufferValidationWithExtendedMapTest, MappableUsageWithFeatureEnabled) { |
| wgpu::BufferDescriptor desc; |
| desc.size = 4; |
| |
| desc.usage = wgpu::BufferUsage::TexelBuffer | wgpu::BufferUsage::MapRead; |
| device.CreateBuffer(&desc); |
| |
| desc.usage = wgpu::BufferUsage::TexelBuffer | wgpu::BufferUsage::MapWrite; |
| device.CreateBuffer(&desc); |
| } |
| |
| class TexelBufferFeatureDisabledTest : public ValidationTest { |
| protected: |
| void SetUp() override { |
| std::vector<const char*> features = {"texel_buffers"}; |
| wgpu::DawnWGSLBlocklist blocklist; |
| blocklist.blocklistedFeatureCount = features.size(); |
| blocklist.blocklistedFeatures = features.data(); |
| |
| wgpu::DawnTogglesDescriptor togglesDesc; |
| togglesDesc.nextInChain = &blocklist; |
| |
| wgpu::InstanceDescriptor nativeDesc; |
| nativeDesc.nextInChain = &togglesDesc; |
| |
| wgpu::DawnWireWGSLControl wgslControl; |
| wgslControl.nextInChain = &blocklist; |
| wgslControl.enableExperimental = false; |
| wgslControl.enableUnsafe = false; |
| wgslControl.enableTesting = true; |
| |
| wgpu::InstanceDescriptor wireDesc; |
| wireDesc.nextInChain = &wgslControl; |
| |
| ValidationTest::SetUp(&nativeDesc, &wireDesc); |
| } |
| }; |
| |
| // Creating a buffer with texel buffer bit without enabling the feature fails. |
| TEST_F(TexelBufferFeatureDisabledTest, BufferRequiresFeature) { |
| wgpu::BufferDescriptor desc; |
| desc.size = 4; |
| desc.usage = wgpu::BufferUsage::TexelBuffer; |
| ASSERT_DEVICE_ERROR(device.CreateBuffer(&desc)); |
| } |
| |
| // Creating a texel buffer layout without enabling the feature fails. |
| TEST_F(TexelBufferFeatureDisabledTest, LayoutRequiresFeature) { |
| wgpu::TexelBufferBindingLayout layout = {}; |
| layout.access = wgpu::TexelBufferAccess::ReadOnly; |
| layout.format = wgpu::TextureFormat::RGBA8Uint; |
| |
| wgpu::BindGroupLayoutEntry entry = {}; |
| entry.binding = 0; |
| entry.visibility = wgpu::ShaderStage::Fragment; |
| entry.nextInChain = &layout; |
| |
| wgpu::BindGroupLayoutDescriptor desc = {}; |
| desc.entryCount = 1; |
| desc.entries = &entry; |
| |
| ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&desc)); |
| } |
| |
| } // anonymous namespace |
| } // namespace dawn |