Add support for first and either sampling parameter

This adds support for `@interpolate(flat, first)` and
`@interpolate(first, either)`. If no sampling parameter
is provided it's assumed to be 'first'. 'first' is not
allowed in compat mode.

Bug: 340278447
Change-Id: Ic18ffc6680470f485e6c0da220f05f01cf0fb786
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/195234
Commit-Queue: Gregg Tavares <gman@chromium.org>
Reviewed-by: James Price <jrprice@google.com>
diff --git a/src/dawn/native/BlitBufferToDepthStencil.cpp b/src/dawn/native/BlitBufferToDepthStencil.cpp
index 9ee91f7..c6bf28a 100644
--- a/src/dawn/native/BlitBufferToDepthStencil.cpp
+++ b/src/dawn/native/BlitBufferToDepthStencil.cpp
@@ -101,7 +101,7 @@
 @group(0) @binding(1) var<uniform> params : Params;
 
 struct VertexOutputs {
-  @location(0) @interpolate(flat) stencil_val : u32,
+  @location(0) @interpolate(flat, either) stencil_val : u32,
   @builtin(position) position : vec4f,
 };
 
diff --git a/src/dawn/native/ShaderModule.cpp b/src/dawn/native/ShaderModule.cpp
index c0c3672..2c0fcba 100644
--- a/src/dawn/native/ShaderModule.cpp
+++ b/src/dawn/native/ShaderModule.cpp
@@ -307,6 +307,10 @@
             return InterpolationSampling::Centroid;
         case tint::inspector::InterpolationSampling::kSample:
             return InterpolationSampling::Sample;
+        case tint::inspector::InterpolationSampling::kFirst:
+            return InterpolationSampling::First;
+        case tint::inspector::InterpolationSampling::kEither:
+            return InterpolationSampling::Either;
         case tint::inspector::InterpolationSampling::kUnknown:
             return DAWN_VALIDATION_ERROR(
                 "Attempted to convert 'Unknown' interpolation sampling type from Tint");
diff --git a/src/dawn/native/ShaderModule.h b/src/dawn/native/ShaderModule.h
index d703e5c..6dff012 100644
--- a/src/dawn/native/ShaderModule.h
+++ b/src/dawn/native/ShaderModule.h
@@ -92,6 +92,8 @@
     Center,
     Centroid,
     Sample,
+    First,
+    Either,
 };
 
 enum class PixelLocalMemberType {
diff --git a/src/dawn/native/webgpu_absl_format.cpp b/src/dawn/native/webgpu_absl_format.cpp
index 2165c40..f8226f9 100644
--- a/src/dawn/native/webgpu_absl_format.cpp
+++ b/src/dawn/native/webgpu_absl_format.cpp
@@ -647,6 +647,12 @@
         case InterpolationSampling::Sample:
             s->Append("Sample");
             break;
+        case InterpolationSampling::First:
+            s->Append("First");
+            break;
+        case InterpolationSampling::Either:
+            s->Append("Either");
+            break;
     }
     return {true};
 }
