// Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // FLUTTER_NOLINT: https://github.com/flutter/flutter/issues/105732 #include "impeller/compiler/reflector.h" #include #include #include #include #include "flutter/fml/logging.h" #include "fml/backtrace.h" #include "impeller/base/strings.h" #include "impeller/base/validation.h" #include "impeller/compiler/code_gen_template.h" #include "impeller/compiler/shader_bundle_data.h" #include "impeller/compiler/types.h" #include "impeller/compiler/uniform_sorter.h" #include "impeller/compiler/utilities.h" #include "impeller/core/runtime_types.h" #include "impeller/geometry/half.h" #include "impeller/geometry/matrix.h" #include "impeller/geometry/scalar.h" #include "impeller/runtime_stage/runtime_stage.h" #include "spirv_common.hpp" namespace impeller { namespace compiler { static std::string ExecutionModelToString(spv::ExecutionModel model) { switch (model) { case spv::ExecutionModel::ExecutionModelVertex: return "vertex"; case spv::ExecutionModel::ExecutionModelFragment: return "fragment"; case spv::ExecutionModel::ExecutionModelGLCompute: return "compute"; default: return "unsupported"; } } static std::string StringToShaderStage(const std::string& str) { if (str == "vertex") { return "ShaderStage::kVertex"; } if (str == "fragment") { return "ShaderStage::kFragment"; } if (str == "compute") { return "ShaderStage::kCompute"; } return "ShaderStage::kUnknown"; } Reflector::Reflector(Options options, const std::shared_ptr& ir, const std::shared_ptr& shader_data, const CompilerBackend& compiler) : options_(std::move(options)), ir_(ir), shader_data_(shader_data), compiler_(compiler) { if (!ir_ || !compiler_) { return; } if (auto template_arguments = GenerateTemplateArguments(); template_arguments.has_value()) { template_arguments_ = std::make_unique(std::move(template_arguments.value())); } else { return; } reflection_header_ = GenerateReflectionHeader(); if (!reflection_header_) { return; } reflection_cc_ = GenerateReflectionCC(); if (!reflection_cc_) { return; } runtime_stage_shader_ = GenerateRuntimeStageData(); shader_bundle_data_ = GenerateShaderBundleData(); if (!shader_bundle_data_) { return; } is_valid_ = true; } Reflector::~Reflector() = default; bool Reflector::IsValid() const { return is_valid_; } std::shared_ptr Reflector::GetReflectionJSON() const { if (!is_valid_) { return nullptr; } auto json_string = std::make_shared(template_arguments_->dump(2u)); return std::make_shared( reinterpret_cast(json_string->data()), json_string->size(), [json_string](auto, auto) {}); } std::shared_ptr Reflector::GetReflectionHeader() const { return reflection_header_; } std::shared_ptr Reflector::GetReflectionCC() const { return reflection_cc_; } std::shared_ptr Reflector::GetRuntimeStageShaderData() const { return runtime_stage_shader_; } std::shared_ptr Reflector::GetShaderBundleData() const { return shader_bundle_data_; } std::optional Reflector::GenerateTemplateArguments() const { nlohmann::json root; const auto& entrypoints = compiler_->get_entry_points_and_stages(); if (entrypoints.size() != 1) { VALIDATION_LOG << "Incorrect number of entrypoints in the shader. Found " << entrypoints.size() << " but expected 1."; return std::nullopt; } auto execution_model = entrypoints.front().execution_model; { root["entrypoint"] = options_.entry_point_name; root["shader_name"] = options_.shader_name; root["shader_stage"] = ExecutionModelToString(execution_model); root["header_file_name"] = options_.header_file_name; } const auto shader_resources = compiler_->get_shader_resources(); // Subpass Inputs. { auto& subpass_inputs = root["subpass_inputs"] = nlohmann::json::array_t{}; if (auto subpass_inputs_json = ReflectResources(shader_resources.subpass_inputs); subpass_inputs_json.has_value()) { for (auto subpass_input : subpass_inputs_json.value()) { subpass_input["descriptor_type"] = "DescriptorType::kInputAttachment"; subpass_inputs.emplace_back(std::move(subpass_input)); } } else { return std::nullopt; } } // Uniform and storage buffers. { auto& buffers = root["buffers"] = nlohmann::json::array_t{}; if (auto uniform_buffers_json = ReflectResources(shader_resources.uniform_buffers); uniform_buffers_json.has_value()) { for (auto uniform_buffer : uniform_buffers_json.value()) { uniform_buffer["descriptor_type"] = "DescriptorType::kUniformBuffer"; buffers.emplace_back(std::move(uniform_buffer)); } } else { return std::nullopt; } if (auto storage_buffers_json = ReflectResources(shader_resources.storage_buffers); storage_buffers_json.has_value()) { for (auto uniform_buffer : storage_buffers_json.value()) { uniform_buffer["descriptor_type"] = "DescriptorType::kStorageBuffer"; buffers.emplace_back(std::move(uniform_buffer)); } } else { return std::nullopt; } } { auto& stage_inputs = root["stage_inputs"] = nlohmann::json::array_t{}; if (auto stage_inputs_json = ReflectResources( shader_resources.stage_inputs, /*compute_offsets=*/execution_model == spv::ExecutionModelVertex); stage_inputs_json.has_value()) { stage_inputs = std::move(stage_inputs_json.value()); } else { return std::nullopt; } } { auto combined_sampled_images = ReflectResources(shader_resources.sampled_images); auto images = ReflectResources(shader_resources.separate_images); auto samplers = ReflectResources(shader_resources.separate_samplers); if (!combined_sampled_images.has_value() || !images.has_value() || !samplers.has_value()) { return std::nullopt; } auto& sampled_images = root["sampled_images"] = nlohmann::json::array_t{}; for (auto value : combined_sampled_images.value()) { value["descriptor_type"] = "DescriptorType::kSampledImage"; sampled_images.emplace_back(std::move(value)); } for (auto value : images.value()) { value["descriptor_type"] = "DescriptorType::kImage"; sampled_images.emplace_back(std::move(value)); } for (auto value : samplers.value()) { value["descriptor_type"] = "DescriptorType::kSampledSampler"; sampled_images.emplace_back(std::move(value)); } } if (auto stage_outputs = ReflectResources(shader_resources.stage_outputs); stage_outputs.has_value()) { root["stage_outputs"] = std::move(stage_outputs.value()); } else { return std::nullopt; } { auto& struct_definitions = root["struct_definitions"] = nlohmann::json::array_t{}; if (entrypoints.front().execution_model == spv::ExecutionModel::ExecutionModelVertex && !shader_resources.stage_inputs.empty()) { if (auto struc = ReflectPerVertexStructDefinition(shader_resources.stage_inputs); struc.has_value()) { struct_definitions.emplace_back(EmitStructDefinition(struc.value())); } else { // If there are stage inputs, it is an error to not generate a per // vertex data struct for a vertex like shader stage. return std::nullopt; } } std::set known_structs; ir_->for_each_typed_id( [&](uint32_t, const spirv_cross::SPIRType& type) { if (type.basetype != spirv_cross::SPIRType::BaseType::Struct) { return; } // Skip structs that do not have layout offset decorations. // These structs are used internally within the shader and are not // part of the shader's interface. for (size_t i = 0; i < type.member_types.size(); i++) { if (!compiler_->has_member_decoration(type.self, i, spv::DecorationOffset)) { return; } } if (known_structs.find(type.self) != known_structs.end()) { // Iterating over types this way leads to duplicates which may cause // duplicate struct definitions. return; } known_structs.insert(type.self); if (auto struc = ReflectStructDefinition(type.self); struc.has_value()) { struct_definitions.emplace_back( EmitStructDefinition(struc.value())); } }); } root["bind_prototypes"] = EmitBindPrototypes(shader_resources, execution_model); return root; } std::shared_ptr Reflector::GenerateReflectionHeader() const { return InflateTemplate(kReflectionHeaderTemplate); } std::shared_ptr Reflector::GenerateReflectionCC() const { return InflateTemplate(kReflectionCCTemplate); } static std::optional GetRuntimeStageBackend( TargetPlatform target_platform) { switch (target_platform) { case TargetPlatform::kUnknown: case TargetPlatform::kMetalDesktop: case TargetPlatform::kMetalIOS: case TargetPlatform::kOpenGLES: case TargetPlatform::kOpenGLDesktop: case TargetPlatform::kVulkan: return std::nullopt; case TargetPlatform::kRuntimeStageMetal: return RuntimeStageBackend::kMetal; case TargetPlatform::kRuntimeStageGLES: return RuntimeStageBackend::kOpenGLES; case TargetPlatform::kRuntimeStageVulkan: return RuntimeStageBackend::kVulkan; case TargetPlatform::kSkSL: return RuntimeStageBackend::kSkSL; } FML_UNREACHABLE(); } std::shared_ptr Reflector::GenerateRuntimeStageData() const { auto backend = GetRuntimeStageBackend(options_.target_platform); if (!backend.has_value()) { return nullptr; } const auto& entrypoints = compiler_->get_entry_points_and_stages(); if (entrypoints.size() != 1u) { VALIDATION_LOG << "Single entrypoint not found."; return nullptr; } auto data = std::make_unique(); data->entrypoint = options_.entry_point_name; data->stage = entrypoints.front().execution_model; data->shader = shader_data_; data->backend = backend.value(); // Sort the IR so that the uniforms are in declaration order. std::vector uniforms = SortUniforms(ir_.get(), compiler_.GetCompiler()); for (auto& sorted_id : uniforms) { auto var = ir_->ids[sorted_id].get(); const auto spir_type = compiler_->get_type(var.basetype); UniformDescription uniform_description; uniform_description.name = compiler_->get_name(var.self); uniform_description.location = compiler_->get_decoration( var.self, spv::Decoration::DecorationLocation); uniform_description.binding = compiler_->get_decoration(var.self, spv::Decoration::DecorationBinding); uniform_description.type = spir_type.basetype; uniform_description.rows = spir_type.vecsize; uniform_description.columns = spir_type.columns; uniform_description.bit_width = spir_type.width; uniform_description.array_elements = GetArrayElements(spir_type); FML_CHECK(data->backend != RuntimeStageBackend::kVulkan || spir_type.basetype == spirv_cross::SPIRType::BaseType::SampledImage) << "Vulkan runtime effect had unexpected uniforms outside of the " "uniform buffer object."; data->uniforms.emplace_back(std::move(uniform_description)); } const auto ubos = compiler_->get_shader_resources().uniform_buffers; if (data->backend == RuntimeStageBackend::kVulkan && !ubos.empty()) { if (ubos.size() != 1 && ubos[0].name != RuntimeStage::kVulkanUBOName) { VALIDATION_LOG << "Expected a single UBO resource named " "'" << RuntimeStage::kVulkanUBOName << "' " "for Vulkan runtime stage backend."; return nullptr; } const auto& ubo = ubos[0]; size_t binding = compiler_->get_decoration(ubo.id, spv::Decoration::DecorationBinding); auto members = ReadStructMembers(ubo.type_id); std::vector struct_layout; size_t float_count = 0; for (size_t i = 0; i < members.size(); i += 1) { const auto& member = members[i]; std::vector bytes; switch (member.underlying_type) { case StructMember::UnderlyingType::kPadding: { size_t padding_count = (member.size + sizeof(float) - 1) / sizeof(float); while (padding_count > 0) { struct_layout.push_back(0); padding_count--; } break; } case StructMember::UnderlyingType::kFloat: { size_t member_float_count = member.byte_length / sizeof(float); float_count += member_float_count; while (member_float_count > 0) { struct_layout.push_back(1); member_float_count--; } break; } case StructMember::UnderlyingType::kOther: VALIDATION_LOG << "Non-floating-type struct member " << member.name << " is not supported."; return nullptr; } } data->uniforms.emplace_back(UniformDescription{ .name = ubo.name, .location = binding, .binding = binding, .type = spirv_cross::SPIRType::Struct, .struct_layout = std::move(struct_layout), .struct_float_count = float_count, }); } // We only need to worry about storing vertex attributes. if (entrypoints.front().execution_model == spv::ExecutionModelVertex) { const auto inputs = compiler_->get_shader_resources().stage_inputs; auto input_offsets = ComputeOffsets(inputs); for (const auto& input : inputs) { std::optional offset = GetOffset(input.id, input_offsets); const auto type = compiler_->get_type(input.type_id); InputDescription input_description; input_description.name = input.name; input_description.location = compiler_->get_decoration( input.id, spv::Decoration::DecorationLocation); input_description.set = compiler_->get_decoration( input.id, spv::Decoration::DecorationDescriptorSet); input_description.binding = compiler_->get_decoration( input.id, spv::Decoration::DecorationBinding); input_description.type = type.basetype; input_description.bit_width = type.width; input_description.vec_size = type.vecsize; input_description.columns = type.columns; input_description.offset = offset.value_or(0u); data->inputs.emplace_back(std::move(input_description)); } } return data; } std::shared_ptr Reflector::GenerateShaderBundleData() const { const auto& entrypoints = compiler_->get_entry_points_and_stages(); if (entrypoints.size() != 1u) { VALIDATION_LOG << "Single entrypoint not found."; return nullptr; } auto data = std::make_shared( options_.entry_point_name, // entrypoints.front().execution_model, // options_.target_platform // ); data->SetShaderData(shader_data_); const auto uniforms = compiler_->get_shader_resources().uniform_buffers; for (const auto& uniform : uniforms) { ShaderBundleData::ShaderUniformStruct uniform_struct; uniform_struct.name = uniform.name; uniform_struct.ext_res_0 = compiler_.GetExtendedMSLResourceBinding( CompilerBackend::ExtendedResourceIndex::kPrimary, uniform.id); uniform_struct.set = compiler_->get_decoration( uniform.id, spv::Decoration::DecorationDescriptorSet); uniform_struct.binding = compiler_->get_decoration( uniform.id, spv::Decoration::DecorationBinding); const auto type = compiler_->get_type(uniform.type_id); if (type.basetype != spirv_cross::SPIRType::BaseType::Struct) { std::cerr << "Error: Uniform \"" << uniform.name << "\" is not a struct. All Flutter GPU shader uniforms must " "be structs." << std::endl; return nullptr; } size_t size_in_bytes = 0; for (const auto& struct_member : ReadStructMembers(uniform.type_id)) { size_in_bytes += struct_member.byte_length; if (StringStartsWith(struct_member.name, "_PADDING_")) { continue; } ShaderBundleData::ShaderUniformStructField uniform_struct_field; uniform_struct_field.name = struct_member.name; uniform_struct_field.type = struct_member.base_type; uniform_struct_field.offset_in_bytes = struct_member.offset; uniform_struct_field.element_size_in_bytes = struct_member.size; uniform_struct_field.total_size_in_bytes = struct_member.byte_length; uniform_struct_field.array_elements = struct_member.array_elements; uniform_struct.fields.push_back(uniform_struct_field); } uniform_struct.size_in_bytes = size_in_bytes; data->AddUniformStruct(uniform_struct); } const auto sampled_images = compiler_->get_shader_resources().sampled_images; for (const auto& image : sampled_images) { ShaderBundleData::ShaderUniformTexture uniform_texture; uniform_texture.name = image.name; uniform_texture.ext_res_0 = compiler_.GetExtendedMSLResourceBinding( CompilerBackend::ExtendedResourceIndex::kPrimary, image.id); uniform_texture.set = compiler_->get_decoration( image.id, spv::Decoration::DecorationDescriptorSet); uniform_texture.binding = compiler_->get_decoration(image.id, spv::Decoration::DecorationBinding); data->AddUniformTexture(uniform_texture); } // We only need to worry about storing vertex attributes. if (entrypoints.front().execution_model == spv::ExecutionModelVertex) { const auto inputs = compiler_->get_shader_resources().stage_inputs; auto input_offsets = ComputeOffsets(inputs); for (const auto& input : inputs) { std::optional offset = GetOffset(input.id, input_offsets); const auto type = compiler_->get_type(input.type_id); InputDescription input_description; input_description.name = input.name; input_description.location = compiler_->get_decoration( input.id, spv::Decoration::DecorationLocation); input_description.set = compiler_->get_decoration( input.id, spv::Decoration::DecorationDescriptorSet); input_description.binding = compiler_->get_decoration( input.id, spv::Decoration::DecorationBinding); input_description.type = type.basetype; input_description.bit_width = type.width; input_description.vec_size = type.vecsize; input_description.columns = type.columns; input_description.offset = offset.value_or(0u); data->AddInputDescription(std::move(input_description)); } } return data; } std::optional Reflector::GetArrayElements( const spirv_cross::SPIRType& type) const { if (type.array.empty()) { return std::nullopt; } FML_CHECK(type.array.size() == 1) << "Multi-dimensional arrays are not supported."; FML_CHECK(type.array_size_literal.front()) << "Must use a literal for array sizes."; return type.array.front(); } static std::string ToString(CompilerBackend::Type type) { switch (type) { case CompilerBackend::Type::kMSL: return "Metal Shading Language"; case CompilerBackend::Type::kGLSL: return "OpenGL Shading Language"; case CompilerBackend::Type::kGLSLVulkan: return "OpenGL Shading Language (Relaxed Vulkan Semantics)"; case CompilerBackend::Type::kSkSL: return "SkSL Shading Language"; } FML_UNREACHABLE(); } std::shared_ptr Reflector::InflateTemplate( std::string_view tmpl) const { inja::Environment env; env.set_trim_blocks(true); env.set_lstrip_blocks(true); env.add_callback("camel_case", 1u, [](inja::Arguments& args) { return ToCamelCase(args.at(0u)->get()); }); env.add_callback("to_shader_stage", 1u, [](inja::Arguments& args) { return StringToShaderStage(args.at(0u)->get()); }); env.add_callback("get_generator_name", 0u, [type = compiler_.GetType()](inja::Arguments& args) { return ToString(type); }); auto inflated_template = std::make_shared(env.render(tmpl, *template_arguments_)); return std::make_shared( reinterpret_cast(inflated_template->data()), inflated_template->size(), [inflated_template](auto, auto) {}); } std::vector Reflector::ComputeOffsets( const spirv_cross::SmallVector& resources) const { std::vector offsets(resources.size(), 0); if (resources.size() == 0) { return offsets; } for (const auto& resource : resources) { const auto type = compiler_->get_type(resource.type_id); auto location = compiler_->get_decoration( resource.id, spv::Decoration::DecorationLocation); // Malformed shader, will be caught later on. if (location >= resources.size() || location < 0) { location = 0; } offsets[location] = (type.width * type.vecsize) / 8; } for (size_t i = 1; i < resources.size(); i++) { offsets[i] += offsets[i - 1]; } for (size_t i = resources.size() - 1; i > 0; i--) { offsets[i] = offsets[i - 1]; } offsets[0] = 0; return offsets; } std::optional Reflector::GetOffset( spirv_cross::ID id, const std::vector& offsets) const { uint32_t location = compiler_->get_decoration(id, spv::Decoration::DecorationLocation); if (location >= offsets.size()) { return std::nullopt; } return offsets[location]; } std::optional Reflector::ReflectResource( const spirv_cross::Resource& resource, std::optional offset) const { nlohmann::json::object_t result; result["name"] = resource.name; result["descriptor_set"] = compiler_->get_decoration( resource.id, spv::Decoration::DecorationDescriptorSet); result["binding"] = compiler_->get_decoration( resource.id, spv::Decoration::DecorationBinding); result["set"] = compiler_->get_decoration( resource.id, spv::Decoration::DecorationDescriptorSet); result["location"] = compiler_->get_decoration( resource.id, spv::Decoration::DecorationLocation); result["index"] = compiler_->get_decoration(resource.id, spv::Decoration::DecorationIndex); result["ext_res_0"] = compiler_.GetExtendedMSLResourceBinding( CompilerBackend::ExtendedResourceIndex::kPrimary, resource.id); result["ext_res_1"] = compiler_.GetExtendedMSLResourceBinding( CompilerBackend::ExtendedResourceIndex::kSecondary, resource.id); result["relaxed_precision"] = compiler_->get_decoration( resource.id, spv::Decoration::DecorationRelaxedPrecision) == 1; result["offset"] = offset.value_or(0u); auto type = ReflectType(resource.type_id); if (!type.has_value()) { return std::nullopt; } result["type"] = std::move(type.value()); return result; } std::optional Reflector::ReflectType( const spirv_cross::TypeID& type_id) const { nlohmann::json::object_t result; const auto type = compiler_->get_type(type_id); result["type_name"] = StructMember::BaseTypeToString(type.basetype); result["bit_width"] = type.width; result["vec_size"] = type.vecsize; result["columns"] = type.columns; auto& members = result["members"] = nlohmann::json::array_t{}; if (type.basetype == spirv_cross::SPIRType::BaseType::Struct) { for (const auto& struct_member : ReadStructMembers(type_id)) { auto member = nlohmann::json::object_t{}; member["name"] = struct_member.name; member["type"] = struct_member.type; member["base_type"] = StructMember::BaseTypeToString(struct_member.base_type); member["offset"] = struct_member.offset; member["size"] = struct_member.size; member["byte_length"] = struct_member.byte_length; if (struct_member.array_elements.has_value()) { member["array_elements"] = struct_member.array_elements.value(); } else { member["array_elements"] = "std::nullopt"; } members.emplace_back(std::move(member)); } } return result; } std::optional Reflector::ReflectResources( const spirv_cross::SmallVector& resources, bool compute_offsets) const { nlohmann::json::array_t result; result.reserve(resources.size()); std::vector offsets; if (compute_offsets) { offsets = ComputeOffsets(resources); } for (const auto& resource : resources) { std::optional maybe_offset = std::nullopt; if (compute_offsets) { maybe_offset = GetOffset(resource.id, offsets); } if (auto reflected = ReflectResource(resource, maybe_offset); reflected.has_value()) { result.emplace_back(std::move(reflected.value())); } else { return std::nullopt; } } return result; } static std::string TypeNameWithPaddingOfSize(size_t size) { std::stringstream stream; stream << "Padding<" << size << ">"; return stream.str(); } struct KnownType { std::string name; size_t byte_size = 0; }; static std::optional ReadKnownScalarType( spirv_cross::SPIRType::BaseType type) { switch (type) { case spirv_cross::SPIRType::BaseType::Boolean: return KnownType{ .name = "bool", .byte_size = sizeof(bool), }; case spirv_cross::SPIRType::BaseType::Float: return KnownType{ .name = "Scalar", .byte_size = sizeof(Scalar), }; case spirv_cross::SPIRType::BaseType::Half: return KnownType{ .name = "Half", .byte_size = sizeof(Half), }; case spirv_cross::SPIRType::BaseType::UInt: return KnownType{ .name = "uint32_t", .byte_size = sizeof(uint32_t), }; case spirv_cross::SPIRType::BaseType::Int: return KnownType{ .name = "int32_t", .byte_size = sizeof(int32_t), }; default: break; } return std::nullopt; } //------------------------------------------------------------------------------ /// @brief Get the reflected struct size. In the vast majority of the /// cases, this is the same as the declared struct size as given by /// the compiler. But, additional padding may need to be introduced /// after the end of the struct to keep in line with the alignment /// requirement of the individual struct members. This method /// figures out the actual size of the reflected struct that can be /// referenced in native code. /// /// @param[in] members The members /// /// @return The reflected structure size. /// static size_t GetReflectedStructSize(const std::vector& members) { auto struct_size = 0u; for (const auto& member : members) { struct_size += member.byte_length; } return struct_size; } std::vector Reflector::ReadStructMembers( const spirv_cross::TypeID& type_id) const { const auto& struct_type = compiler_->get_type(type_id); FML_CHECK(struct_type.basetype == spirv_cross::SPIRType::BaseType::Struct); std::vector result; size_t current_byte_offset = 0; size_t max_member_alignment = 0; for (size_t i = 0; i < struct_type.member_types.size(); i++) { const auto& member = compiler_->get_type(struct_type.member_types[i]); const auto struct_member_offset = compiler_->type_struct_member_offset(struct_type, i); auto array_elements = GetArrayElements(member); if (struct_member_offset > current_byte_offset) { const auto alignment_pad = struct_member_offset - current_byte_offset; result.emplace_back(StructMember{ TypeNameWithPaddingOfSize(alignment_pad), // type spirv_cross::SPIRType::BaseType::Void, // basetype SPrintF("_PADDING_%s_", GetMemberNameAtIndex(struct_type, i).c_str()), // name current_byte_offset, // offset alignment_pad, // size alignment_pad, // byte_length std::nullopt, // array_elements 0, // element_padding }); current_byte_offset += alignment_pad; } max_member_alignment = std::max(max_member_alignment, (member.width / 8) * member.columns * member.vecsize); FML_CHECK(current_byte_offset == struct_member_offset); // A user defined struct. if (member.basetype == spirv_cross::SPIRType::BaseType::Struct) { const size_t size = GetReflectedStructSize(ReadStructMembers(member.self)); uint32_t stride = GetArrayStride<0>(struct_type, member, i); if (stride == 0) { stride = size; } uint32_t element_padding = stride - size; result.emplace_back(StructMember{ compiler_->get_name(member.self), // type member.basetype, // basetype GetMemberNameAtIndex(struct_type, i), // name struct_member_offset, // offset size, // size stride * array_elements.value_or(1), // byte_length array_elements, // array_elements element_padding, // element_padding }); current_byte_offset += stride * array_elements.value_or(1); continue; } // Tightly packed 4x4 Matrix is special cased as we know how to work with // those. if (member.basetype == spirv_cross::SPIRType::BaseType::Float && // member.width == sizeof(Scalar) * 8 && // member.columns == 4 && // member.vecsize == 4 // ) { uint32_t stride = GetArrayStride(struct_type, member, i); uint32_t element_padding = stride - sizeof(Matrix); result.emplace_back(StructMember{ "Matrix", // type member.basetype, // basetype GetMemberNameAtIndex(struct_type, i), // name struct_member_offset, // offset sizeof(Matrix), // size stride * array_elements.value_or(1), // byte_length array_elements, // array_elements element_padding, // element_padding }); current_byte_offset += stride * array_elements.value_or(1); continue; } // Tightly packed UintPoint32 (uvec2) if (member.basetype == spirv_cross::SPIRType::BaseType::UInt && // member.width == sizeof(uint32_t) * 8 && // member.columns == 1 && // member.vecsize == 2 // ) { uint32_t stride = GetArrayStride(struct_type, member, i); uint32_t element_padding = stride - sizeof(UintPoint32); result.emplace_back(StructMember{ "UintPoint32", // type member.basetype, // basetype GetMemberNameAtIndex(struct_type, i), // name struct_member_offset, // offset sizeof(UintPoint32), // size stride * array_elements.value_or(1), // byte_length array_elements, // array_elements element_padding, // element_padding }); current_byte_offset += stride * array_elements.value_or(1); continue; } // Tightly packed UintPoint32 (ivec2) if (member.basetype == spirv_cross::SPIRType::BaseType::Int && // member.width == sizeof(int32_t) * 8 && // member.columns == 1 && // member.vecsize == 2 // ) { uint32_t stride = GetArrayStride(struct_type, member, i); uint32_t element_padding = stride - sizeof(IPoint32); result.emplace_back(StructMember{ "IPoint32", // type member.basetype, // basetype GetMemberNameAtIndex(struct_type, i), // name struct_member_offset, // offset sizeof(IPoint32), // size stride * array_elements.value_or(1), // byte_length array_elements, // array_elements element_padding, // element_padding }); current_byte_offset += stride * array_elements.value_or(1); continue; } // Tightly packed Point (vec2). if (member.basetype == spirv_cross::SPIRType::BaseType::Float && // member.width == sizeof(float) * 8 && // member.columns == 1 && // member.vecsize == 2 // ) { uint32_t stride = GetArrayStride(struct_type, member, i); uint32_t element_padding = stride - sizeof(Point); result.emplace_back(StructMember{ "Point", // type member.basetype, // basetype GetMemberNameAtIndex(struct_type, i), // name struct_member_offset, // offset sizeof(Point), // size stride * array_elements.value_or(1), // byte_length array_elements, // array_elements element_padding, // element_padding }); current_byte_offset += stride * array_elements.value_or(1); continue; } // Tightly packed Vector3. if (member.basetype == spirv_cross::SPIRType::BaseType::Float && // member.width == sizeof(float) * 8 && // member.columns == 1 && // member.vecsize == 3 // ) { uint32_t stride = GetArrayStride(struct_type, member, i); uint32_t element_padding = stride - sizeof(Vector3); result.emplace_back(StructMember{ "Vector3", // type member.basetype, // basetype GetMemberNameAtIndex(struct_type, i), // name struct_member_offset, // offset sizeof(Vector3), // size stride * array_elements.value_or(1), // byte_length array_elements, // array_elements element_padding, // element_padding }); current_byte_offset += stride * array_elements.value_or(1); continue; } // Tightly packed Vector4. if (member.basetype == spirv_cross::SPIRType::BaseType::Float && // member.width == sizeof(float) * 8 && // member.columns == 1 && // member.vecsize == 4 // ) { uint32_t stride = GetArrayStride(struct_type, member, i); uint32_t element_padding = stride - sizeof(Vector4); result.emplace_back(StructMember{ "Vector4", // type member.basetype, // basetype GetMemberNameAtIndex(struct_type, i), // name struct_member_offset, // offset sizeof(Vector4), // size stride * array_elements.value_or(1), // byte_length array_elements, // array_elements element_padding, // element_padding }); current_byte_offset += stride * array_elements.value_or(1); continue; } // Tightly packed half Point (vec2). if (member.basetype == spirv_cross::SPIRType::BaseType::Half && // member.width == sizeof(Half) * 8 && // member.columns == 1 && // member.vecsize == 2 // ) { uint32_t stride = GetArrayStride(struct_type, member, i); uint32_t element_padding = stride - sizeof(HalfVector2); result.emplace_back(StructMember{ "HalfVector2", // type member.basetype, // basetype GetMemberNameAtIndex(struct_type, i), // name struct_member_offset, // offset sizeof(HalfVector2), // size stride * array_elements.value_or(1), // byte_length array_elements, // array_elements element_padding, // element_padding }); current_byte_offset += stride * array_elements.value_or(1); continue; } // Tightly packed Half Float Vector3. if (member.basetype == spirv_cross::SPIRType::BaseType::Half && // member.width == sizeof(Half) * 8 && // member.columns == 1 && // member.vecsize == 3 // ) { uint32_t stride = GetArrayStride(struct_type, member, i); uint32_t element_padding = stride - sizeof(HalfVector3); result.emplace_back(StructMember{ "HalfVector3", // type member.basetype, // basetype GetMemberNameAtIndex(struct_type, i), // name struct_member_offset, // offset sizeof(HalfVector3), // size stride * array_elements.value_or(1), // byte_length array_elements, // array_elements element_padding, // element_padding }); current_byte_offset += stride * array_elements.value_or(1); continue; } // Tightly packed Half Float Vector4. if (member.basetype == spirv_cross::SPIRType::BaseType::Half && // member.width == sizeof(Half) * 8 && // member.columns == 1 && // member.vecsize == 4 // ) { uint32_t stride = GetArrayStride(struct_type, member, i); uint32_t element_padding = stride - sizeof(HalfVector4); result.emplace_back(StructMember{ "HalfVector4", // type member.basetype, // basetype GetMemberNameAtIndex(struct_type, i), // name struct_member_offset, // offset sizeof(HalfVector4), // size stride * array_elements.value_or(1), // byte_length array_elements, // array_elements element_padding, // element_padding }); current_byte_offset += stride * array_elements.value_or(1); continue; } // Other isolated scalars (like bool, int, float/Scalar, etc..). { auto maybe_known_type = ReadKnownScalarType(member.basetype); if (maybe_known_type.has_value() && // member.columns == 1 && // member.vecsize == 1 // ) { uint32_t stride = GetArrayStride<0>(struct_type, member, i); if (stride == 0) { stride = maybe_known_type.value().byte_size; } uint32_t element_padding = stride - maybe_known_type.value().byte_size; // Add the type directly. result.emplace_back(StructMember{ maybe_known_type.value().name, // type member.basetype, // basetype GetMemberNameAtIndex(struct_type, i), // name struct_member_offset, // offset maybe_known_type.value().byte_size, // size stride * array_elements.value_or(1), // byte_length array_elements, // array_elements element_padding, // element_padding }); current_byte_offset += stride * array_elements.value_or(1); continue; } } // Catch all for unknown types. Just add the necessary padding to the struct // and move on. { const size_t size = (member.width * member.columns * member.vecsize) / 8u; uint32_t stride = GetArrayStride<0>(struct_type, member, i); if (stride == 0) { stride = size; } auto element_padding = stride - size; result.emplace_back(StructMember{ TypeNameWithPaddingOfSize(size), // type member.basetype, // basetype GetMemberNameAtIndex(struct_type, i), // name struct_member_offset, // offset size, // size stride * array_elements.value_or(1), // byte_length array_elements, // array_elements element_padding, // element_padding }); current_byte_offset += stride * array_elements.value_or(1); continue; } } if (max_member_alignment > 0u) { const auto struct_length = current_byte_offset; { const auto excess = struct_length % max_member_alignment; if (excess != 0) { const auto padding = max_member_alignment - excess; result.emplace_back(StructMember{ TypeNameWithPaddingOfSize(padding), // type spirv_cross::SPIRType::BaseType::Void, // basetype "_PADDING_", // name current_byte_offset, // offset padding, // size padding, // byte_length std::nullopt, // array_elements 0, // element_padding }); } } } return result; } std::optional Reflector::ReflectStructDefinition( const spirv_cross::TypeID& type_id) const { const auto& type = compiler_->get_type(type_id); if (type.basetype != spirv_cross::SPIRType::BaseType::Struct) { return std::nullopt; } const auto struct_name = compiler_->get_name(type_id); if (struct_name.find("_RESERVED_IDENTIFIER_") != std::string::npos) { return std::nullopt; } auto struct_members = ReadStructMembers(type_id); auto reflected_struct_size = GetReflectedStructSize(struct_members); StructDefinition struc; struc.name = struct_name; struc.byte_length = reflected_struct_size; struc.members = std::move(struct_members); return struc; } nlohmann::json::object_t Reflector::EmitStructDefinition( std::optional struc) const { nlohmann::json::object_t result; result["name"] = struc->name; result["byte_length"] = struc->byte_length; auto& members = result["members"] = nlohmann::json::array_t{}; for (const auto& struct_member : struc->members) { auto& member = members.emplace_back(nlohmann::json::object_t{}); member["name"] = struct_member.name; member["type"] = struct_member.type; member["base_type"] = StructMember::BaseTypeToString(struct_member.base_type); member["offset"] = struct_member.offset; member["byte_length"] = struct_member.byte_length; if (struct_member.array_elements.has_value()) { member["array_elements"] = struct_member.array_elements.value(); } else { member["array_elements"] = "std::nullopt"; } member["element_padding"] = struct_member.element_padding; } return result; } struct VertexType { std::string type_name; spirv_cross::SPIRType::BaseType base_type; std::string variable_name; size_t byte_length = 0u; }; static VertexType VertexTypeFromInputResource( const spirv_cross::Compiler& compiler, const spirv_cross::Resource* resource) { VertexType result; result.variable_name = resource->name; const auto& type = compiler.get_type(resource->type_id); result.base_type = type.basetype; const auto total_size = type.columns * type.vecsize * type.width / 8u; result.byte_length = total_size; if (type.basetype == spirv_cross::SPIRType::BaseType::Float && type.columns == 1u && type.vecsize == 2u && type.width == sizeof(float) * 8u) { result.type_name = "Point"; } else if (type.basetype == spirv_cross::SPIRType::BaseType::Float && type.columns == 1u && type.vecsize == 4u && type.width == sizeof(float) * 8u) { result.type_name = "Vector4"; } else if (type.basetype == spirv_cross::SPIRType::BaseType::Float && type.columns == 1u && type.vecsize == 3u && type.width == sizeof(float) * 8u) { result.type_name = "Vector3"; } else if (type.basetype == spirv_cross::SPIRType::BaseType::Float && type.columns == 1u && type.vecsize == 1u && type.width == sizeof(float) * 8u) { result.type_name = "Scalar"; } else if (type.basetype == spirv_cross::SPIRType::BaseType::Int && type.columns == 1u && type.vecsize == 1u && type.width == sizeof(int32_t) * 8u) { result.type_name = "int32_t"; } else { // Catch all unknown padding. result.type_name = TypeNameWithPaddingOfSize(total_size); } return result; } std::optional Reflector::ReflectPerVertexStructDefinition( const spirv_cross::SmallVector& stage_inputs) const { // Avoid emitting a zero sized structure. The code gen templates assume a // non-zero size. if (stage_inputs.empty()) { return std::nullopt; } // Validate locations are contiguous and there are no duplicates. std::set locations; for (const auto& input : stage_inputs) { auto location = compiler_->get_decoration( input.id, spv::Decoration::DecorationLocation); if (locations.count(location) != 0) { // Duplicate location. Bail. return std::nullopt; } locations.insert(location); } for (size_t i = 0; i < locations.size(); i++) { if (locations.count(i) != 1) { // Locations are not contiguous. This usually happens when a single stage // input takes multiple input slots. No reflection information can be // generated for such cases anyway. So bail! It is up to the shader author // to make sure one stage input maps to a single input slot. return std::nullopt; } } auto input_for_location = [&](uint32_t queried_location) -> const spirv_cross::Resource* { for (const auto& input : stage_inputs) { auto location = compiler_->get_decoration( input.id, spv::Decoration::DecorationLocation); if (location == queried_location) { return &input; } } // This really cannot happen with all the validation above. FML_UNREACHABLE(); return nullptr; }; StructDefinition struc; struc.name = "PerVertexData"; struc.byte_length = 0u; for (size_t i = 0; i < locations.size(); i++) { auto resource = input_for_location(i); if (resource == nullptr) { return std::nullopt; } const auto vertex_type = VertexTypeFromInputResource(*compiler_.GetCompiler(), resource); auto member = StructMember{ vertex_type.type_name, // type vertex_type.base_type, // base type vertex_type.variable_name, // name struc.byte_length, // offset vertex_type.byte_length, // size vertex_type.byte_length, // byte_length std::nullopt, // array_elements 0, // element_padding }; struc.byte_length += vertex_type.byte_length; struc.members.emplace_back(std::move(member)); } return struc; } std::optional Reflector::GetMemberNameAtIndexIfExists( const spirv_cross::SPIRType& parent_type, size_t index) const { if (parent_type.type_alias != 0) { return GetMemberNameAtIndexIfExists( compiler_->get_type(parent_type.type_alias), index); } if (auto found = ir_->meta.find(parent_type.self); found != ir_->meta.end()) { const auto& members = found->second.members; if (index < members.size() && !members[index].alias.empty()) { return members[index].alias; } } return std::nullopt; } std::string Reflector::GetMemberNameAtIndex( const spirv_cross::SPIRType& parent_type, size_t index, std::string suffix) const { if (auto name = GetMemberNameAtIndexIfExists(parent_type, index); name.has_value()) { return name.value(); } static std::atomic_size_t sUnnamedMembersID; std::stringstream stream; stream << "unnamed_" << sUnnamedMembersID++ << suffix; return stream.str(); } std::vector Reflector::ReflectBindPrototypes( const spirv_cross::ShaderResources& resources, spv::ExecutionModel execution_model) const { std::vector prototypes; for (const auto& uniform_buffer : resources.uniform_buffers) { auto& proto = prototypes.emplace_back(BindPrototype{}); proto.return_type = "bool"; proto.name = ToCamelCase(uniform_buffer.name); proto.descriptor_type = "DescriptorType::kUniformBuffer"; { std::stringstream stream; stream << "Bind uniform buffer for resource named " << uniform_buffer.name << "."; proto.docstring = stream.str(); } proto.args.push_back(BindPrototypeArgument{ .type_name = "ResourceBinder&", .argument_name = "command", }); proto.args.push_back(BindPrototypeArgument{ .type_name = "BufferView", .argument_name = "view", }); } for (const auto& storage_buffer : resources.storage_buffers) { auto& proto = prototypes.emplace_back(BindPrototype{}); proto.return_type = "bool"; proto.name = ToCamelCase(storage_buffer.name); proto.descriptor_type = "DescriptorType::kStorageBuffer"; { std::stringstream stream; stream << "Bind storage buffer for resource named " << storage_buffer.name << "."; proto.docstring = stream.str(); } proto.args.push_back(BindPrototypeArgument{ .type_name = "ResourceBinder&", .argument_name = "command", }); proto.args.push_back(BindPrototypeArgument{ .type_name = "BufferView", .argument_name = "view", }); } for (const auto& sampled_image : resources.sampled_images) { auto& proto = prototypes.emplace_back(BindPrototype{}); proto.return_type = "bool"; proto.name = ToCamelCase(sampled_image.name); proto.descriptor_type = "DescriptorType::kSampledImage"; { std::stringstream stream; stream << "Bind combined image sampler for resource named " << sampled_image.name << "."; proto.docstring = stream.str(); } proto.args.push_back(BindPrototypeArgument{ .type_name = "ResourceBinder&", .argument_name = "command", }); proto.args.push_back(BindPrototypeArgument{ .type_name = "std::shared_ptr", .argument_name = "texture", }); proto.args.push_back(BindPrototypeArgument{ .type_name = "const std::unique_ptr&", .argument_name = "sampler", }); } for (const auto& separate_image : resources.separate_images) { auto& proto = prototypes.emplace_back(BindPrototype{}); proto.return_type = "bool"; proto.name = ToCamelCase(separate_image.name); proto.descriptor_type = "DescriptorType::kImage"; { std::stringstream stream; stream << "Bind separate image for resource named " << separate_image.name << "."; proto.docstring = stream.str(); } proto.args.push_back(BindPrototypeArgument{ .type_name = "Command&", .argument_name = "command", }); proto.args.push_back(BindPrototypeArgument{ .type_name = "std::shared_ptr", .argument_name = "texture", }); } for (const auto& separate_sampler : resources.separate_samplers) { auto& proto = prototypes.emplace_back(BindPrototype{}); proto.return_type = "bool"; proto.name = ToCamelCase(separate_sampler.name); proto.descriptor_type = "DescriptorType::kSampler"; { std::stringstream stream; stream << "Bind separate sampler for resource named " << separate_sampler.name << "."; proto.docstring = stream.str(); } proto.args.push_back(BindPrototypeArgument{ .type_name = "Command&", .argument_name = "command", }); proto.args.push_back(BindPrototypeArgument{ .type_name = "std::shared_ptr", .argument_name = "sampler", }); } return prototypes; } nlohmann::json::array_t Reflector::EmitBindPrototypes( const spirv_cross::ShaderResources& resources, spv::ExecutionModel execution_model) const { const auto prototypes = ReflectBindPrototypes(resources, execution_model); nlohmann::json::array_t result; for (const auto& res : prototypes) { auto& item = result.emplace_back(nlohmann::json::object_t{}); item["return_type"] = res.return_type; item["name"] = res.name; item["docstring"] = res.docstring; item["descriptor_type"] = res.descriptor_type; auto& args = item["args"] = nlohmann::json::array_t{}; for (const auto& arg : res.args) { auto& json_arg = args.emplace_back(nlohmann::json::object_t{}); json_arg["type_name"] = arg.type_name; json_arg["argument_name"] = arg.argument_name; } } return result; } } // namespace compiler } // namespace impeller