
There are only 3 or 4 sampler's active at any given time in a flutter app. rather than store them in a hashmap, just use a vector.
1463 lines
54 KiB
C++
1463 lines
54 KiB
C++
// 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 <atomic>
|
|
#include <optional>
|
|
#include <set>
|
|
#include <sstream>
|
|
|
|
#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<const spirv_cross::ParsedIR>& ir,
|
|
const std::shared_ptr<fml::Mapping>& 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<nlohmann::json>(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<fml::Mapping> Reflector::GetReflectionJSON() const {
|
|
if (!is_valid_) {
|
|
return nullptr;
|
|
}
|
|
|
|
auto json_string =
|
|
std::make_shared<std::string>(template_arguments_->dump(2u));
|
|
|
|
return std::make_shared<fml::NonOwnedMapping>(
|
|
reinterpret_cast<const uint8_t*>(json_string->data()),
|
|
json_string->size(), [json_string](auto, auto) {});
|
|
}
|
|
|
|
std::shared_ptr<fml::Mapping> Reflector::GetReflectionHeader() const {
|
|
return reflection_header_;
|
|
}
|
|
|
|
std::shared_ptr<fml::Mapping> Reflector::GetReflectionCC() const {
|
|
return reflection_cc_;
|
|
}
|
|
|
|
std::shared_ptr<RuntimeStageData::Shader> Reflector::GetRuntimeStageShaderData()
|
|
const {
|
|
return runtime_stage_shader_;
|
|
}
|
|
|
|
std::shared_ptr<ShaderBundleData> Reflector::GetShaderBundleData() const {
|
|
return shader_bundle_data_;
|
|
}
|
|
|
|
std::optional<nlohmann::json> 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<spirv_cross::ID> known_structs;
|
|
ir_->for_each_typed_id<spirv_cross::SPIRType>(
|
|
[&](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<fml::Mapping> Reflector::GenerateReflectionHeader() const {
|
|
return InflateTemplate(kReflectionHeaderTemplate);
|
|
}
|
|
|
|
std::shared_ptr<fml::Mapping> Reflector::GenerateReflectionCC() const {
|
|
return InflateTemplate(kReflectionCCTemplate);
|
|
}
|
|
|
|
static std::optional<RuntimeStageBackend> 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::kRuntimeStageGLES3:
|
|
return RuntimeStageBackend::kOpenGLES3;
|
|
case TargetPlatform::kRuntimeStageVulkan:
|
|
return RuntimeStageBackend::kVulkan;
|
|
case TargetPlatform::kSkSL:
|
|
return RuntimeStageBackend::kSkSL;
|
|
}
|
|
FML_UNREACHABLE();
|
|
}
|
|
|
|
std::shared_ptr<RuntimeStageData::Shader> 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<RuntimeStageData::Shader>();
|
|
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<spirv_cross::ID> uniforms =
|
|
SortUniforms(ir_.get(), compiler_.GetCompiler());
|
|
for (auto& sorted_id : uniforms) {
|
|
auto var = ir_->ids[sorted_id].get<spirv_cross::SPIRVariable>();
|
|
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<uint8_t> struct_layout;
|
|
size_t float_count = 0;
|
|
|
|
for (size_t i = 0; i < members.size(); i += 1) {
|
|
const auto& member = members[i];
|
|
std::vector<int> 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<size_t> 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<ShaderBundleData> 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<ShaderBundleData>(
|
|
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<size_t> 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<uint32_t> 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<fml::Mapping> 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<std::string>());
|
|
});
|
|
|
|
env.add_callback("to_shader_stage", 1u, [](inja::Arguments& args) {
|
|
return StringToShaderStage(args.at(0u)->get<std::string>());
|
|
});
|
|
|
|
env.add_callback("get_generator_name", 0u,
|
|
[type = compiler_.GetType()](inja::Arguments& args) {
|
|
return ToString(type);
|
|
});
|
|
|
|
auto inflated_template =
|
|
std::make_shared<std::string>(env.render(tmpl, *template_arguments_));
|
|
|
|
return std::make_shared<fml::NonOwnedMapping>(
|
|
reinterpret_cast<const uint8_t*>(inflated_template->data()),
|
|
inflated_template->size(), [inflated_template](auto, auto) {});
|
|
}
|
|
|
|
std::vector<size_t> Reflector::ComputeOffsets(
|
|
const spirv_cross::SmallVector<spirv_cross::Resource>& resources) const {
|
|
std::vector<size_t> 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<size_t> Reflector::GetOffset(
|
|
spirv_cross::ID id,
|
|
const std::vector<size_t>& offsets) const {
|
|
uint32_t location =
|
|
compiler_->get_decoration(id, spv::Decoration::DecorationLocation);
|
|
if (location >= offsets.size()) {
|
|
return std::nullopt;
|
|
}
|
|
return offsets[location];
|
|
}
|
|
|
|
std::optional<nlohmann::json::object_t> Reflector::ReflectResource(
|
|
const spirv_cross::Resource& resource,
|
|
std::optional<size_t> 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<nlohmann::json::object_t> 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<nlohmann::json::array_t> Reflector::ReflectResources(
|
|
const spirv_cross::SmallVector<spirv_cross::Resource>& resources,
|
|
bool compute_offsets) const {
|
|
nlohmann::json::array_t result;
|
|
result.reserve(resources.size());
|
|
std::vector<size_t> offsets;
|
|
if (compute_offsets) {
|
|
offsets = ComputeOffsets(resources);
|
|
}
|
|
for (const auto& resource : resources) {
|
|
std::optional<size_t> 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<KnownType> 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<StructMember>& members) {
|
|
auto struct_size = 0u;
|
|
for (const auto& member : members) {
|
|
struct_size += member.byte_length;
|
|
}
|
|
return struct_size;
|
|
}
|
|
|
|
std::vector<StructMember> 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<StructMember> 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<size_t>(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<sizeof(Matrix)>(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<sizeof(UintPoint32)>(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<sizeof(IPoint32)>(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<sizeof(Point)>(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<sizeof(Vector3)>(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<sizeof(Vector4)>(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<sizeof(HalfVector2)>(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<sizeof(HalfVector3)>(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<sizeof(HalfVector4)>(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::StructDefinition> 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<Reflector::StructDefinition> 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::StructDefinition>
|
|
Reflector::ReflectPerVertexStructDefinition(
|
|
const spirv_cross::SmallVector<spirv_cross::Resource>& 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<uint32_t> 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<std::string> 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::BindPrototype> Reflector::ReflectBindPrototypes(
|
|
const spirv_cross::ShaderResources& resources,
|
|
spv::ExecutionModel execution_model) const {
|
|
std::vector<BindPrototype> 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<const Texture>",
|
|
.argument_name = "texture",
|
|
});
|
|
proto.args.push_back(BindPrototypeArgument{
|
|
.type_name = "raw_ptr<const Sampler>",
|
|
.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<const Texture>",
|
|
.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<const Sampler>",
|
|
.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
|