Add WebGPU specific validation for FragDepth BuiltIn decoration (#2332)

Part of resolving #2276
diff --git a/source/val/validate_builtins.cpp b/source/val/validate_builtins.cpp
index 21a27a4..0f84641 100644
--- a/source/val/validate_builtins.cpp
+++ b/source/val/validate_builtins.cpp
@@ -998,12 +998,14 @@
 
 spv_result_t BuiltInsValidator::ValidateFragDepthAtDefinition(
     const Decoration& decoration, const Instruction& inst) {
-  if (spvIsVulkanEnv(_.context()->target_env)) {
+  if (spvIsVulkanOrWebGPUEnv(_.context()->target_env)) {
     if (spv_result_t error = ValidateF32(
             decoration, inst,
             [this, &inst](const std::string& message) -> spv_result_t {
               return _.diag(SPV_ERROR_INVALID_DATA, &inst)
-                     << "According to the Vulkan spec BuiltIn FragDepth "
+                     << "According to the "
+                     << spvLogStringForEnv(_.context()->target_env)
+                     << " spec BuiltIn FragDepth "
                         "variable needs to be a 32-bit float scalar. "
                      << message;
             })) {
@@ -1019,12 +1021,13 @@
     const Decoration& decoration, const Instruction& built_in_inst,
     const Instruction& referenced_inst,
     const Instruction& referenced_from_inst) {
-  if (spvIsVulkanEnv(_.context()->target_env)) {
+  if (spvIsVulkanOrWebGPUEnv(_.context()->target_env)) {
     const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst);
     if (storage_class != SpvStorageClassMax &&
         storage_class != SpvStorageClassOutput) {
       return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
-             << "Vulkan spec allows BuiltIn FragDepth to be only used for "
+             << spvLogStringForEnv(_.context()->target_env)
+             << " spec allows BuiltIn FragDepth to be only used for "
                 "variables with Output storage class. "
              << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
                                  referenced_from_inst)
@@ -1034,7 +1037,8 @@
     for (const SpvExecutionModel execution_model : execution_models_) {
       if (execution_model != SpvExecutionModelFragment) {
         return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
-               << "Vulkan spec allows BuiltIn FragDepth to be used only with "
+               << spvLogStringForEnv(_.context()->target_env)
+               << " spec allows BuiltIn FragDepth to be used only with "
                   "Fragment execution model. "
                << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
                                    referenced_from_inst, execution_model);
@@ -1047,7 +1051,8 @@
       const auto* modes = _.GetExecutionModes(entry_point);
       if (!modes || !modes->count(SpvExecutionModeDepthReplacing)) {
         return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
-               << "Vulkan spec requires DepthReplacing execution mode to be "
+               << spvLogStringForEnv(_.context()->target_env)
+               << " spec requires DepthReplacing execution mode to be "
                   "declared when using BuiltIn FragDepth. "
                << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
                                    referenced_from_inst);
diff --git a/test/val/val_builtins_test.cpp b/test/val/val_builtins_test.cpp
index f3b9b43..0c0b5d4 100644
--- a/test/val/val_builtins_test.cpp
+++ b/test/val/val_builtins_test.cpp
@@ -596,6 +596,11 @@
             Values("%f32"), Values(TestResult())));
 
 INSTANTIATE_TEST_SUITE_P(
+    FragDepthSuccess, ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("FragDepth"), Values("Fragment"), Values("Output"),
+            Values("%f32"), Values(TestResult())));
+
+INSTANTIATE_TEST_SUITE_P(
     FragDepthNotFragment,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(
@@ -607,6 +612,15 @@
                           "to be used only with Fragment execution model"))));
 
 INSTANTIATE_TEST_SUITE_P(
+    FragDepthNotFragment,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(
+        Values("FragDepth"), Values("Vertex", "GLCompute"), Values("Output"),
+        Values("%f32"),
+        Values(TestResult(SPV_ERROR_INVALID_DATA,
+                          "to be used only with Fragment execution model"))));
+
+INSTANTIATE_TEST_SUITE_P(
     FragDepthNotOutput,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("FragDepth"), Values("Fragment"), Values("Input"),
@@ -617,6 +631,16 @@
                 "uses storage class Input"))));
 
 INSTANTIATE_TEST_SUITE_P(
+    FragDepthNotOutput,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("FragDepth"), Values("Fragment"), Values("Input"),
+            Values("%f32"),
+            Values(TestResult(
+                SPV_ERROR_INVALID_DATA,
+                "to be only used for variables with Output storage class",
+                "uses storage class Input"))));
+
+INSTANTIATE_TEST_SUITE_P(
     FragDepthNotFloatScalar,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("FragDepth"), Values("Fragment"), Values("Output"),
@@ -626,6 +650,15 @@
                               "is not a float scalar"))));
 
 INSTANTIATE_TEST_SUITE_P(
+    FragDepthNotFloatScalar,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("FragDepth"), Values("Fragment"), Values("Output"),
+            Values("%f32vec4", "%u32"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "needs to be a 32-bit float scalar",
+                              "is not a float scalar"))));
+
+INSTANTIATE_TEST_SUITE_P(
     FragDepthNotF32, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("FragDepth"), Values("Fragment"), Values("Output"),
             Values("%f64"),
@@ -2335,16 +2368,20 @@
               HasSubstr("called with execution model Fragment"));
 }
 
