Add ClampFragDepth transform to GLSL writer.

Also, make the ClampFragDepth transform play well with
existing push constants in the AST. This required implementing a ClampFragDepth::Config containing byte offsets for the min_depth and max_depth push constants, so they can be specified by Dawn.

These are std::optional, and leaving them null indicates not to
run the transform. Run the transform if any shaders being compiled
into a pipeline by Dawn write to frag_depth. In practice, this means to
force-run the transform in the vertex shader if the fragment shader is
writing to frag_depth. This keeps the PushConstants struct identical
at link time.

Bug: dawn:2185
Change-Id: Ieb9e282f94b8c74374f11b90324dacf2325183f0
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/172640
Reviewed-by: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Stephen White <senorblanco@chromium.org>
diff --git a/src/dawn/native/opengl/CommandBufferGL.cpp b/src/dawn/native/opengl/CommandBufferGL.cpp
index 5797278b..51b0b53 100644
--- a/src/dawn/native/opengl/CommandBufferGL.cpp
+++ b/src/dawn/native/opengl/CommandBufferGL.cpp
@@ -1025,7 +1025,10 @@
     persistentPipelineState.SetDefaultState(gl);
     gl.BlendColor(0, 0, 0, 0);
     gl.Viewport(0, 0, renderPass->width, renderPass->height);
-    gl.DepthRangef(0.0, 1.0);
+    float minDepth = 0.0f;
+    float maxDepth = 1.0f;
+    gl.DepthRangef(minDepth, maxDepth);
+
     gl.Scissor(0, 0, renderPass->width, renderPass->height);
 
     // Clear framebuffer attachments as needed
@@ -1230,6 +1233,10 @@
 
                 vertexStateBufferBindingTracker.OnSetPipeline(lastPipeline);
                 bindGroupTracker.OnSetPipeline(lastPipeline);
+                if (lastPipeline->UsesFragDepth()) {
+                    gl.Uniform1f(PipelineLayout::PushConstantLocation::MinDepth, minDepth);
+                    gl.Uniform1f(PipelineLayout::PushConstantLocation::MaxDepth, maxDepth);
+                }
                 break;
             }
 
@@ -1310,7 +1317,13 @@
                     gl.Viewport(static_cast<int>(cmd->x), static_cast<int>(cmd->y),
                                 static_cast<int>(cmd->width), static_cast<int>(cmd->height));
                 }
-                gl.DepthRangef(cmd->minDepth, cmd->maxDepth);
+                minDepth = cmd->minDepth;
+                maxDepth = cmd->maxDepth;
+                gl.DepthRangef(minDepth, maxDepth);
+                if (lastPipeline && lastPipeline->UsesFragDepth()) {
+                    gl.Uniform1f(PipelineLayout::PushConstantLocation::MinDepth, minDepth);
+                    gl.Uniform1f(PipelineLayout::PushConstantLocation::MaxDepth, maxDepth);
+                }
                 break;
             }
 
diff --git a/src/dawn/native/opengl/ComputePipelineGL.cpp b/src/dawn/native/opengl/ComputePipelineGL.cpp
index 6dfa69a8..3344cd5 100644
--- a/src/dawn/native/opengl/ComputePipelineGL.cpp
+++ b/src/dawn/native/opengl/ComputePipelineGL.cpp
@@ -47,7 +47,7 @@
 
 MaybeError ComputePipeline::InitializeImpl() {
     DAWN_TRY(InitializeBase(ToBackend(GetDevice())->GetGL(), ToBackend(GetLayout()), GetAllStages(),
-                            /* usesInstanceIndex */ false));
+                            /* usesInstanceIndex */ false, /* usesFragDepth */ false));
     return {};
 }
 
diff --git a/src/dawn/native/opengl/PipelineGL.cpp b/src/dawn/native/opengl/PipelineGL.cpp
index 08c4ebe..5c665a2 100644
--- a/src/dawn/native/opengl/PipelineGL.cpp
+++ b/src/dawn/native/opengl/PipelineGL.cpp
@@ -53,7 +53,8 @@
 MaybeError PipelineGL::InitializeBase(const OpenGLFunctions& gl,
                                       const PipelineLayout* layout,
                                       const PerStage<ProgrammableStage>& stages,
-                                      bool usesInstanceIndex) {
+                                      bool usesInstanceIndex,
+                                      bool usesFragDepth) {
     mProgram = gl.CreateProgram();
 
     // Compute the set of active stages.
@@ -72,8 +73,8 @@
         const ShaderModule* module = ToBackend(stages[stage].module.Get());
         GLuint shader;
         DAWN_TRY_ASSIGN(shader, module->CompileShader(gl, stages[stage], stage, usesInstanceIndex,
-                                                      &combinedSamplers[stage], layout,
-                                                      &needsPlaceholderSampler,
+                                                      usesFragDepth, &combinedSamplers[stage],
+                                                      layout, &needsPlaceholderSampler,
                                                       &mNeedsTextureBuiltinUniformBuffer,
                                                       &mBindingPointEmulatedBuiltins));
         gl.AttachShader(mProgram, shader);
diff --git a/src/dawn/native/opengl/PipelineGL.h b/src/dawn/native/opengl/PipelineGL.h
index 60fc43f..2370f55 100644
--- a/src/dawn/native/opengl/PipelineGL.h
+++ b/src/dawn/native/opengl/PipelineGL.h
@@ -74,7 +74,8 @@
     MaybeError InitializeBase(const OpenGLFunctions& gl,
                               const PipelineLayout* layout,
                               const PerStage<ProgrammableStage>& stages,
-                              bool usesInstanceIndex);
+                              bool usesInstanceIndex,
+                              bool usesFragDepth);
     void DeleteProgram(const OpenGLFunctions& gl);
 
   private:
