blob: 20c295e88e5e0b5417401b257ee8b3119c337939 [file]
// 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/core/ir/transform/shader_io.h"
#include <memory>
#include <utility>
#include "src/tint/lang/core/ir/builder.h"
#include "src/tint/lang/core/ir/module.h"
#include "src/tint/lang/core/type/struct.h"
#include "src/tint/utils/containers/vector.h"
using namespace tint::core::fluent_types; // NOLINT
using namespace tint::core::number_suffixes; // NOLINT
namespace tint::core::ir::transform {
namespace {
/// PIMPL state for the transform.
struct State {
/// The function that creates a backend state object.
std::function<MakeBackendStateFunc> make_backend_state;
/// The IR module.
Module& ir;
/// The IR builder.
Builder b{ir};
/// The type manager.
core::type::Manager& ty{ir.Types()};
/// The entry point currently being processed.
Function* ep = nullptr;
/// The backend state object for the current entry point.
std::unique_ptr<ShaderIOBackendState> backend{};
/// Process the module.
void Process() {
// Collect all structures before the transform has run, so that we can strip their shader IO
// attributes later.
Vector<const type::Struct*, 16> structures_to_strip;
for (auto* type : ir.Types()) {
if (auto* str = type->As<type::Struct>()) {
structures_to_strip.Push(str);
}
}
// Process the entry points.
// Take a copy of the function list since the transform adds new functions to the module.
auto functions = ir.functions;
for (auto& func : functions) {
// Only process entry points.
if (func->Stage() == Function::PipelineStage::kUndefined) {
continue;
}
// Skip entry points with no inputs or outputs.
if (func->Params().IsEmpty() && func->ReturnType()->Is<core::type::Void>()) {
continue;
}
ProcessEntryPoint(func, make_backend_state(ir, func));
}
// Remove IO attributes from all structure members that had them prior to this transform.
for (auto* str : structures_to_strip) {
for (auto* member : str->Members()) {
// TODO(crbug.com/tint/745): Remove the const_cast.
const_cast<core::type::StructMember*>(member)->SetAttributes({});
}
}
}
/// Process an entry point.
/// @param f the original entry point function
/// @param bs the backend state object
void ProcessEntryPoint(Function* f, std::unique_ptr<ShaderIOBackendState> bs) {
TINT_SCOPED_ASSIGNMENT(ep, f);
backend = std::move(bs);
TINT_DEFER(backend = nullptr);
// Process the parameters and return value to prepare for building a wrapper function.
GatherInputs(); // Calls backend->AddInput() for each input
GatherOutput(); // Calls backend->AddOutput() for each output
// Add an output for the vertex point size if needed.
std::optional<uint32_t> vertex_point_size_index;
if (ep->Stage() == Function::PipelineStage::kVertex && backend->NeedsVertexPointSize()) {
vertex_point_size_index =
backend->AddOutput(ir.symbols.New("vertex_point_size"), ty.f32(),
core::IOAttributes{
/* location */ std::nullopt,
/* index */ std::nullopt,
/* color */ std::nullopt,
/* builtin */ core::BuiltinValue::kPointSize,
/* interpolation */ std::nullopt,
/* invariant */ false,
});
}
auto new_params = backend->FinalizeInputs();
auto* new_ret_ty = backend->FinalizeOutputs();
// Rename the old function and remove its pipeline stage and workgroup size, as we will be
// wrapping it with a new entry point.
auto name = ir.NameOf(ep).Name();
auto stage = ep->Stage();
auto wgsize = ep->WorkgroupSize();
ir.SetName(ep, name + "_inner");
ep->SetStage(Function::PipelineStage::kUndefined);
ep->ClearWorkgroupSize();
// Create the entry point wrapper function.
auto* wrapper_ep = b.Function(name, new_ret_ty);
wrapper_ep->SetParams(std::move(new_params));
wrapper_ep->SetStage(stage);
if (wgsize) {
wrapper_ep->SetWorkgroupSize((*wgsize)[0], (*wgsize)[1], (*wgsize)[2]);
}
auto wrapper = b.Append(wrapper_ep->Block());
// Call the original function, passing it the inputs and capturing its return value.
auto inner_call_args = BuildInnerCallArgs(wrapper);
auto* inner_result = wrapper.Call(ep->ReturnType(), ep, std::move(inner_call_args));
SetOutputs(wrapper, inner_result->Result(0));
if (vertex_point_size_index) {
backend->SetOutput(wrapper, vertex_point_size_index.value(), b.Constant(1_f));
}
// Return the new result.
wrapper.Return(wrapper_ep, backend->MakeReturnValue(wrapper));
}
/// Gather the shader inputs.
void GatherInputs() {
for (auto* param : ep->Params()) {
if (auto* str = param->Type()->As<core::type::Struct>()) {
for (auto* member : str->Members()) {
auto name = str->Name().Name() + "_" + member->Name().Name();
auto attributes = member->Attributes();
if (attributes.interpolation &&
ep->Stage() != Function::PipelineStage::kFragment) {
// Strip interpolation on non-fragment inputs
attributes.interpolation = {};
}
backend->AddInput(ir.symbols.Register(name), member->Type(),
std::move(attributes));
}
} else {
// Pull out the IO attributes and remove them from the parameter.
auto attributes = param->Attributes();
if (attributes.interpolation && ep->Stage() != Function::PipelineStage::kFragment) {
// Strip interpolation on non-fragment inputs
attributes.interpolation = {};
}
param->SetAttributes({});
auto name = ir.NameOf(param);
backend->AddInput(name, param->Type(), std::move(attributes));
}
}
}
/// Gather the shader outputs.
void GatherOutput() {
if (ep->ReturnType()->Is<core::type::Void>()) {
return;
}
if (auto* str = ep->ReturnType()->As<core::type::Struct>()) {
for (auto* member : str->Members()) {
auto name = str->Name().Name() + "_" + member->Name().Name();
auto attributes = member->Attributes();
if (attributes.interpolation && ep->Stage() != Function::PipelineStage::kVertex) {
// Strip interpolation on non-vertex outputs
attributes.interpolation = {};
}
backend->AddOutput(ir.symbols.Register(name), member->Type(), attributes);
}
} else {
// Pull out the IO attributes and remove them from the original function.
auto attributes = ep->ReturnAttributes();
if (attributes.interpolation && ep->Stage() != Function::PipelineStage::kVertex) {
// Strip interpolation on non-vertex outputs
attributes.interpolation = {};
}
ep->SetReturnAttributes({});
backend->AddOutput(ir.symbols.New(), ep->ReturnType(), std::move(attributes));
}
}
/// Build the argument list to call the original entry point function.
/// @param builder the IR builder for new instructions
/// @returns the argument list
Vector<Value*, 4> BuildInnerCallArgs(Builder& builder) {
uint32_t input_idx = 0;
Vector<Value*, 4> args;
for (auto* param : ep->Params()) {
if (auto* str = param->Type()->As<core::type::Struct>()) {
Vector<Value*, 4> construct_args;
for (uint32_t i = 0; i < str->Members().Length(); i++) {
construct_args.Push(backend->GetInput(builder, input_idx++));
}
args.Push(builder.Construct(param->Type(), construct_args)->Result(0));
} else {
args.Push(backend->GetInput(builder, input_idx++));
}
}
return args;
}
/// Propagate outputs from the inner function call to their final destination.
/// @param builder the IR builder for new instructions
/// @param inner_result the return value from calling the original entry point function
void SetOutputs(Builder& builder, Value* inner_result) {
if (auto* str = inner_result->Type()->As<core::type::Struct>()) {
for (auto* member : str->Members()) {
Value* from =
builder.Access(member->Type(), inner_result, u32(member->Index()))->Result(0);
backend->SetOutput(builder, member->Index(), from);
}
} else if (!inner_result->Type()->Is<core::type::Void>()) {
backend->SetOutput(builder, 0u, inner_result);
}
}
};
} // namespace
void RunShaderIOBase(Module& module, std::function<MakeBackendStateFunc> make_backend_state) {
State{make_backend_state, module}.Process();
}
ShaderIOBackendState::~ShaderIOBackendState() = default;
} // namespace tint::core::ir::transform