msl: Add generator option to emit [[point_size]]

This option is passed through to the CanonicalizeEntryPointIO
transform, which adds it to the set of builtin shader outputs for all
vertex shaders in the module.

Bug: tint:1000
Change-Id: Ibba4adde2c468b11ebfd7012fcb42ee48aad04e4
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/60522
Auto-Submit: James Price <jrprice@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: James Price <jrprice@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/transform/canonicalize_entry_point_io.cc b/src/transform/canonicalize_entry_point_io.cc
index 4eee792..1ddd315 100644
--- a/src/transform/canonicalize_entry_point_io.cc
+++ b/src/transform/canonicalize_entry_point_io.cc
@@ -289,6 +289,13 @@
               ctx.dst->Expr(cfg.fixed_sample_mask));
   }
 
+  /// Add a point size builtin to the wrapper function output.
+  void AddVertexPointSize() {
+    // Create a new output value and assign it a literal 1.0 value.
+    AddOutput("vertex_point_size", ctx.dst->ty.f32(),
+              {ctx.dst->Builtin(ast::Builtin::kPointSize)}, ctx.dst->Expr(1.f));
+  }
+
   /// Create the wrapper function's struct parameter and type objects.
   void CreateInputStruct() {
     // Sort the struct members to satisfy HLSL interfacing matching rules.
@@ -376,14 +383,20 @@
   /// Process the entry point function.
   void Process() {
     bool needs_fixed_sample_mask = false;
+    bool needs_vertex_point_size = false;
     if (func_ast->pipeline_stage() == ast::PipelineStage::kFragment &&
         cfg.fixed_sample_mask != 0xFFFFFFFF) {
       needs_fixed_sample_mask = true;
     }
+    if (func_ast->pipeline_stage() == ast::PipelineStage::kVertex &&
+        cfg.emit_vertex_point_size) {
+      needs_vertex_point_size = true;
+    }
 
     // Exit early if there is no shader IO to handle.
     if (func_sem->Parameters().size() == 0 &&
-        func_sem->ReturnType()->Is<sem::Void>() && !needs_fixed_sample_mask) {
+        func_sem->ReturnType()->Is<sem::Void>() && !needs_fixed_sample_mask &&
+        !needs_vertex_point_size) {
       return;
     }
 
@@ -430,6 +443,11 @@
       AddFixedSampleMask();
     }
 
+    // Add the pointsize builtin, if necessary.
+    if (needs_vertex_point_size) {
+      AddVertexPointSize();
+    }
+
     // Produce the entry point outputs, if necessary.
     if (!wrapper_output_values.empty()) {
       auto* output_struct = CreateOutputStruct();
@@ -488,8 +506,11 @@
 }
 
 CanonicalizeEntryPointIO::Config::Config(BuiltinStyle builtins,
-                                         uint32_t sample_mask)
-    : builtin_style(builtins), fixed_sample_mask(sample_mask) {}
+                                         uint32_t sample_mask,
+                                         bool emit_point_size)
+    : builtin_style(builtins),
+      fixed_sample_mask(sample_mask),
+      emit_vertex_point_size(emit_point_size) {}
 
 CanonicalizeEntryPointIO::Config::Config(const Config&) = default;
 CanonicalizeEntryPointIO::Config::~Config() = default;
diff --git a/src/transform/canonicalize_entry_point_io.h b/src/transform/canonicalize_entry_point_io.h
index 60a1584..e3296ed 100644
--- a/src/transform/canonicalize_entry_point_io.h
+++ b/src/transform/canonicalize_entry_point_io.h
@@ -96,7 +96,10 @@
     /// Constructor
     /// @param builtins the approach to use for emitting builtins.
     /// @param sample_mask an optional sample mask to combine with shader masks
-    explicit Config(BuiltinStyle builtins, uint32_t sample_mask = 0xFFFFFFFF);
+    /// @param emit_vertex_point_size `true` to generate a pointsize builtin
+    explicit Config(BuiltinStyle builtins,
+                    uint32_t sample_mask = 0xFFFFFFFF,
+                    bool emit_vertex_point_size = false);
 
     /// Copy constructor
     Config(const Config&);
@@ -109,6 +112,10 @@
 
     /// A fixed sample mask to combine into masks produced by fragment shaders.
     uint32_t const fixed_sample_mask;
+
+    /// Set to `true` to generate a pointsize builtin and have it set to 1.0
+    /// from all vertex shaders in the module.
+    bool const emit_vertex_point_size;
   };
 
   /// Constructor
diff --git a/src/transform/canonicalize_entry_point_io_test.cc b/src/transform/canonicalize_entry_point_io_test.cc
index 504d60d..6936d83 100644
--- a/src/transform/canonicalize_entry_point_io_test.cc
+++ b/src/transform/canonicalize_entry_point_io_test.cc
@@ -1424,6 +1424,141 @@
   EXPECT_EQ(expect, str(got));
 }
 
