[Static samplers] Make BindingInfo variant

This CL adds a BindingInfo variant for static samplers and does an
initial pass at handling the variant everywhere that it needs to be
handled. Backend implementations are stubbed out.

Change-Id: If2c16cbcedce64fd800d7163b3a0eb804d61033c
Bug: dawn:2643
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/181360
Reviewed-by: Austin Eng <enga@chromium.org>
Reviewed-by: Nicolette Prevost <nicolettep@google.com>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Colin Blundell <blundell@chromium.org>
diff --git a/src/dawn/native/BindGroup.cpp b/src/dawn/native/BindGroup.cpp
index 4e2a217..65ff312 100644
--- a/src/dawn/native/BindGroup.cpp
+++ b/src/dawn/native/BindGroup.cpp
@@ -305,11 +305,17 @@
     DAWN_TRY(device->ValidateObject(descriptor->layout));
 
     BindGroupLayoutInternalBase* layout = descriptor->layout->GetInternalBindGroupLayout();
+
+    // NOTE: Static sampler layout bindings should not have bind group entries,
+    // as the sampler is specified in the layout itself.
+    const auto expectedBindingsCount =
+        layout->GetUnexpandedBindingCount() - layout->GetStaticSamplerCount();
+
     DAWN_INVALID_IF(
-        descriptor->entryCount != layout->GetUnexpandedBindingCount(),
-        "Number of entries (%u) did not match the number of entries (%u) specified in %s."
+        descriptor->entryCount != expectedBindingsCount,
+        "Number of entries (%u) did not match the expected number of entries (%u) for %s."
         "\nExpected layout: %s",
-        descriptor->entryCount, static_cast<uint32_t>(layout->GetBindingCount()), layout,
+        descriptor->entryCount, static_cast<uint32_t>(expectedBindingsCount), layout,
         layout->EntriesToString());
 
     const BindGroupLayoutInternalBase::BindingMap& bindingMap = layout->GetBindingMap();
@@ -389,6 +395,12 @@
                                  "\nExpected entry layout: %s",
                                  i, layout);
                 return {};
+            },
+            [&](const StaticSamplerHolderBindingLayout& layout) -> MaybeError {
+                return DAWN_VALIDATION_ERROR(
+                    "entries[%u] is provided when the layout contains a static sampler for that "
+                    "binding.",
+                    i);
             }));
     }
 
@@ -397,7 +409,7 @@
     //  - Each binding must be set at most once
     //
     // We don't validate the equality because it wouldn't be possible to cover it with a test.
-    DAWN_ASSERT(bindingsSet.count() == layout->GetUnexpandedBindingCount());
+    DAWN_ASSERT(bindingsSet.count() == expectedBindingsCount);
 
     return {};
 }
diff --git a/src/dawn/native/BindGroupLayoutInternal.cpp b/src/dawn/native/BindGroupLayoutInternal.cpp
index 0755617..d20bc05 100644
--- a/src/dawn/native/BindGroupLayoutInternal.cpp
+++ b/src/dawn/native/BindGroupLayoutInternal.cpp
@@ -356,6 +356,11 @@
             const SamplerBindingLayout& layoutB = std::get<SamplerBindingLayout>(b.bindingLayout);
             return layoutA.type != layoutB.type;
         },
+        [&](const StaticSamplerHolderBindingLayout& layoutA) -> bool {
+            const StaticSamplerHolderBindingLayout& layoutB =
+                std::get<StaticSamplerHolderBindingLayout>(b.bindingLayout);
+            return layoutA.sampler != layoutB.sampler;
+        },
         [&](const TextureBindingLayout& layoutA) -> bool {
             const TextureBindingLayout& layoutB = std::get<TextureBindingLayout>(b.bindingLayout);
             return layoutA.sampleType != layoutB.sampleType ||
@@ -405,7 +410,9 @@
         }
         bindingInfo.bindingLayout = bindingLayout;
     } else if (auto* staticSamplerBindingLayout = binding.Get<StaticSamplerBindingLayout>()) {
-        // TODO(crbug.com/dawn/2463): Populate BindingInfo for this entry.
+        StaticSamplerHolderBindingLayout bindingLayout;
+        bindingLayout.sampler = staticSamplerBindingLayout->sampler;
+        bindingInfo.bindingLayout = bindingLayout;
     } else {
         DAWN_UNREACHABLE();
     }
@@ -504,6 +511,14 @@
             }
             break;
         }
+        case BindingInfoType::StaticSampler: {
+            const auto& aLayout = std::get<StaticSamplerHolderBindingLayout>(aInfo.bindingLayout);
+            const auto& bLayout = std::get<StaticSamplerHolderBindingLayout>(bInfo.bindingLayout);
+            if (aLayout.sampler != bLayout.sampler) {
+                return aLayout.sampler < bLayout.sampler;
+            }
+            break;
+        }
         case BindingInfoType::ExternalTexture:
             DAWN_UNREACHABLE();
             break;
@@ -632,6 +647,9 @@
             [&](const StorageTextureBindingLayout& layout) {
                 recorder.Record(BindingInfoType::StorageTexture, layout.access, layout.format,
                                 layout.viewDimension);
+            },
+            [&](const StaticSamplerHolderBindingLayout& layout) {
+                recorder.Record(BindingInfoType::StaticSampler, layout.sampler->GetContentHash());
             });
     }
 
