| // 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. |
| |
| #include "impeller/compiler/runtime_stage_data.h" |
| |
| #include <array> |
| #include <cstdint> |
| #include <memory> |
| #include <optional> |
| |
| #include "fml/backtrace.h" |
| #include "impeller/core/runtime_types.h" |
| #include "inja/inja.hpp" |
| |
| #include "impeller/base/validation.h" |
| #include "impeller/runtime_stage/runtime_stage_flatbuffers.h" |
| #include "runtime_stage_types_flatbuffers.h" |
| |
| namespace impeller { |
| namespace compiler { |
| |
| RuntimeStageData::RuntimeStageData() = default; |
| |
| RuntimeStageData::~RuntimeStageData() = default; |
| |
| void RuntimeStageData::AddShader(const std::shared_ptr<Shader>& data) { |
| FML_DCHECK(data); |
| FML_DCHECK(data_.find(data->backend) == data_.end()); |
| data_[data->backend] = data; |
| } |
| |
| static std::optional<fb::Stage> ToStage(spv::ExecutionModel stage) { |
| switch (stage) { |
| case spv::ExecutionModel::ExecutionModelVertex: |
| return fb::Stage::kVertex; |
| case spv::ExecutionModel::ExecutionModelFragment: |
| return fb::Stage::kFragment; |
| case spv::ExecutionModel::ExecutionModelGLCompute: |
| return fb::Stage::kCompute; |
| default: |
| return std::nullopt; |
| } |
| FML_UNREACHABLE(); |
| } |
| |
| static std::optional<fb::Stage> ToJsonStage(spv::ExecutionModel stage) { |
| switch (stage) { |
| case spv::ExecutionModel::ExecutionModelVertex: |
| return fb::Stage::kVertex; |
| case spv::ExecutionModel::ExecutionModelFragment: |
| return fb::Stage::kFragment; |
| case spv::ExecutionModel::ExecutionModelGLCompute: |
| return fb::Stage::kCompute; |
| default: |
| return std::nullopt; |
| } |
| FML_UNREACHABLE(); |
| } |
| |
| static std::optional<fb::UniformDataType> ToUniformType( |
| spirv_cross::SPIRType::BaseType type) { |
| switch (type) { |
| case spirv_cross::SPIRType::Float: |
| return fb::UniformDataType::kFloat; |
| case spirv_cross::SPIRType::SampledImage: |
| return fb::UniformDataType::kSampledImage; |
| case spirv_cross::SPIRType::Struct: |
| return fb::UniformDataType::kStruct; |
| case spirv_cross::SPIRType::Boolean: |
| case spirv_cross::SPIRType::SByte: |
| case spirv_cross::SPIRType::UByte: |
| case spirv_cross::SPIRType::Short: |
| case spirv_cross::SPIRType::UShort: |
| case spirv_cross::SPIRType::Int: |
| case spirv_cross::SPIRType::UInt: |
| case spirv_cross::SPIRType::Int64: |
| case spirv_cross::SPIRType::UInt64: |
| case spirv_cross::SPIRType::Half: |
| case spirv_cross::SPIRType::Double: |
| case spirv_cross::SPIRType::AccelerationStructure: |
| case spirv_cross::SPIRType::AtomicCounter: |
| case spirv_cross::SPIRType::Char: |
| case spirv_cross::SPIRType::ControlPointArray: |
| case spirv_cross::SPIRType::Image: |
| case spirv_cross::SPIRType::Interpolant: |
| case spirv_cross::SPIRType::RayQuery: |
| case spirv_cross::SPIRType::Sampler: |
| case spirv_cross::SPIRType::Unknown: |
| case spirv_cross::SPIRType::Void: |
| return std::nullopt; |
| } |
| FML_UNREACHABLE(); |
| } |
| static std::optional<fb::InputDataType> ToInputType( |
| spirv_cross::SPIRType::BaseType type) { |
| switch (type) { |
| case spirv_cross::SPIRType::Boolean: |
| return fb::InputDataType::kBoolean; |
| case spirv_cross::SPIRType::SByte: |
| return fb::InputDataType::kSignedByte; |
| case spirv_cross::SPIRType::UByte: |
| return fb::InputDataType::kUnsignedByte; |
| case spirv_cross::SPIRType::Short: |
| return fb::InputDataType::kSignedShort; |
| case spirv_cross::SPIRType::UShort: |
| return fb::InputDataType::kUnsignedShort; |
| case spirv_cross::SPIRType::Int: |
| return fb::InputDataType::kSignedInt; |
| case spirv_cross::SPIRType::UInt: |
| return fb::InputDataType::kUnsignedInt; |
| case spirv_cross::SPIRType::Int64: |
| return fb::InputDataType::kSignedInt64; |
| case spirv_cross::SPIRType::UInt64: |
| return fb::InputDataType::kUnsignedInt64; |
| case spirv_cross::SPIRType::Float: |
| return fb::InputDataType::kFloat; |
| case spirv_cross::SPIRType::Double: |
| return fb::InputDataType::kDouble; |
| case spirv_cross::SPIRType::Unknown: |
| case spirv_cross::SPIRType::Void: |
| case spirv_cross::SPIRType::Half: |
| case spirv_cross::SPIRType::AtomicCounter: |
| case spirv_cross::SPIRType::Struct: |
| case spirv_cross::SPIRType::Image: |
| case spirv_cross::SPIRType::SampledImage: |
| case spirv_cross::SPIRType::Sampler: |
| case spirv_cross::SPIRType::AccelerationStructure: |
| case spirv_cross::SPIRType::RayQuery: |
| case spirv_cross::SPIRType::ControlPointArray: |
| case spirv_cross::SPIRType::Interpolant: |
| case spirv_cross::SPIRType::Char: |
| return std::nullopt; |
| } |
| FML_UNREACHABLE(); |
| } |
| |
| static std::optional<uint32_t> ToJsonType( |
| spirv_cross::SPIRType::BaseType type) { |
| switch (type) { |
| case spirv_cross::SPIRType::Boolean: |
| return 0; // fb::UniformDataType::kBoolean; |
| case spirv_cross::SPIRType::SByte: |
| return 1; // fb::UniformDataType::kSignedByte; |
| case spirv_cross::SPIRType::UByte: |
| return 2; // fb::UniformDataType::kUnsignedByte; |
| case spirv_cross::SPIRType::Short: |
| return 3; // fb::UniformDataType::kSignedShort; |
| case spirv_cross::SPIRType::UShort: |
| return 4; // fb::UniformDataType::kUnsignedShort; |
| case spirv_cross::SPIRType::Int: |
| return 5; // fb::UniformDataType::kSignedInt; |
| case spirv_cross::SPIRType::UInt: |
| return 6; // fb::UniformDataType::kUnsignedInt; |
| case spirv_cross::SPIRType::Int64: |
| return 7; // fb::UniformDataType::kSignedInt64; |
| case spirv_cross::SPIRType::UInt64: |
| return 8; // fb::UniformDataType::kUnsignedInt64; |
| case spirv_cross::SPIRType::Half: |
| return 9; // b::UniformDataType::kHalfFloat; |
| case spirv_cross::SPIRType::Float: |
| return 10; // fb::UniformDataType::kFloat; |
| case spirv_cross::SPIRType::Double: |
| return 11; // fb::UniformDataType::kDouble; |
| case spirv_cross::SPIRType::SampledImage: |
| return 12; // fb::UniformDataType::kSampledImage; |
| case spirv_cross::SPIRType::Struct: |
| return 13; |
| case spirv_cross::SPIRType::AccelerationStructure: |
| case spirv_cross::SPIRType::AtomicCounter: |
| case spirv_cross::SPIRType::Char: |
| case spirv_cross::SPIRType::ControlPointArray: |
| case spirv_cross::SPIRType::Image: |
| case spirv_cross::SPIRType::Interpolant: |
| case spirv_cross::SPIRType::RayQuery: |
| case spirv_cross::SPIRType::Sampler: |
| case spirv_cross::SPIRType::Unknown: |
| case spirv_cross::SPIRType::Void: |
| return std::nullopt; |
| } |
| FML_UNREACHABLE(); |
| } |
| |
| static const char* kStageKey = "stage"; |
| static const char* kTargetPlatformKey = "target_platform"; |
| static const char* kEntrypointKey = "entrypoint"; |
| static const char* kUniformsKey = "uniforms"; |
| static const char* kShaderKey = "shader"; |
| static const char* kUniformNameKey = "name"; |
| static const char* kUniformLocationKey = "location"; |
| static const char* kUniformTypeKey = "type"; |
| static const char* kUniformRowsKey = "rows"; |
| static const char* kUniformColumnsKey = "columns"; |
| static const char* kUniformBitWidthKey = "bit_width"; |
| static const char* kUniformArrayElementsKey = "array_elements"; |
| |
| static std::string RuntimeStageBackendToString(RuntimeStageBackend backend) { |
| switch (backend) { |
| case RuntimeStageBackend::kSkSL: |
| return "sksl"; |
| case RuntimeStageBackend::kMetal: |
| return "metal"; |
| case RuntimeStageBackend::kOpenGLES: |
| return "opengles"; |
| case RuntimeStageBackend::kVulkan: |
| return "vulkan"; |
| } |
| } |
| |
| std::shared_ptr<fml::Mapping> RuntimeStageData::CreateJsonMapping() const { |
| // Runtime Stage Data JSON format |
| // { |
| // "sksl": { |
| // "stage": 0, |
| // "entrypoint": "", |
| // "shader": "", |
| // "uniforms": [ |
| // { |
| // "name": "..", |
| // "location": 0, |
| // "type": 0, |
| // "rows": 0, |
| // "columns": 0, |
| // "bit_width": 0, |
| // "array_elements": 0, |
| // } |
| // ] |
| // }, |
| // "metal": ... |
| // }, |
| nlohmann::json root; |
| |
| for (const auto& kvp : data_) { |
| nlohmann::json platform_object; |
| |
| const auto stage = ToJsonStage(kvp.second->stage); |
| if (!stage.has_value()) { |
| VALIDATION_LOG << "Invalid runtime stage."; |
| return nullptr; |
| } |
| platform_object[kStageKey] = static_cast<uint32_t>(stage.value()); |
| platform_object[kEntrypointKey] = kvp.second->entrypoint; |
| |
| if (kvp.second->shader->GetSize() > 0u) { |
| std::string shader( |
| reinterpret_cast<const char*>(kvp.second->shader->GetMapping()), |
| kvp.second->shader->GetSize()); |
| platform_object[kShaderKey] = shader.c_str(); |
| } |
| |
| auto& uniforms = platform_object[kUniformsKey] = nlohmann::json::array_t{}; |
| for (const auto& uniform : kvp.second->uniforms) { |
| nlohmann::json uniform_object; |
| uniform_object[kUniformNameKey] = uniform.name.c_str(); |
| uniform_object[kUniformLocationKey] = uniform.location; |
| uniform_object[kUniformRowsKey] = uniform.rows; |
| uniform_object[kUniformColumnsKey] = uniform.columns; |
| |
| auto uniform_type = ToJsonType(uniform.type); |
| if (!uniform_type.has_value()) { |
| VALIDATION_LOG << "Invalid uniform type for runtime stage."; |
| return nullptr; |
| } |
| |
| uniform_object[kUniformTypeKey] = uniform_type.value(); |
| uniform_object[kUniformBitWidthKey] = uniform.bit_width; |
| uniform_object[kUniformArrayElementsKey] = |
| uniform.array_elements.value_or(0); |
| |
| uniforms.push_back(uniform_object); |
| } |
| |
| root[RuntimeStageBackendToString(kvp.first)] = platform_object; |
| } |
| |
| auto json_string = std::make_shared<std::string>(root.dump(2u)); |
| |
| return std::make_shared<fml::NonOwnedMapping>( |
| reinterpret_cast<const uint8_t*>(json_string->data()), |
| json_string->size(), [json_string](auto, auto) {}); |
| } |
| |
| std::unique_ptr<fb::RuntimeStageT> RuntimeStageData::CreateStageFlatbuffer( |
| impeller::RuntimeStageBackend backend) const { |
| auto kvp = data_.find(backend); |
| if (kvp == data_.end()) { |
| return nullptr; |
| } |
| |
| auto runtime_stage = std::make_unique<fb::RuntimeStageT>(); |
| runtime_stage->entrypoint = kvp->second->entrypoint; |
| const auto stage = ToStage(kvp->second->stage); |
| if (!stage.has_value()) { |
| VALIDATION_LOG << "Invalid runtime stage."; |
| return nullptr; |
| } |
| runtime_stage->stage = stage.value(); |
| if (!kvp->second->shader) { |
| VALIDATION_LOG << "No shader specified for runtime stage."; |
| return nullptr; |
| } |
| if (kvp->second->shader->GetSize() > 0u) { |
| runtime_stage->shader = { |
| kvp->second->shader->GetMapping(), |
| kvp->second->shader->GetMapping() + kvp->second->shader->GetSize()}; |
| } |
| for (const auto& uniform : kvp->second->uniforms) { |
| auto desc = std::make_unique<fb::UniformDescriptionT>(); |
| |
| desc->name = uniform.name; |
| if (desc->name.empty()) { |
| VALIDATION_LOG << "Uniform name cannot be empty."; |
| return nullptr; |
| } |
| desc->location = uniform.location; |
| desc->rows = uniform.rows; |
| desc->columns = uniform.columns; |
| auto uniform_type = ToUniformType(uniform.type); |
| if (!uniform_type.has_value()) { |
| VALIDATION_LOG << "Invalid uniform type for runtime stage."; |
| return nullptr; |
| } |
| desc->type = uniform_type.value(); |
| desc->bit_width = uniform.bit_width; |
| if (uniform.array_elements.has_value()) { |
| desc->array_elements = uniform.array_elements.value(); |
| } |
| |
| for (const auto& byte_type : uniform.struct_layout) { |
| desc->struct_layout.push_back(static_cast<fb::StructByteType>(byte_type)); |
| } |
| desc->struct_float_count = uniform.struct_float_count; |
| |
| runtime_stage->uniforms.emplace_back(std::move(desc)); |
| } |
| |
| for (const auto& input : kvp->second->inputs) { |
| auto desc = std::make_unique<fb::StageInputT>(); |
| |
| desc->name = input.name; |
| |
| if (desc->name.empty()) { |
| VALIDATION_LOG << "Stage input name cannot be empty."; |
| return nullptr; |
| } |
| desc->location = input.location; |
| desc->set = input.set; |
| desc->binding = input.binding; |
| auto input_type = ToInputType(input.type); |
| if (!input_type.has_value()) { |
| VALIDATION_LOG << "Invalid uniform type for runtime stage."; |
| return nullptr; |
| } |
| desc->type = input_type.value(); |
| desc->bit_width = input.bit_width; |
| desc->vec_size = input.vec_size; |
| desc->columns = input.columns; |
| desc->offset = input.offset; |
| |
| runtime_stage->inputs.emplace_back(std::move(desc)); |
| } |
| |
| return runtime_stage; |
| } |
| |
| std::unique_ptr<fb::RuntimeStagesT> |
| RuntimeStageData::CreateMultiStageFlatbuffer() const { |
| // The high level object API is used here for writing to the buffer. This is |
| // just a convenience. |
| auto runtime_stages = std::make_unique<fb::RuntimeStagesT>(); |
| |
| for (const auto& kvp : data_) { |
| auto runtime_stage = CreateStageFlatbuffer(kvp.first); |
| switch (kvp.first) { |
| case RuntimeStageBackend::kSkSL: |
| runtime_stages->sksl = std::move(runtime_stage); |
| break; |
| case RuntimeStageBackend::kMetal: |
| runtime_stages->metal = std::move(runtime_stage); |
| break; |
| case RuntimeStageBackend::kOpenGLES: |
| runtime_stages->opengles = std::move(runtime_stage); |
| break; |
| case RuntimeStageBackend::kVulkan: |
| runtime_stages->vulkan = std::move(runtime_stage); |
| break; |
| } |
| } |
| return runtime_stages; |
| } |
| |
| std::shared_ptr<fml::Mapping> RuntimeStageData::CreateMapping() const { |
| auto runtime_stages = CreateMultiStageFlatbuffer(); |
| if (!runtime_stages) { |
| return nullptr; |
| } |
| |
| auto builder = std::make_shared<flatbuffers::FlatBufferBuilder>(); |
| builder->Finish(fb::RuntimeStages::Pack(*builder.get(), runtime_stages.get()), |
| fb::RuntimeStagesIdentifier()); |
| return std::make_shared<fml::NonOwnedMapping>(builder->GetBufferPointer(), |
| builder->GetSize(), |
| [builder](auto, auto) {}); |
| } |
| |
| } // namespace compiler |
| } // namespace impeller |