diff --git a/src/dawn/native/opengl/PipelineLayoutGL.h b/src/dawn/native/opengl/PipelineLayoutGL.h
index e2ab802..bc0fe3a 100644
--- a/src/dawn/native/opengl/PipelineLayoutGL.h
+++ b/src/dawn/native/opengl/PipelineLayoutGL.h
@@ -57,6 +57,8 @@
 
     enum PushConstantLocation {
         FirstInstance = 0,
+        MinDepth = 1,
+        MaxDepth = 2,
     };
 
   private:
diff --git a/src/dawn/native/opengl/RenderPipelineGL.cpp b/src/dawn/native/opengl/RenderPipelineGL.cpp
index 5819a32..9923bb9 100644
--- a/src/dawn/native/opengl/RenderPipelineGL.cpp
+++ b/src/dawn/native/opengl/RenderPipelineGL.cpp
@@ -257,7 +257,7 @@
 
 MaybeError RenderPipeline::InitializeImpl() {
     DAWN_TRY(InitializeBase(ToBackend(GetDevice())->GetGL(), ToBackend(GetLayout()), GetAllStages(),
-                            UsesInstanceIndex()));
+                            UsesInstanceIndex(), UsesFragDepth()));
     CreateVAOForVertexState();
     return {};
 }
diff --git a/src/dawn/native/opengl/ShaderModuleGL.cpp b/src/dawn/native/opengl/ShaderModuleGL.cpp
index b4659b5..aa5768b 100644
--- a/src/dawn/native/opengl/ShaderModuleGL.cpp
+++ b/src/dawn/native/opengl/ShaderModuleGL.cpp
@@ -168,6 +168,7 @@
     const ProgrammableStage& programmableStage,
     SingleShaderStage stage,
     bool usesInstanceIndex,
+    bool usesFragDepth,
     CombinedSamplerInfo* combinedSamplers,
     const PipelineLayout* layout,
     bool* needsPlaceholderSampler,
@@ -294,6 +295,12 @@
             4 * PipelineLayout::PushConstantLocation::FirstInstance;
     }
 
+    if (usesFragDepth) {
+        req.tintOptions.min_depth_offset = 4 * PipelineLayout::PushConstantLocation::MinDepth;
+
+        req.tintOptions.max_depth_offset = 4 * PipelineLayout::PushConstantLocation::MaxDepth;
+    }
+
     req.disableSymbolRenaming = GetDevice()->IsToggleEnabled(Toggle::DisableSymbolRenaming);
 
     req.interstageVariables = {};
diff --git a/src/dawn/native/opengl/ShaderModuleGL.h b/src/dawn/native/opengl/ShaderModuleGL.h
index 1a3b3a1..71251e4 100644
--- a/src/dawn/native/opengl/ShaderModuleGL.h
+++ b/src/dawn/native/opengl/ShaderModuleGL.h
@@ -90,6 +90,7 @@
                                         const ProgrammableStage& programmableStage,
                                         SingleShaderStage stage,
                                         bool usesInstanceIndex,
+                                        bool usesFragDepth,
                                         CombinedSamplerInfo* combinedSamplers,
                                         const PipelineLayout* layout,
                                         bool* needsPlaceholderSampler,