@@ -663,6 +681,10 @@
     return mBindingCounts.unverifiedBufferCount;
 }
 
+uint32_t BindGroupLayoutInternalBase::GetStaticSamplerCount() const {
+    return mBindingCounts.staticSamplerCount;
+}
+
 uint32_t BindGroupLayoutInternalBase::GetExternalTextureBindingCount() const {
     return mExternalTextureBindingExpansionMap.size();
 }
diff --git a/src/dawn/native/BindGroupLayoutInternal.h b/src/dawn/native/BindGroupLayoutInternal.h
index d41c983..43fddc9 100644
--- a/src/dawn/native/BindGroupLayoutInternal.h
+++ b/src/dawn/native/BindGroupLayoutInternal.h
@@ -97,6 +97,7 @@
     // Returns |BindingIndex| because dynamic buffers are packed at the front.
     BindingIndex GetDynamicBufferCount() const;
     uint32_t GetUnverifiedBufferCount() const;
+    uint32_t GetStaticSamplerCount() const;
 
     // Used to get counts and validate them in pipeline layout creation. Other getters
     // should be used to get typed integer counts.
diff --git a/src/dawn/native/BindingInfo.cpp b/src/dawn/native/BindingInfo.cpp
index 879104e..32b3468 100644
--- a/src/dawn/native/BindingInfo.cpp
+++ b/src/dawn/native/BindingInfo.cpp
@@ -41,6 +41,9 @@
         [](const TextureBindingLayout&) -> BindingInfoType { return BindingInfoType::Texture; },
         [](const StorageTextureBindingLayout&) -> BindingInfoType {
             return BindingInfoType::StorageTexture;
+        },
+        [](const StaticSamplerHolderBindingLayout&) -> BindingInfoType {
+            return BindingInfoType::StaticSampler;
         });
 }
 
@@ -89,6 +92,7 @@
     } else if (auto* externalTextureBindingLayout = entry.Get<ExternalTextureBindingLayout>()) {
         perStageBindingCountMember = &PerStageBindingCounts::externalTextureCount;
     } else if (auto* staticSamplerBindingLayout = entry.Get<StaticSamplerBindingLayout>()) {
+        ++bindingCounts->staticSamplerCount;
         perStageBindingCountMember = &PerStageBindingCounts::staticSamplerCount;
     }
 
diff --git a/src/dawn/native/BindingInfo.h b/src/dawn/native/BindingInfo.h
index 1eba544..638d2f2 100644
--- a/src/dawn/native/BindingInfo.h
+++ b/src/dawn/native/BindingInfo.h
@@ -33,6 +33,7 @@
 #include <vector>
 
 #include "dawn/common/Constants.h"
+#include "dawn/common/Ref.h"
 #include "dawn/common/ityp_array.h"
 #include "dawn/native/Error.h"
 #include "dawn/native/Format.h"
@@ -56,7 +57,18 @@
 // TODO(enga): Figure out a good number for this.
 static constexpr uint32_t kMaxOptimalBindingsPerGroup = 32;
 
-enum class BindingInfoType { Buffer, Sampler, Texture, StorageTexture, ExternalTexture };
+enum class BindingInfoType {
+    Buffer,
+    Sampler,
+    Texture,
+    StorageTexture,
+    ExternalTexture,
+    StaticSampler
+};
+
+struct StaticSamplerHolderBindingLayout {
+    Ref<SamplerBase> sampler;
+};
 
 struct BindingInfo {
     BindingNumber binding;
@@ -65,7 +77,8 @@
     std::variant<BufferBindingLayout,
                  SamplerBindingLayout,
                  TextureBindingLayout,
-                 StorageTextureBindingLayout>
+                 StorageTextureBindingLayout,
+                 StaticSamplerHolderBindingLayout>
         bindingLayout;
 };
 
@@ -92,6 +105,7 @@
     uint32_t unverifiedBufferCount;  // Buffers with minimum buffer size unspecified
     uint32_t dynamicUniformBufferCount;
     uint32_t dynamicStorageBufferCount;
+    uint32_t staticSamplerCount;
     PerStage<PerStageBindingCounts> perStage;
 };
 
diff --git a/src/dawn/native/PassResourceUsageTracker.cpp b/src/dawn/native/PassResourceUsageTracker.cpp
index 8a9e8e4..96dff82 100644
--- a/src/dawn/native/PassResourceUsageTracker.cpp
+++ b/src/dawn/native/PassResourceUsageTracker.cpp
@@ -160,7 +160,8 @@
                         DAWN_UNREACHABLE();
                 }
             },
-            [&](const SamplerBindingLayout&) {});
+            [&](const SamplerBindingLayout&) {},  //
+            [&](const StaticSamplerHolderBindingLayout&) {});
     }
 
     for (const Ref<ExternalTextureBase>& externalTexture : group->GetBoundExternalTextures()) {
@@ -225,7 +226,7 @@
                 mUsage.referencedTextures.insert(
                     group->GetBindingAsTextureView(index)->GetTexture());
             },