diff --git a/src/dawn/tests/end2end/FirstIndexOffsetTests.cpp b/src/dawn/tests/end2end/FirstIndexOffsetTests.cpp
index b186235..c710aad 100644
--- a/src/dawn/tests/end2end/FirstIndexOffsetTests.cpp
+++ b/src/dawn/tests/end2end/FirstIndexOffsetTests.cpp
@@ -117,6 +117,10 @@
                                      CheckIndex checkIndex,
                                      uint32_t firstVertex,
                                      uint32_t firstInstance) {
+    // Compatibility mode does not support @interpolate(flat, first).
+    // It only supports @interpolate(flat, either).
+    DAWN_TEST_UNSUPPORTED_IF(IsCompatibilityMode());
+
     using wgpu::operator&;
 
     std::stringstream vertexInputs;
diff --git a/src/dawn/tests/end2end/ShaderTests.cpp b/src/dawn/tests/end2end/ShaderTests.cpp
index 87986da..05974b5 100644
--- a/src/dawn/tests/end2end/ShaderTests.cpp
+++ b/src/dawn/tests/end2end/ShaderTests.cpp
@@ -1220,10 +1220,10 @@
     wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"(
 struct ShaderIO {
     @location(1) var1: f32,
-    @location(3) @interpolate(flat) var3: u32,
-    @location(5) @interpolate(flat) var5: i32,
+    @location(3) @interpolate(flat, either) var3: u32,
+    @location(5) @interpolate(flat, either) var5: i32,
     @location(7) var7: f32,
-    @location(9) @interpolate(flat) var9: u32,
+    @location(9) @interpolate(flat, either) var9: u32,
     @builtin(position) pos: vec4f,
 }
 
@@ -1247,7 +1247,7 @@
 
     wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"(
 struct ShaderIO {
-    @location(3) @interpolate(flat) var3: u32,
+    @location(3) @interpolate(flat, either) var3: u32,
     @location(7) var7: f32,
 }
 
diff --git a/src/dawn/tests/unittests/validation/CompatValidationTests.cpp b/src/dawn/tests/unittests/validation/CompatValidationTests.cpp
index fec272f..48a1cb2 100644
--- a/src/dawn/tests/unittests/validation/CompatValidationTests.cpp
+++ b/src/dawn/tests/unittests/validation/CompatValidationTests.cpp
@@ -236,8 +236,7 @@
 TEST_F(CompatValidationTest, CanNotUseShaderWithUnsupportedInterpolateTypeOrSampling) {
     static const char* interpolateParams[] = {
         "perspective",  // should pass
-        "linear",
-        "perspective, sample",
+        "linear",      "perspective, sample", "flat", "flat, first",
     };
     for (auto interpolateParam : interpolateParams) {
         auto wgsl = absl::StrFormat(R"(
diff --git a/src/dawn/tests/unittests/validation/RenderPipelineValidationTests.cpp b/src/dawn/tests/unittests/validation/RenderPipelineValidationTests.cpp
index 68e4a3b..abba2a3 100644
--- a/src/dawn/tests/unittests/validation/RenderPipelineValidationTests.cpp
+++ b/src/dawn/tests/unittests/validation/RenderPipelineValidationTests.cpp
@@ -2028,7 +2028,8 @@
         std::string interfaceDeclaration;
         {
             std::ostringstream sstream;
-            sstream << "struct A { @location(0) @interpolate(flat) a: " << kTypes[i] << ",\n";
+            sstream << "struct A { @location(0) @interpolate(flat, either) a: " << kTypes[i]
+                    << ",\n";
             interfaceDeclaration = sstream.str();
         }
 
@@ -2084,22 +2085,25 @@
         Center,
         Centroid,
         Sample,
+        First,
+        Either,
         Count,
     };
     constexpr std::array<const char*, static_cast<size_t>(InterpolationType::Count)>
         kInterpolationTypeString = {{"", "perspective", "linear", "flat"}};
     constexpr std::array<const char*, static_cast<size_t>(InterpolationSampling::Count)>
-        kInterpolationSamplingString = {{"", "center", "centroid", "sample"}};
+        kInterpolationSamplingString = {{"", "center", "centroid", "sample", "first", "either"}};
 
     struct InterpolationAttribute {
         InterpolationType interpolationType;
         InterpolationSampling interpolationSampling;
     };
 
-    // Interpolation sampling is not used with flat interpolation.
-    constexpr std::array<InterpolationAttribute, 10> validInterpolationAttributes = {{
+    constexpr std::array<InterpolationAttribute, 12> validInterpolationAttributes = {{
         {InterpolationType::None, InterpolationSampling::None},
         {InterpolationType::Flat, InterpolationSampling::None},
+        {InterpolationType::Flat, InterpolationSampling::First},
+        {InterpolationType::Flat, InterpolationSampling::Either},
         {InterpolationType::Linear, InterpolationSampling::None},
         {InterpolationType::Linear, InterpolationSampling::Center},
         {InterpolationType::Linear, InterpolationSampling::Centroid},
@@ -2176,6 +2180,9 @@
                 break;
 
             case InterpolationType::Flat:
+                if (appliedAttribute.interpolationSampling == InterpolationSampling::None) {
+                    appliedAttribute.interpolationSampling = InterpolationSampling::First;
+                }
                 break;
             default:
                 DAWN_UNREACHABLE();
diff --git a/src/tint/cmd/common/helper.cc b/src/tint/cmd/common/helper.cc
index 0a92cc4..ffe4dc9 100644
--- a/src/tint/cmd/common/helper.cc
+++ b/src/tint/cmd/common/helper.cc
@@ -555,6 +555,10 @@
             return "centroid";
         case tint::inspector::InterpolationSampling::kSample:
             return "sample";
+        case tint::inspector::InterpolationSampling::kFirst:
+            return "first";
+        case tint::inspector::InterpolationSampling::kEither:
+            return "either";
     }
     return "unknown";
 }
diff --git a/src/tint/cmd/fuzz/wgsl/dictionary.txt b/src/tint/cmd/fuzz/wgsl/dictionary.txt
index 1f893aa..7f7d75b 100644
--- a/src/tint/cmd/fuzz/wgsl/dictionary.txt
+++ b/src/tint/cmd/fuzz/wgsl/dictionary.txt
@@ -199,6 +199,7 @@
 "dpdyCoarse"
 "dpdyFine"
 "dual_source_blending"
+"either"
 "elements"
 "else"
 "enable"
@@ -212,6 +213,7 @@
 "faceForward"
 "fallthrough"
 "false"
+"first"
 "firstLeadingBit"
 "firstTrailingBit"
 "flat"
diff --git a/src/tint/lang/core/core.def b/src/tint/lang/core/core.def
index dce777d..aa7bc09 100644
--- a/src/tint/lang/core/core.def
+++ b/src/tint/lang/core/core.def
@@ -77,6 +77,8 @@
   center
   centroid
   sample
+  first
+  either
 }
 
 enum builtin_type {
diff --git a/src/tint/lang/core/interpolation_sampling.cc b/src/tint/lang/core/interpolation_sampling.cc
index 7f6930c..ba37004 100644
--- a/src/tint/lang/core/interpolation_sampling.cc
+++ b/src/tint/lang/core/interpolation_sampling.cc
@@ -51,6 +51,12 @@
     if (str == "centroid") {
         return InterpolationSampling::kCentroid;
     }
+    if (str == "either") {
+        return InterpolationSampling::kEither;
+    }
+    if (str == "first") {
+        return InterpolationSampling::kFirst;
+    }
     if (str == "sample") {
         return InterpolationSampling::kSample;
     }
@@ -65,6 +71,10 @@
             return "center";
         case InterpolationSampling::kCentroid:
             return "centroid";
+        case InterpolationSampling::kEither:
+            return "either";
+        case InterpolationSampling::kFirst:
+            return "first";
         case InterpolationSampling::kSample:
             return "sample";
     }
diff --git a/src/tint/lang/core/interpolation_sampling.h b/src/tint/lang/core/interpolation_sampling.h
index ca7e075..014c207 100644
--- a/src/tint/lang/core/interpolation_sampling.h
+++ b/src/tint/lang/core/interpolation_sampling.h
@@ -49,6 +49,8 @@
     kUndefined,
     kCenter,
     kCentroid,
+    kEither,
+    kFirst,
     kSample,
 };
 
@@ -71,9 +73,7 @@
 InterpolationSampling ParseInterpolationSampling(std::string_view str);
 
 constexpr std::string_view kInterpolationSamplingStrings[] = {
-    "center",
-    "centroid",
-    "sample",
+    "center", "centroid", "either", "first", "sample",
 };
 
 }  // namespace tint::core
diff --git a/src/tint/lang/core/interpolation_sampling_bench.cc b/src/tint/lang/core/interpolation_sampling_bench.cc
index c3da1ea..460a150 100644
--- a/src/tint/lang/core/interpolation_sampling_bench.cc
+++ b/src/tint/lang/core/interpolation_sampling_bench.cc
@@ -44,9 +44,11 @@
 
 void InterpolationSamplingParser(::benchmark::State& state) {
     const char* kStrings[] = {
-        "ccnter",     "c3r",   "centeV",  "center",   "1enter",    "cnqqer",    "centll77",
-        "qqenrppHid", "cntov", "cenGoid", "centroid", "ceviiroid", "ceWWtro8d", "cxxtMoid",
-        "saXggl",     "Xmle",  "sam3le",  "sample",   "sEmple",    "amTTlPP",   "ddamxxl",
+        "ccnter",     "c3r",        "centeV",  "center",   "1enter",    "cnqqer",    "centll77",
+        "qqenrppHid", "cntov",      "cenGoid", "centroid", "ceviiroid", "ceWWtro8d", "cxxtMoid",
+        "eiXgge",     "Xter",       "eit3er",  "either",   "eEther",    "itTTePP",   "dditxxe",
+        "f44rst",     "fiVVSSt",    "22RRt",   "first",    "fFst",      "firt",      "ROOHVt",
+        "saypl",      "snnm77lrre", "004mple", "sample",   "spoo",      "amzze",     "1implpp",
     };
     for (auto _ : state) {
         for (auto* str : kStrings) {
diff --git a/src/tint/lang/core/interpolation_sampling_test.cc b/src/tint/lang/core/interpolation_sampling_test.cc
index 6d658b2..52d0168 100644
--- a/src/tint/lang/core/interpolation_sampling_test.cc
+++ b/src/tint/lang/core/interpolation_sampling_test.cc
@@ -58,8 +58,8 @@
 }
 
 static constexpr Case kValidCases[] = {
-    {"center", InterpolationSampling::kCenter},
-    {"centroid", InterpolationSampling::kCentroid},
+    {"center", InterpolationSampling::kCenter}, {"centroid", InterpolationSampling::kCentroid},
+    {"either", InterpolationSampling::kEither}, {"first", InterpolationSampling::kFirst},
     {"sample", InterpolationSampling::kSample},
 };
 
@@ -70,9 +70,15 @@
     {"1entroid", InterpolationSampling::kUndefined},
     {"enJrqqid", InterpolationSampling::kUndefined},
     {"llen77roid", InterpolationSampling::kUndefined},
-    {"sapppHHe", InterpolationSampling::kUndefined},
-    {"cam", InterpolationSampling::kUndefined},
-    {"sbGpl", InterpolationSampling::kUndefined},
+    {"eipphHHr", InterpolationSampling::kUndefined},
+    {"cit", InterpolationSampling::kUndefined},
+    {"ebGhe", InterpolationSampling::kUndefined},
+    {"fivist", InterpolationSampling::kUndefined},
+    {"fi8WWt", InterpolationSampling::kUndefined},
+    {"Mxxrs", InterpolationSampling::kUndefined},
+    {"saXggl", InterpolationSampling::kUndefined},
+    {"Xmle", InterpolationSampling::kUndefined},
+    {"sam3le", InterpolationSampling::kUndefined},
 };
 
 using InterpolationSamplingParseTest = testing::TestWithParam<Case>;
diff --git a/src/tint/lang/core/ir/binary/decode.cc b/src/tint/lang/core/ir/binary/decode.cc
index 8f1006f..96e4154 100644
--- a/src/tint/lang/core/ir/binary/decode.cc
+++ b/src/tint/lang/core/ir/binary/decode.cc
@@ -1315,6 +1315,10 @@
                 return core::InterpolationSampling::kCentroid;
             case pb::InterpolationSampling::sample:
                 return core::InterpolationSampling::kSample;
+            case pb::InterpolationSampling::first:
+                return core::InterpolationSampling::kFirst;
+            case pb::InterpolationSampling::either:
+                return core::InterpolationSampling::kEither;
 
             case pb::InterpolationSampling::InterpolationSampling_INT_MIN_SENTINEL_DO_NOT_USE_:
             case pb::InterpolationSampling::InterpolationSampling_INT_MAX_SENTINEL_DO_NOT_USE_:
diff --git a/src/tint/lang/core/ir/binary/encode.cc b/src/tint/lang/core/ir/binary/encode.cc
index 25583b0..b1dc1e4 100644
--- a/src/tint/lang/core/ir/binary/encode.cc
+++ b/src/tint/lang/core/ir/binary/encode.cc
@@ -847,6 +847,10 @@
                 return pb::InterpolationSampling::centroid;
             case core::InterpolationSampling::kSample:
                 return pb::InterpolationSampling::sample;
+            case core::InterpolationSampling::kFirst:
+                return pb::InterpolationSampling::first;
+            case core::InterpolationSampling::kEither:
+                return pb::InterpolationSampling::either;
             case core::InterpolationSampling::kUndefined:
                 break;
         }
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 434850a..9e5b52f 100644
--- a/src/tint/lang/glsl/writer/ast_printer/ast_printer.cc
+++ b/src/tint/lang/glsl/writer/ast_printer/ast_printer.cc
@@ -2150,6 +2150,8 @@
                         break;
                     case core::InterpolationSampling::kSample:
                     case core::InterpolationSampling::kCenter:
+                    case core::InterpolationSampling::kFirst:
+                    case core::InterpolationSampling::kEither:
                     case core::InterpolationSampling::kUndefined:
                         break;
                 }
diff --git a/src/tint/lang/hlsl/writer/ast_printer/ast_printer.cc b/src/tint/lang/hlsl/writer/ast_printer/ast_printer.cc
index 97c61da..93c9b1c 100644
--- a/src/tint/lang/hlsl/writer/ast_printer/ast_printer.cc
+++ b/src/tint/lang/hlsl/writer/ast_printer/ast_printer.cc
@@ -3576,6 +3576,8 @@
             modifiers += "sample ";
             break;
         case core::InterpolationSampling::kCenter:
+        case core::InterpolationSampling::kFirst:
+        case core::InterpolationSampling::kEither:
         case core::InterpolationSampling::kUndefined:
             break;
     }