diff --git a/src/dawn/tests/end2end/FragDepthTests.cpp b/src/dawn/tests/end2end/FragDepthTests.cpp
index b98bbba..9af0533 100644
--- a/src/dawn/tests/end2end/FragDepthTests.cpp
+++ b/src/dawn/tests/end2end/FragDepthTests.cpp
@@ -38,9 +38,6 @@
 
 // Test that when writing to FragDepth the result is clamped to the viewport.
 TEST_P(FragDepthTests, FragDepthIsClampedToViewport) {
-    // TODO(dawn:1125): Add the shader transform to clamp the frag depth to the GL backend.
-    DAWN_SUPPRESS_TEST_IF(IsOpenGL() || IsOpenGLES());
-
     wgpu::ShaderModule module = utils::CreateShaderModule(device, R"(
         @vertex fn vs() -> @builtin(position) vec4f {
             return vec4f(0.0, 0.0, 0.5, 1.0);
@@ -91,12 +88,12 @@
 // Test for the push constant logic for ClampFragDepth in Vulkan to check that changing the
 // pipeline layout doesn't invalidate the push constants that were set.
 TEST_P(FragDepthTests, ChangingPipelineLayoutDoesntInvalidateViewport) {
-    // TODO(dawn:1125): Add the shader transform to clamp the frag depth to the GL backend.
-    DAWN_SUPPRESS_TEST_IF(IsOpenGL() || IsOpenGLES());
-
     // TODO(dawn:1805): Load ByteAddressBuffer in Pixel Shader doesn't work with NVIDIA on D3D11
     DAWN_SUPPRESS_TEST_IF(IsD3D11() && IsNvidia());
 
+    // TODO(dawn:2393): ANGLE/D3D11 fails in HLSL shader compilation (UAV vs PS register bug)
+    DAWN_SUPPRESS_TEST_IF(IsANGLED3D11());
+
     wgpu::ShaderModule module = utils::CreateShaderModule(device, R"(
         @vertex fn vs() -> @builtin(position) vec4f {
             return vec4f(0.0, 0.0, 0.5, 1.0);
diff --git a/src/dawn/tests/end2end/ShaderTests.cpp b/src/dawn/tests/end2end/ShaderTests.cpp
index f10a4ea..bc16efa 100644
--- a/src/dawn/tests/end2end/ShaderTests.cpp
+++ b/src/dawn/tests/end2end/ShaderTests.cpp
@@ -2160,6 +2160,32 @@
     device.CreateRenderPipeline(&desc);
 }
 
+// Test that accessing instance_index in the vert shader and assigning to frag_depth in the frag
+// shader works.
+TEST_P(ShaderTests, FragDepthAndInstanceIndex) {
+    wgpu::ShaderModule module = utils::CreateShaderModule(device, R"(
+        @group(0) @binding(0) var<uniform> a : f32;
+
+        @fragment fn fragment() -> @builtin(frag_depth) f32 {
+          return a;
+        }
+
+        @vertex fn vertex(@builtin(instance_index) instance : u32) -> @builtin(position) vec4f {
+          return vec4f(f32(instance));
+        }
+    )");
+
+    utils::ComboRenderPipelineDescriptor desc;
+    desc.vertex.module = module;
+    desc.cFragment.module = module;
+    desc.cFragment.targetCount = 0;
+    wgpu::DepthStencilState* dsState = desc.EnableDepthStencil();
+    dsState->depthWriteEnabled = true;
+    dsState->depthCompare = wgpu::CompareFunction::Always;
+
+    device.CreateRenderPipeline(&desc);
+}
+
 // Having different block contents at the same binding point used in different stages is allowed.
 TEST_P(ShaderTests, UniformAcrossStagesSameBindingPointCollide) {
     wgpu::ShaderModule module = utils::CreateShaderModule(device, R"(
diff --git a/src/tint/cmd/tint/main.cc b/src/tint/cmd/tint/main.cc
index 4d18bb9..1601a76 100644
--- a/src/tint/cmd/tint/main.cc
+++ b/src/tint/cmd/tint/main.cc
@@ -1080,11 +1080,18 @@
         gen_options.texture_builtins_from_uniform = std::move(textureBuiltinsFromUniform);
 
         auto entry_point = inspector.GetEntryPoint(entry_point_name);
+        uint32_t offset = entry_point.push_constant_size;
 
         if (entry_point.instance_index_used) {
             // Place the first_instance push constant member after user-defined push constants (if
             // any).
-            gen_options.first_instance_offset = entry_point.push_constant_size;
+            gen_options.first_instance_offset = offset;
+            offset += 4;
+        }
+        if (entry_point.frag_depth_used) {
+            gen_options.min_depth_offset = offset + 0;
+            gen_options.max_depth_offset = offset + 4;
+            offset += 8;
         }
 
         auto result = tint::glsl::writer::Generate(prg, gen_options, entry_point_name);
diff --git a/src/tint/lang/glsl/writer/ast_printer/ast_printer.cc b/src/tint/lang/glsl/writer/ast_printer/ast_printer.cc
index 326c1a2..764b779 100644
--- a/src/tint/lang/glsl/writer/ast_printer/ast_printer.cc
+++ b/src/tint/lang/glsl/writer/ast_printer/ast_printer.cc
@@ -61,6 +61,7 @@
 #include "src/tint/lang/wgsl/ast/transform/binding_remapper.h"
 #include "src/tint/lang/wgsl/ast/transform/builtin_polyfill.h"
 #include "src/tint/lang/wgsl/ast/transform/canonicalize_entry_point_io.h"
+#include "src/tint/lang/wgsl/ast/transform/clamp_frag_depth.h"
 #include "src/tint/lang/wgsl/ast/transform/demote_to_helper.h"
 #include "src/tint/lang/wgsl/ast/transform/direct_variable_access.h"
 #include "src/tint/lang/wgsl/ast/transform/disable_uniformity_analysis.h"
@@ -206,6 +207,10 @@
 
     manager.Add<ast::transform::OffsetFirstIndex>();
 
+    // ClampFragDepth must come before CanonicalizeEntryPointIO, or the assignments to FragDepth are
+    // lost
+    manager.Add<ast::transform::ClampFragDepth>();
+
     // CanonicalizeEntryPointIO must come after Robustness
     manager.Add<ast::transform::CanonicalizeEntryPointIO>();
 
@@ -249,6 +254,8 @@
 
     data.Add<ast::transform::OffsetFirstIndex::Config>(std::nullopt, options.first_instance_offset);
 
+    data.Add<ast::transform::ClampFragDepth::Config>(options.min_depth_offset,
+                                                     options.max_depth_offset);
     SanitizedResult result;
     ast::transform::DataMap outputs;
     result.program = manager.Run(in, data, outputs);
diff --git a/src/tint/lang/glsl/writer/common/options.h b/src/tint/lang/glsl/writer/common/options.h
index 7545366..62cd80c 100644
--- a/src/tint/lang/glsl/writer/common/options.h
+++ b/src/tint/lang/glsl/writer/common/options.h
@@ -82,6 +82,12 @@
     /// Offset of the firstInstance push constant.
     std::optional<int32_t> first_instance_offset;
 
+    /// Offset of the minDepth push constant.
+    std::optional<uint32_t> min_depth_offset;
+
+    /// Offset of the maxDepth push constant.
+    std::optional<uint32_t> max_depth_offset;
+
     /// Options used to map WGSL textureNumLevels/textureNumSamples builtins to internal uniform
     /// buffer values. If not specified, emits corresponding GLSL builtins
     /// textureQueryLevels/textureSamples directly.
@@ -98,6 +104,8 @@
                  binding_remapper_options,
                  external_texture_options,
                  first_instance_offset,
+                 min_depth_offset,
+                 max_depth_offset,
                  texture_builtins_from_uniform);
 };
 
diff --git a/src/tint/lang/spirv/writer/ast_printer/ast_printer.cc b/src/tint/lang/spirv/writer/ast_printer/ast_printer.cc
index 4272172..c782db5 100644
--- a/src/tint/lang/spirv/writer/ast_printer/ast_printer.cc
+++ b/src/tint/lang/spirv/writer/ast_printer/ast_printer.cc
@@ -66,6 +66,7 @@
 
     if (options.clamp_frag_depth) {
         manager.Add<ast::transform::ClampFragDepth>();
+        data.Add<ast::transform::ClampFragDepth::Config>(0, 4);
     }
 
     manager.Add<ast::transform::DisableUniformityAnalysis>();
diff --git a/src/tint/lang/wgsl/ast/transform/clamp_frag_depth.cc b/src/tint/lang/wgsl/ast/transform/clamp_frag_depth.cc
index 59e6dd6..2b5e6a2 100644
--- a/src/tint/lang/wgsl/ast/transform/clamp_frag_depth.cc
+++ b/src/tint/lang/wgsl/ast/transform/clamp_frag_depth.cc
@@ -35,6 +35,7 @@
 #include "src/tint/lang/wgsl/ast/function.h"
 #include "src/tint/lang/wgsl/ast/module.h"
 #include "src/tint/lang/wgsl/ast/struct.h"
+#include "src/tint/lang/wgsl/ast/transform/push_constant_helper.h"
 #include "src/tint/lang/wgsl/program/clone_context.h"
 #include "src/tint/lang/wgsl/program/program_builder.h"
 #include "src/tint/lang/wgsl/resolver/resolve.h"
@@ -45,6 +46,7 @@
 #include "src/tint/utils/macros/scoped_assignment.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::ast::transform::ClampFragDepth);
+TINT_INSTANTIATE_TYPEINFO(tint::ast::transform::ClampFragDepth::Config);
 
 namespace tint::ast::transform {
 
@@ -63,49 +65,37 @@
 
     /// Runs the transform
     /// @returns the new program or SkipTransform if the transform is not required
-    Transform::ApplyResult Run() {
-        // Abort on any use of push constants in the module.
-        for (auto* global : src.AST().GlobalVariables()) {
-            if (auto* var = global->As<ast::Var>()) {
-                auto* v = src.Sem().Get(var);
-                if (TINT_UNLIKELY(v->AddressSpace() == core::AddressSpace::kPushConstant)) {
-                    TINT_ICE()
-                        << "ClampFragDepth doesn't know how to handle module that already use push "
-                           "constants";
-                    return resolver::Resolve(b);
-                }
-            }
-        }
-
-        if (!ShouldRun()) {
+    Transform::ApplyResult Run(const DataMap& inputs) {
+        const Config* cfg = inputs.Get<Config>();
+        if (!cfg || !cfg->min_depth_offset.has_value() || !cfg->max_depth_offset.has_value()) {
             return SkipTransform;
         }
 
+        PushConstantHelper push_constant_helper(ctx);
+
         // At least one entry-point needs clamping. Add the following to the module:
         //
         //   enable chromium_experimental_push_constant;
         //
-        //   struct FragDepthClampArgs {
-        //       min : f32,
-        //       max : f32,
+        //   struct PushConstants {
+        //       min_depth : f32,
+        //       max_depth : f32,
         //   }
-        //   var<push_constant> frag_depth_clamp_args : FragDepthClampArgs;
+        //   var<push_constant> push_constants : PushConstants;
         //
         //   fn clamp_frag_depth(v : f32) -> f32 {
-        //       return clamp(v, frag_depth_clamp_args.min, frag_depth_clamp_args.max);
+        //       return clamp(v, push_constants.min, push_constants.max_depth);
         //   }
-        b.Enable(wgsl::Extension::kChromiumExperimentalPushConstant);
 
-        b.Structure(b.Symbols().New("FragDepthClampArgs"),
-                    Vector{b.Member("min", b.ty.f32()), b.Member("max", b.ty.f32())});
+        push_constant_helper.InsertMember("min_depth", b.ty.f32(), *cfg->min_depth_offset);
+        push_constant_helper.InsertMember("max_depth", b.ty.f32(), *cfg->max_depth_offset);
 
-        auto args_sym = b.Symbols().New("frag_depth_clamp_args");
-        b.GlobalVar(args_sym, b.ty("FragDepthClampArgs"), core::AddressSpace::kPushConstant);
+        Symbol buffer_name = push_constant_helper.Run();
 
         auto base_fn_sym = b.Symbols().New("clamp_frag_depth");
         b.Func(base_fn_sym, Vector{b.Param("v", b.ty.f32())}, b.ty.f32(),
-               Vector{b.Return(b.Call("clamp", "v", b.MemberAccessor(args_sym, "min"),
-                                      b.MemberAccessor(args_sym, "max")))});
+               Vector{b.Return(b.Call("clamp", "v", b.MemberAccessor(buffer_name, "min_depth"),
+                                      b.MemberAccessor(buffer_name, "max_depth")))});
 
         // If true, the currently cloned function returns frag depth directly as a scalar
         bool returns_frag_depth_as_value = false;
@@ -185,17 +175,6 @@
     }
 
   private:
-    /// @returns true if the transform should run
-    bool ShouldRun() {
-        for (auto* fn : src.AST().Functions()) {
-            if (fn->PipelineStage() == ast::PipelineStage::kFragment &&
-                (ReturnsFragDepthAsValue(fn) || ReturnsFragDepthInStruct(fn))) {
-                return true;
-            }
-        }
-
-        return false;
-    }
     /// @param attrs the attributes to examine
     /// @returns true if @p attrs contains a `@builtin(frag_depth)` attribute
     bool ContainsFragDepth(VectorRef<const ast::Attribute*> attrs) {
@@ -237,9 +216,15 @@
 ClampFragDepth::~ClampFragDepth() = default;
 
 ast::transform::Transform::ApplyResult ClampFragDepth::Apply(const Program& src,
-                                                             const ast::transform::DataMap&,
+                                                             const ast::transform::DataMap& inputs,
                                                              ast::transform::DataMap&) const {
-    return State{src}.Run();
+    return State{src}.Run(inputs);
 }
 
+ClampFragDepth::Config::Config(std::optional<uint32_t> min_depth_off,
+                               std::optional<uint32_t> max_depth_off)
+    : min_depth_offset(min_depth_off), max_depth_offset(max_depth_off) {}
+
+ClampFragDepth::Config::~Config() = default;
+
 }  // namespace tint::ast::transform
diff --git a/src/tint/lang/wgsl/ast/transform/clamp_frag_depth.h b/src/tint/lang/wgsl/ast/transform/clamp_frag_depth.h
index 5bcd3ea..599b91c 100644
--- a/src/tint/lang/wgsl/ast/transform/clamp_frag_depth.h
+++ b/src/tint/lang/wgsl/ast/transform/clamp_frag_depth.h
@@ -69,6 +69,23 @@
     /// Destructor
     ~ClampFragDepth() override;
 
+    /// Transform configuration options
+    struct Config final : public Castable<Config, ast::transform::Data> {
+        /// Constructor
+        /// @param min_depth_off Offset of the minDepth push constant
+        /// @param max_depth_off Offset of the maxDepth push constant
+        Config(std::optional<uint32_t> min_depth_off, std::optional<uint32_t> max_depth_off);
+
+        /// Destructor
+        ~Config() override;
+
+        /// Offset of the min_depth push constant
+        std::optional<uint32_t> min_depth_offset;
+
+        /// Offset of the min_depth push constant
+        std::optional<uint32_t> max_depth_offset;
+    };
+
     /// @copydoc ast::transform::Transform::Apply
     ApplyResult Apply(const Program& program,
                       const ast::transform::DataMap& inputs,
diff --git a/src/tint/lang/wgsl/ast/transform/clamp_frag_depth_test.cc b/src/tint/lang/wgsl/ast/transform/clamp_frag_depth_test.cc
index cc09c6f..97f1f31 100644
--- a/src/tint/lang/wgsl/ast/transform/clamp_frag_depth_test.cc
+++ b/src/tint/lang/wgsl/ast/transform/clamp_frag_depth_test.cc
@@ -40,48 +40,102 @@
     EXPECT_FALSE(ShouldRun<ClampFragDepth>(src));
 }
 
-TEST_F(ClampFragDepthTest, ShouldRunNoFragmentShader) {
+TEST_F(ClampFragDepthTest, ShouldRunNoConfig) {
     auto* src = R"(
-        fn f() -> f32 {
+        @fragment fn main() -> @builtin(frag_depth) f32 {
             return 0.0;
         }
-
-        @compute @workgroup_size(1) fn cs() {
-        }
-
-        @vertex fn vs() -> @builtin(position) vec4<f32> {
-            return vec4<f32>();
-        }
     )";
 
     EXPECT_FALSE(ShouldRun<ClampFragDepth>(src));
 }
 
-TEST_F(ClampFragDepthTest, ShouldRunFragmentShaderNoReturnType) {
+TEST_F(ClampFragDepthTest, ShouldRunNoMin) {
     auto* src = R"(
-        @fragment fn main() {
-        }
-    )";
-
-    EXPECT_FALSE(ShouldRun<ClampFragDepth>(src));
-}
-
-TEST_F(ClampFragDepthTest, ShouldRunFragmentShaderNoFragDepth) {
-    auto* src = R"(
-        @fragment fn main() -> @location(0) f32 {
+        @fragment fn main() -> @builtin(frag_depth) f32 {
             return 0.0;
         }
+    )";
 
-        struct S {
-            @location(0) a : f32,
-            @builtin(sample_mask) b : u32,
-        }
-        @fragment fn main2() -> S {
-            return S();
+    DataMap config;
+    config.Add<ClampFragDepth::Config>(std::nullopt, 4);
+
+    EXPECT_FALSE(ShouldRun<ClampFragDepth>(src, config));
+}
+
+TEST_F(ClampFragDepthTest, ShouldRunNoMinNoMax) {
+    auto* src = R"(
+        @fragment fn main() -> @builtin(frag_depth) f32 {
+            return 0.0;
         }
     )";
 
-    EXPECT_FALSE(ShouldRun<ClampFragDepth>(src));
+    DataMap config;
+    config.Add<ClampFragDepth::Config>(0, std::nullopt);
+
+    EXPECT_FALSE(ShouldRun<ClampFragDepth>(src, config));
+}
+
+TEST_F(ClampFragDepthTest, ShouldRun) {
+    auto* src = R"(
+        @fragment fn main() -> @builtin(frag_depth) f32 {
+            return 0.0;
+        }
+    )";
+
+    DataMap config;
+    config.Add<ClampFragDepth::Config>(0, 4);
+
+    EXPECT_TRUE(ShouldRun<ClampFragDepth>(src, config));
+}
+
+TEST_F(ClampFragDepthTest, ExistingPushConstant) {
+    auto* src = R"(
+        enable chromium_experimental_push_constant;
+
+        struct PushConstants {
+          a : f32,
+        }
+
+        var<push_constant> push_constants : PushConstants;
+        @fragment fn main() -> @builtin(frag_depth) f32 {
+            return push_constants.a;
+        }
+
+    )";
+
+    auto* expect = R"(
+enable chromium_experimental_push_constant;
+
+struct PushConstants_1 {
+  a : f32,
+  /* @offset(4) */
+  min_depth : f32,
+  /* @offset(8) */
+  max_depth : f32,
+}
+
+fn clamp_frag_depth(v : f32) -> f32 {
+  return clamp(v, push_constants.min_depth, push_constants.max_depth);
+}
+
+struct PushConstants {
+  a : f32,
+}
+
+var<push_constant> push_constants : PushConstants_1;
+
+@fragment
+fn main() -> @builtin(frag_depth) f32 {
+  return clamp_frag_depth(push_constants.a);
+}
+)";
+
+    DataMap config;
+    config.Add<ClampFragDepth::Config>(4, 8);
+
+    auto got = Run<ClampFragDepth>(src, config);
+    EXPECT_EQ(expect, str(got));
 }
 
 TEST_F(ClampFragDepthTest, ShouldRunFragDepthAsDirectReturn) {
@@ -91,7 +145,10 @@
         }
     )";
 
-    EXPECT_TRUE(ShouldRun<ClampFragDepth>(src));
+    DataMap config;
+    config.Add<ClampFragDepth::Config>(0, 4);
+
+    EXPECT_TRUE(ShouldRun<ClampFragDepth>(src, config));
 }
 
 TEST_F(ClampFragDepthTest, ShouldRunFragDepthInStruct) {
@@ -106,7 +163,10 @@
         }
     )";
 
-    EXPECT_TRUE(ShouldRun<ClampFragDepth>(src));
+    DataMap config;
+    config.Add<ClampFragDepth::Config>(0, 4);
+
+    EXPECT_TRUE(ShouldRun<ClampFragDepth>(src, config));
 }
 
 TEST_F(ClampFragDepthTest, SingleReturnOfFragDepth) {
@@ -119,15 +179,17 @@
     auto* expect = R"(
 enable chromium_experimental_push_constant;
 
-struct FragDepthClampArgs {
-  min : f32,
-  max : f32,
+struct PushConstants {
+  /* @offset(0) */
+  min_depth : f32,
+  /* @offset(4) */
+  max_depth : f32,
 }
 
-var<push_constant> frag_depth_clamp_args : FragDepthClampArgs;
+var<push_constant> push_constants : PushConstants;
 
 fn clamp_frag_depth(v : f32) -> f32 {
-  return clamp(v, frag_depth_clamp_args.min, frag_depth_clamp_args.max);
+  return clamp(v, push_constants.min_depth, push_constants.max_depth);
 }
 
 @fragment
@@ -136,7 +198,9 @@
 }
 )";
 
-    auto got = Run<ClampFragDepth>(src);
+    DataMap config;
+    config.Add<ClampFragDepth::Config>(0, 4);
+    auto got = Run<ClampFragDepth>(src, config);
     EXPECT_EQ(expect, str(got));
 }
 
