// Copyright 2021 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/array_length_from_uniform.h" #include #include #include "src/ast/struct_block_decoration.h" #include "src/program_builder.h" #include "src/sem/call.h" #include "src/sem/variable.h" #include "src/transform/inline_pointer_lets.h" #include "src/transform/simplify.h" TINT_INSTANTIATE_TYPEINFO(tint::transform::ArrayLengthFromUniform); TINT_INSTANTIATE_TYPEINFO(tint::transform::ArrayLengthFromUniform::Config); TINT_INSTANTIATE_TYPEINFO(tint::transform::ArrayLengthFromUniform::Result); namespace tint { namespace transform { ArrayLengthFromUniform::ArrayLengthFromUniform() = default; ArrayLengthFromUniform::~ArrayLengthFromUniform() = default; void ArrayLengthFromUniform::Run(CloneContext& ctx, const DataMap& inputs, DataMap& outputs) { if (!Requires(ctx)) { return; } auto* cfg = inputs.Get(); if (cfg == nullptr) { ctx.dst->Diagnostics().add_error( diag::System::Transform, "missing transform data for ArrayLengthFromUniform"); return; } auto& sem = ctx.src->Sem(); const char* kBufferSizeMemberName = "buffer_size"; // Determine the size of the buffer size array. uint32_t max_buffer_size_index = 0; for (auto& idx : cfg->bindpoint_to_size_index) { if (idx.second > max_buffer_size_index) { max_buffer_size_index = idx.second; } } // Get (or create, on first call) the uniform buffer that will receive the // size of each storage buffer in the module. ast::Variable* buffer_size_ubo = nullptr; auto get_ubo = [&]() { if (!buffer_size_ubo) { auto* buffer_size_struct = ctx.dst->Structure( ctx.dst->Sym(), {ctx.dst->Member( kBufferSizeMemberName, ctx.dst->ty.array(ctx.dst->ty.u32(), max_buffer_size_index + 1))}, ast::DecorationList{ctx.dst->create()}); buffer_size_ubo = ctx.dst->Global( ctx.dst->Sym(), ctx.dst->ty.Of(buffer_size_struct), ast::StorageClass::kUniform, ast::DecorationList{ ctx.dst->create(cfg->ubo_binding.group), ctx.dst->create( cfg->ubo_binding.binding)}); } return buffer_size_ubo; }; // Find all calls to the arrayLength() intrinsic. for (auto* node : ctx.src->ASTNodes().Objects()) { auto* call_expr = node->As(); if (!call_expr) { continue; } auto* call = sem.Get(call_expr); auto* intrinsic = call->Target()->As(); if (!intrinsic || intrinsic->Type() != sem::IntrinsicType::kArrayLength) { continue; } // Get the storage buffer that contains the runtime array. // We assume that the argument to `arrayLength` has the form // `&resource.array`, which requires that `InlinePointerLets` and `Simplify` // have been run before this transform. auto* param = call_expr->params()[0]->As(); if (!param || param->op() != ast::UnaryOp::kAddressOf) { TINT_ICE(Transform, ctx.dst->Diagnostics()) << "expected form of arrayLength argument to be &resource.array"; break; } auto* accessor = param->expr()->As(); if (!accessor) { TINT_ICE(Transform, ctx.dst->Diagnostics()) << "expected form of arrayLength argument to be &resource.array"; break; } auto* storage_buffer_expr = accessor->structure(); auto* storage_buffer_sem = sem.Get(storage_buffer_expr)->As(); if (!storage_buffer_sem) { TINT_ICE(Transform, ctx.dst->Diagnostics()) << "expected form of arrayLength argument to be &resource.array"; break; } // Get the index to use for the buffer size array. auto binding = storage_buffer_sem->Variable()->BindingPoint(); auto idx_itr = cfg->bindpoint_to_size_index.find(binding); if (idx_itr == cfg->bindpoint_to_size_index.end()) { ctx.dst->Diagnostics().add_error( diag::System::Transform, "missing size index mapping for binding point (" + std::to_string(binding.group) + "," + std::to_string(binding.binding) + ")"); continue; } // Load the total storage buffer size from the UBO. auto* total_storage_buffer_size = ctx.dst->IndexAccessor( ctx.dst->MemberAccessor(get_ubo()->symbol(), kBufferSizeMemberName), idx_itr->second); // Calculate actual array length // total_storage_buffer_size - array_offset // array_length = ---------------------------------------- // array_stride auto* storage_buffer_type = storage_buffer_sem->Type()->UnwrapRef()->As(); auto* array_member_sem = storage_buffer_type->Members().back(); uint32_t array_offset = array_member_sem->Offset(); uint32_t array_stride = array_member_sem->Size(); auto* array_length = ctx.dst->Div( ctx.dst->Sub(total_storage_buffer_size, array_offset), array_stride); ctx.Replace(call_expr, array_length); } ctx.Clone(); outputs.Add(buffer_size_ubo ? true : false); } ArrayLengthFromUniform::Config::Config(sem::BindingPoint ubo_bp) : ubo_binding(ubo_bp) {} ArrayLengthFromUniform::Config::Config(const Config&) = default; ArrayLengthFromUniform::Config::~Config() = default; ArrayLengthFromUniform::Result::Result(bool needs_sizes) : needs_buffer_sizes(needs_sizes) {} ArrayLengthFromUniform::Result::Result(const Result&) = default; ArrayLengthFromUniform::Result::~Result() = default; } // namespace transform } // namespace tint