+TEST_F(CanonicalizeEntryPointIOTest, EmitVertexPointSize_ReturnNonStruct) {
+  auto* src = R"(
+[[stage(vertex)]]
+fn vert_main() -> [[builtin(position)]] vec4<f32> {
+  return vec4<f32>();
+}
+)";
+
+  auto* expect = R"(
+struct tint_symbol {
+  [[builtin(position)]]
+  value : vec4<f32>;
+  [[builtin(pointsize)]]
+  vertex_point_size : f32;
+};
+
+fn vert_main_inner() -> vec4<f32> {
+  return vec4<f32>();
+}
+
+[[stage(vertex)]]
+fn vert_main() -> tint_symbol {
+  let inner_result = vert_main_inner();
+  var wrapper_result : tint_symbol;
+  wrapper_result.value = inner_result;
+  wrapper_result.vertex_point_size = 1.0;
+  return wrapper_result;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::BuiltinStyle::kParameter, 0xFFFFFFFF, true);
+  auto got = Run<CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, EmitVertexPointSize_ReturnStruct) {
+  auto* src = R"(
+struct VertOut {
+  [[builtin(position)]] pos : vec4<f32>;
+};
+
+[[stage(vertex)]]
+fn vert_main() -> VertOut {
+  return VertOut();
+}
+)";
+
+  auto* expect = R"(
+struct VertOut {
+  pos : vec4<f32>;
+};
+
+struct tint_symbol {
+  [[builtin(position)]]
+  pos : vec4<f32>;
+  [[builtin(pointsize)]]
+  vertex_point_size : f32;
+};
+
+fn vert_main_inner() -> VertOut {
+  return VertOut();
+}
+
+[[stage(vertex)]]
+fn vert_main() -> tint_symbol {
+  let inner_result = vert_main_inner();
+  var wrapper_result : tint_symbol;
+  wrapper_result.pos = inner_result.pos;
+  wrapper_result.vertex_point_size = 1.0;
+  return wrapper_result;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::BuiltinStyle::kParameter, 0xFFFFFFFF, true);
+  auto got = Run<CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CanonicalizeEntryPointIOTest, EmitVertexPointSize_AvoidNameClash) {
+  auto* src = R"(
+struct VertOut {
+  [[location(0)]] vertex_point_size : vec4<f32>;
+  [[builtin(position)]] vertex_point_size_1 : vec4<f32>;
+};
+
+[[stage(vertex)]]
+fn vert_main() -> VertOut {
+  return VertOut();
+}
+)";
+
+  auto* expect = R"(
+struct VertOut {
+  vertex_point_size : vec4<f32>;
+  vertex_point_size_1 : vec4<f32>;
+};
+
+struct tint_symbol {
+  [[location(0)]]
+  vertex_point_size : vec4<f32>;
+  [[builtin(position)]]
+  vertex_point_size_1 : vec4<f32>;
+  [[builtin(pointsize)]]
+  vertex_point_size_2 : f32;
+};
+
+fn vert_main_inner() -> VertOut {
+  return VertOut();
+}
+
+[[stage(vertex)]]
+fn vert_main() -> tint_symbol {
+  let inner_result = vert_main_inner();
+  var wrapper_result : tint_symbol;
+  wrapper_result.vertex_point_size = inner_result.vertex_point_size;
+  wrapper_result.vertex_point_size_1 = inner_result.vertex_point_size_1;
+  wrapper_result.vertex_point_size_2 = 1.0;
+  return wrapper_result;
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::BuiltinStyle::kParameter, 0xFFFFFFFF, true);
+  auto got = Run<CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
 }  // namespace
 }  // namespace transform
 }  // namespace tint
diff --git a/src/transform/msl.cc b/src/transform/msl.cc
index 54f20b2..b9ec2af 100644
--- a/src/transform/msl.cc
+++ b/src/transform/msl.cc
@@ -55,14 +55,17 @@
   // Build the configs for the internal transforms.
   uint32_t buffer_size_ubo_index = kDefaultBufferSizeUniformIndex;
   uint32_t fixed_sample_mask = 0xFFFFFFFF;