@@ -153,15 +217,17 @@
     auto* expect = R"(
 enable chromium_experimental_push_constant;
 
-struct FragDepthClampArgs {
-  min : f32,
-  max : f32,
+struct PushConstants {
+  /* @offset(0) */
+  min_depth : f32,
+  /* @offset(4) */
+  max_depth : f32,
 }
 
-var<push_constant> frag_depth_clamp_args : FragDepthClampArgs;
+var<push_constant> push_constants : PushConstants;
 
 fn clamp_frag_depth(v : f32) -> f32 {
-  return clamp(v, frag_depth_clamp_args.min, frag_depth_clamp_args.max);
+  return clamp(v, push_constants.min_depth, push_constants.max_depth);
 }
 
 @fragment
@@ -173,7 +239,9 @@
 }
 )";
 
-    auto got = Run<ClampFragDepth>(src);
+    DataMap config;
+    config.Add<ClampFragDepth::Config>(0, 4);
+    auto got = Run<ClampFragDepth>(src, config);
     EXPECT_EQ(expect, str(got));
 }
 
@@ -190,15 +258,17 @@
     auto* expect = R"(
 enable chromium_experimental_push_constant;
 
-struct FragDepthClampArgs {
-  min : f32,
-  max : f32,
+struct PushConstants {
+  /* @offset(0) */
+  min_depth : f32,
+  /* @offset(4) */
+  max_depth : f32,
 }
 
-var<push_constant> frag_depth_clamp_args : FragDepthClampArgs;
+var<push_constant> push_constants : PushConstants;
 
 fn clamp_frag_depth(v : f32) -> f32 {
-  return clamp(v, frag_depth_clamp_args.min, frag_depth_clamp_args.max);
+  return clamp(v, push_constants.min_depth, push_constants.max_depth);
 }
 
 @fragment
@@ -212,7 +282,9 @@
 }
 )";
 
