blob: 1d6fb0a6db92c717b6084162957eac066d08992e [file] [log] [blame]
// Copyright 2023 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "src/tint/lang/msl/writer/ast_raise/pixel_local.h"
#include <utility>
#include "src/tint/lang/core/fluent_types.h"
#include "src/tint/lang/wgsl/program/clone_context.h"
#include "src/tint/lang/wgsl/resolver/resolve.h"
#include "src/tint/lang/wgsl/sem/function.h"
#include "src/tint/lang/wgsl/sem/module.h"
#include "src/tint/lang/wgsl/sem/statement.h"
#include "src/tint/lang/wgsl/sem/struct.h"
#include "src/tint/utils/containers/transform.h"
TINT_INSTANTIATE_TYPEINFO(tint::msl::writer::PixelLocal);
TINT_INSTANTIATE_TYPEINFO(tint::msl::writer::PixelLocal::Config);
using namespace tint::core::number_suffixes; // NOLINT
using namespace tint::core::fluent_types; // NOLINT
namespace tint::msl::writer {
/// PIMPL state for the transform
struct PixelLocal::State {
/// The source program
const Program& src;
/// The target program builder
ProgramBuilder b;
/// The clone context
program::CloneContext ctx = {&b, &src, /* auto_clone_symbols */ true};
/// The transform config
const Config& cfg;
/// Constructor
/// @param program the source program
/// @param config the transform config
State(const Program& program, const Config& config) : src(program), cfg(config) {}
/// Runs the transform
/// @returns the new program or SkipTransform if the transform is not required
ApplyResult Run() {
auto& sem = src.Sem();
// If the pixel local extension isn't enabled, then there must be no use of pixel_local
// variables, and so there's nothing for this transform to do.
if (!sem.Module()->Extensions().Contains(
wgsl::Extension::kChromiumExperimentalPixelLocal)) {
return SkipTransform;
}
bool made_changes = false;
// Change all module scope `var<pixel_local>` variables to `var<private>`.
// We need to do this even if the variable is not referenced by the entry point as later
// stages do not understand the pixel_local address space.
for (auto* global : src.AST().GlobalVariables()) {
if (auto* var = global->As<ast::Var>()) {
if (sem.Get(var)->AddressSpace() == core::AddressSpace::kPixelLocal) {
// Change the 'var<pixel_local>' to 'var<private>'
ctx.Replace(var->declared_address_space, b.Expr(core::AddressSpace::kPrivate));
made_changes = true;
}
}
}
// Find the single entry point
const sem::Function* entry_point = nullptr;
for (auto* fn : src.AST().Functions()) {
if (fn->IsEntryPoint()) {
if (entry_point != nullptr) {
TINT_ICE() << "PixelLocal transform requires that the SingleEntryPoint "
"transform has already been run";
return SkipTransform;
}
entry_point = sem.Get(fn);
// Look for a `var<pixel_local>` used by the entry point...
for (auto* global : entry_point->TransitivelyReferencedGlobals()) {
if (global->AddressSpace() != core::AddressSpace::kPixelLocal) {
continue;
}
// Obtain struct of the pixel local.
auto* pixel_local_str = global->Type()->UnwrapRef()->As<sem::Struct>();
// Add an Color attribute to each member of the pixel_local structure.
for (auto* member : pixel_local_str->Members()) {
ctx.InsertBack(member->Declaration()->attributes,
b.Color(u32(AttachmentIndex(member->Index()))));
ctx.InsertBack(member->Declaration()->attributes,
b.Disable(ast::DisabledValidation::kEntryPointParameter));
}
TransformEntryPoint(entry_point, global, pixel_local_str);
made_changes = true;
break; // Only a single `var<pixel_local>` can be used by an entry point.
}
}
}
if (!made_changes) {
return SkipTransform;
}
// At this point, the `var<pixel_local>` will have been replaced with `var<private>`, and
// the entry point will use `@color`, which requires the framebuffer fetch extension.
// Replace the `chromium_experimental_pixel_local` enable with
// `chromium_experimental_framebuffer_fetch`.
for (auto* enable : src.AST().Enables()) {
for (auto* ext : enable->extensions) {
if (ext->name == wgsl::Extension::kChromiumExperimentalPixelLocal) {
ctx.Replace(ext, b.create<ast::Extension>(
wgsl::Extension::kChromiumExperimentalFramebufferFetch));
}
}
}
ctx.Clone();
return resolver::Resolve(b);
}
/// Transforms the entry point @p entry_point to handle the direct or transitive usage of the
/// `var<pixel_local>` @p pixel_local_var.
/// @param entry_point the entry point
/// @param pixel_local_var the `var<pixel_local>`
/// @param pixel_local_str the struct type of the var
void TransformEntryPoint(const sem::Function* entry_point,
const sem::GlobalVariable* pixel_local_var,
const sem::Struct* pixel_local_str) {
auto* fn = entry_point->Declaration();
auto fn_name = fn->name->symbol.Name();
auto pixel_local_str_name = ctx.Clone(pixel_local_str->Name());
auto pixel_local_var_name = ctx.Clone(pixel_local_var->Declaration()->name->symbol);
// Remove the @fragment attribute from the entry point
ctx.Remove(fn->attributes, ast::GetAttribute<ast::StageAttribute>(fn->attributes));
// Rename the entry point
auto inner_name = b.Symbols().New(fn_name + "_inner");
ctx.Replace(fn->name, b.Ident(inner_name));
// Create a new function that wraps the entry point.
// This function has all the existing entry point parameters and an additional
// parameter for the input pixel local structure.
auto params = ctx.Clone(fn->params);
auto pl_param = b.Symbols().New("pixel_local");
params.Push(b.Param(pl_param, b.ty(pixel_local_str_name)));
// Remove any entry-point attributes from the inner function.
// This must come after `ctx.Clone(fn->params)` as we want these attributes on the outer
// function.
for (auto* param : fn->params) {
for (auto* attr : param->attributes) {
if (attr->IsAnyOf<ast::BuiltinAttribute, ast::LocationAttribute,
ast::InterpolateAttribute, ast::InvariantAttribute>()) {
ctx.Remove(param->attributes, attr);
}
}
}
// Build the outer function's statements, starting with an assignment of the pixel local
// parameter to the module scope var.
Vector<const ast::Statement*, 3> body{
b.Assign(pixel_local_var_name, pl_param),
};
// Build the arguments to call the inner function
auto call_args =
tint::Transform(fn->params, [&](auto* p) { return b.Expr(ctx.Clone(p->name)); });
// Create a structure to hold the combined flattened result of the entry point and the pixel
// local structure.
auto str_name = b.Symbols().New(fn_name + "_res");
Vector<const ast::StructMember*, 8> members;
Vector<const ast::Expression*, 8> return_args; // arguments to the final `return` statement
auto add_member = [&](const core::type::Type* ty, VectorRef<const ast::Attribute*> attrs) {
members.Push(b.Member("output_" + std::to_string(members.Length()),
CreateASTTypeFor(ctx, ty), std::move(attrs)));
};
for (auto* member : pixel_local_str->Members()) {
add_member(member->Type(), Vector{
b.Location(AInt(AttachmentIndex(member->Index()))),
});
return_args.Push(b.MemberAccessor(pixel_local_var_name, ctx.Clone(member->Name())));
}
if (fn->return_type) {
Symbol call_result = b.Symbols().New("result");
if (auto* str = entry_point->ReturnType()->As<sem::Struct>()) {
// The entry point returned a structure.
for (auto* member : str->Members()) {
auto& member_attrs = member->Declaration()->attributes;
add_member(member->Type(), ctx.Clone(member_attrs));
return_args.Push(b.MemberAccessor(call_result, ctx.Clone(member->Name())));
if (auto* location = ast::GetAttribute<ast::LocationAttribute>(member_attrs)) {
// Remove the @location attribute from the member of the inner function's
// output structure.
// Note: This will break other entry points that share the same output
// structure, however this transform assumes that the SingleEntryPoint
// transform will have already been run.
ctx.Remove(member_attrs, location);
}
}
} else {
// The entry point returned a non-structure
add_member(entry_point->ReturnType(), ctx.Clone(fn->return_type_attributes));
return_args.Push(b.Expr(call_result));
// Remove the @location from the inner function's return type attributes
ctx.Remove(fn->return_type_attributes,
ast::GetAttribute<ast::LocationAttribute>(fn->return_type_attributes));
}
body.Push(b.Decl(b.Let(call_result, b.Call(inner_name, std::move(call_args)))));
} else {
body.Push(b.CallStmt(b.Call(inner_name, std::move(call_args))));
}
// Declare the output structure
b.Structure(str_name, std::move(members));
// Return the output structure
body.Push(b.Return(b.Call(str_name, std::move(return_args))));
// Declare the new entry point that calls the inner function
b.Func(fn_name, std::move(params), b.ty(str_name), body,
Vector{b.Stage(ast::PipelineStage::kFragment)});
}
/// @returns the attachment index for the pixel local field with the given index
/// @param field_index the pixel local field index
uint32_t AttachmentIndex(uint32_t field_index) {
auto idx = cfg.attachments.Get(field_index);
if (TINT_UNLIKELY(!idx)) {
b.Diagnostics().add_error(diag::System::Transform,
"PixelLocal::Config::attachments missing entry for field " +
std::to_string(field_index));
return 0;
}
return *idx;
}
};
PixelLocal::PixelLocal() = default;
PixelLocal::~PixelLocal() = default;
ast::transform::Transform::ApplyResult PixelLocal::Apply(const Program& src,
const ast::transform::DataMap& inputs,
ast::transform::DataMap&) const {
auto* cfg = inputs.Get<Config>();
if (!cfg) {
ProgramBuilder b;
b.Diagnostics().add_error(diag::System::Transform,
"missing transform data for " + std::string(TypeInfo().name));
return resolver::Resolve(b);
}
return State(src, *cfg).Run();
}
PixelLocal::Config::Config() = default;
PixelLocal::Config::Config(const Config&) = default;
PixelLocal::Config::~Config() = default;
} // namespace tint::msl::writer