-            [](const SamplerBindingLayout&) {});
+            [](const SamplerBindingLayout&) {}, [](const StaticSamplerHolderBindingLayout&) {});
     }
 
     for (const Ref<ExternalTextureBase>& externalTexture : group->GetBoundExternalTextures()) {
diff --git a/src/dawn/native/ShaderModule.cpp b/src/dawn/native/ShaderModule.cpp
index 4e24341..fc10ab3 100644
--- a/src/dawn/native/ShaderModule.cpp
+++ b/src/dawn/native/ShaderModule.cpp
@@ -468,6 +468,15 @@
 
     BindingInfoType bindingLayoutType = GetBindingInfoType(layoutInfo);
     BindingInfoType shaderBindingType = GetShaderBindingType(shaderInfo);
+
+    if (bindingLayoutType == BindingInfoType::StaticSampler) {
+        DAWN_INVALID_IF(shaderBindingType != BindingInfoType::Sampler,
+                        "Binding type in the shader (%s) doesn't match the required type of %s for "
+                        "the %s type in the layout.",
+                        shaderBindingType, BindingInfoType::Sampler, bindingLayoutType);
+        return {};
+    }
+
     DAWN_INVALID_IF(bindingLayoutType != shaderBindingType,
                     "Binding type in the shader (%s) doesn't match the type in the layout (%s).",
                     shaderBindingType, bindingLayoutType);
@@ -902,6 +911,9 @@
                 info.bindingInfo.emplace<ExternalTextureBindingInfo>();
                 break;
             }
+            case BindingInfoType::StaticSampler: {
+                return DAWN_VALIDATION_ERROR("Static samplers not supported in WGSL");
+            }
             default:
                 return DAWN_VALIDATION_ERROR("Unknown binding type in Shader");
         }
diff --git a/src/dawn/native/d3d11/BindGroupTrackerD3D11.cpp b/src/dawn/native/d3d11/BindGroupTrackerD3D11.cpp
index df0c29f..46954c4 100644
--- a/src/dawn/native/d3d11/BindGroupTrackerD3D11.cpp
+++ b/src/dawn/native/d3d11/BindGroupTrackerD3D11.cpp
@@ -228,7 +228,13 @@
                         return {};
                     },
                     [](const TextureBindingLayout&) -> MaybeError { return {}; },
-                    [](const SamplerBindingLayout&) -> MaybeError { return {}; }));
+                    [](const SamplerBindingLayout&) -> MaybeError { return {}; },
+                    [](const StaticSamplerHolderBindingLayout&) -> MaybeError {
+                        // Static samplers are implemented in the frontend on
+                        // D3D11.
+                        DAWN_UNREACHABLE();
+                        return {};
+                    }));
             }
         }
 
@@ -366,6 +372,12 @@
                 }
                 return {};
             },
+            [&](const StaticSamplerHolderBindingLayout&) -> MaybeError {
+                // Static samplers are implemented in the frontend on
+                // D3D11.
+                DAWN_UNREACHABLE();
+                return {};
+            },
             [&](const SamplerBindingLayout&) -> MaybeError {
                 Sampler* sampler = ToBackend(group->GetBindingAsSampler(bindingIndex));
                 ID3D11SamplerState* d3d11SamplerState = sampler->GetD3D11SamplerState();
@@ -503,6 +515,11 @@
                         DAWN_UNREACHABLE();
                 }
             },
+            [&](const StaticSamplerHolderBindingLayout&) {
+                // Static samplers are implemented in the frontend on
+                // D3D11.
+                DAWN_UNREACHABLE();
+            },
             [&](const SamplerBindingLayout&) {
                 ID3D11SamplerState* nullSampler = nullptr;
                 if (bindingVisibility & wgpu::ShaderStage::Vertex) {
diff --git a/src/dawn/native/d3d11/PipelineLayoutD3D11.cpp b/src/dawn/native/d3d11/PipelineLayoutD3D11.cpp
index b0915be..f3d7400 100644
--- a/src/dawn/native/d3d11/PipelineLayoutD3D11.cpp
+++ b/src/dawn/native/d3d11/PipelineLayoutD3D11.cpp
@@ -84,6 +84,11 @@
                 [&](const SamplerBindingLayout&) {
                     mIndexInfo[group][bindingIndex] = samplerIndex++;
                 },
+                [&](const StaticSamplerHolderBindingLayout&) {
+                    // Static samplers are implemented in the frontend on
+                    // D3D11.
+                    DAWN_UNREACHABLE();
+                },
                 [&](const TextureBindingLayout&) {
                     mIndexInfo[group][bindingIndex] = shaderResourceViewIndex++;
                 },
diff --git a/src/dawn/native/d3d12/BindGroupD3D12.cpp b/src/dawn/native/d3d12/BindGroupD3D12.cpp
index 7298e6b..2698840 100644
--- a/src/dawn/native/d3d12/BindGroupD3D12.cpp
+++ b/src/dawn/native/d3d12/BindGroupD3D12.cpp
@@ -194,6 +194,12 @@
                         DAWN_UNREACHABLE();
                 }
             },
+            [](const StaticSamplerHolderBindingLayout&) {
+                // Static samplers are handled in the frontend.
+                // TODO(crbug.com/dawn/2483): Implement static samplers in the
+                // D3D12 backend.
+                DAWN_UNREACHABLE();
+            },
             // No-op as samplers will be later initialized by CreateSamplers().
             [](const SamplerBindingLayout&) {});
     }
diff --git a/src/dawn/native/d3d12/BindGroupLayoutD3D12.cpp b/src/dawn/native/d3d12/BindGroupLayoutD3D12.cpp
index ed02fde..7a8d088 100644
--- a/src/dawn/native/d3d12/BindGroupLayoutD3D12.cpp
+++ b/src/dawn/native/d3d12/BindGroupLayoutD3D12.cpp
@@ -53,6 +53,13 @@
                     DAWN_UNREACHABLE();
             }
         },