+  bool emit_point_size = false;
   if (cfg) {
     buffer_size_ubo_index = cfg->buffer_size_ubo_index;
     fixed_sample_mask = cfg->fixed_sample_mask;
+    emit_point_size = cfg->emit_vertex_point_size;
   }
   auto array_length_from_uniform_cfg = ArrayLengthFromUniform::Config(
       sem::BindingPoint{0, buffer_size_ubo_index});
   auto entry_point_io_cfg = CanonicalizeEntryPointIO::Config(
-      CanonicalizeEntryPointIO::BuiltinStyle::kParameter, fixed_sample_mask);
+      CanonicalizeEntryPointIO::BuiltinStyle::kParameter, fixed_sample_mask,
+      emit_point_size);
 
   // Use the SSBO binding numbers as the indices for the buffer size lookups.
   for (auto* var : in->AST().GlobalVariables()) {
@@ -310,9 +313,11 @@
 
 Msl::Config::Config(uint32_t buffer_size_ubo_idx,
                     uint32_t sample_mask,
+                    bool emit_point_size,
                     bool disable_wi)
     : buffer_size_ubo_index(buffer_size_ubo_idx),
       fixed_sample_mask(sample_mask),
+      emit_vertex_point_size(emit_point_size),
       disable_workgroup_init(disable_wi) {}
 Msl::Config::Config(const Config&) = default;
 Msl::Config::~Config() = default;
diff --git a/src/transform/msl.h b/src/transform/msl.h
index bb9a013..29e068d 100644
--- a/src/transform/msl.h
+++ b/src/transform/msl.h
@@ -33,10 +33,12 @@
     /// Constructor
     /// @param buffer_size_ubo_idx the index to use for the buffer size UBO
     /// @param sample_mask the fixed sample mask to use for fragment shaders
+    /// @param emit_point_size `true` to emit a vertex point size builtin
     /// @param disable_workgroup_init `true` to disable workgroup memory zero
     ///        initialization
     Config(uint32_t buffer_size_ubo_idx,
            uint32_t sample_mask = 0xFFFFFFFF,
+           bool emit_point_size = false,
            bool disable_workgroup_init = false);
 
     /// Copy constructor
@@ -51,6 +53,10 @@
     /// The fixed sample mask to combine with fragment shader outputs.
     uint32_t fixed_sample_mask = 0xFFFFFFFF;
 
+    /// Set to `true` to generate a [[point_size]] attribute which is set to 1.0
+    /// for all vertex shaders in the module.
+    bool emit_vertex_point_size = false;
+
     /// Set to `true` to disable workgroup memory zero initialization
     bool disable_workgroup_init = false;
   };
diff --git a/src/writer/msl/generator.cc b/src/writer/msl/generator.cc
index 7155e94..61c314a 100644
--- a/src/writer/msl/generator.cc
+++ b/src/writer/msl/generator.cc
@@ -31,9 +31,9 @@
   // Run the MSL sanitizer.
   transform::Msl sanitizer;
   transform::DataMap transform_input;
-  transform_input.Add<transform::Msl::Config>(options.buffer_size_ubo_index,
-                                              options.fixed_sample_mask,
-                                              options.disable_workgroup_init);
+  transform_input.Add<transform::Msl::Config>(
+      options.buffer_size_ubo_index, options.fixed_sample_mask,
+      options.emit_vertex_point_size, options.disable_workgroup_init);
   auto output = sanitizer.Run(program, transform_input);
   if (!output.program.IsValid()) {
     result.success = false;
diff --git a/src/writer/msl/generator.h b/src/writer/msl/generator.h
index 4de190b..aa8d88e 100644
--- a/src/writer/msl/generator.h
+++ b/src/writer/msl/generator.h
@@ -40,6 +40,10 @@
   /// Defaults to 0xFFFFFFFF.
   uint32_t fixed_sample_mask = 0xFFFFFFFF;
 
+  /// Set to `true` to generate a [[point_size]] attribute which is set to 1.0
+  /// for all vertex shaders in the module.
+  bool emit_vertex_point_size = false;
+
   /// Set to `true` to disable workgroup memory zero initialization
   bool disable_workgroup_init = false;
 };
diff --git a/src/writer/msl/generator_impl.cc b/src/writer/msl/generator_impl.cc
index b4f1173..57ff8b3 100644
--- a/src/writer/msl/generator_impl.cc
+++ b/src/writer/msl/generator_impl.cc
@@ -1531,6 +1531,8 @@
       return "sample_id";
     case ast::Builtin::kSampleMask:
       return "sample_mask";
+    case ast::Builtin::kPointSize:
+      return "point_size";
     default:
       break;
   }
diff --git a/src/writer/msl/generator_impl_test.cc b/src/writer/msl/generator_impl_test.cc
index f7c23e5..5ac5b73 100644
--- a/src/writer/msl/generator_impl_test.cc
+++ b/src/writer/msl/generator_impl_test.cc
@@ -86,7 +86,8 @@
                     MslBuiltinData{ast::Builtin::kWorkgroupId,
                                    "threadgroup_position_in_grid"},
                     MslBuiltinData{ast::Builtin::kSampleIndex, "sample_id"},
-                    MslBuiltinData{ast::Builtin::kSampleMask, "sample_mask"}));
+                    MslBuiltinData{ast::Builtin::kSampleMask, "sample_mask"},
+                    MslBuiltinData{ast::Builtin::kPointSize, "point_size"}));
 
 TEST_F(MslGeneratorImplTest, HasInvariantAttribute_True) {
   auto* out = Structure(