From a5f24e590ae0d0432dc9f95ec38022e752b12bd3 Mon Sep 17 00:00:00 2001 From: Austin Eng Date: Fri, 2 Jul 2021 02:29:40 +0000 Subject: [PATCH] Validate textures with filtering/non-filtering/comparison samplers Renames dawn_native::ComponentTypeBit to SampleTypeBit and makes the bitmask match wgpu::TextureSampleType. wgpu::TextureComponentType should be removed in a follow-up CL. The Format table is augmented with float/unfilterable-float information so that textures can be validated against the BGLEntry's TextureSampleType. EntryPointMetadata::ShaderBindingInfo no longer inherits BindingInfo because the two types are diverging further. Most notably, this CL reflects from Tint the supported SampleTypeBits for texture bindings. This bitset is validated against the bind group layout. Adds an isFiltering getter to SamplerBase. A filtering sampler must not be used with a non-filtering sampler binding. Lastly, the CL reflects sampler/texture pairs from Tint and validates an entrypoint against the pipeline layout that a filtering sampler is not used with an unfilterable-float texture binding. Bug: dawn:367 Change-Id: If9f2c0d8fbad5641c2ecc30615a3c68a6ed6150a Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/56521 Reviewed-by: Jiawei Shao Commit-Queue: Austin Eng --- src/dawn_native/BindGroup.cpp | 28 +- src/dawn_native/BindingInfo.h | 5 + src/dawn_native/Format.cpp | 187 ++++++----- src/dawn_native/Format.h | 21 +- src/dawn_native/PipelineLayout.cpp | 75 ++++- src/dawn_native/Sampler.cpp | 7 +- src/dawn_native/Sampler.h | 3 +- src/dawn_native/ShaderModule.cpp | 139 +++++--- src/dawn_native/ShaderModule.h | 32 +- src/dawn_native/SpirvUtils.cpp | 9 +- src/dawn_native/SpirvUtils.h | 3 +- .../end2end/CopyTextureForBrowserTests.cpp | 4 +- .../end2end/DepthStencilSamplingTests.cpp | 22 +- .../validation/BindGroupValidationTests.cpp | 297 ++++++++++++++---- .../GetBindGroupLayoutValidationTests.cpp | 122 ++++++- 15 files changed, 712 insertions(+), 242 deletions(-) diff --git a/src/dawn_native/BindGroup.cpp b/src/dawn_native/BindGroup.cpp index b4bbfd7038..bc43346c51 100644 --- a/src/dawn_native/BindGroup.cpp +++ b/src/dawn_native/BindGroup.cpp @@ -136,10 +136,10 @@ namespace dawn_native { TextureBase* texture = view->GetTexture(); switch (bindingInfo.bindingType) { case BindingInfoType::Texture: { - ComponentTypeBit supportedTypes = - texture->GetFormat().GetAspectInfo(aspect).supportedComponentTypes; - ComponentTypeBit requiredType = - SampleTypeToComponentTypeBit(bindingInfo.texture.sampleType); + SampleTypeBit supportedTypes = + texture->GetFormat().GetAspectInfo(aspect).supportedSampleTypes; + SampleTypeBit requiredType = + SampleTypeToSampleTypeBit(bindingInfo.texture.sampleType); if (!(texture->GetUsage() & wgpu::TextureUsage::Sampled)) { return DAWN_VALIDATION_ERROR("Texture binding usage mismatch"); @@ -193,15 +193,25 @@ namespace dawn_native { ASSERT(bindingInfo.bindingType == BindingInfoType::Sampler); switch (bindingInfo.sampler.type) { - case wgpu::SamplerBindingType::Filtering: case wgpu::SamplerBindingType::NonFiltering: - if (entry.sampler->HasCompareFunction()) { - return DAWN_VALIDATION_ERROR("Did not expect comparison sampler"); + if (entry.sampler->IsFiltering()) { + return DAWN_VALIDATION_ERROR( + "Filtering sampler is incompatible with non-filtering sampler " + "binding."); + } + DAWN_FALLTHROUGH; + case wgpu::SamplerBindingType::Filtering: + if (entry.sampler->IsComparison()) { + return DAWN_VALIDATION_ERROR( + "Comparison sampler is incompatible with non-comparison sampler " + "binding."); } break; case wgpu::SamplerBindingType::Comparison: - if (!entry.sampler->HasCompareFunction()) { - return DAWN_VALIDATION_ERROR("Expected comparison sampler"); + if (!entry.sampler->IsComparison()) { + return DAWN_VALIDATION_ERROR( + "Non-comparison sampler is imcompatible with comparison sampler " + "binding."); } break; default: diff --git a/src/dawn_native/BindingInfo.h b/src/dawn_native/BindingInfo.h index f4a1730f14..0d6cfc02ae 100644 --- a/src/dawn_native/BindingInfo.h +++ b/src/dawn_native/BindingInfo.h @@ -63,6 +63,11 @@ namespace dawn_native { StorageTextureBindingLayout storageTexture; }; + struct BindingSlot { + BindGroupIndex group; + BindingNumber binding; + }; + struct PerStageBindingCounts { uint32_t sampledTextureCount; uint32_t samplerCount; diff --git a/src/dawn_native/Format.cpp b/src/dawn_native/Format.cpp index 0bebc18e58..a60d3b94e6 100644 --- a/src/dawn_native/Format.cpp +++ b/src/dawn_native/Format.cpp @@ -26,55 +26,61 @@ namespace dawn_native { // Format // TODO(dawn:527): Remove when unused. - ComponentTypeBit ToComponentTypeBit(wgpu::TextureComponentType type) { + SampleTypeBit ToSampleTypeBit(wgpu::TextureComponentType type) { switch (type) { case wgpu::TextureComponentType::Float: + return SampleTypeBit::Float; case wgpu::TextureComponentType::Sint: + return SampleTypeBit::Sint; case wgpu::TextureComponentType::Uint: + return SampleTypeBit::Uint; case wgpu::TextureComponentType::DepthComparison: + return SampleTypeBit::Depth; + } + } + + SampleTypeBit SampleTypeToSampleTypeBit(wgpu::TextureSampleType sampleType) { + switch (sampleType) { + case wgpu::TextureSampleType::Float: + case wgpu::TextureSampleType::UnfilterableFloat: + case wgpu::TextureSampleType::Sint: + case wgpu::TextureSampleType::Uint: + case wgpu::TextureSampleType::Depth: + case wgpu::TextureSampleType::Undefined: // When the compiler complains that you need to add a case statement here, please // also add a corresponding static assert below! break; } - // Check that ComponentTypeBit bits are in the same position / order as the respective - // wgpu::TextureComponentType value. - static_assert(ComponentTypeBit::Float == - static_cast( - 1 << static_cast(wgpu::TextureComponentType::Float)), - ""); - static_assert(ComponentTypeBit::Uint == - static_cast( - 1 << static_cast(wgpu::TextureComponentType::Uint)), - ""); - static_assert(ComponentTypeBit::Sint == - static_cast( - 1 << static_cast(wgpu::TextureComponentType::Sint)), - ""); - static_assert( - ComponentTypeBit::DepthComparison == - static_cast( - 1 << static_cast(wgpu::TextureComponentType::DepthComparison)), - ""); - return static_cast(1 << static_cast(type)); - } - - ComponentTypeBit SampleTypeToComponentTypeBit(wgpu::TextureSampleType sampleType) { - switch (sampleType) { - case wgpu::TextureSampleType::Float: - case wgpu::TextureSampleType::UnfilterableFloat: - return ComponentTypeBit::Float; - case wgpu::TextureSampleType::Sint: - return ComponentTypeBit::Sint; - case wgpu::TextureSampleType::Uint: - return ComponentTypeBit::Uint; - case wgpu::TextureSampleType::Depth: - return ComponentTypeBit::DepthComparison; - case wgpu::TextureSampleType::Undefined: - UNREACHABLE(); + static_assert(static_cast(wgpu::TextureSampleType::Undefined) == 0, ""); + if (sampleType == wgpu::TextureSampleType::Undefined) { + return SampleTypeBit::None; } - // TODO(dawn:527): Ideally we can get this path to use that static_cast method as well. + // Check that SampleTypeBit bits are in the same position / order as the respective + // wgpu::TextureSampleType value. + static_assert(SampleTypeBit::Float == + static_cast( + 1 << (static_cast(wgpu::TextureSampleType::Float) - 1)), + ""); + static_assert( + SampleTypeBit::UnfilterableFloat == + static_cast( + 1 << (static_cast(wgpu::TextureSampleType::UnfilterableFloat) - 1)), + ""); + static_assert(SampleTypeBit::Uint == + static_cast( + 1 << (static_cast(wgpu::TextureSampleType::Uint) - 1)), + ""); + static_assert(SampleTypeBit::Sint == + static_cast( + 1 << (static_cast(wgpu::TextureSampleType::Sint) - 1)), + ""); + static_assert(SampleTypeBit::Depth == + static_cast( + 1 << (static_cast(wgpu::TextureSampleType::Depth) - 1)), + ""); + return static_cast(1 << (static_cast(sampleType) - 1)); } bool Format::IsColor() const { @@ -129,7 +135,8 @@ namespace dawn_native { FormatTable table; std::bitset formatsSet; - using Type = wgpu::TextureComponentType; + static constexpr SampleTypeBit kAnyFloat = + SampleTypeBit::Float | SampleTypeBit::UnfilterableFloat; auto AddFormat = [&table, &formatsSet](Format format) { size_t index = ComputeFormatIndex(format.format); @@ -151,7 +158,7 @@ namespace dawn_native { auto AddColorFormat = [&AddFormat](wgpu::TextureFormat format, bool renderable, bool supportsStorageUsage, uint32_t byteSize, - Type type) { + SampleTypeBit sampleTypes) { Format internalFormat; internalFormat.format = format; internalFormat.isRenderable = renderable; @@ -163,8 +170,26 @@ namespace dawn_native { firstAspect->block.byteSize = byteSize; firstAspect->block.width = 1; firstAspect->block.height = 1; - firstAspect->baseType = type; - firstAspect->supportedComponentTypes = ToComponentTypeBit(type); + if (HasOneBit(sampleTypes)) { + switch (sampleTypes) { + case SampleTypeBit::Float: + case SampleTypeBit::UnfilterableFloat: + firstAspect->baseType = wgpu::TextureComponentType::Float; + break; + case SampleTypeBit::Sint: + firstAspect->baseType = wgpu::TextureComponentType::Sint; + break; + case SampleTypeBit::Uint: + firstAspect->baseType = wgpu::TextureComponentType::Uint; + break; + default: + UNREACHABLE(); + } + } else { + ASSERT((sampleTypes & SampleTypeBit::Float) != 0); + firstAspect->baseType = wgpu::TextureComponentType::Float; + } + firstAspect->supportedSampleTypes = sampleTypes; firstAspect->format = format; AddFormat(internalFormat); }; @@ -182,8 +207,7 @@ namespace dawn_native { firstAspect->block.width = 1; firstAspect->block.height = 1; firstAspect->baseType = wgpu::TextureComponentType::Float; - firstAspect->supportedComponentTypes = - ComponentTypeBit::Float | ComponentTypeBit::DepthComparison; + firstAspect->supportedSampleTypes = kAnyFloat | SampleTypeBit::Depth; firstAspect->format = format; AddFormat(internalFormat); }; @@ -201,7 +225,7 @@ namespace dawn_native { firstAspect->block.width = 1; firstAspect->block.height = 1; firstAspect->baseType = wgpu::TextureComponentType::Uint; - firstAspect->supportedComponentTypes = ComponentTypeBit::Uint; + firstAspect->supportedSampleTypes = SampleTypeBit::Uint; firstAspect->format = format; AddFormat(internalFormat); }; @@ -220,7 +244,7 @@ namespace dawn_native { firstAspect->block.width = width; firstAspect->block.height = height; firstAspect->baseType = wgpu::TextureComponentType::Float; - firstAspect->supportedComponentTypes = ComponentTypeBit::Float; + firstAspect->supportedSampleTypes = kAnyFloat; firstAspect->format = format; AddFormat(internalFormat); }; @@ -247,53 +271,52 @@ namespace dawn_native { }; // clang-format off - // 1 byte color formats - AddColorFormat(wgpu::TextureFormat::R8Unorm, true, false, 1, Type::Float); - AddColorFormat(wgpu::TextureFormat::R8Snorm, false, false, 1, Type::Float); - AddColorFormat(wgpu::TextureFormat::R8Uint, true, false, 1, Type::Uint); - AddColorFormat(wgpu::TextureFormat::R8Sint, true, false, 1, Type::Sint); + AddColorFormat(wgpu::TextureFormat::R8Unorm, true, false, 1, kAnyFloat); + AddColorFormat(wgpu::TextureFormat::R8Snorm, false, false, 1, kAnyFloat); + AddColorFormat(wgpu::TextureFormat::R8Uint, true, false, 1, SampleTypeBit::Uint); + AddColorFormat(wgpu::TextureFormat::R8Sint, true, false, 1, SampleTypeBit::Sint); // 2 bytes color formats - AddColorFormat(wgpu::TextureFormat::R16Uint, true, false, 2, Type::Uint); - AddColorFormat(wgpu::TextureFormat::R16Sint, true, false, 2, Type::Sint); - AddColorFormat(wgpu::TextureFormat::R16Float, true, false, 2, Type::Float); - AddColorFormat(wgpu::TextureFormat::RG8Unorm, true, false, 2, Type::Float); - AddColorFormat(wgpu::TextureFormat::RG8Snorm, false, false, 2, Type::Float); - AddColorFormat(wgpu::TextureFormat::RG8Uint, true, false, 2, Type::Uint); - AddColorFormat(wgpu::TextureFormat::RG8Sint, true, false, 2, Type::Sint); + AddColorFormat(wgpu::TextureFormat::R16Uint, true, false, 2, SampleTypeBit::Uint); + AddColorFormat(wgpu::TextureFormat::R16Sint, true, false, 2, SampleTypeBit::Sint); + AddColorFormat(wgpu::TextureFormat::R16Float, true, false, 2, kAnyFloat); + AddColorFormat(wgpu::TextureFormat::RG8Unorm, true, false, 2, kAnyFloat); + AddColorFormat(wgpu::TextureFormat::RG8Snorm, false, false, 2, kAnyFloat); + AddColorFormat(wgpu::TextureFormat::RG8Uint, true, false, 2, SampleTypeBit::Uint); + AddColorFormat(wgpu::TextureFormat::RG8Sint, true, false, 2, SampleTypeBit::Sint); // 4 bytes color formats - AddColorFormat(wgpu::TextureFormat::R32Uint, true, true, 4, Type::Uint); - AddColorFormat(wgpu::TextureFormat::R32Sint, true, true, 4, Type::Sint); - AddColorFormat(wgpu::TextureFormat::R32Float, true, true, 4, Type::Float); - AddColorFormat(wgpu::TextureFormat::RG16Uint, true, false, 4, Type::Uint); - AddColorFormat(wgpu::TextureFormat::RG16Sint, true, false, 4, Type::Sint); - AddColorFormat(wgpu::TextureFormat::RG16Float, true, false, 4, Type::Float); - AddColorFormat(wgpu::TextureFormat::RGBA8Unorm, true, true, 4, Type::Float); - AddColorFormat(wgpu::TextureFormat::RGBA8UnormSrgb, true, false, 4, Type::Float); - AddColorFormat(wgpu::TextureFormat::RGBA8Snorm, false, true, 4, Type::Float); - AddColorFormat(wgpu::TextureFormat::RGBA8Uint, true, true, 4, Type::Uint); - AddColorFormat(wgpu::TextureFormat::RGBA8Sint, true, true, 4, Type::Sint); - AddColorFormat(wgpu::TextureFormat::BGRA8Unorm, true, false, 4, Type::Float); - AddColorFormat(wgpu::TextureFormat::BGRA8UnormSrgb, true, false, 4, Type::Float); - AddColorFormat(wgpu::TextureFormat::RGB10A2Unorm, true, false, 4, Type::Float); + AddColorFormat(wgpu::TextureFormat::R32Uint, true, true, 4, SampleTypeBit::Uint); + AddColorFormat(wgpu::TextureFormat::R32Sint, true, true, 4, SampleTypeBit::Sint); + AddColorFormat(wgpu::TextureFormat::R32Float, true, true, 4, SampleTypeBit::UnfilterableFloat); + AddColorFormat(wgpu::TextureFormat::RG16Uint, true, false, 4, SampleTypeBit::Uint); + AddColorFormat(wgpu::TextureFormat::RG16Sint, true, false, 4, SampleTypeBit::Sint); + AddColorFormat(wgpu::TextureFormat::RG16Float, true, false, 4, kAnyFloat); + AddColorFormat(wgpu::TextureFormat::RGBA8Unorm, true, true, 4, kAnyFloat); + AddColorFormat(wgpu::TextureFormat::RGBA8UnormSrgb, true, false, 4, kAnyFloat); + AddColorFormat(wgpu::TextureFormat::RGBA8Snorm, false, true, 4, kAnyFloat); + AddColorFormat(wgpu::TextureFormat::RGBA8Uint, true, true, 4, SampleTypeBit::Uint); + AddColorFormat(wgpu::TextureFormat::RGBA8Sint, true, true, 4, SampleTypeBit::Sint); + AddColorFormat(wgpu::TextureFormat::BGRA8Unorm, true, false, 4, kAnyFloat); + AddColorFormat(wgpu::TextureFormat::BGRA8UnormSrgb, true, false, 4, kAnyFloat); + AddColorFormat(wgpu::TextureFormat::RGB10A2Unorm, true, false, 4, kAnyFloat); - AddColorFormat(wgpu::TextureFormat::RG11B10Ufloat, false, false, 4, Type::Float); - AddColorFormat(wgpu::TextureFormat::RGB9E5Ufloat, false, false, 4, Type::Float); + AddColorFormat(wgpu::TextureFormat::RG11B10Ufloat, false, false, 4, kAnyFloat); + AddColorFormat(wgpu::TextureFormat::RGB9E5Ufloat, false, false, 4, kAnyFloat); // 8 bytes color formats - AddColorFormat(wgpu::TextureFormat::RG32Uint, true, true, 8, Type::Uint); - AddColorFormat(wgpu::TextureFormat::RG32Sint, true, true, 8, Type::Sint); - AddColorFormat(wgpu::TextureFormat::RG32Float, true, true, 8, Type::Float); - AddColorFormat(wgpu::TextureFormat::RGBA16Uint, true, true, 8, Type::Uint); - AddColorFormat(wgpu::TextureFormat::RGBA16Sint, true, true, 8, Type::Sint); - AddColorFormat(wgpu::TextureFormat::RGBA16Float, true, true, 8, Type::Float); + AddColorFormat(wgpu::TextureFormat::RG32Uint, true, true, 8, SampleTypeBit::Uint); + AddColorFormat(wgpu::TextureFormat::RG32Sint, true, true, 8, SampleTypeBit::Sint); + AddColorFormat(wgpu::TextureFormat::RG32Float, true, true, 8, SampleTypeBit::UnfilterableFloat); + AddColorFormat(wgpu::TextureFormat::RGBA16Uint, true, true, 8, SampleTypeBit::Uint); + AddColorFormat(wgpu::TextureFormat::RGBA16Sint, true, true, 8, SampleTypeBit::Sint); + AddColorFormat(wgpu::TextureFormat::RGBA16Float, true, true, 8, kAnyFloat); // 16 bytes color formats - AddColorFormat(wgpu::TextureFormat::RGBA32Uint, true, true, 16, Type::Uint); - AddColorFormat(wgpu::TextureFormat::RGBA32Sint, true, true, 16, Type::Sint); - AddColorFormat(wgpu::TextureFormat::RGBA32Float, true, true, 16, Type::Float); + AddColorFormat(wgpu::TextureFormat::RGBA32Uint, true, true, 16, SampleTypeBit::Uint); + AddColorFormat(wgpu::TextureFormat::RGBA32Sint, true, true, 16, SampleTypeBit::Sint); + AddColorFormat(wgpu::TextureFormat::RGBA32Float, true, true, 16, SampleTypeBit::UnfilterableFloat); // Depth-stencil formats AddDepthFormat(wgpu::TextureFormat::Depth32Float, 4); diff --git a/src/dawn_native/Format.h b/src/dawn_native/Format.h index 01457d1369..2708d04ba6 100644 --- a/src/dawn_native/Format.h +++ b/src/dawn_native/Format.h @@ -45,19 +45,20 @@ namespace dawn_native { enum class Aspect : uint8_t; class DeviceBase; - // This mirrors wgpu::TextureComponentType as a bitmask instead. - enum class ComponentTypeBit : uint8_t { + // This mirrors wgpu::TextureSampleType as a bitmask instead. + enum class SampleTypeBit : uint8_t { None = 0x0, Float = 0x1, - Sint = 0x2, - Uint = 0x4, - DepthComparison = 0x8, + UnfilterableFloat = 0x2, + Depth = 0x4, + Sint = 0x8, + Uint = 0x10, }; // Converts an wgpu::TextureComponentType to its bitmask representation. - ComponentTypeBit ToComponentTypeBit(wgpu::TextureComponentType type); + SampleTypeBit ToSampleTypeBit(wgpu::TextureComponentType type); // Converts an wgpu::TextureSampleType to its bitmask representation. - ComponentTypeBit SampleTypeToComponentTypeBit(wgpu::TextureSampleType sampleType); + SampleTypeBit SampleTypeToSampleTypeBit(wgpu::TextureSampleType sampleType); struct TexelBlockInfo { uint32_t byteSize; @@ -67,8 +68,10 @@ namespace dawn_native { struct AspectInfo { TexelBlockInfo block; + // TODO(crbug.com/dawn/367): Replace TextureComponentType with TextureSampleType, or make it + // an internal Dawn enum. wgpu::TextureComponentType baseType; - ComponentTypeBit supportedComponentTypes; + SampleTypeBit supportedSampleTypes; wgpu::TextureFormat format; }; @@ -127,7 +130,7 @@ namespace dawn_native { namespace wgpu { template <> - struct IsDawnBitmask { + struct IsDawnBitmask { static constexpr bool enable = true; }; diff --git a/src/dawn_native/PipelineLayout.cpp b/src/dawn_native/PipelineLayout.cpp index 52e2008f42..9a4b3a5990 100644 --- a/src/dawn_native/PipelineLayout.cpp +++ b/src/dawn_native/PipelineLayout.cpp @@ -88,7 +88,9 @@ namespace dawn_native { modifiedEntry->binding == mergedEntry.binding && modifiedEntry->buffer.type == mergedEntry.buffer.type && modifiedEntry->sampler.type == mergedEntry.sampler.type && - modifiedEntry->texture.sampleType == mergedEntry.texture.sampleType && + // Compatibility between these sample types is checked below. + (modifiedEntry->texture.sampleType != wgpu::TextureSampleType::Undefined) == + (mergedEntry.texture.sampleType != wgpu::TextureSampleType::Undefined) && modifiedEntry->storageTexture.access == mergedEntry.storageTexture.access; // Minimum buffer binding size excluded because we take the maximum seen across stages. @@ -98,8 +100,18 @@ namespace dawn_native { } if (modifiedEntry->texture.sampleType != wgpu::TextureSampleType::Undefined) { + // Sample types are compatible if they are exactly equal, + // or if the |modifiedEntry| is Float and the |mergedEntry| is UnfilterableFloat. + // Note that the |mergedEntry| never has type Float. Texture bindings all start + // as UnfilterableFloat and are promoted to Float if they are statically used with + // a sampler. + ASSERT(mergedEntry.texture.sampleType != wgpu::TextureSampleType::Float); + bool compatibleSampleTypes = + modifiedEntry->texture.sampleType == mergedEntry.texture.sampleType || + (modifiedEntry->texture.sampleType == wgpu::TextureSampleType::Float && + mergedEntry.texture.sampleType == wgpu::TextureSampleType::UnfilterableFloat); compatible = - compatible && + compatible && compatibleSampleTypes && modifiedEntry->texture.viewDimension == mergedEntry.texture.viewDimension && modifiedEntry->texture.multisampled == mergedEntry.texture.multisampled; } @@ -136,16 +148,51 @@ namespace dawn_native { BindGroupLayoutEntry entry = {}; switch (shaderBinding.bindingType) { case BindingInfoType::Buffer: - entry.buffer = shaderBinding.buffer; + entry.buffer.type = shaderBinding.buffer.type; + entry.buffer.hasDynamicOffset = shaderBinding.buffer.hasDynamicOffset; + entry.buffer.minBindingSize = shaderBinding.buffer.minBindingSize; break; case BindingInfoType::Sampler: - entry.sampler = shaderBinding.sampler; + if (shaderBinding.sampler.isComparison) { + entry.sampler.type = wgpu::SamplerBindingType::Comparison; + } else { + entry.sampler.type = wgpu::SamplerBindingType::Filtering; + } break; case BindingInfoType::Texture: - entry.texture = shaderBinding.texture; + switch (shaderBinding.texture.compatibleSampleTypes) { + case SampleTypeBit::Depth: + entry.texture.sampleType = wgpu::TextureSampleType::Depth; + break; + case SampleTypeBit::Sint: + entry.texture.sampleType = wgpu::TextureSampleType::Sint; + break; + case SampleTypeBit::Uint: + entry.texture.sampleType = wgpu::TextureSampleType::Uint; + break; + case SampleTypeBit::Float: + case SampleTypeBit::UnfilterableFloat: + case SampleTypeBit::None: + UNREACHABLE(); + break; + default: + if (shaderBinding.texture.compatibleSampleTypes == + (SampleTypeBit::Float | SampleTypeBit::UnfilterableFloat)) { + // Default to UnfilterableFloat. It will be promoted to Float if it + // is used with a sampler. + entry.texture.sampleType = + wgpu::TextureSampleType::UnfilterableFloat; + } else { + UNREACHABLE(); + } + } + entry.texture.viewDimension = shaderBinding.texture.viewDimension; + entry.texture.multisampled = shaderBinding.texture.multisampled; break; case BindingInfoType::StorageTexture: - entry.storageTexture = shaderBinding.storageTexture; + entry.storageTexture.access = shaderBinding.storageTexture.access; + entry.storageTexture.format = shaderBinding.storageTexture.format; + entry.storageTexture.viewDimension = shaderBinding.storageTexture.viewDimension; break; case BindingInfoType::ExternalTexture: // TODO(dawn:728) On backend configurations that use SPIRV-Cross to reflect @@ -185,11 +232,10 @@ namespace dawn_native { // Loops over all the reflected BindGroupLayoutEntries from shaders. for (const StageAndDescriptor& stage : stages) { - const EntryPointMetadata::BindingInfoArray& info = - stage.module->GetEntryPoint(stage.entryPoint).bindings; + const EntryPointMetadata& metadata = stage.module->GetEntryPoint(stage.entryPoint); - for (BindGroupIndex group(0); group < info.size(); ++group) { - for (const auto& bindingIt : info[group]) { + for (BindGroupIndex group(0); group < metadata.bindings.size(); ++group) { + for (const auto& bindingIt : metadata.bindings[group]) { BindingNumber bindingNumber = bindingIt.first; const EntryPointMetadata::ShaderBindingInfo& shaderBinding = bindingIt.second; @@ -206,6 +252,15 @@ namespace dawn_native { } } } + + // Promote any Unfilterable textures used with a sampler to Filtering. + for (const EntryPointMetadata::SamplerTexturePair& pair : + metadata.samplerTexturePairs) { + BindGroupLayoutEntry* entry = &entryData[pair.texture.group][pair.texture.binding]; + if (entry->texture.sampleType == wgpu::TextureSampleType::UnfilterableFloat) { + entry->texture.sampleType = wgpu::TextureSampleType::Float; + } + } } // Create the bind group layouts. We need to keep track of the last non-empty BGL because diff --git a/src/dawn_native/Sampler.cpp b/src/dawn_native/Sampler.cpp index 51db0805f5..1637b16da7 100644 --- a/src/dawn_native/Sampler.cpp +++ b/src/dawn_native/Sampler.cpp @@ -100,10 +100,15 @@ namespace dawn_native { return new SamplerBase(device, ObjectBase::kError); } - bool SamplerBase::HasCompareFunction() const { + bool SamplerBase::IsComparison() const { return mCompareFunction != wgpu::CompareFunction::Undefined; } + bool SamplerBase::IsFiltering() const { + return mMinFilter == wgpu::FilterMode::Linear || mMagFilter == wgpu::FilterMode::Linear || + mMipmapFilter == wgpu::FilterMode::Linear; + } + size_t SamplerBase::ComputeContentHash() { ObjectContentHasher recorder; recorder.Record(mAddressModeU, mAddressModeV, mAddressModeW, mMagFilter, mMinFilter, diff --git a/src/dawn_native/Sampler.h b/src/dawn_native/Sampler.h index 2fd938c154..aa74966e48 100644 --- a/src/dawn_native/Sampler.h +++ b/src/dawn_native/Sampler.h @@ -33,7 +33,8 @@ namespace dawn_native { static SamplerBase* MakeError(DeviceBase* device); - bool HasCompareFunction() const; + bool IsComparison() const; + bool IsFiltering() const; // Functions necessary for the unordered_set-based cache. size_t ComputeContentHash() override; diff --git a/src/dawn_native/ShaderModule.cpp b/src/dawn_native/ShaderModule.cpp index 18c8cb13bb..e201251ed3 100644 --- a/src/dawn_native/ShaderModule.cpp +++ b/src/dawn_native/ShaderModule.cpp @@ -266,17 +266,17 @@ namespace dawn_native { } } - wgpu::TextureSampleType TintSampledKindToTextureSampleType( + SampleTypeBit TintSampledKindToSampleTypeBit( tint::inspector::ResourceBinding::SampledKind s) { switch (s) { case tint::inspector::ResourceBinding::SampledKind::kSInt: - return wgpu::TextureSampleType::Sint; + return SampleTypeBit::Sint; case tint::inspector::ResourceBinding::SampledKind::kUInt: - return wgpu::TextureSampleType::Uint; + return SampleTypeBit::Uint; case tint::inspector::ResourceBinding::SampledKind::kFloat: - return wgpu::TextureSampleType::Float; + return SampleTypeBit::Float | SampleTypeBit::UnfilterableFloat; case tint::inspector::ResourceBinding::SampledKind::kUnknown: - return wgpu::TextureSampleType::Undefined; + return SampleTypeBit::None; } } @@ -535,10 +535,11 @@ namespace dawn_native { GetShaderDeclarationString(group, bindingNumber)); } - if (layoutInfo.texture.sampleType != shaderInfo.texture.sampleType) { + if ((SampleTypeToSampleTypeBit(layoutInfo.texture.sampleType) & + shaderInfo.texture.compatibleSampleTypes) == 0) { return DAWN_VALIDATION_ERROR( "The texture sampleType of the bind group layout entry is " - "different from " + + "not compatible with " + GetShaderDeclarationString(group, bindingNumber)); } @@ -621,10 +622,13 @@ namespace dawn_native { } case BindingInfoType::Sampler: - // TODO(crbug.com/dawn/367): Temporarily allow using either a sampler or a - // comparison sampler until we can perform the proper shader analysis of - // what type is used in the shader module. - break; + if ((layoutInfo.sampler.type == wgpu::SamplerBindingType::Comparison) != + shaderInfo.sampler.isComparison) { + return DAWN_VALIDATION_ERROR( + "The sampler type of the bind group layout entry is " + "not compatible with " + + GetShaderDeclarationString(group, bindingNumber)); + } } } @@ -695,21 +699,23 @@ namespace dawn_native { info->texture.viewDimension = SpirvDimToTextureViewDimension(imageType.dim, imageType.arrayed); - info->texture.sampleType = - SpirvBaseTypeToTextureSampleType(textureComponentType); info->texture.multisampled = imageType.ms; + info->texture.compatibleSampleTypes = + SpirvBaseTypeToSampleTypeBit(textureComponentType); if (imageType.depth) { if (imageType.ms) { return DAWN_VALIDATION_ERROR( "Multisampled depth textures aren't supported"); } - if (info->texture.sampleType != wgpu::TextureSampleType::Float) { + if ((info->texture.compatibleSampleTypes & SampleTypeBit::Float) == + 0) { return DAWN_VALIDATION_ERROR( "Depth textures must have a float type"); } - info->texture.sampleType = wgpu::TextureSampleType::Depth; + info->texture.compatibleSampleTypes = SampleTypeBit::Depth; } + if (imageType.ms && imageType.arrayed) { return DAWN_VALIDATION_ERROR( "Multisampled array textures aren't supported"); @@ -779,7 +785,7 @@ namespace dawn_native { break; } case BindingInfoType::Sampler: { - info->sampler.type = wgpu::SamplerBindingType::Filtering; + info->sampler.isComparison = false; break; } case BindingInfoType::ExternalTexture: { @@ -897,26 +903,6 @@ namespace dawn_native { auto metadata = std::make_unique(); DAWN_TRY_ASSIGN(metadata->stage, TintPipelineStageToShaderStage(entryPoint.stage)); - if (metadata->stage == SingleShaderStage::Vertex) { - for (auto& stage_input : entryPoint.input_variables) { - if (!stage_input.has_location_decoration) { - return DAWN_VALIDATION_ERROR( - "Need Location decoration on Vertex input"); - } - uint32_t location = stage_input.location_decoration; - if (location >= kMaxVertexAttributes) { - return DAWN_VALIDATION_ERROR("Attribute location over limits"); - } - metadata->usedVertexAttributes.set(location); - } - - for (auto& stage_output : entryPoint.output_variables) { - if (!stage_output.has_location_decoration) { - return DAWN_VALIDATION_ERROR( - "Need Location decoration on Vertex output"); - } - } - } if (metadata->stage == SingleShaderStage::Compute) { metadata->localWorkgroupSize.x = entryPoint.workgroup_size_x; @@ -926,11 +912,11 @@ namespace dawn_native { if (metadata->stage == SingleShaderStage::Vertex) { for (const auto& input_var : entryPoint.input_variables) { - uint32_t location = 0; - if (input_var.has_location_decoration) { - location = input_var.location_decoration; + if (!input_var.has_location_decoration) { + return DAWN_VALIDATION_ERROR( + "Need Location decoration on Vertex input"); } - + uint32_t location = input_var.location_decoration; if (DAWN_UNLIKELY(location >= kMaxVertexAttributes)) { std::stringstream ss; ss << "Attribute location (" << location << ") over limits"; @@ -978,7 +964,8 @@ namespace dawn_native { } } - for (auto& resource : inspector.GetResourceBindings(entryPoint.name)) { + for (const tint::inspector::ResourceBinding& resource : + inspector.GetResourceBindings(entryPoint.name)) { BindingNumber bindingNumber(resource.binding); BindGroupIndex bindGroupIndex(resource.bind_group); if (bindGroupIndex >= kMaxBindGroupsTyped) { @@ -1001,17 +988,27 @@ namespace dawn_native { resource.resource_type)); break; case BindingInfoType::Sampler: - info->sampler.type = wgpu::SamplerBindingType::Filtering; + switch (resource.resource_type) { + case tint::inspector::ResourceBinding::ResourceType::kSampler: + info->sampler.isComparison = false; + break; + case tint::inspector::ResourceBinding::ResourceType:: + kComparisonSampler: + info->sampler.isComparison = true; + break; + default: + UNREACHABLE(); + } break; case BindingInfoType::Texture: info->texture.viewDimension = TintTextureDimensionToTextureViewDimension(resource.dim); if (resource.resource_type == tint::inspector::ResourceBinding::ResourceType::kDepthTexture) { - info->texture.sampleType = wgpu::TextureSampleType::Depth; + info->texture.compatibleSampleTypes = SampleTypeBit::Depth; } else { - info->texture.sampleType = - TintSampledKindToTextureSampleType(resource.sampled_kind); + info->texture.compatibleSampleTypes = + TintSampledKindToSampleTypeBit(resource.sampled_kind); } info->texture.multisampled = resource.resource_type == tint::inspector::ResourceBinding:: @@ -1035,6 +1032,21 @@ namespace dawn_native { } } + std::vector samplerTextureUses = + inspector.GetSamplerTextureUses(entryPoint.name); + metadata->samplerTexturePairs.reserve(samplerTextureUses.size()); + std::transform( + samplerTextureUses.begin(), samplerTextureUses.end(), + std::back_inserter(metadata->samplerTexturePairs), + [](const tint::inspector::SamplerTexturePair& pair) { + EntryPointMetadata::SamplerTexturePair result; + result.sampler = {BindGroupIndex(pair.sampler_binding_point.group), + BindingNumber(pair.sampler_binding_point.binding)}; + result.texture = {BindGroupIndex(pair.texture_binding_point.group), + BindingNumber(pair.texture_binding_point.binding)}; + return result; + }); + result[entryPoint.name] = std::move(metadata); } return std::move(result); @@ -1233,6 +1245,43 @@ namespace dawn_native { } } + // Validate that filtering samplers are not used with unfilterable textures. + for (const auto& pair : entryPoint.samplerTexturePairs) { + const BindGroupLayoutBase* samplerBGL = layout->GetBindGroupLayout(pair.sampler.group); + const BindingInfo& samplerInfo = + samplerBGL->GetBindingInfo(samplerBGL->GetBindingIndex(pair.sampler.binding)); + if (samplerInfo.sampler.type != wgpu::SamplerBindingType::Filtering) { + continue; + } + const BindGroupLayoutBase* textureBGL = layout->GetBindGroupLayout(pair.texture.group); + const BindingInfo& textureInfo = + textureBGL->GetBindingInfo(textureBGL->GetBindingIndex(pair.texture.binding)); + + ASSERT(textureInfo.bindingType != BindingInfoType::Buffer && + textureInfo.bindingType != BindingInfoType::Sampler && + textureInfo.bindingType != BindingInfoType::StorageTexture); + + if (textureInfo.bindingType != BindingInfoType::Texture) { + continue; + } + + // Uint/sint can't be statically used with a sampler, so they any + // texture bindings reflected must be float or depth textures. If + // the shader uses a float/depth texture but the bind group layout + // specifies a uint/sint texture binding, + // |ValidateCompatibilityWithBindGroupLayout| will fail since the + // sampleType does not match. + ASSERT(textureInfo.texture.sampleType != wgpu::TextureSampleType::Undefined && + textureInfo.texture.sampleType != wgpu::TextureSampleType::Uint && + textureInfo.texture.sampleType != wgpu::TextureSampleType::Sint); + + if (textureInfo.texture.sampleType == wgpu::TextureSampleType::UnfilterableFloat) { + return DAWN_VALIDATION_ERROR( + "unfilterable-float texture bindings cannot be sampled with a " + "filtering sampler"); + } + } + return {}; } diff --git a/src/dawn_native/ShaderModule.h b/src/dawn_native/ShaderModule.h index d39d464055..dd3d34a993 100644 --- a/src/dawn_native/ShaderModule.h +++ b/src/dawn_native/ShaderModule.h @@ -104,16 +104,34 @@ namespace dawn_native { // pointers to EntryPointMetadata are safe to store as long as you also keep a Ref to the // ShaderModuleBase. struct EntryPointMetadata { + // Mirrors wgpu::SamplerBindingLayout but instead stores a single boolean + // for isComparison instead of a wgpu::SamplerBindingType enum. + struct ShaderSamplerBindingInfo { + bool isComparison; + }; + + // Mirrors wgpu::TextureBindingLayout but instead has a set of compatible sampleTypes + // instead of a single enum. + struct ShaderTextureBindingInfo { + SampleTypeBit compatibleSampleTypes; + wgpu::TextureViewDimension viewDimension; + bool multisampled; + }; + // Per-binding shader metadata contains some SPIRV specific information in addition to // most of the frontend per-binding information. - struct ShaderBindingInfo : BindingInfo { + struct ShaderBindingInfo { // The SPIRV ID of the resource. uint32_t id; uint32_t base_type_id; - private: - // Disallow access to unused members. - using BindingInfo::visibility; + BindingNumber binding; + BindingInfoType bindingType; + + BufferBindingLayout buffer; + ShaderSamplerBindingInfo sampler; + ShaderTextureBindingInfo texture; + StorageTextureBindingLayout storageTexture; }; // bindings[G][B] is the reflection data for the binding defined with @@ -122,6 +140,12 @@ namespace dawn_native { using BindingInfoArray = ityp::array; BindingInfoArray bindings; + struct SamplerTexturePair { + BindingSlot sampler; + BindingSlot texture; + }; + std::vector samplerTexturePairs; + // The set of vertex attributes this entryPoint uses. std::bitset usedVertexAttributes; diff --git a/src/dawn_native/SpirvUtils.cpp b/src/dawn_native/SpirvUtils.cpp index fafd6c2247..9472508c38 100644 --- a/src/dawn_native/SpirvUtils.cpp +++ b/src/dawn_native/SpirvUtils.cpp @@ -148,15 +148,14 @@ namespace dawn_native { } } - wgpu::TextureSampleType SpirvBaseTypeToTextureSampleType( - spirv_cross::SPIRType::BaseType spirvBaseType) { + SampleTypeBit SpirvBaseTypeToSampleTypeBit(spirv_cross::SPIRType::BaseType spirvBaseType) { switch (spirvBaseType) { case spirv_cross::SPIRType::Float: - return wgpu::TextureSampleType::Float; + return SampleTypeBit::Float | SampleTypeBit::UnfilterableFloat; case spirv_cross::SPIRType::Int: - return wgpu::TextureSampleType::Sint; + return SampleTypeBit::Sint; case spirv_cross::SPIRType::UInt: - return wgpu::TextureSampleType::Uint; + return SampleTypeBit::Uint; default: UNREACHABLE(); } diff --git a/src/dawn_native/SpirvUtils.h b/src/dawn_native/SpirvUtils.h index 9ed94a5f8d..158b165e7c 100644 --- a/src/dawn_native/SpirvUtils.h +++ b/src/dawn_native/SpirvUtils.h @@ -39,8 +39,7 @@ namespace dawn_native { // Returns the format "component type" corresponding to the SPIRV base type. wgpu::TextureComponentType SpirvBaseTypeToTextureComponentType( spirv_cross::SPIRType::BaseType spirvBaseType); - wgpu::TextureSampleType SpirvBaseTypeToTextureSampleType( - spirv_cross::SPIRType::BaseType spirvBaseType); + SampleTypeBit SpirvBaseTypeToSampleTypeBit(spirv_cross::SPIRType::BaseType spirvBaseType); } // namespace dawn_native diff --git a/src/tests/end2end/CopyTextureForBrowserTests.cpp b/src/tests/end2end/CopyTextureForBrowserTests.cpp index 08ce0590c3..1ca8ea47e9 100644 --- a/src/tests/end2end/CopyTextureForBrowserTests.cpp +++ b/src/tests/end2end/CopyTextureForBrowserTests.cpp @@ -235,8 +235,8 @@ class CopyTextureForBrowserTests : public DawnTest { } default: { break; - } - } + } + } // Not use loop and variable index format to workaround // crbug.com/tint/638. diff --git a/src/tests/end2end/DepthStencilSamplingTests.cpp b/src/tests/end2end/DepthStencilSamplingTests.cpp index ad727c30a8..461fa00db6 100644 --- a/src/tests/end2end/DepthStencilSamplingTests.cpp +++ b/src/tests/end2end/DepthStencilSamplingTests.cpp @@ -193,17 +193,9 @@ class DepthStencilSamplingTest : public DawnTest { return textureSampleCompare(tex, samp, vec2(0.5, 0.5), uniforms.compareRef); })"); - // TODO(crbug.com/dawn/367): Cannot use GetBindGroupLayout for comparison samplers without - // shader reflection data. - wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( - device, {{0, wgpu::ShaderStage::Fragment, wgpu::SamplerBindingType::Comparison}, - {1, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Depth}, - {2, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::Uniform}}); - utils::ComboRenderPipelineDescriptor pipelineDescriptor; pipelineDescriptor.vertex.module = vsModule; pipelineDescriptor.cFragment.module = fsModule; - pipelineDescriptor.layout = utils::MakeBasicPipelineLayout(device, &bgl); pipelineDescriptor.primitive.topology = wgpu::PrimitiveTopology::PointList; pipelineDescriptor.cTargets[0].format = wgpu::TextureFormat::R32Float; @@ -228,15 +220,7 @@ class DepthStencilSamplingTest : public DawnTest { samplerResult.value = textureSampleCompare(tex, samp, vec2(0.5, 0.5), uniforms.compareRef); })"); - // TODO(crbug.com/dawn/367): Cannot use GetBindGroupLayout without shader reflection data. - wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( - device, {{0, wgpu::ShaderStage::Compute, wgpu::SamplerBindingType::Comparison}, - {1, wgpu::ShaderStage::Compute, wgpu::TextureSampleType::Depth}, - {2, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Uniform}, - {3, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Storage}}); - wgpu::ComputePipelineDescriptor pipelineDescriptor; - pipelineDescriptor.layout = utils::MakeBasicPipelineLayout(device, &bgl); pipelineDescriptor.compute.module = csModule; pipelineDescriptor.compute.entryPoint = "main"; @@ -709,6 +693,9 @@ TEST_P(DepthStencilSamplingTest, CompareFunctionsRender) { // Initialization via renderPass loadOp doesn't work on Mac Intel. DAWN_SUPPRESS_TEST_IF(IsMetal() && IsIntel()); + // Depends on Tint's shader reflection + DAWN_TEST_UNSUPPORTED_IF(!HasToggleEnabled("use_tint_generator")); + wgpu::RenderPipeline pipeline = CreateComparisonRenderPipeline(); for (wgpu::TextureFormat format : kDepthFormats) { @@ -728,6 +715,9 @@ TEST_P(DepthStencilSamplingTest, DISABLED_CompareFunctionsCompute) { // Initialization via renderPass loadOp doesn't work on Mac Intel. DAWN_SUPPRESS_TEST_IF(IsMetal() && IsIntel()); + // Depends on Tint's shader reflection + DAWN_TEST_UNSUPPORTED_IF(!HasToggleEnabled("use_tint_generator")); + wgpu::ComputePipeline pipeline = CreateComparisonComputePipeline(); for (wgpu::TextureFormat format : kDepthFormats) { diff --git a/src/tests/unittests/validation/BindGroupValidationTests.cpp b/src/tests/unittests/validation/BindGroupValidationTests.cpp index 04f62dccdc..b6031dd80e 100644 --- a/src/tests/unittests/validation/BindGroupValidationTests.cpp +++ b/src/tests/unittests/validation/BindGroupValidationTests.cpp @@ -435,20 +435,61 @@ TEST_F(BindGroupValidationTest, StorageTextureUsage) { ASSERT_DEVICE_ERROR(utils::MakeBindGroup(device, layout, {{0, view}})); } -// Check that a texture must have the correct component type -TEST_F(BindGroupValidationTest, TextureComponentType) { - wgpu::BindGroupLayout layout = utils::MakeBindGroupLayout( - device, {{0, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float}}); +// Check that a texture must have the correct sample type +TEST_F(BindGroupValidationTest, TextureSampleType) { + auto DoTest = [this](bool success, wgpu::TextureFormat format, + wgpu::TextureSampleType sampleType) { + wgpu::BindGroupLayout layout = + utils::MakeBindGroupLayout(device, {{0, wgpu::ShaderStage::Fragment, sampleType}}); - // Control case: setting a Float typed texture view works. - utils::MakeBindGroup(device, layout, {{0, mSampledTextureView}}); + wgpu::TextureDescriptor descriptor; + descriptor.size = {4, 4, 1}; + descriptor.usage = wgpu::TextureUsage::Sampled; + descriptor.format = format; - // Make a Uint component typed texture and try to set it to a Float component binding. - wgpu::Texture uintTexture = - CreateTexture(wgpu::TextureUsage::Sampled, wgpu::TextureFormat::RGBA8Uint, 1); - wgpu::TextureView uintTextureView = uintTexture.CreateView(); + wgpu::TextureView view = device.CreateTexture(&descriptor).CreateView(); - ASSERT_DEVICE_ERROR(utils::MakeBindGroup(device, layout, {{0, uintTextureView}})); + if (success) { + utils::MakeBindGroup(device, layout, {{0, view}}); + } else { + ASSERT_DEVICE_ERROR(utils::MakeBindGroup(device, layout, {{0, view}})); + } + }; + + // Test that RGBA8Unorm is only compatible with float/unfilterable-float + DoTest(true, wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureSampleType::Float); + DoTest(true, wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureSampleType::UnfilterableFloat); + DoTest(false, wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureSampleType::Depth); + DoTest(false, wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureSampleType::Uint); + DoTest(false, wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureSampleType::Sint); + + // Test that R32Float is only compatible with unfilterable-float + DoTest(false, wgpu::TextureFormat::R32Float, wgpu::TextureSampleType::Float); + DoTest(true, wgpu::TextureFormat::R32Float, wgpu::TextureSampleType::UnfilterableFloat); + DoTest(false, wgpu::TextureFormat::R32Float, wgpu::TextureSampleType::Depth); + DoTest(false, wgpu::TextureFormat::R32Float, wgpu::TextureSampleType::Uint); + DoTest(false, wgpu::TextureFormat::R32Float, wgpu::TextureSampleType::Sint); + + // Test that Depth32Float is only compatible with float/unfilterable-float/depth + DoTest(true, wgpu::TextureFormat::Depth32Float, wgpu::TextureSampleType::Float); + DoTest(true, wgpu::TextureFormat::Depth32Float, wgpu::TextureSampleType::UnfilterableFloat); + DoTest(true, wgpu::TextureFormat::Depth32Float, wgpu::TextureSampleType::Depth); + DoTest(false, wgpu::TextureFormat::Depth32Float, wgpu::TextureSampleType::Uint); + DoTest(false, wgpu::TextureFormat::Depth32Float, wgpu::TextureSampleType::Sint); + + // Test that RG8Uint is only compatible with uint + DoTest(false, wgpu::TextureFormat::RG8Uint, wgpu::TextureSampleType::Float); + DoTest(false, wgpu::TextureFormat::RG8Uint, wgpu::TextureSampleType::UnfilterableFloat); + DoTest(false, wgpu::TextureFormat::RG8Uint, wgpu::TextureSampleType::Depth); + DoTest(true, wgpu::TextureFormat::RG8Uint, wgpu::TextureSampleType::Uint); + DoTest(false, wgpu::TextureFormat::RG8Uint, wgpu::TextureSampleType::Sint); + + // Test that R16Sint is only compatible with sint + DoTest(false, wgpu::TextureFormat::R16Sint, wgpu::TextureSampleType::Float); + DoTest(false, wgpu::TextureFormat::R16Sint, wgpu::TextureSampleType::UnfilterableFloat); + DoTest(false, wgpu::TextureFormat::R16Sint, wgpu::TextureSampleType::Depth); + DoTest(false, wgpu::TextureFormat::R16Sint, wgpu::TextureSampleType::Uint); + DoTest(true, wgpu::TextureFormat::R16Sint, wgpu::TextureSampleType::Sint); } // Test which depth-stencil formats are allowed to be sampled (all). @@ -493,36 +534,6 @@ TEST_F(BindGroupValidationTest, SamplingDepthStencilTexture) { } } -// Check that a texture must have a correct format for DepthComparison -TEST_F(BindGroupValidationTest, TextureComponentTypeDepthComparison) { - wgpu::BindGroupLayout depthLayout = utils::MakeBindGroupLayout( - device, {{0, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Depth}}); - - // Control case: setting a depth texture works. - wgpu::Texture depthTexture = - CreateTexture(wgpu::TextureUsage::Sampled, wgpu::TextureFormat::Depth32Float, 1); - utils::MakeBindGroup(device, depthLayout, {{0, depthTexture.CreateView()}}); - - // Error case: setting a Float typed texture view fails. - ASSERT_DEVICE_ERROR(utils::MakeBindGroup(device, depthLayout, {{0, mSampledTextureView}})); -} - -// Check that a depth texture is allowed to be used for both TextureComponentType::Float and -// ::DepthComparison -TEST_F(BindGroupValidationTest, TextureComponentTypeForDepthTexture) { - wgpu::BindGroupLayout depthLayout = utils::MakeBindGroupLayout( - device, {{0, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Depth}}); - - wgpu::BindGroupLayout floatLayout = utils::MakeBindGroupLayout( - device, {{0, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float}}); - - wgpu::Texture depthTexture = - CreateTexture(wgpu::TextureUsage::Sampled, wgpu::TextureFormat::Depth32Float, 1); - - utils::MakeBindGroup(device, depthLayout, {{0, depthTexture.CreateView()}}); - utils::MakeBindGroup(device, floatLayout, {{0, depthTexture.CreateView()}}); -} - // Check that a texture must have the correct dimension TEST_F(BindGroupValidationTest, TextureDimension) { wgpu::BindGroupLayout layout = utils::MakeBindGroupLayout( @@ -2324,7 +2335,7 @@ TEST_F(BindingsValidationTest, BindGroupsWithLessBindingsThanPipelineLayout) { TestComputePassBindings(bg.data(), kBindingNum, computePipeline, false); } -class ComparisonSamplerBindingTest : public ValidationTest { +class SamplerTypeBindingTest : public ValidationTest { protected: wgpu::RenderPipeline CreateFragmentPipeline(wgpu::BindGroupLayout* bindGroupLayout, const char* fragmentSource) { @@ -2345,10 +2356,13 @@ class ComparisonSamplerBindingTest : public ValidationTest { } }; -// TODO(crbug.com/dawn/367): Disabled until we can perform shader analysis -// of which samplers are comparison samplers. -TEST_F(ComparisonSamplerBindingTest, DISABLED_ShaderAndBGLMatches) { - // Test that sampler binding works with normal sampler in the shader. +// Test that the use of sampler and comparison_sampler in the shader must match the bind group +// layout. +TEST_F(SamplerTypeBindingTest, ShaderAndBGLMatches) { + // Tint needed for proper shader reflection. + DAWN_SKIP_TEST_IF(!HasToggleEnabled("use_tint_generator")); + + // Test that a filtering sampler binding works with normal sampler in the shader. { wgpu::BindGroupLayout bindGroupLayout = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Fragment, wgpu::SamplerBindingType::Filtering}}); @@ -2356,11 +2370,23 @@ TEST_F(ComparisonSamplerBindingTest, DISABLED_ShaderAndBGLMatches) { CreateFragmentPipeline(&bindGroupLayout, R"( [[group(0), binding(0)]] var mySampler: sampler; [[stage(fragment)]] fn main() { - let s : sampler = mySampler; + ignore(mySampler); })"); } - // Test that comparison sampler binding works with shadow sampler in the shader. + // Test that a non-filtering sampler binding works with normal sampler in the shader. + { + wgpu::BindGroupLayout bindGroupLayout = utils::MakeBindGroupLayout( + device, {{0, wgpu::ShaderStage::Fragment, wgpu::SamplerBindingType::NonFiltering}}); + + CreateFragmentPipeline(&bindGroupLayout, R"( + [[group(0), binding(0)]] var mySampler: sampler; + [[stage(fragment)]] fn main() { + ignore(mySampler); + })"); + } + + // Test that comparison sampler binding works with comparison sampler in the shader. { wgpu::BindGroupLayout bindGroupLayout = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Fragment, wgpu::SamplerBindingType::Comparison}}); @@ -2368,11 +2394,11 @@ TEST_F(ComparisonSamplerBindingTest, DISABLED_ShaderAndBGLMatches) { CreateFragmentPipeline(&bindGroupLayout, R"( [[group(0), binding(0)]] var mySampler: sampler_comparison; [[stage(fragment)]] fn main() { - let s : sampler_comparison = mySampler; + ignore(mySampler); })"); } - // Test that sampler binding does not work with comparison sampler in the shader. + // Test that filtering sampler binding does not work with comparison sampler in the shader. { wgpu::BindGroupLayout bindGroupLayout = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Fragment, wgpu::SamplerBindingType::Filtering}}); @@ -2380,7 +2406,19 @@ TEST_F(ComparisonSamplerBindingTest, DISABLED_ShaderAndBGLMatches) { ASSERT_DEVICE_ERROR(CreateFragmentPipeline(&bindGroupLayout, R"( [[group(0), binding(0)]] var mySampler: sampler_comparison; [[stage(fragment)]] fn main() { - let s : sampler_comparison = mySampler; + ignore(mySampler); + })")); + } + + // Test that non-filtering sampler binding does not work with comparison sampler in the shader. + { + wgpu::BindGroupLayout bindGroupLayout = utils::MakeBindGroupLayout( + device, {{0, wgpu::ShaderStage::Fragment, wgpu::SamplerBindingType::NonFiltering}}); + + ASSERT_DEVICE_ERROR(CreateFragmentPipeline(&bindGroupLayout, R"( + [[group(0), binding(0)]] var mySampler: sampler_comparison; + [[stage(fragment)]] fn main() { + ignore(mySampler); })")); } @@ -2392,12 +2430,110 @@ TEST_F(ComparisonSamplerBindingTest, DISABLED_ShaderAndBGLMatches) { ASSERT_DEVICE_ERROR(CreateFragmentPipeline(&bindGroupLayout, R"( [[group(0), binding(0)]] var mySampler: sampler; [[stage(fragment)]] fn main() { - let s : sampler = mySampler; + ignore(mySampler); })")); } + + // Test that a filtering sampler can be used to sample a float texture. + { + wgpu::BindGroupLayout bindGroupLayout = utils::MakeBindGroupLayout( + device, {{0, wgpu::ShaderStage::Fragment, wgpu::SamplerBindingType::Filtering}, + {1, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float}}); + + CreateFragmentPipeline(&bindGroupLayout, R"( + [[group(0), binding(0)]] var mySampler: sampler; + [[group(0), binding(1)]] var myTexture: texture_2d; + [[stage(fragment)]] fn main() { + ignore(textureSample(myTexture, mySampler, vec2(0.0, 0.0))); + })"); + } + + // Test that a non-filtering sampler can be used to sample a float texture. + { + wgpu::BindGroupLayout bindGroupLayout = utils::MakeBindGroupLayout( + device, {{0, wgpu::ShaderStage::Fragment, wgpu::SamplerBindingType::NonFiltering}, + {1, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float}}); + + CreateFragmentPipeline(&bindGroupLayout, R"( + [[group(0), binding(0)]] var mySampler: sampler; + [[group(0), binding(1)]] var myTexture: texture_2d; + [[stage(fragment)]] fn main() { + ignore(textureSample(myTexture, mySampler, vec2(0.0, 0.0))); + })"); + } + + // Test that a filtering sampler can be used to sample a depth texture. + { + wgpu::BindGroupLayout bindGroupLayout = utils::MakeBindGroupLayout( + device, {{0, wgpu::ShaderStage::Fragment, wgpu::SamplerBindingType::Filtering}, + {1, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Depth}}); + + CreateFragmentPipeline(&bindGroupLayout, R"( + [[group(0), binding(0)]] var mySampler: sampler; + [[group(0), binding(1)]] var myTexture: texture_depth_2d; + [[stage(fragment)]] fn main() { + ignore(textureSample(myTexture, mySampler, vec2(0.0, 0.0))); + })"); + } + + // Test that a non-filtering sampler can be used to sample a depth texture. + { + wgpu::BindGroupLayout bindGroupLayout = utils::MakeBindGroupLayout( + device, {{0, wgpu::ShaderStage::Fragment, wgpu::SamplerBindingType::NonFiltering}, + {1, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Depth}}); + + CreateFragmentPipeline(&bindGroupLayout, R"( + [[group(0), binding(0)]] var mySampler: sampler; + [[group(0), binding(1)]] var myTexture: texture_depth_2d; + [[stage(fragment)]] fn main() { + ignore(textureSample(myTexture, mySampler, vec2(0.0, 0.0))); + })"); + } + + // Test that a comparison sampler can be used to sample a depth texture. + { + wgpu::BindGroupLayout bindGroupLayout = utils::MakeBindGroupLayout( + device, {{0, wgpu::ShaderStage::Fragment, wgpu::SamplerBindingType::Comparison}, + {1, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Depth}}); + + CreateFragmentPipeline(&bindGroupLayout, R"( + [[group(0), binding(0)]] var mySampler: sampler_comparison; + [[group(0), binding(1)]] var myTexture: texture_depth_2d; + [[stage(fragment)]] fn main() { + ignore(textureSampleCompare(myTexture, mySampler, vec2(0.0, 0.0), 0.0)); + })"); + } + + // Test that a filtering sampler cannot be used to sample an unfilterable-float texture. + { + wgpu::BindGroupLayout bindGroupLayout = utils::MakeBindGroupLayout( + device, {{0, wgpu::ShaderStage::Fragment, wgpu::SamplerBindingType::Filtering}, + {1, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::UnfilterableFloat}}); + + ASSERT_DEVICE_ERROR(CreateFragmentPipeline(&bindGroupLayout, R"( + [[group(0), binding(0)]] var mySampler: sampler; + [[group(0), binding(1)]] var myTexture: texture_2d; + [[stage(fragment)]] fn main() { + ignore(textureSample(myTexture, mySampler, vec2(0.0, 0.0))); + })")); + } + + // Test that a non-filtering sampler can be used to sample an unfilterable-float texture. + { + wgpu::BindGroupLayout bindGroupLayout = utils::MakeBindGroupLayout( + device, {{0, wgpu::ShaderStage::Fragment, wgpu::SamplerBindingType::NonFiltering}, + {1, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::UnfilterableFloat}}); + + CreateFragmentPipeline(&bindGroupLayout, R"( + [[group(0), binding(0)]] var mySampler: sampler; + [[group(0), binding(1)]] var myTexture: texture_2d; + [[stage(fragment)]] fn main() { + ignore(textureSample(myTexture, mySampler, vec2(0.0, 0.0))); + })"); + } } -TEST_F(ComparisonSamplerBindingTest, SamplerAndBindGroupMatches) { +TEST_F(SamplerTypeBindingTest, SamplerAndBindGroupMatches) { // Test that sampler binding works with normal sampler. { wgpu::BindGroupLayout bindGroupLayout = utils::MakeBindGroupLayout( @@ -2436,4 +2572,59 @@ TEST_F(ComparisonSamplerBindingTest, SamplerAndBindGroupMatches) { ASSERT_DEVICE_ERROR( utils::MakeBindGroup(device, bindGroupLayout, {{0, device.CreateSampler(&desc)}})); } + + // Test that filtering sampler binding works with a filtering or non-filtering sampler. + { + wgpu::BindGroupLayout bindGroupLayout = utils::MakeBindGroupLayout( + device, {{0, wgpu::ShaderStage::Fragment, wgpu::SamplerBindingType::Filtering}}); + + // Test each filter member + { + wgpu::SamplerDescriptor desc; + desc.minFilter = wgpu::FilterMode::Linear; + utils::MakeBindGroup(device, bindGroupLayout, {{0, device.CreateSampler(&desc)}}); + } + { + wgpu::SamplerDescriptor desc; + desc.magFilter = wgpu::FilterMode::Linear; + utils::MakeBindGroup(device, bindGroupLayout, {{0, device.CreateSampler(&desc)}}); + } + { + wgpu::SamplerDescriptor desc; + desc.mipmapFilter = wgpu::FilterMode::Linear; + utils::MakeBindGroup(device, bindGroupLayout, {{0, device.CreateSampler(&desc)}}); + } + + // Test non-filtering sampler + utils::MakeBindGroup(device, bindGroupLayout, {{0, device.CreateSampler()}}); + } + + // Test that non-filtering sampler binding does not work with a filtering sampler. + { + wgpu::BindGroupLayout bindGroupLayout = utils::MakeBindGroupLayout( + device, {{0, wgpu::ShaderStage::Fragment, wgpu::SamplerBindingType::NonFiltering}}); + + // Test each filter member + { + wgpu::SamplerDescriptor desc; + desc.minFilter = wgpu::FilterMode::Linear; + ASSERT_DEVICE_ERROR( + utils::MakeBindGroup(device, bindGroupLayout, {{0, device.CreateSampler(&desc)}})); + } + { + wgpu::SamplerDescriptor desc; + desc.magFilter = wgpu::FilterMode::Linear; + ASSERT_DEVICE_ERROR( + utils::MakeBindGroup(device, bindGroupLayout, {{0, device.CreateSampler(&desc)}})); + } + { + wgpu::SamplerDescriptor desc; + desc.mipmapFilter = wgpu::FilterMode::Linear; + ASSERT_DEVICE_ERROR( + utils::MakeBindGroup(device, bindGroupLayout, {{0, device.CreateSampler(&desc)}})); + } + + // Test non-filtering sampler + utils::MakeBindGroup(device, bindGroupLayout, {{0, device.CreateSampler()}}); + } } diff --git a/src/tests/unittests/validation/GetBindGroupLayoutValidationTests.cpp b/src/tests/unittests/validation/GetBindGroupLayoutValidationTests.cpp index 13d4ddd839..51ba72d8a5 100644 --- a/src/tests/unittests/validation/GetBindGroupLayoutValidationTests.cpp +++ b/src/tests/unittests/validation/GetBindGroupLayoutValidationTests.cpp @@ -139,6 +139,122 @@ TEST_F(GetBindGroupLayoutTests, DefaultShaderStageAndDynamicOffsets) { EXPECT_NE(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get()); } +TEST_F(GetBindGroupLayoutTests, DefaultTextureSampleType) { + // This test works assuming Dawn Native's object deduplication. + // Getting the same pointer to equivalent bind group layouts is an implementation detail of Dawn + // Native. + DAWN_SKIP_TEST_IF(UsesWire()); + // Relies on Tint shader reflection. + DAWN_SKIP_TEST_IF(!HasToggleEnabled("use_tint_generator")); + + wgpu::BindGroupLayout filteringBGL = utils::MakeBindGroupLayout( + device, {{0, wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment, + wgpu::TextureSampleType::Float}, + {1, wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment, + wgpu::SamplerBindingType::Filtering}}); + + wgpu::BindGroupLayout nonFilteringBGL = utils::MakeBindGroupLayout( + device, {{0, wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment, + wgpu::TextureSampleType::UnfilterableFloat}, + {1, wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment, + wgpu::SamplerBindingType::Filtering}}); + + wgpu::ShaderModule emptyVertexModule = utils::CreateShaderModule(device, R"( + [[group(0), binding(0)]] var myTexture : texture_2d; + [[group(0), binding(1)]] var mySampler : sampler; + [[stage(vertex)]] fn main() -> [[builtin(position)]] vec4 { + ignore(myTexture); + ignore(mySampler); + return vec4(); + })"); + + wgpu::ShaderModule textureLoadVertexModule = utils::CreateShaderModule(device, R"( + [[group(0), binding(0)]] var myTexture : texture_2d; + [[group(0), binding(1)]] var mySampler : sampler; + [[stage(vertex)]] fn main() -> [[builtin(position)]] vec4 { + ignore(textureLoad(myTexture, vec2(), 0)); + ignore(mySampler); + return vec4(); + })"); + + wgpu::ShaderModule textureSampleVertexModule = utils::CreateShaderModule(device, R"( + [[group(0), binding(0)]] var myTexture : texture_2d; + [[group(0), binding(1)]] var mySampler : sampler; + [[stage(vertex)]] fn main() -> [[builtin(position)]] vec4 { + ignore(textureSampleLevel(myTexture, mySampler, vec2(), 0.0)); + return vec4(); + })"); + + wgpu::ShaderModule unusedTextureFragmentModule = utils::CreateShaderModule(device, R"( + [[group(0), binding(0)]] var myTexture : texture_2d; + [[group(0), binding(1)]] var mySampler : sampler; + [[stage(fragment)]] fn main() { + ignore(myTexture); + ignore(mySampler); + })"); + + wgpu::ShaderModule textureLoadFragmentModule = utils::CreateShaderModule(device, R"( + [[group(0), binding(0)]] var myTexture : texture_2d; + [[group(0), binding(1)]] var mySampler : sampler; + [[stage(fragment)]] fn main() { + ignore(textureLoad(myTexture, vec2(), 0)); + ignore(mySampler); + })"); + + wgpu::ShaderModule textureSampleFragmentModule = utils::CreateShaderModule(device, R"( + [[group(0), binding(0)]] var myTexture : texture_2d; + [[group(0), binding(1)]] var mySampler : sampler; + [[stage(fragment)]] fn main() { + ignore(textureSample(myTexture, mySampler, vec2())); + })"); + + auto BGLFromModules = [this](wgpu::ShaderModule vertexModule, + wgpu::ShaderModule fragmentModule) { + utils::ComboRenderPipelineDescriptor descriptor; + descriptor.vertex.module = vertexModule; + descriptor.cFragment.module = fragmentModule; + return device.CreateRenderPipeline(&descriptor).GetBindGroupLayout(0); + }; + + // Textures not used default to non-filtering + EXPECT_EQ(BGLFromModules(emptyVertexModule, unusedTextureFragmentModule).Get(), + nonFilteringBGL.Get()); + EXPECT_NE(BGLFromModules(emptyVertexModule, unusedTextureFragmentModule).Get(), + filteringBGL.Get()); + + // Textures used with textureLoad default to non-filtering + EXPECT_EQ(BGLFromModules(emptyVertexModule, textureLoadFragmentModule).Get(), + nonFilteringBGL.Get()); + EXPECT_NE(BGLFromModules(emptyVertexModule, textureLoadFragmentModule).Get(), + filteringBGL.Get()); + + // Textures used with textureLoad on both stages default to non-filtering + EXPECT_EQ(BGLFromModules(textureLoadVertexModule, textureLoadFragmentModule).Get(), + nonFilteringBGL.Get()); + EXPECT_NE(BGLFromModules(textureLoadVertexModule, textureLoadFragmentModule).Get(), + filteringBGL.Get()); + + // Textures used with textureSample default to filtering + EXPECT_NE(BGLFromModules(emptyVertexModule, textureSampleFragmentModule).Get(), + nonFilteringBGL.Get()); + EXPECT_EQ(BGLFromModules(emptyVertexModule, textureSampleFragmentModule).Get(), + filteringBGL.Get()); + EXPECT_NE(BGLFromModules(textureSampleVertexModule, unusedTextureFragmentModule).Get(), + nonFilteringBGL.Get()); + EXPECT_EQ(BGLFromModules(textureSampleVertexModule, unusedTextureFragmentModule).Get(), + filteringBGL.Get()); + + // Textures used with both textureLoad and textureSample default to filtering + EXPECT_NE(BGLFromModules(textureLoadVertexModule, textureSampleFragmentModule).Get(), + nonFilteringBGL.Get()); + EXPECT_EQ(BGLFromModules(textureLoadVertexModule, textureSampleFragmentModule).Get(), + filteringBGL.Get()); + EXPECT_NE(BGLFromModules(textureSampleVertexModule, textureLoadFragmentModule).Get(), + nonFilteringBGL.Get()); + EXPECT_EQ(BGLFromModules(textureSampleVertexModule, textureLoadFragmentModule).Get(), + filteringBGL.Get()); +} + // Test GetBindGroupLayout works with a compute pipeline TEST_F(GetBindGroupLayoutTests, ComputePipeline) { // This test works assuming Dawn Native's object deduplication. @@ -240,7 +356,7 @@ TEST_F(GetBindGroupLayoutTests, BindingType) { binding.buffer.type = wgpu::BufferBindingType::Undefined; binding.buffer.minBindingSize = 0; { - binding.texture.sampleType = wgpu::TextureSampleType::Float; + binding.texture.sampleType = wgpu::TextureSampleType::UnfilterableFloat; wgpu::RenderPipeline pipeline = RenderPipelineFromFragmentShader(R"( [[group(0), binding(0)]] var myTexture : texture_2d; @@ -311,7 +427,7 @@ TEST_F(GetBindGroupLayoutTests, ViewDimension) { wgpu::BindGroupLayoutEntry binding = {}; binding.binding = 0; binding.visibility = wgpu::ShaderStage::Fragment; - binding.texture.sampleType = wgpu::TextureSampleType::Float; + binding.texture.sampleType = wgpu::TextureSampleType::UnfilterableFloat; wgpu::BindGroupLayoutDescriptor desc = {}; desc.entryCount = 1; @@ -400,7 +516,7 @@ TEST_F(GetBindGroupLayoutTests, TextureComponentType) { desc.entries = &binding; { - binding.texture.sampleType = wgpu::TextureSampleType::Float; + binding.texture.sampleType = wgpu::TextureSampleType::UnfilterableFloat; wgpu::RenderPipeline pipeline = RenderPipelineFromFragmentShader(R"( [[group(0), binding(0)]] var myTexture : texture_2d;