diff --git a/src/transform/canonicalize_entry_point_io.cc b/src/transform/canonicalize_entry_point_io.cc index 4eee792af5..1ddd315663 100644 --- a/src/transform/canonicalize_entry_point_io.cc +++ b/src/transform/canonicalize_entry_point_io.cc @@ -289,6 +289,13 @@ struct CanonicalizeEntryPointIO::State { 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 @@ struct CanonicalizeEntryPointIO::State { /// 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() && !needs_fixed_sample_mask) { + func_sem->ReturnType()->Is() && !needs_fixed_sample_mask && + !needs_vertex_point_size) { return; } @@ -430,6 +443,11 @@ struct CanonicalizeEntryPointIO::State { 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 @@ void CanonicalizeEntryPointIO::Run(CloneContext& ctx, } 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 60a1584f9a..e3296ed28d 100644 --- a/src/transform/canonicalize_entry_point_io.h +++ b/src/transform/canonicalize_entry_point_io.h @@ -96,7 +96,10 @@ class CanonicalizeEntryPointIO /// 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 @@ class CanonicalizeEntryPointIO /// 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 504d60d88f..6936d83c29 100644 --- a/src/transform/canonicalize_entry_point_io_test.cc +++ b/src/transform/canonicalize_entry_point_io_test.cc @@ -1424,6 +1424,141 @@ fn frag_main() -> tint_symbol { EXPECT_EQ(expect, str(got)); } +TEST_F(CanonicalizeEntryPointIOTest, EmitVertexPointSize_ReturnNonStruct) { + auto* src = R"( +[[stage(vertex)]] +fn vert_main() -> [[builtin(position)]] vec4 { + return vec4(); +} +)"; + + auto* expect = R"( +struct tint_symbol { + [[builtin(position)]] + value : vec4; + [[builtin(pointsize)]] + vertex_point_size : f32; +}; + +fn vert_main_inner() -> vec4 { + return vec4(); +} + +[[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::BuiltinStyle::kParameter, 0xFFFFFFFF, true); + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(CanonicalizeEntryPointIOTest, EmitVertexPointSize_ReturnStruct) { + auto* src = R"( +struct VertOut { + [[builtin(position)]] pos : vec4; +}; + +[[stage(vertex)]] +fn vert_main() -> VertOut { + return VertOut(); +} +)"; + + auto* expect = R"( +struct VertOut { + pos : vec4; +}; + +struct tint_symbol { + [[builtin(position)]] + pos : vec4; + [[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::BuiltinStyle::kParameter, 0xFFFFFFFF, true); + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(CanonicalizeEntryPointIOTest, EmitVertexPointSize_AvoidNameClash) { + auto* src = R"( +struct VertOut { + [[location(0)]] vertex_point_size : vec4; + [[builtin(position)]] vertex_point_size_1 : vec4; +}; + +[[stage(vertex)]] +fn vert_main() -> VertOut { + return VertOut(); +} +)"; + + auto* expect = R"( +struct VertOut { + vertex_point_size : vec4; + vertex_point_size_1 : vec4; +}; + +struct tint_symbol { + [[location(0)]] + vertex_point_size : vec4; + [[builtin(position)]] + vertex_point_size_1 : vec4; + [[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::BuiltinStyle::kParameter, 0xFFFFFFFF, true); + auto got = Run(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 54f20b2e0c..b9ec2afa2f 100644 --- a/src/transform/msl.cc +++ b/src/transform/msl.cc @@ -55,14 +55,17 @@ Output Msl::Run(const Program* in, const DataMap& inputs) { // 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 @@ void Msl::HandleModuleScopeVariables(CloneContext& ctx) const { 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 bb9a013a03..29e068d458 100644 --- a/src/transform/msl.h +++ b/src/transform/msl.h @@ -33,10 +33,12 @@ class Msl : public Castable { /// 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 @@ class Msl : public Castable { /// 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 7155e94f57..61c314adba 100644 --- a/src/writer/msl/generator.cc +++ b/src/writer/msl/generator.cc @@ -31,9 +31,9 @@ Result Generate(const Program* program, const Options& options) { // Run the MSL sanitizer. transform::Msl sanitizer; transform::DataMap transform_input; - transform_input.Add(options.buffer_size_ubo_index, - options.fixed_sample_mask, - options.disable_workgroup_init); + transform_input.Add( + 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 4de190b13a..aa8d88e1e4 100644 --- a/src/writer/msl/generator.h +++ b/src/writer/msl/generator.h @@ -40,6 +40,10 @@ struct Options { /// 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 b4f1173ef8..57ff8b3323 100644 --- a/src/writer/msl/generator_impl.cc +++ b/src/writer/msl/generator_impl.cc @@ -1531,6 +1531,8 @@ std::string GeneratorImpl::builtin_to_attribute(ast::Builtin builtin) const { 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 f7c23e59cb..5ac5b733d9 100644 --- a/src/writer/msl/generator_impl_test.cc +++ b/src/writer/msl/generator_impl_test.cc @@ -86,7 +86,8 @@ INSTANTIATE_TEST_SUITE_P( 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(