-    auto got = Run<ClampFragDepth>(src);
+    DataMap config;
+    config.Add<ClampFragDepth::Config>(0, 4);
+    auto got = Run<ClampFragDepth>(src, config);
     EXPECT_EQ(expect, str(got));
 }
 
@@ -230,15 +302,17 @@
     auto* expect = R"(
 enable chromium_experimental_push_constant;
 
-struct FragDepthClampArgs {
-  min : f32,
-  max : f32,
+struct PushConstants {
+  /* @offset(0) */
+  min_depth : f32,
+  /* @offset(4) */
+  max_depth : f32,
 }
 
-var<push_constant> frag_depth_clamp_args : FragDepthClampArgs;
+var<push_constant> push_constants : PushConstants;
 
 fn clamp_frag_depth(v : f32) -> f32 {
-  return clamp(v, frag_depth_clamp_args.min, frag_depth_clamp_args.max);
+  return clamp(v, push_constants.min_depth, push_constants.max_depth);
 }
 
 struct S {
@@ -256,7 +330,9 @@
 }
 )";
 
-    auto got = Run<ClampFragDepth>(src);
+    DataMap config;
+    config.Add<ClampFragDepth::Config>(0, 4);
+    auto got = Run<ClampFragDepth>(src, config);
     EXPECT_EQ(expect, str(got));
 }
 