+        [](const StaticSamplerHolderBindingLayout&) -> D3D12_DESCRIPTOR_RANGE_TYPE {
+            // Static samplers are handled in the frontend.
+            // TODO(crbug.com/dawn/2483): Implement static samplers in the
+            // D3D12 backend.
+            DAWN_UNREACHABLE();
+            return D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER;
+        },
         [](const SamplerBindingLayout&) -> D3D12_DESCRIPTOR_RANGE_TYPE {
             return D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER;
         },
@@ -120,6 +127,13 @@
         // don't need to set DESCRIPTORS_VOLATILE for any binding types.
         range.Flags = MatchVariant(
             bindingInfo.bindingLayout,
+            [](const StaticSamplerHolderBindingLayout&) -> D3D12_DESCRIPTOR_RANGE_FLAGS {
+                // Static samplers are handled in the frontend.
+                // TODO(crbug.com/dawn/2483): Implement static samplers in the
+                // D3D12 backend.
+                DAWN_UNREACHABLE();
+                return D3D12_DESCRIPTOR_RANGE_FLAG_NONE;
+            },
             [](const SamplerBindingLayout&) -> D3D12_DESCRIPTOR_RANGE_FLAGS {
                 // Sampler descriptor ranges don't support DATA_* flags at all since samplers do not
                 // point to data.
diff --git a/src/dawn/native/metal/CommandBufferMTL.mm b/src/dawn/native/metal/CommandBufferMTL.mm
index ce703a7..fe3d2c4 100644
--- a/src/dawn/native/metal/CommandBufferMTL.mm
+++ b/src/dawn/native/metal/CommandBufferMTL.mm
@@ -624,6 +624,12 @@
                                          atIndex:computeIndex];
                     }
                 },
+                [&](const StaticSamplerHolderBindingLayout&) {
+                    // Static samplers are handled in the frontend.
+                    // TODO(crbug.com/dawn/2482): Implement static samplers in the
+                    // Metal backend.
+                    DAWN_UNREACHABLE();
+                },
                 [&](const TextureBindingLayout&) {
                     auto textureView = ToBackend(group->GetBindingAsTextureView(bindingIndex));
                     if (hasVertStage) {
diff --git a/src/dawn/native/metal/PipelineLayoutMTL.mm b/src/dawn/native/metal/PipelineLayoutMTL.mm
index 6bfc9dd..0eca24f 100644
--- a/src/dawn/native/metal/PipelineLayoutMTL.mm
+++ b/src/dawn/native/metal/PipelineLayoutMTL.mm
@@ -78,6 +78,12 @@
                     [&](const StorageTextureBindingLayout&) {
                         mIndexInfo[stage][group][bindingIndex] = textureIndex;
                         textureIndex++;
+                    },
+                    [&](const StaticSamplerHolderBindingLayout&) {
+                        // Static samplers are handled in the frontend.
+                        // TODO(crbug.com/dawn/2482): Implement static samplers in the
+                        // Metal backend.
+                        DAWN_UNREACHABLE();
                     });
             }
         }
diff --git a/src/dawn/native/opengl/CommandBufferGL.cpp b/src/dawn/native/opengl/CommandBufferGL.cpp
index a129dd7..321e0e1 100644
--- a/src/dawn/native/opengl/CommandBufferGL.cpp
+++ b/src/dawn/native/opengl/CommandBufferGL.cpp
@@ -308,6 +308,11 @@
 
                     gl.BindBufferRange(target, index, buffer, offset, binding.size);
                 },
+                [&](const StaticSamplerHolderBindingLayout&) {
+                    // Static samplers are implemented in the frontend on
+                    // GL.
+                    DAWN_UNREACHABLE();
+                },
                 [&](const SamplerBindingLayout&) {
                     Sampler* sampler = ToBackend(group->GetBindingAsSampler(bindingIndex));
                     GLuint samplerIndex = indices[bindingIndex];
diff --git a/src/dawn/native/opengl/PipelineLayoutGL.cpp b/src/dawn/native/opengl/PipelineLayoutGL.cpp
index 4b44aa6..b4dc75b 100644
--- a/src/dawn/native/opengl/PipelineLayoutGL.cpp
+++ b/src/dawn/native/opengl/PipelineLayoutGL.cpp
@@ -67,6 +67,11 @@
                             DAWN_UNREACHABLE();
                     }
                 },
+                [&](const StaticSamplerHolderBindingLayout&) {
+                    // Static samplers are implemented in the frontend on
+                    // GL.
+                    DAWN_UNREACHABLE();
+                },
                 [&](const SamplerBindingLayout&) {
                     mIndexInfo[group][bindingIndex] = samplerIndex;
                     samplerIndex++;
diff --git a/src/dawn/native/vulkan/BindGroupLayoutVk.cpp b/src/dawn/native/vulkan/BindGroupLayoutVk.cpp
index 70e90bc..7c73c29 100644
--- a/src/dawn/native/vulkan/BindGroupLayoutVk.cpp
+++ b/src/dawn/native/vulkan/BindGroupLayoutVk.cpp
@@ -85,6 +85,13 @@
             }
         },
         [](const SamplerBindingLayout&) { return VK_DESCRIPTOR_TYPE_SAMPLER; },