diff --git a/src/tint/lang/hlsl/writer/printer/printer.cc b/src/tint/lang/hlsl/writer/printer/printer.cc
index 485986a..f45410d 100644
--- a/src/tint/lang/hlsl/writer/printer/printer.cc
+++ b/src/tint/lang/hlsl/writer/printer/printer.cc
@@ -1502,6 +1502,8 @@
                 modifiers += "sample ";
                 break;
             case core::InterpolationSampling::kCenter:
+            case core::InterpolationSampling::kFirst:
+            case core::InterpolationSampling::kEither:
             case core::InterpolationSampling::kUndefined:
                 break;
         }
diff --git a/src/tint/lang/msl/writer/common/printer_support.cc b/src/tint/lang/msl/writer/common/printer_support.cc
index 2f9bfa0..d3dbba0 100644
--- a/src/tint/lang/msl/writer/common/printer_support.cc
+++ b/src/tint/lang/msl/writer/common/printer_support.cc
@@ -101,6 +101,9 @@
                 attr = "center_";
             }
             break;
+        case core::InterpolationSampling::kFirst:
+        case core::InterpolationSampling::kEither:
+            break;
     }
     switch (type) {
         case core::InterpolationType::kPerspective:
diff --git a/src/tint/lang/spirv/writer/ast_printer/builder.cc b/src/tint/lang/spirv/writer/ast_printer/builder.cc
index 1d925a2..a0d6f98 100644
--- a/src/tint/lang/spirv/writer/ast_printer/builder.cc
+++ b/src/tint/lang/spirv/writer/ast_printer/builder.cc
@@ -4095,6 +4095,8 @@
             module_.PushAnnot(spv::Op::OpDecorate, {Operand(id), U32Operand(SpvDecorationSample)});
             break;
         case core::InterpolationSampling::kCenter:
+        case core::InterpolationSampling::kFirst:
+        case core::InterpolationSampling::kEither:
         case core::InterpolationSampling::kUndefined:
             break;
     }
