#include "graphics/shader/recompiler/ir/passes/BindingLayout.h" #include "graphics/shader/recompiler/ir/ValueProgram.h" #include #include #include #include namespace Libs::Graphics::ShaderRecompiler::IR { namespace { constexpr uint32_t MaxPushConstantBytes = 128; constexpr std::array ImageBindingKinds = { DescriptorBindingKind::Sampled1D, DescriptorBindingKind::Sampled1DArray, DescriptorBindingKind::Sampled2D, DescriptorBindingKind::Sampled2DArray, DescriptorBindingKind::Sampled2DMsaa, DescriptorBindingKind::Sampled2DMsaaArray, DescriptorBindingKind::Sampled3D, DescriptorBindingKind::SampledUint1D, DescriptorBindingKind::SampledUint1DArray, DescriptorBindingKind::SampledUint2D, DescriptorBindingKind::SampledUint2DArray, DescriptorBindingKind::SampledUint2DMsaa, DescriptorBindingKind::SampledUint2DMsaaArray, DescriptorBindingKind::SampledUint3D, DescriptorBindingKind::Storage1D, DescriptorBindingKind::Storage1DArray, DescriptorBindingKind::Storage2D, DescriptorBindingKind::Storage2DArray, DescriptorBindingKind::Storage3D, DescriptorBindingKind::StorageUint1D, DescriptorBindingKind::StorageUint1DArray, DescriptorBindingKind::StorageUint2D, DescriptorBindingKind::StorageUint2DArray, DescriptorBindingKind::StorageUint3D, }; bool ImageBinding(const ImageResource& image, DescriptorBindingKind& result) { using Dimension = Decoder::ImageDimension; using Kind = DescriptorBindingKind; switch (image.kind) { case ResourceKind::Image: switch (image.dimension) { case Dimension::Dim1D: result = Kind::Sampled1D; return true; case Dimension::Dim1DArray: result = Kind::Sampled1DArray; return true; case Dimension::Dim2D: result = Kind::Sampled2D; return true; case Dimension::Dim2DArray: result = Kind::Sampled2DArray; return true; case Dimension::Dim2DMsaa: result = Kind::Sampled2DMsaa; return true; case Dimension::Dim2DMsaaArray: result = Kind::Sampled2DMsaaArray; return true; case Dimension::Dim3D: result = Kind::Sampled3D; return true; default: return false; } case ResourceKind::ImageUint: switch (image.dimension) { case Dimension::Dim1D: result = Kind::SampledUint1D; return true; case Dimension::Dim1DArray: result = Kind::SampledUint1DArray; return true; case Dimension::Dim2D: result = Kind::SampledUint2D; return true; case Dimension::Dim2DArray: result = Kind::SampledUint2DArray; return true; case Dimension::Dim2DMsaa: result = Kind::SampledUint2DMsaa; return true; case Dimension::Dim2DMsaaArray: result = Kind::SampledUint2DMsaaArray; return true; case Dimension::Dim3D: result = Kind::SampledUint3D; return true; default: return false; } case ResourceKind::StorageImage: switch (image.dimension) { case Dimension::Dim1D: result = Kind::Storage1D; return true; case Dimension::Dim1DArray: result = Kind::Storage1DArray; return true; case Dimension::Dim2D: result = Kind::Storage2D; return true; case Dimension::Dim2DArray: result = Kind::Storage2DArray; return true; case Dimension::Dim3D: result = Kind::Storage3D; return true; default: return false; } case ResourceKind::StorageImageUint: switch (image.dimension) { case Dimension::Dim1D: result = Kind::StorageUint1D; return true; case Dimension::Dim1DArray: result = Kind::StorageUint1DArray; return true; case Dimension::Dim2D: result = Kind::StorageUint2D; return true; case Dimension::Dim2DArray: result = Kind::StorageUint2DArray; return true; case Dimension::Dim3D: result = Kind::StorageUint3D; return true; default: return false; } default: return false; } } bool CollectUserData(const Program& program, std::vector& result) { std::set registers; for (const auto* block: program.values->blocks) { for (const auto& inst: *block) { if (inst.GetOpcode() != ValueOpcode::GetUserData || !inst.HasUses()) { continue; } if (inst.Arg(0).GetType() != Type::ScalarReg) { return false; } const auto index = RegIndex(inst.Arg(0).ScalarRegister()); if (index >= NumScalarRegs) { return false; } registers.insert(index); } } result.assign(registers.begin(), registers.end()); return true; } void AddBinding(BindingLayout& layout, DescriptorBindingKind kind, std::vector resources = {}) { layout.descriptors.push_back( {kind, static_cast(layout.descriptors.size()), std::move(resources)}); } bool UsesGds(const Program& program, bool& uses_gds) { uses_gds = false; for (const auto* block: program.values->blocks) { for (const auto& inst: *block) { if (SharedAccessOf(inst.GetOpcode()) == SharedAccess::None) { continue; } const auto index = inst.Flags().index; if (index >= program.values->memory_info.size()) { return false; } const auto kind = program.values->memory_info[index].kind; if (kind != ResourceKind::Lds && kind != ResourceKind::Gds) { return false; } uses_gds |= kind == ResourceKind::Gds; } } return true; } } // namespace bool AllocateBindings(Program& program, const BindingLayoutOptions& options, std::string* error) { if (!program.shader_info_complete || program.binding_layout_complete) { if (error != nullptr) { *error = !program.shader_info_complete ? "shader info is not ready" : "binding layout already allocated"; } return false; } if (options.push_constant_offset > MaxPushConstantBytes) { if (error != nullptr) { *error = "push-constant offset exceeds the Vulkan minimum guarantee"; } return false; } if (options.push_constant_offset % 4u != 0) { if (error != nullptr) { *error = "push-constant offset is not dword aligned"; } return false; } if (program.values == nullptr) { if (error != nullptr) { *error = "typed value program is not available"; } return false; } BindingLayout next; next.descriptor_set = options.descriptor_set; next.push_constant_offset = options.push_constant_offset; if (!CollectUserData(program, next.user_data_registers)) { if (error != nullptr) { *error = "typed shader contains an invalid user-data register"; } return false; } next.buffer_offset_dword = static_cast(next.user_data_registers.size()); next.buffer_offset_count = static_cast(program.info.buffers.size()); if (!program.info.buffers.empty()) { std::vector resources(program.info.buffers.size()); for (uint32_t i = 0; i < resources.size(); i++) { resources[i] = i; } AddBinding(next, DescriptorBindingKind::Buffers, std::move(resources)); } std::array, ImageBindingKinds.size()> image_groups; for (uint32_t i = 0; i < program.info.images.size(); i++) { DescriptorBindingKind kind; if (!ImageBinding(program.info.images[i], kind)) { if (error != nullptr) { *error = "shader info contains an invalid image binding class"; } return false; } const auto group = std::find(ImageBindingKinds.begin(), ImageBindingKinds.end(), kind); if (group == ImageBindingKinds.end()) { if (error != nullptr) { *error = "shader info contains an unmapped image binding class"; } return false; } auto& resources = image_groups[static_cast(group - ImageBindingKinds.begin())]; const auto dynamic = program.info.images[i].mip_mode == ImageMipMode::DynamicStorage; const auto count = dynamic ? program.info.images[i].mip_count : 1u; if (count == 0u || (!dynamic && program.info.images[i].mip_count != 1u)) { if (error != nullptr) { *error = "image has an invalid specialized mip descriptor count"; } return false; } resources.insert(resources.end(), count, i); } for (uint32_t i = 0; i < image_groups.size(); i++) { if (!image_groups[i].empty()) { AddBinding(next, ImageBindingKinds[i], std::move(image_groups[i])); } } if (!program.info.samplers.empty()) { std::vector resources(program.info.samplers.size()); for (uint32_t i = 0; i < resources.size(); i++) { resources[i] = i; } AddBinding(next, DescriptorBindingKind::Samplers, std::move(resources)); } bool uses_gds = false; if (!UsesGds(program, uses_gds)) { if (error != nullptr) { *error = "typed shader contains invalid shared-memory metadata"; } return false; } if (uses_gds) { AddBinding(next, DescriptorBindingKind::Gds); } if (!program.info.addresses.empty()) { std::vector resources(program.info.addresses.size()); for (uint32_t i = 0; i < resources.size(); i++) { resources[i] = i; } AddBinding(next, DescriptorBindingKind::AddressMemory, std::move(resources)); } const bool uses_flattened_runtime = !program.values->srt_reads.empty() || std::ranges::any_of(program.info.images, [](const ImageResource& image) { return image.indirect_mapping_capacity != 0u; }); if (uses_flattened_runtime) { AddBinding(next, DescriptorBindingKind::FlattenedSrt); } const auto available_push_dwords = (MaxPushConstantBytes - options.push_constant_offset) / 4u; const auto push_limit = std::min(options.max_push_dwords, available_push_dwords); if (next.ShaderDataDwords() <= push_limit) { next.push_constant_size = next.ShaderDataDwords() * sizeof(uint32_t); } else { AddBinding(next, DescriptorBindingKind::UserData); } program.bindings = std::move(next); program.binding_layout_complete = true; return true; } const DescriptorBinding* FindBinding(const BindingLayout& layout, DescriptorBindingKind kind) { for (const auto& binding: layout.descriptors) { if (binding.kind == kind) { return &binding; } } return nullptr; } } // namespace Libs::Graphics::ShaderRecompiler::IR