+        [](const StaticSamplerHolderBindingLayout&) {
+            // Static samplers are implemented in the frontend.
+            // TODO(crbug.com/dawn/2463): Implement static samplers in the backend
+            // on Vulkan.
+            DAWN_UNREACHABLE();
+            return VK_DESCRIPTOR_TYPE_SAMPLER;
+        },
         [](const TextureBindingLayout&) { return VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; },
         [](const StorageTextureBindingLayout&) { return VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; });
 }
diff --git a/src/dawn/native/vulkan/BindGroupVk.cpp b/src/dawn/native/vulkan/BindGroupVk.cpp
index fa40523..e0ee26d 100644
--- a/src/dawn/native/vulkan/BindGroupVk.cpp
+++ b/src/dawn/native/vulkan/BindGroupVk.cpp
@@ -104,6 +104,13 @@
                 write.pImageInfo = &writeImageInfo[numWrites];
                 return true;
             },
+            [&](const StaticSamplerHolderBindingLayout&) -> bool {
+                // Static samplers are implemented in the frontend.
+                // TODO(crbug.com/dawn/2463): Implement static samplers in the backend
+                // on Vulkan.
+                DAWN_UNREACHABLE();
+                return true;
+            },
             [&](const TextureBindingLayout&) -> bool {
                 TextureView* view = ToBackend(GetBindingAsTextureView(bindingIndex));
 
diff --git a/src/dawn/native/webgpu_absl_format.cpp b/src/dawn/native/webgpu_absl_format.cpp
index e41b2f7..ef1adcf 100644
--- a/src/dawn/native/webgpu_absl_format.cpp
+++ b/src/dawn/native/webgpu_absl_format.cpp
@@ -40,6 +40,7 @@
 #include "dawn/native/PerStage.h"
 #include "dawn/native/ProgrammableEncoder.h"
 #include "dawn/native/RenderPipeline.h"
+#include "dawn/native/Sampler.h"
 #include "dawn/native/ShaderModule.h"
 #include "dawn/native/Subresource.h"
 #include "dawn/native/Surface.h"
@@ -127,6 +128,10 @@
             s->Append(absl::StrFormat(*fmt, static_cast<uint32_t>(value.binding), value.visibility,
                                       BindingInfoType::Sampler, layout));
         },
+        [&](const StaticSamplerHolderBindingLayout& layout) {
+            s->Append(absl::StrFormat(*fmt, static_cast<uint32_t>(value.binding), value.visibility,
+                                      BindingInfoType::StaticSampler, layout));
+        },
         [&](const TextureBindingLayout& layout) {
             s->Append(absl::StrFormat(*fmt, static_cast<uint32_t>(value.binding), value.visibility,
                                       BindingInfoType::Texture, layout));
@@ -181,6 +186,15 @@
     return {true};
 }
 
+absl::FormatConvertResult<absl::FormatConversionCharSet::kString> AbslFormatConvert(
+    const StaticSamplerHolderBindingLayout& value,
+    const absl::FormatConversionSpec& spec,
+    absl::FormatSink* s) {
+    s->Append(
+        absl::StrFormat("{type: StaticSamplerBindingLayout, sampler: %s}", value.sampler.Get()));
+    return {true};
+}
+
 //
 // Objects
 //
@@ -448,6 +462,9 @@
         case BindingInfoType::ExternalTexture:
             s->Append("externalTexture");
             break;
+        case BindingInfoType::StaticSampler:
+            s->Append("staticSampler");
+            break;
     }
     return {true};
 }
diff --git a/src/dawn/native/webgpu_absl_format.h b/src/dawn/native/webgpu_absl_format.h
index dceb3bc..79879bb 100644
--- a/src/dawn/native/webgpu_absl_format.h
+++ b/src/dawn/native/webgpu_absl_format.h
@@ -100,6 +100,12 @@
     const absl::FormatConversionSpec& spec,
     absl::FormatSink* s);
 
+struct StaticSamplerHolderBindingLayout;
+absl::FormatConvertResult<absl::FormatConversionCharSet::kString> AbslFormatConvert(
+    const StaticSamplerHolderBindingLayout& value,
+    const absl::FormatConversionSpec& spec,
+    absl::FormatSink* s);
+
 //
 // Objects
 //
diff --git a/src/dawn/tests/unittests/validation/BindGroupValidationTests.cpp b/src/dawn/tests/unittests/validation/BindGroupValidationTests.cpp
index c856614..999b4b0 100644
--- a/src/dawn/tests/unittests/validation/BindGroupValidationTests.cpp
+++ b/src/dawn/tests/unittests/validation/BindGroupValidationTests.cpp
@@ -1818,10 +1818,182 @@
     ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&desc));
 }
 