diff --git a/src/tint/lang/spirv/writer/printer/printer.cc b/src/tint/lang/spirv/writer/printer/printer.cc
index a827c98..272c251 100644
--- a/src/tint/lang/spirv/writer/printer/printer.cc
+++ b/src/tint/lang/spirv/writer/printer/printer.cc
@@ -2040,6 +2040,8 @@
                     module_.PushAnnot(spv::Op::OpDecorate, {id, U32Operand(SpvDecorationSample)});
                     break;
                 case core::InterpolationSampling::kCenter:
+                case core::InterpolationSampling::kFirst:
+                case core::InterpolationSampling::kEither:
                 case core::InterpolationSampling::kUndefined:
                     break;
             }
diff --git a/src/tint/lang/wgsl/inspector/entry_point.h b/src/tint/lang/wgsl/inspector/entry_point.h
index 3946e63..50b380b 100644
--- a/src/tint/lang/wgsl/inspector/entry_point.h
+++ b/src/tint/lang/wgsl/inspector/entry_point.h
@@ -70,7 +70,15 @@
 enum class InterpolationType : uint8_t { kPerspective, kLinear, kFlat, kUnknown };
 
 /// Type of interpolation sampling of a stage variable.
-enum class InterpolationSampling : uint8_t { kNone, kCenter, kCentroid, kSample, kUnknown };
+enum class InterpolationSampling : uint8_t {
+    kNone,
+    kCenter,
+    kCentroid,
+    kSample,
+    kFirst,
+    kEither,
+    kUnknown
+};
 
 /// Reflection data about an entry point input or output.
 struct StageVariable {
diff --git a/src/tint/lang/wgsl/inspector/inspector.cc b/src/tint/lang/wgsl/inspector/inspector.cc
index 1dea16a..e7bd852 100644
--- a/src/tint/lang/wgsl/inspector/inspector.cc
+++ b/src/tint/lang/wgsl/inspector/inspector.cc
@@ -662,7 +662,7 @@
     stage_variable.attributes.color = color;
 
     std::tie(stage_variable.interpolation_type, stage_variable.interpolation_sampling) =
-        CalculateInterpolationData(type, attributes);
+        CalculateInterpolationData(attributes);
 
     variables.push_back(stage_variable);
 }