-TEST_F(ValidateBuiltIns, FragmentFragDepthNoDepthReplacing) {
-  CodeGenerator generator = CodeGenerator::GetDefaultShaderCodeGenerator();
+CodeGenerator GetNoDepthReplacingGenerator(spv_target_env env) {
+  CodeGenerator generator =
+      spvIsWebGPUEnv(env) ? CodeGenerator::GetWebGPUShaderCodeGenerator()
+                          : CodeGenerator::GetDefaultShaderCodeGenerator();
+
   generator.before_types_ = R"(
 OpMemberDecorate %output_type 0 BuiltIn FragDepth
 )";
 
   generator.after_types_ = R"(
 %output_type = OpTypeStruct %f32
+%output_null = OpConstantNull %output_type
 %output_ptr = OpTypePointer Output %output_type
-%output = OpVariable %output_ptr Output
+%output = OpVariable %output_ptr Output %output_null
 %output_f32_ptr = OpTypePointer Output %f32
 )";
 
@@ -2358,7 +2395,7 @@
 )";
   generator.entry_points_.push_back(std::move(entry_point));
 
-  generator.add_at_the_end_ = R"(
+  const std::string function_body = R"(
 %foo = OpFunction %void None %func
 %foo_entry = OpLabel
 %frag_depth = OpAccessChain %output_f32_ptr %output %u32_0
@@ -2367,6 +2404,18 @@
 OpFunctionEnd
 )";
 
+  if (spvIsWebGPUEnv(env)) {
+    generator.after_types_ += function_body;
+  } else {
+    generator.add_at_the_end_ = function_body;
+  }
+
+  return generator;
+}
+
+TEST_F(ValidateBuiltIns, VulkanFragmentFragDepthNoDepthReplacing) {
+  CodeGenerator generator = GetNoDepthReplacingGenerator(SPV_ENV_VULKAN_1_0);
+
   CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
   EXPECT_THAT(getDiagnosticString(),
@@ -2374,16 +2423,31 @@
                         "be declared when using BuiltIn FragDepth"));
 }
 
-TEST_F(ValidateBuiltIns, FragmentFragDepthOneMainHasDepthReplacingOtherHasnt) {
-  CodeGenerator generator = CodeGenerator::GetDefaultShaderCodeGenerator();
+TEST_F(ValidateBuiltIns, WebGPUFragmentFragDepthNoDepthReplacing) {
+  CodeGenerator generator = GetNoDepthReplacingGenerator(SPV_ENV_WEBGPU_0);
+
+  CompileSuccessfully(generator.Build(), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("WebGPU spec requires DepthReplacing execution mode to "
+                        "be declared when using BuiltIn FragDepth"));
+}
+
+CodeGenerator GetOneMainHasDepthReplacingOtherHasntGenerator(
+    spv_target_env env) {
+  CodeGenerator generator =
+      spvIsWebGPUEnv(env) ? CodeGenerator::GetWebGPUShaderCodeGenerator()
+                          : CodeGenerator::GetDefaultShaderCodeGenerator();
+
   generator.before_types_ = R"(
 OpMemberDecorate %output_type 0 BuiltIn FragDepth
 )";
 
   generator.after_types_ = R"(
 %output_type = OpTypeStruct %f32
+%output_null = OpConstantNull %output_type
 %output_ptr = OpTypePointer Output %output_type
-%output = OpVariable %output_ptr Output
+%output = OpVariable %output_ptr Output %output_null
 %output_f32_ptr = OpTypePointer Output %f32
 )";
 
@@ -2408,7 +2472,7 @@
 )";
   generator.entry_points_.push_back(std::move(entry_point));
 
-  generator.add_at_the_end_ = R"(
+  const std::string function_body = R"(
 %foo = OpFunction %void None %func
 %foo_entry = OpLabel
 %frag_depth = OpAccessChain %output_f32_ptr %output %u32_0
@@ -2417,6 +2481,20 @@
 OpFunctionEnd
 )";
 
+  if (spvIsWebGPUEnv(env)) {
+    generator.after_types_ += function_body;
+  } else {
+    generator.add_at_the_end_ = function_body;
+  }
+
+  return generator;
+}
+
+TEST_F(ValidateBuiltIns,
+       VulkanFragmentFragDepthOneMainHasDepthReplacingOtherHasnt) {
+  CodeGenerator generator =
+      GetOneMainHasDepthReplacingOtherHasntGenerator(SPV_ENV_VULKAN_1_0);
+
   CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
   EXPECT_THAT(getDiagnosticString(),
@@ -2424,6 +2502,18 @@
                         "be declared when using BuiltIn FragDepth"));
 }
 
+TEST_F(ValidateBuiltIns,
+       WebGPUFragmentFragDepthOneMainHasDepthReplacingOtherHasnt) {
+  CodeGenerator generator =
+      GetOneMainHasDepthReplacingOtherHasntGenerator(SPV_ENV_WEBGPU_0);
+
+  CompileSuccessfully(generator.Build(), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("WebGPU spec requires DepthReplacing execution mode to "
+                        "be declared when using BuiltIn FragDepth"));
+}
+
 TEST_F(ValidateBuiltIns, AllowInstanceIdWithIntersectionShader) {
   CodeGenerator generator = CodeGenerator::GetDefaultShaderCodeGenerator();
   generator.capabilities_ += R"(