@@ -285,15 +361,17 @@
     auto* expect = R"(
 enable chromium_experimental_push_constant;
 
-struct FragDepthClampArgs {
-  min : f32,
-  max : f32,
+struct PushConstants {
+  /* @offset(0) */
+  min_depth : f32,
+  /* @offset(4) */
+  max_depth : f32,
 }
 
-var<push_constant> frag_depth_clamp_args : FragDepthClampArgs;
+var<push_constant> push_constants : PushConstants;
 
 fn clamp_frag_depth(v : f32) -> f32 {
-  return clamp(v, frag_depth_clamp_args.min, frag_depth_clamp_args.max);
+  return clamp(v, push_constants.min_depth, push_constants.max_depth);
 }
 
 struct S {
@@ -330,7 +408,9 @@
 }
 )";
 
-    auto got = Run<ClampFragDepth>(src);
+    DataMap config;
+    config.Add<ClampFragDepth::Config>(0, 4);
+    auto got = Run<ClampFragDepth>(src, config);
     EXPECT_EQ(expect, str(got));
 }
 
@@ -352,15 +432,17 @@
     auto* expect = R"(
 enable chromium_experimental_push_constant;
 
-struct FragDepthClampArgs {
-  min : f32,
-  max : f32,
+struct PushConstants {
+  /* @offset(0) */
+  min_depth : f32,
+  /* @offset(4) */
+  max_depth : f32,
 }
 
-var<push_constant> frag_depth_clamp_args : FragDepthClampArgs;
+var<push_constant> push_constants : PushConstants;
 
 fn clamp_frag_depth(v : f32) -> f32 {
-  return clamp(v, frag_depth_clamp_args.min, frag_depth_clamp_args.max);
+  return clamp(v, push_constants.min_depth, push_constants.max_depth);
 }
 
 struct S {
@@ -386,7 +468,9 @@
 }
 )";
 