+// Verifies that creation of a bind group with no entry for a static sampler
+// succeeds.
+TEST_F(BindGroupLayoutWithStaticSamplersValidationTest, CreateBindGroupWithStaticSamplerSupported) {
+    wgpu::BindGroupLayoutEntry binding = {};
+    binding.binding = 0;
+    wgpu::StaticSamplerBindingLayout staticSamplerBinding = {};
+    staticSamplerBinding.sampler = device.CreateSampler();
+    binding.nextInChain = &staticSamplerBinding;
+
+    wgpu::BindGroupLayoutDescriptor desc = {};
+    desc.entryCount = 1;
+    desc.entries = &binding;
+
+    wgpu::BindGroupLayout layout = device.CreateBindGroupLayout(&desc);
+
+    wgpu::BindGroupDescriptor descriptor;
+    descriptor.layout = layout;
+    descriptor.entryCount = 0;
+
+    device.CreateBindGroup(&descriptor);
+}
+
+// Verifies that creation of a correctly-specified bind group for a layout that
+// has a static sampler and a sampler succeeds.
+TEST_F(BindGroupLayoutWithStaticSamplersValidationTest,
+       CreateBindGroupWithStaticSamplerAndSamplerSupported) {
+    std::vector<wgpu::BindGroupLayoutEntry> entries;
+
+    wgpu::BindGroupLayoutEntry binding0 = {};
+    binding0.binding = 0;
+    wgpu::StaticSamplerBindingLayout staticSamplerBinding = {};
+    staticSamplerBinding.sampler = device.CreateSampler();
+    binding0.nextInChain = &staticSamplerBinding;
+    entries.push_back(binding0);
+
+    wgpu::BindGroupLayoutEntry binding1 = {};
+    binding1.binding = 1;
+    binding1.sampler.type = wgpu::SamplerBindingType::Filtering;
+    entries.push_back(binding1);
+
+    wgpu::BindGroupLayoutDescriptor desc = {};
+    desc.entryCount = 2;
+    desc.entries = entries.data();
+
+    wgpu::BindGroupLayout layout = device.CreateBindGroupLayout(&desc);
+
+    wgpu::SamplerDescriptor samplerDesc;
+    samplerDesc.minFilter = wgpu::FilterMode::Linear;
+    utils::MakeBindGroup(device, layout, {{1, device.CreateSampler(&samplerDesc)}});
+}
+
+// Verifies that creation of a correctly-specified bind group for a layout that
+// has a sampler and a static sampler succeeds.
+TEST_F(BindGroupLayoutWithStaticSamplersValidationTest,
+       CreateBindGroupWithSamplerAndStaticSamplerSupported) {
+    std::vector<wgpu::BindGroupLayoutEntry> entries;
+
+    wgpu::BindGroupLayoutEntry binding0 = {};
+    binding0.binding = 0;
+    binding0.sampler.type = wgpu::SamplerBindingType::Filtering;
+    entries.push_back(binding0);
+
+    wgpu::BindGroupLayoutEntry binding1 = {};
+    binding1.binding = 1;
+    wgpu::StaticSamplerBindingLayout staticSamplerBinding = {};
+    staticSamplerBinding.sampler = device.CreateSampler();
+    binding1.nextInChain = &staticSamplerBinding;
+    entries.push_back(binding1);
+
+    wgpu::BindGroupLayoutDescriptor desc = {};
+    desc.entryCount = 2;
+    desc.entries = entries.data();
+
+    wgpu::BindGroupLayout layout = device.CreateBindGroupLayout(&desc);
+
+    wgpu::SamplerDescriptor samplerDesc;
+    samplerDesc.minFilter = wgpu::FilterMode::Linear;
+    utils::MakeBindGroup(device, layout, {{0, device.CreateSampler(&samplerDesc)}});
+}
+
+// Verifies that creating a bind group with an entry for a static sampler causes
+// an error.
+TEST_F(BindGroupLayoutWithStaticSamplersValidationTest,
+       EntryForStaticSamplerInBindGroupCausesError) {
+    wgpu::BindGroupLayoutEntry binding = {};
+    binding.binding = 0;
+    wgpu::StaticSamplerBindingLayout staticSamplerBinding = {};
+    staticSamplerBinding.sampler = device.CreateSampler();
+    binding.nextInChain = &staticSamplerBinding;
+
+    wgpu::BindGroupLayoutDescriptor desc = {};
+    desc.entryCount = 1;
+    desc.entries = &binding;
+
+    wgpu::BindGroupLayout layout = device.CreateBindGroupLayout(&desc);
+
+    wgpu::SamplerDescriptor samplerDesc;
+    samplerDesc.minFilter = wgpu::FilterMode::Linear;
+    ASSERT_DEVICE_ERROR(
+        utils::MakeBindGroup(device, layout, {{0, device.CreateSampler(&samplerDesc)}}));
+}
+
+// Verifies that creation of a bind group with the correct number of entries for a layout that has a
+// sampler and a static sampler raises an error if the entry is specified at the
+// index of the static sampler rather than that of the sampler.
+TEST_F(BindGroupLayoutWithStaticSamplersValidationTest,
+       CorrectNumberOfEntriesButEntryForStaticSamplerAtSecondIndexInBindGroupCausesError) {
+    std::vector<wgpu::BindGroupLayoutEntry> entries;
+
+    wgpu::BindGroupLayoutEntry binding0 = {};
+    binding0.binding = 0;
+    binding0.sampler.type = wgpu::SamplerBindingType::Filtering;
+    entries.push_back(binding0);
+
+    wgpu::BindGroupLayoutEntry binding1 = {};
+    binding1.binding = 1;
+    wgpu::StaticSamplerBindingLayout staticSamplerBinding = {};
+    staticSamplerBinding.sampler = device.CreateSampler();
+    binding1.nextInChain = &staticSamplerBinding;
+    entries.push_back(binding1);
+
+    wgpu::BindGroupLayoutDescriptor desc = {};
+    desc.entryCount = 2;
+    desc.entries = entries.data();
+
+    wgpu::BindGroupLayout layout = device.CreateBindGroupLayout(&desc);
+
+    wgpu::SamplerDescriptor samplerDesc;
+    samplerDesc.minFilter = wgpu::FilterMode::Linear;
+    ASSERT_DEVICE_ERROR(
+        utils::MakeBindGroup(device, layout, {{1, device.CreateSampler(&samplerDesc)}}));
+}
+
+// Verifies that creation of a bind group with the correct number of entries for a layout that has a
+// static sampler and a sampler raises an error if the entry is specified at the
+// index of the static sampler rather than that of the sampler.
+TEST_F(BindGroupLayoutWithStaticSamplersValidationTest,
+       CorrectNumberOfEntriesButEntryForStaticSamplerInBindGroupCausesError) {
+    std::vector<wgpu::BindGroupLayoutEntry> entries;
+
+    wgpu::BindGroupLayoutEntry binding0 = {};
+    binding0.binding = 0;
+    wgpu::StaticSamplerBindingLayout staticSamplerBinding = {};
+    staticSamplerBinding.sampler = device.CreateSampler();
+    binding0.nextInChain = &staticSamplerBinding;
+    entries.push_back(binding0);
+
+    wgpu::BindGroupLayoutEntry binding1 = {};
+    binding1.binding = 1;
+    binding1.sampler.type = wgpu::SamplerBindingType::Filtering;
+    entries.push_back(binding1);
+
+    wgpu::BindGroupLayoutDescriptor desc = {};
+    desc.entryCount = 2;
+    desc.entries = entries.data();
+
+    wgpu::BindGroupLayout layout = device.CreateBindGroupLayout(&desc);
+
+    wgpu::SamplerDescriptor samplerDesc;
+    samplerDesc.minFilter = wgpu::FilterMode::Linear;
+    ASSERT_DEVICE_ERROR(
+        utils::MakeBindGroup(device, layout, {{0, device.CreateSampler(&samplerDesc)}}));
+}
+
 constexpr uint32_t kBindingSize = 8;
 
 class SetBindGroupValidationTest : public ValidationTest {
   public:
+    WGPUDevice CreateTestDevice(native::Adapter dawnAdapter,
+                                wgpu::DeviceDescriptor descriptor) override {
+        wgpu::FeatureName requiredFeatures[1] = {wgpu::FeatureName::StaticSamplers};
+        descriptor.requiredFeatures = requiredFeatures;
+        descriptor.requiredFeatureCount = 1;
+        return dawnAdapter.CreateDevice(&descriptor);
+    }
+
     void SetUp() override {
         ValidationTest::SetUp();
 
@@ -2457,6 +2629,63 @@
     }
 }
 