@@ -888,12 +888,8 @@
 }
 
 std::tuple<InterpolationType, InterpolationSampling> Inspector::CalculateInterpolationData(
-    const core::type::Type* type,
     VectorRef<const ast::Attribute*> attributes) const {
     auto* interpolation_attribute = ast::GetAttribute<ast::InterpolateAttribute>(attributes);
-    if (type->is_integer_scalar_or_vector()) {
-        return {InterpolationType::kFlat, InterpolationSampling::kNone};
-    }
 
     if (!interpolation_attribute) {
         return {InterpolationType::kPerspective, InterpolationSampling::kCenter};
@@ -912,9 +908,12 @@
                                 ->Value();
     }
 
-    if (ast_interpolation_type != core::InterpolationType::kFlat &&
-        ast_sampling_type == core::InterpolationSampling::kUndefined) {
-        ast_sampling_type = core::InterpolationSampling::kCenter;
+    if (ast_sampling_type == core::InterpolationSampling::kUndefined) {
+        if (ast_interpolation_type == core::InterpolationType::kFlat) {
+            ast_sampling_type = core::InterpolationSampling::kFirst;
+        } else {
+            ast_sampling_type = core::InterpolationSampling::kCenter;
+        }
     }
 
     auto interpolation_type = InterpolationType::kUnknown;
@@ -946,6 +945,12 @@
         case core::InterpolationSampling::kSample:
             sampling_type = InterpolationSampling::kSample;
             break;
+        case core::InterpolationSampling::kFirst:
+            sampling_type = InterpolationSampling::kFirst;
+            break;
+        case core::InterpolationSampling::kEither:
+            sampling_type = InterpolationSampling::kEither;
+            break;
     }
 
     return {interpolation_type, sampling_type};
diff --git a/src/tint/lang/wgsl/inspector/inspector.h b/src/tint/lang/wgsl/inspector/inspector.h
index bcadfa3..6e4dca8 100644
--- a/src/tint/lang/wgsl/inspector/inspector.h
+++ b/src/tint/lang/wgsl/inspector/inspector.h
@@ -262,11 +262,9 @@
     /// Constructs |sampler_targets_| if it hasn't already been instantiated.
     void GenerateSamplerTargets();
 
-    /// @param type the type of the parameter or structure member
     /// @param attributes attributes associated with the parameter or structure member
     /// @returns the interpolation type and sampling modes for the value
     std::tuple<InterpolationType, InterpolationSampling> CalculateInterpolationData(
-        const core::type::Type* type,
         VectorRef<const ast::Attribute*> attributes) const;
 
     /// @param func the root function of the callgraph to consider for the computation.
diff --git a/src/tint/lang/wgsl/inspector/inspector_test.cc b/src/tint/lang/wgsl/inspector/inspector_test.cc
index ca4c4cb..9ba40fc 100644
--- a/src/tint/lang/wgsl/inspector/inspector_test.cc
+++ b/src/tint/lang/wgsl/inspector/inspector_test.cc
@@ -595,7 +595,7 @@
     EXPECT_EQ("in_var4", result[0].input_variables[2].variable_name);
     EXPECT_EQ(std::nullopt, result[0].input_variables[2].attributes.location);
     EXPECT_EQ(2u, result[0].input_variables[2].attributes.color);
-    EXPECT_EQ(InterpolationType::kFlat, result[0].input_variables[2].interpolation_type);
+    EXPECT_EQ(InterpolationType::kPerspective, result[0].input_variables[2].interpolation_type);
     EXPECT_EQ(ComponentType::kU32, result[0].input_variables[2].component_type);
 
     ASSERT_EQ(1u, result[0].output_variables.size());
@@ -1727,7 +1727,13 @@
             InterpolationType::kLinear, InterpolationSampling::kCenter},
         InspectorGetEntryPointInterpolateTestParams{
             core::InterpolationType::kFlat, core::InterpolationSampling::kUndefined,
-            InterpolationType::kFlat, InterpolationSampling::kNone}));
+            InterpolationType::kFlat, InterpolationSampling::kFirst},
+        InspectorGetEntryPointInterpolateTestParams{
+            core::InterpolationType::kFlat, core::InterpolationSampling::kFirst,
+            InterpolationType::kFlat, InterpolationSampling::kFirst},
+        InspectorGetEntryPointInterpolateTestParams{
+            core::InterpolationType::kFlat, core::InterpolationSampling::kEither,
+            InterpolationType::kFlat, InterpolationSampling::kEither}));
 
 TEST_F(InspectorGetOverrideDefaultValuesTest, Bool) {
     GlobalConst("C", Expr(true));
diff --git a/src/tint/lang/wgsl/reader/parser/variable_attribute_test.cc b/src/tint/lang/wgsl/reader/parser/variable_attribute_test.cc
index bc930e7..025399f 100644
--- a/src/tint/lang/wgsl/reader/parser/variable_attribute_test.cc
+++ b/src/tint/lang/wgsl/reader/parser/variable_attribute_test.cc
@@ -325,6 +325,38 @@
     EXPECT_EQ(interp->sampling, nullptr);
 }
 
+TEST_F(WGSLParserTest, Attribute_Interpolate_Flat_First) {
+    auto p = parser("interpolate(flat, first)");
+    auto attr = p->attribute();
+    EXPECT_TRUE(attr.matched);
+    EXPECT_FALSE(attr.errored);
+    ASSERT_NE(attr.value, nullptr);
+    auto* var_attr = attr.value->As<ast::Attribute>();
+    ASSERT_NE(var_attr, nullptr);
+    ASSERT_FALSE(p->has_error());
+    ASSERT_TRUE(var_attr->Is<ast::InterpolateAttribute>());
+
+    auto* interp = var_attr->As<ast::InterpolateAttribute>();
+    ast::CheckIdentifier(interp->type, "flat");
+    ast::CheckIdentifier(interp->sampling, "first");
+}
+
+TEST_F(WGSLParserTest, Attribute_Interpolate_Either) {
+    auto p = parser("interpolate(flat, either)");
+    auto attr = p->attribute();
+    EXPECT_TRUE(attr.matched);
+    EXPECT_FALSE(attr.errored);
+    ASSERT_NE(attr.value, nullptr);
+    auto* var_attr = attr.value->As<ast::Attribute>();
+    ASSERT_NE(var_attr, nullptr);
+    ASSERT_FALSE(p->has_error());
+    ASSERT_TRUE(var_attr->Is<ast::InterpolateAttribute>());
+
+    auto* interp = var_attr->As<ast::InterpolateAttribute>();
+    ast::CheckIdentifier(interp->type, "flat");
+    ast::CheckIdentifier(interp->sampling, "either");
+}
+
 TEST_F(WGSLParserTest, Attribute_Interpolate_Single_TrailingComma) {
     auto p = parser("interpolate(flat,)");
     auto attr = p->attribute();
diff --git a/src/tint/lang/wgsl/resolver/attribute_validation_test.cc b/src/tint/lang/wgsl/resolver/attribute_validation_test.cc
index d5eceef..ef6cac9 100644
--- a/src/tint/lang/wgsl/resolver/attribute_validation_test.cc
+++ b/src/tint/lang/wgsl/resolver/attribute_validation_test.cc
@@ -2533,7 +2533,9 @@
         EXPECT_FALSE(r()->Resolve());
         EXPECT_EQ(
             r()->error(),
-            R"(12:34 error: flat interpolation attribute must not have a sampling parameter)");
+            params.type == core::InterpolationType::kFlat
+                ? R"(12:34 error: flat interpolation can only use 'first' and 'either' sampling parameters)"
+                : R"(12:34 error: 'first' and 'either' sampling parameters can only be used with flat interpolation)");
     }
 }
 
@@ -2563,7 +2565,9 @@
         EXPECT_FALSE(r()->Resolve());
         EXPECT_EQ(
             r()->error(),
-            R"(12:34 error: flat interpolation attribute must not have a sampling parameter)");
+            params.type == core::InterpolationType::kFlat
+                ? R"(12:34 error: flat interpolation can only use 'first' and 'either' sampling parameters)"
+                : R"(12:34 error: 'first' and 'either' sampling parameters can only be used with flat interpolation)");
     }
 }
 
@@ -2593,7 +2597,9 @@
         EXPECT_FALSE(r()->Resolve());
         EXPECT_EQ(
             r()->error(),
-            R"(12:34 error: flat interpolation attribute must not have a sampling parameter)");
+            params.type == core::InterpolationType::kFlat
+                ? R"(12:34 error: flat interpolation can only use 'first' and 'either' sampling parameters)"
+                : R"(12:34 error: 'first' and 'either' sampling parameters can only be used with flat interpolation)");
     }
 }
 
@@ -2606,15 +2612,22 @@
         Params{core::InterpolationType::kPerspective, core::InterpolationSampling::kCenter, true},
         Params{core::InterpolationType::kPerspective, core::InterpolationSampling::kCentroid, true},
         Params{core::InterpolationType::kPerspective, core::InterpolationSampling::kSample, true},
+        Params{core::InterpolationType::kPerspective, core::InterpolationSampling::kFirst, false},
+        Params{core::InterpolationType::kPerspective, core::InterpolationSampling::kEither, false},
+
         Params{core::InterpolationType::kLinear, core::InterpolationSampling::kUndefined, true},
         Params{core::InterpolationType::kLinear, core::InterpolationSampling::kCenter, true},
         Params{core::InterpolationType::kLinear, core::InterpolationSampling::kCentroid, true},
         Params{core::InterpolationType::kLinear, core::InterpolationSampling::kSample, true},
-        // flat interpolation must not have a sampling type
+        Params{core::InterpolationType::kLinear, core::InterpolationSampling::kFirst, false},
+        Params{core::InterpolationType::kLinear, core::InterpolationSampling::kEither, false},
+
         Params{core::InterpolationType::kFlat, core::InterpolationSampling::kUndefined, true},
         Params{core::InterpolationType::kFlat, core::InterpolationSampling::kCenter, false},
         Params{core::InterpolationType::kFlat, core::InterpolationSampling::kCentroid, false},
-        Params{core::InterpolationType::kFlat, core::InterpolationSampling::kSample, false}));
+        Params{core::InterpolationType::kFlat, core::InterpolationSampling::kSample, false},
+        Params{core::InterpolationType::kFlat, core::InterpolationSampling::kFirst, true},
+        Params{core::InterpolationType::kFlat, core::InterpolationSampling::kEither, true}));
 
 TEST_F(InterpolateTest, FragmentInput_Integer_MissingFlatInterpolation) {
     Func("main", Vector{Param(Source{{12, 34}}, "a", ty.i32(), Vector{Location(0_a)})}, ty.void_(),
diff --git a/src/tint/lang/wgsl/resolver/compatibility_mode_test.cc b/src/tint/lang/wgsl/resolver/compatibility_mode_test.cc
index 28f52e9..c24821c 100644
--- a/src/tint/lang/wgsl/resolver/compatibility_mode_test.cc
+++ b/src/tint/lang/wgsl/resolver/compatibility_mode_test.cc
@@ -320,6 +320,98 @@
         R"(12:34 error: use of '@interpolate(..., sample)' is not allowed in compatibility mode)");
 }
 
+TEST_F(ResolverCompatibilityModeTest, FirstInterpolation_Parameter) {
+    // @fragment
+    // fn main(@location(1) @interpolate(flat, first) value : f32) {
+    // }
+
+    Func("main",
+         Vector{Param("value", ty.f32(),
+                      Vector{
+                          Location(1_i),
+                          Interpolate(Source{{12, 34}}, core::InterpolationType::kFlat,
+                                      core::InterpolationSampling::kFirst),
+                      })},
+         ty.void_(), Empty,
+         Vector{
+             Stage(ast::PipelineStage::kFragment),
+         },
+         Vector{
+             Builtin(core::BuiltinValue::kPosition),
+         });
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(
+        r()->error(),
+        R"(12:34 error: flat interpolation must use 'either' sampling parameter in compatibility mode)");
+}
+
+TEST_F(ResolverCompatibilityModeTest, FirstInterpolation_StructMember) {
+    // struct S {
+    //   @location(1) @interpolate(flat, first) value : f32,
+    // }
+
+    Structure("S", Vector{
+                       Member("value", ty.f32(),
+                              Vector{
+                                  Location(1_i),
+                                  Interpolate(Source{{12, 34}}, core::InterpolationType::kFlat,
+                                              core::InterpolationSampling::kFirst),
+                              }),
+                   });
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(
+        r()->error(),
+        R"(12:34 error: flat interpolation must use 'either' sampling parameter in compatibility mode)");
+}
+
+TEST_F(ResolverCompatibilityModeTest, FlatNoneInterpolation_Parameter) {
+    // @fragment
+    // fn main(@location(1) @interpolate(flat) value : f32) {
+    // }
+
+    Func("main",
+         Vector{Param("value", ty.f32(),
+                      Vector{
+                          Location(1_i),
+                          Interpolate(Source{{12, 34}}, core::InterpolationType::kFlat,
+                                      core::InterpolationSampling::kUndefined),
+                      })},
+         ty.void_(), Empty,
+         Vector{
+             Stage(ast::PipelineStage::kFragment),
+         },
+         Vector{
+             Builtin(core::BuiltinValue::kPosition),
+         });
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(
+        r()->error(),
+        R"(12:34 error: flat interpolation must use 'either' sampling parameter in compatibility mode)");
+}
+
+TEST_F(ResolverCompatibilityModeTest, FlatNoneInterpolation_StructMember) {
+    // struct S {
+    //   @location(1) @interpolate(flat) value : f32,
+    // }
+
+    Structure("S", Vector{
+                       Member("value", ty.f32(),
+                              Vector{
+                                  Location(1_i),
+                                  Interpolate(Source{{12, 34}}, core::InterpolationType::kFlat,
+                                              core::InterpolationSampling::kUndefined),
+                              }),
+                   });
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(
+        r()->error(),
+        R"(12:34 error: flat interpolation must use 'either' sampling parameter in compatibility mode)");
+}
+
 class ResolverCompatibilityModeTest_TextureLoad : public ResolverCompatibilityModeTest {
   protected:
     void add_call_param(std::string name, ast::Type type, ExpressionList* call_params) {
diff --git a/src/tint/lang/wgsl/resolver/unresolved_identifier_test.cc b/src/tint/lang/wgsl/resolver/unresolved_identifier_test.cc
index c87cd04..2d9a687 100644
--- a/src/tint/lang/wgsl/resolver/unresolved_identifier_test.cc
+++ b/src/tint/lang/wgsl/resolver/unresolved_identifier_test.cc
@@ -100,7 +100,7 @@
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), R"(12:34 error: unresolved interpolation sampling 'centre'
 12:34 note: Did you mean 'center'?
-Possible values: 'center', 'centroid', 'sample')");
+Possible values: 'center', 'centroid', 'either', 'first', 'sample')");
 }
 
 TEST_F(ResolverUnresolvedIdentifierSuggestions, InterpolationType) {
diff --git a/src/tint/lang/wgsl/resolver/validator.cc b/src/tint/lang/wgsl/resolver/validator.cc
index c5def01..3837147 100644
--- a/src/tint/lang/wgsl/resolver/validator.cc
+++ b/src/tint/lang/wgsl/resolver/validator.cc
@@ -1153,27 +1153,53 @@
         return false;
     }
 
-    if (attr->sampling && i_type->Value() == core::InterpolationType::kFlat) {
-        AddError(attr->source) << "flat interpolation attribute must not have a sampling parameter";
-        return false;
-    }
+    if (attr->sampling) {
+        auto s_type = sem_.AsInterpolationSampling(sem_.Get(attr->sampling))->Value();
+        bool is_first_or_either = s_type == core::InterpolationSampling::kFirst ||
+                                  s_type == core::InterpolationSampling::kEither;
 
-    if (mode_ == wgsl::ValidationMode::kCompat) {
-        if (i_type->Value() == core::InterpolationType::kLinear) {
-            AddError(attr->source)
-                << "use of '@interpolate(linear)' is not allowed in compatibility mode";
-            return false;
-        }
+        if (i_type->Value() == core::InterpolationType::kFlat) {
+            if (!is_first_or_either) {
+                AddError(attr->source)
+                    << "flat interpolation can only use 'first' and 'either' sampling parameters";
+                return false;
+            }
+            if (mode_ == wgsl::ValidationMode::kCompat &&
+                s_type == core::InterpolationSampling::kFirst) {
+                AddError(attr->source) << "flat interpolation must use 'either' sampling parameter "
+                                          "in compatibility mode";
+                return false;
+            }
+        } else {
+            if (is_first_or_either) {
+                AddError(attr->source) << "'first' and 'either' sampling parameters can only be "
+                                          "used with flat interpolation";
+                return false;
+            }
 
-        if (attr->sampling) {
-            auto s_type = sem_.AsInterpolationSampling(sem_.Get(attr->sampling));
-            if (s_type->Value() == core::InterpolationSampling::kSample) {
+            if (mode_ == wgsl::ValidationMode::kCompat &&
+                s_type == core::InterpolationSampling::kSample) {
                 AddError(attr->source)
                     << "use of '@interpolate(..., sample)' is not allowed in compatibility mode";
                 return false;
             }
         }
+    } else {
+        if (mode_ == wgsl::ValidationMode::kCompat &&
+            i_type->Value() == core::InterpolationType::kFlat) {
+            AddError(attr->source)
+                << "flat interpolation must use 'either' sampling parameter in compatibility mode";
+            return false;
+        }
     }
+
+    if (mode_ == wgsl::ValidationMode::kCompat &&
+        i_type->Value() == core::InterpolationType::kLinear) {
+        AddError(attr->source)
+            << "use of '@interpolate(linear)' is not allowed in compatibility mode";
+        return false;
+    }
+
     return true;
 }
 
diff --git a/src/tint/utils/protos/ir/ir.proto b/src/tint/utils/protos/ir/ir.proto
index a974bad..b50a5a2 100644
--- a/src/tint/utils/protos/ir/ir.proto
+++ b/src/tint/utils/protos/ir/ir.proto
@@ -487,6 +487,8 @@
     center = 0;
     centroid = 1;
     sample = 2;
+    first = 3;
+    either = 4;
 }
 
 enum BuiltinValue {