-    auto got = Run<ClampFragDepth>(src);
+    DataMap config;
+    config.Add<ClampFragDepth::Config>(0, 4);
+    auto got = Run<ClampFragDepth>(src, config);
     EXPECT_EQ(expect, str(got));
 }
 
diff --git a/test/tint/types/functions/shader_io/fragment_output_builtins.wgsl.expected.glsl b/test/tint/types/functions/shader_io/fragment_output_builtins.wgsl.expected.glsl
index 3e264e1..e7ac574 100644
--- a/test/tint/types/functions/shader_io/fragment_output_builtins.wgsl.expected.glsl
+++ b/test/tint/types/functions/shader_io/fragment_output_builtins.wgsl.expected.glsl
@@ -2,8 +2,18 @@
 precision highp float;
 precision highp int;
 
+struct PushConstants {
+  float min_depth;
+  float max_depth;
+};
+
+layout(location=0) uniform PushConstants push_constants;
+float clamp_frag_depth(float v) {
+  return clamp(v, push_constants.min_depth, push_constants.max_depth);
+}
+
 float main1() {
-  return 1.0f;
+  return clamp_frag_depth(1.0f);
 }
 
 void main() {
diff --git a/test/tint/types/functions/shader_io/fragment_output_builtins_struct.wgsl.expected.glsl b/test/tint/types/functions/shader_io/fragment_output_builtins_struct.wgsl.expected.glsl
index 6f5c106..1385388 100644
--- a/test/tint/types/functions/shader_io/fragment_output_builtins_struct.wgsl.expected.glsl
+++ b/test/tint/types/functions/shader_io/fragment_output_builtins_struct.wgsl.expected.glsl
@@ -3,16 +3,31 @@
 precision highp float;
 precision highp int;
 
+struct PushConstants {
+  float min_depth;
+  float max_depth;
+};
+
+layout(location=0) uniform PushConstants push_constants;
+float clamp_frag_depth(float v) {
+  return clamp(v, push_constants.min_depth, push_constants.max_depth);
+}
+
 struct FragmentOutputs {
   float frag_depth;
   uint sample_mask;
 };
 
-FragmentOutputs tint_symbol() {
-  FragmentOutputs tint_symbol_1 = FragmentOutputs(1.0f, 1u);
+FragmentOutputs clamp_frag_depth_FragmentOutputs(FragmentOutputs s) {
+  FragmentOutputs tint_symbol_1 = FragmentOutputs(clamp_frag_depth(s.frag_depth), s.sample_mask);
   return tint_symbol_1;
 }
 
+FragmentOutputs tint_symbol() {
+  FragmentOutputs tint_symbol_2 = FragmentOutputs(1.0f, 1u);
+  return clamp_frag_depth_FragmentOutputs(tint_symbol_2);
+}
+
 void main() {
   FragmentOutputs inner_result = tint_symbol();
   gl_FragDepth = inner_result.frag_depth;
diff --git a/test/tint/types/functions/shader_io/fragment_output_mixed.wgsl.expected.glsl b/test/tint/types/functions/shader_io/fragment_output_mixed.wgsl.expected.glsl
index c43b3ea..905ec2f 100644
--- a/test/tint/types/functions/shader_io/fragment_output_mixed.wgsl.expected.glsl
+++ b/test/tint/types/functions/shader_io/fragment_output_mixed.wgsl.expected.glsl
@@ -7,6 +7,16 @@
 layout(location = 1) out uint loc1_1;
 layout(location = 2) out float loc2_1;
 layout(location = 3) out vec4 loc3_1;
+struct PushConstants {
+  float min_depth;
+  float max_depth;
+};
+
+layout(location=0) uniform PushConstants push_constants;
+float clamp_frag_depth(float v) {
+  return clamp(v, push_constants.min_depth, push_constants.max_depth);
+}
+
 struct FragmentOutputs {
   int loc0;
   float frag_depth;
@@ -16,11 +26,16 @@
   vec4 loc3;
 };
 
-FragmentOutputs tint_symbol() {
-  FragmentOutputs tint_symbol_1 = FragmentOutputs(1, 2.0f, 1u, 1.0f, 2u, vec4(1.0f, 2.0f, 3.0f, 4.0f));
+FragmentOutputs clamp_frag_depth_FragmentOutputs(FragmentOutputs s) {
+  FragmentOutputs tint_symbol_1 = FragmentOutputs(s.loc0, clamp_frag_depth(s.frag_depth), s.loc1, s.loc2, s.sample_mask, s.loc3);
   return tint_symbol_1;
 }
 
+FragmentOutputs tint_symbol() {
+  FragmentOutputs tint_symbol_2 = FragmentOutputs(1, 2.0f, 1u, 1.0f, 2u, vec4(1.0f, 2.0f, 3.0f, 4.0f));
+  return clamp_frag_depth_FragmentOutputs(tint_symbol_2);
+}
+
 void main() {
   FragmentOutputs inner_result = tint_symbol();
   loc0_1 = inner_result.loc0;
diff --git a/test/tint/types/functions/shader_io/fragment_output_mixed_f16.wgsl.expected.glsl b/test/tint/types/functions/shader_io/fragment_output_mixed_f16.wgsl.expected.glsl
index a10d9c0..5fdfa4d 100644
--- a/test/tint/types/functions/shader_io/fragment_output_mixed_f16.wgsl.expected.glsl
+++ b/test/tint/types/functions/shader_io/fragment_output_mixed_f16.wgsl.expected.glsl
@@ -10,6 +10,16 @@
 layout(location = 3) out vec4 loc3_1;
 layout(location = 4) out float16_t loc4_1;
 layout(location = 5) out f16vec3 loc5_1;
+struct PushConstants {
+  float min_depth;
+  float max_depth;
+};
+
+layout(location=0) uniform PushConstants push_constants;
+float clamp_frag_depth(float v) {
+  return clamp(v, push_constants.min_depth, push_constants.max_depth);
+}
+
 struct FragmentOutputs {
   int loc0;
   float frag_depth;
@@ -21,11 +31,16 @@
   f16vec3 loc5;
 };
 
-FragmentOutputs tint_symbol() {
-  FragmentOutputs tint_symbol_1 = FragmentOutputs(1, 2.0f, 1u, 1.0f, 2u, vec4(1.0f, 2.0f, 3.0f, 4.0f), 2.25hf, f16vec3(3.0hf, 5.0hf, 8.0hf));
+FragmentOutputs clamp_frag_depth_FragmentOutputs(FragmentOutputs s) {
+  FragmentOutputs tint_symbol_1 = FragmentOutputs(s.loc0, clamp_frag_depth(s.frag_depth), s.loc1, s.loc2, s.sample_mask, s.loc3, s.loc4, s.loc5);
   return tint_symbol_1;
 }
 
+FragmentOutputs tint_symbol() {
+  FragmentOutputs tint_symbol_2 = FragmentOutputs(1, 2.0f, 1u, 1.0f, 2u, vec4(1.0f, 2.0f, 3.0f, 4.0f), 2.25hf, f16vec3(3.0hf, 5.0hf, 8.0hf));
+  return clamp_frag_depth_FragmentOutputs(tint_symbol_2);
+}
+
 void main() {
   FragmentOutputs inner_result = tint_symbol();
   loc0_1 = inner_result.loc0;