// Copyright 2020 The Tint Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "src/transform/first_index_offset.h" #include #include #include "src/ast/struct_block_decoration.h" #include "src/program_builder.h" #include "src/semantic/function.h" TINT_INSTANTIATE_TYPEINFO(tint::transform::FirstIndexOffset::Data); namespace tint { namespace transform { namespace { constexpr char kStructName[] = "TintFirstIndexOffsetData"; constexpr char kBufferName[] = "tint_first_index_data"; constexpr char kFirstVertexName[] = "tint_first_vertex_index"; constexpr char kFirstInstanceName[] = "tint_first_instance_index"; constexpr char kIndexOffsetPrefix[] = "tint_first_index_offset_"; ast::Variable* clone_variable_with_new_name(CloneContext* ctx, ast::Variable* in, std::string new_name) { // Clone arguments outside of create() call to have deterministic ordering auto source = ctx->Clone(in->source()); auto symbol = ctx->dst->Symbols().Register(new_name); auto* type = ctx->Clone(in->type()); auto* constructor = ctx->Clone(in->constructor()); auto decorations = ctx->Clone(in->decorations()); return ctx->dst->create( source, symbol, in->declared_storage_class(), type, in->is_const(), constructor, decorations); } } // namespace FirstIndexOffset::Data::Data(bool has_vtx_index, bool has_inst_index, uint32_t first_vtx_offset, uint32_t first_inst_offset) : has_vertex_index(has_vtx_index), has_instance_index(has_inst_index), first_vertex_offset(first_vtx_offset), first_instance_offset(first_inst_offset) {} FirstIndexOffset::Data::Data(const Data&) = default; FirstIndexOffset::Data::~Data() = default; FirstIndexOffset::FirstIndexOffset(uint32_t binding, uint32_t group) : binding_(binding), group_(group) {} FirstIndexOffset::~FirstIndexOffset() = default; Transform::Output FirstIndexOffset::Run(const Program* in) { ProgramBuilder out; // First do a quick check to see if the transform has already been applied. for (ast::Variable* var : in->AST().GlobalVariables()) { if (auto* dec_var = var->As()) { if (dec_var->symbol() == in->Symbols().Get(kBufferName)) { out.Diagnostics().add_error( "First index offset transform has already been applied."); return Output(Program(std::move(out))); } } } State state{&out, binding_, group_}; Symbol vertex_index_sym; Symbol instance_index_sym; // Lazily construct the UniformBuffer on first call to // maybe_create_buffer_var() ast::Variable* buffer_var = nullptr; auto maybe_create_buffer_var = [&]() { if (buffer_var == nullptr) { buffer_var = state.AddUniformBuffer(); } }; // Clone the AST, renaming the kVertexIndex and kInstanceIndex builtins, and // add a CreateFirstIndexOffset() statement to each function that uses one of // these builtins. CloneContext ctx(&out, in); ctx.ReplaceAll([&](ast::Variable* var) -> ast::Variable* { for (ast::VariableDecoration* dec : var->decorations()) { if (auto* blt_dec = dec->As()) { ast::Builtin blt_type = blt_dec->value(); if (blt_type == ast::Builtin::kVertexIndex) { vertex_index_sym = var->symbol(); state.has_vertex_index = true; return clone_variable_with_new_name( &ctx, var, kIndexOffsetPrefix + in->Symbols().NameFor(var->symbol())); } else if (blt_type == ast::Builtin::kInstanceIndex) { instance_index_sym = var->symbol(); state.has_instance_index = true; return clone_variable_with_new_name( &ctx, var, kIndexOffsetPrefix + in->Symbols().NameFor(var->symbol())); } } } return nullptr; // Just clone var }); ctx.ReplaceAll( // Note: This happens in the same pass as the rename above // which determines the original builtin variable names, // but this should be fine, as variables are cloned first. [&](ast::Function* func) -> ast::Function* { maybe_create_buffer_var(); if (buffer_var == nullptr) { return nullptr; // no transform need, just clone func } auto* func_sem = in->Sem().Get(func); ast::StatementList statements; for (const auto& data : func_sem->LocalReferencedBuiltinVariables()) { if (data.second->value() == ast::Builtin::kVertexIndex) { statements.emplace_back(state.CreateFirstIndexOffset( in->Symbols().NameFor(vertex_index_sym), kFirstVertexName, buffer_var)); } else if (data.second->value() == ast::Builtin::kInstanceIndex) { statements.emplace_back(state.CreateFirstIndexOffset( in->Symbols().NameFor(instance_index_sym), kFirstInstanceName, buffer_var)); } } return CloneWithStatementsAtStart(&ctx, func, statements); }); ctx.Clone(); return Output(Program(std::move(out)), std::make_unique( state.has_vertex_index, state.has_instance_index, state.vertex_index_offset, state.instance_index_offset)); } ast::Variable* FirstIndexOffset::State::AddUniformBuffer() { auto* u32_type = dst->create(); ast::StructMemberList members; uint32_t offset = 0; if (has_vertex_index) { ast::StructMemberDecorationList member_dec; member_dec.push_back( dst->create(Source{}, offset)); members.push_back(dst->create( Source{}, dst->Symbols().Register(kFirstVertexName), u32_type, std::move(member_dec))); vertex_index_offset = offset; offset += 4; } if (has_instance_index) { ast::StructMemberDecorationList member_dec; member_dec.push_back( dst->create(Source{}, offset)); members.push_back(dst->create( Source{}, dst->Symbols().Register(kFirstInstanceName), u32_type, std::move(member_dec))); instance_index_offset = offset; offset += 4; } ast::StructDecorationList decos; decos.push_back(dst->create(Source{})); auto* struct_type = dst->create( dst->Symbols().Register(kStructName), dst->create(Source{}, std::move(members), std::move(decos))); auto* idx_var = dst->create( Source{}, // source dst->Symbols().Register(kBufferName), // symbol ast::StorageClass::kUniform, // storage_class struct_type, // type false, // is_const nullptr, // constructor ast::VariableDecorationList{ dst->create(Source{}, binding), dst->create(Source{}, group), }); dst->AST().AddGlobalVariable(idx_var); dst->AST().AddConstructedType(struct_type); return idx_var; } ast::VariableDeclStatement* FirstIndexOffset::State::CreateFirstIndexOffset( const std::string& original_name, const std::string& field_name, ast::Variable* buffer_var) { auto* buffer = dst->create(Source{}, buffer_var->symbol()); auto lhs_name = kIndexOffsetPrefix + original_name; auto* constructor = dst->create( Source{}, ast::BinaryOp::kAdd, dst->create(Source{}, dst->Symbols().Register(lhs_name)), dst->create( Source{}, buffer, dst->create( Source{}, dst->Symbols().Register(field_name)))); auto* var = dst->create( Source{}, // source dst->Symbols().Register(original_name), // symbol ast::StorageClass::kNone, // storage_class dst->create(), // type true, // is_const constructor, // constructor ast::VariableDecorationList{}); // decorations return dst->create(Source{}, var); } } // namespace transform } // namespace tint