+// Test that a static sampler is valid to access from a shader module.
+TEST_F(SetBindGroupValidationTest, StaticSamplerAccessedFromShader) {
+    wgpu::BindGroupLayoutEntry binding = {};
+    binding.binding = 0;
+    binding.visibility = wgpu::ShaderStage::Compute;
+    wgpu::StaticSamplerBindingLayout staticSamplerBinding = {};
+    staticSamplerBinding.sampler = device.CreateSampler();
+    binding.nextInChain = &staticSamplerBinding;
+
+    wgpu::BindGroupLayoutDescriptor desc = {};
+    desc.entryCount = 1;
+    desc.entries = &binding;
+
+    wgpu::BindGroupLayout layout = device.CreateBindGroupLayout(&desc);
+
+    wgpu::PipelineLayout pl = utils::MakePipelineLayout(device, {layout});
+
+    wgpu::ComputePipelineDescriptor pipelineDesc;
+    pipelineDesc.layout = pl;
+    pipelineDesc.compute.module = utils::CreateShaderModule(device, R"(
+        @group(0) @binding(0) var s : sampler;
+        @compute @workgroup_size(1) fn main() {
+            _ = s;
+        }
+    )");
+    device.CreateComputePipeline(&pipelineDesc);
+}
+
+// Test that a static sampler cannot be accessed from a shader module as a
+// non-sampler variable type.
+TEST_F(SetBindGroupValidationTest, StaticSamplerAccessedFromShaderAsNonSamplerTypeCausesError) {
+    wgpu::BindGroupLayoutEntry binding = {};
+    binding.binding = 0;
+    binding.visibility = wgpu::ShaderStage::Compute;
+    wgpu::StaticSamplerBindingLayout staticSamplerBinding = {};
+    staticSamplerBinding.sampler = device.CreateSampler();
+    binding.nextInChain = &staticSamplerBinding;
+
+    wgpu::BindGroupLayoutDescriptor desc = {};
+    desc.entryCount = 1;
+    desc.entries = &binding;
+
+    wgpu::BindGroupLayout layout = device.CreateBindGroupLayout(&desc);
+
+    wgpu::PipelineLayout pl = utils::MakePipelineLayout(device, {layout});
+
+    wgpu::ComputePipelineDescriptor pipelineDesc;
+    pipelineDesc.layout = pl;
+    pipelineDesc.compute.module = utils::CreateShaderModule(device, R"(
+        @group(0) @binding(0) var t : texture_2d<f32>;
+        @compute @workgroup_size(1) fn main() {
+            _ = textureDimensions(t);
+        }
+    )");
+    ASSERT_DEVICE_ERROR(device.CreateComputePipeline(&pipelineDesc));
+}
+
 class SetBindGroupPersistenceValidationTest : public ValidationTest {
   protected:
     void SetUp() override {
diff --git a/src/dawn/tests/unittests/validation/ObjectCachingTests.cpp b/src/dawn/tests/unittests/validation/ObjectCachingTests.cpp
index f412ab0..80280b7 100644
--- a/src/dawn/tests/unittests/validation/ObjectCachingTests.cpp
+++ b/src/dawn/tests/unittests/validation/ObjectCachingTests.cpp
@@ -40,6 +40,14 @@
 // These tests works assuming Dawn Native's object deduplication. Comparing the pointer is
 // exploiting an implementation detail of Dawn Native.
 class ObjectCachingTest : public ValidationTest {
+    WGPUDevice CreateTestDevice(native::Adapter dawnAdapter,
+                                wgpu::DeviceDescriptor descriptor) override {
+        wgpu::FeatureName requiredFeatures[1] = {wgpu::FeatureName::StaticSamplers};
+        descriptor.requiredFeatures = requiredFeatures;
+        descriptor.requiredFeatureCount = 1;
+        return dawnAdapter.CreateDevice(&descriptor);
+    }
+
     void SetUp() override {
         ValidationTest::SetUp();
         DAWN_SKIP_TEST_IF(UsesWire());
@@ -114,6 +122,77 @@
     EXPECT_THAT(bgl, BindGroupLayoutEq(sameBgl));
 }
 
+// Test that BindGroupLayouts with a static sampler entry are correctly
+// deduplicated.
+TEST_F(ObjectCachingTest, BindGroupLayoutStaticSamplerDeduplication) {
+    // TODO(crbug.com/dawn/2489): The inequality check between bind group
+    // layouts with distinct static samplers fails on the MSVC bots.
+    DAWN_SKIP_TEST_IF(DAWN_PLATFORM_IS(WINDOWS));
+
+    wgpu::BindGroupLayoutEntry binding = {};
+    binding.binding = 0;
+    wgpu::StaticSamplerBindingLayout staticSamplerBinding = {};
+    wgpu::SamplerDescriptor samplerDesc;
+    samplerDesc.addressModeU = wgpu::AddressMode::ClampToEdge;
+    staticSamplerBinding.sampler = device.CreateSampler(&samplerDesc);
+    binding.nextInChain = &staticSamplerBinding;
+
+    wgpu::BindGroupLayoutDescriptor desc = {};
+    desc.entryCount = 1;
+    desc.entries = &binding;
+
+    wgpu::SamplerDescriptor otherSamplerDesc;
+    otherSamplerDesc.addressModeU = wgpu::AddressMode::Repeat;
+    wgpu::Sampler otherSampler = device.CreateSampler(&otherSamplerDesc);
+
+    EXPECT_NE(staticSamplerBinding.sampler.Get(), otherSampler.Get());
+
+    wgpu::BindGroupLayoutEntry otherBinding = {};
+    otherBinding.binding = 0;
+    wgpu::StaticSamplerBindingLayout otherStaticSamplerBinding = {};
+    otherStaticSamplerBinding.sampler = otherSampler;
+    otherBinding.nextInChain = &otherStaticSamplerBinding;
+
+    EXPECT_NE(staticSamplerBinding.sampler.Get(), otherStaticSamplerBinding.sampler.Get());
+
+    wgpu::BindGroupLayoutDescriptor otherDesc = {};
+    otherDesc.entryCount = 1;
+    otherDesc.entries = &otherBinding;
+
+    wgpu::BindGroupLayout bgl = device.CreateBindGroupLayout(&desc);
+    wgpu::BindGroupLayout sameBgl = device.CreateBindGroupLayout(&desc);
+    wgpu::BindGroupLayout otherStaticSamplerBgl = device.CreateBindGroupLayout(&otherDesc);
+    wgpu::BindGroupLayout otherBgl = utils::MakeBindGroupLayout(
+        device, {{0, wgpu::ShaderStage::Fragment, wgpu::SamplerBindingType::Filtering}});
+
+    EXPECT_THAT(bgl, BindGroupLayoutEq(sameBgl));
+    EXPECT_THAT(bgl, Not(BindGroupLayoutEq(otherStaticSamplerBgl)));
+    EXPECT_THAT(bgl, Not(BindGroupLayoutEq(otherBgl)));
+}
+
+// Test that BindGroupLayouts with a static sampler entry keep a reference
+// to the static sampler, such that if a sampler is created from the same
+// params the same object will be returned.
+TEST_F(ObjectCachingTest, BindGroupLayoutKeepsRefToStaticSampler) {
+    auto sampler1 = device.CreateSampler();
+    wgpu::BindGroupLayoutEntry binding = {};
+    binding.binding = 0;
+    wgpu::StaticSamplerBindingLayout staticSamplerBinding = {};
+    staticSamplerBinding.sampler = sampler1;
+    binding.nextInChain = &staticSamplerBinding;
+
+    wgpu::BindGroupLayoutDescriptor desc = {};
+    desc.entryCount = 1;
+    desc.entries = &binding;
+
+    wgpu::BindGroupLayout bgl = device.CreateBindGroupLayout(&desc);
+
+    auto samplerRawPtr = sampler1.Get();
+    sampler1 = nullptr;
+    auto sampler2 = device.CreateSampler();
+    EXPECT_EQ(samplerRawPtr, sampler2.Get());
+}
+
 // Test that PipelineLayouts are correctly deduplicated.
 TEST_F(ObjectCachingTest, PipelineLayoutDeduplication) {
     wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout(