// 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/shader_bundle.h" #include "impeller/compiler/compiler.h" #include "impeller/compiler/reflector.h" #include "impeller/compiler/source_options.h" #include "impeller/compiler/types.h" #include "impeller/compiler/utilities.h" #include "impeller/runtime_stage/runtime_stage.h" #include "impeller/shader_bundle/shader_bundle_flatbuffers.h" #include "third_party/json/include/nlohmann/json.hpp" namespace impeller { namespace compiler { std::optional ParseShaderBundleConfig( const std::string& bundle_config_json, std::ostream& error_stream) { auto json = nlohmann::json::parse(bundle_config_json, nullptr, false); if (json.is_discarded() || !json.is_object()) { error_stream << "The shader bundle is not a valid JSON object." << std::endl; return std::nullopt; } ShaderBundleConfig bundle; for (auto& [shader_name, shader_value] : json.items()) { if (bundle.find(shader_name) != bundle.end()) { error_stream << "Duplicate shader \"" << shader_name << "\"." << std::endl; return std::nullopt; } if (!shader_value.is_object()) { error_stream << "Invalid shader entry \"" << shader_name << "\": Entry is not a JSON object." << std::endl; return std::nullopt; } ShaderConfig shader; if (!shader_value.contains("file")) { error_stream << "Invalid shader entry \"" << shader_name << "\": Missing required \"file\" field." << std::endl; return std::nullopt; } shader.source_file_name = shader_value["file"]; if (!shader_value.contains("type")) { error_stream << "Invalid shader entry \"" << shader_name << "\": Missing required \"type\" field." << std::endl; return std::nullopt; } shader.type = SourceTypeFromString(shader_value["type"]); if (shader.type == SourceType::kUnknown) { error_stream << "Invalid shader entry \"" << shader_name << "\": Shader type " << shader_value["type"] << " is unknown." << std::endl; return std::nullopt; } shader.language = shader_value.contains("language") ? ToSourceLanguage(shader_value["language"]) : SourceLanguage::kGLSL; if (shader.language == SourceLanguage::kUnknown) { error_stream << "Invalid shader entry \"" << shader_name << "\": Unknown language type " << shader_value["language"] << "." << std::endl; return std::nullopt; } shader.entry_point = shader_value.contains("entry_point") ? shader_value["entry_point"] : "main"; bundle[shader_name] = shader; } return bundle; } static std::unique_ptr GenerateShaderBackendFB(TargetPlatform target_platform, SourceOptions& options, const std::string& shader_name, const ShaderConfig& shader_config) { auto result = std::make_unique(); std::shared_ptr source_file_mapping = fml::FileMapping::CreateReadOnly(shader_config.source_file_name); if (!source_file_mapping) { std::cerr << "Could not open file for bundled shader \"" << shader_name << "\"." << std::endl; return nullptr; } /// Override options. options.target_platform = target_platform; options.file_name = shader_name; // This is just used for error messages. options.type = shader_config.type; options.source_language = shader_config.language; options.entry_point_name = EntryPointFunctionNameFromSourceName( shader_config.source_file_name, options.type, options.source_language, shader_config.entry_point); Reflector::Options reflector_options; reflector_options.target_platform = options.target_platform; reflector_options.entry_point_name = options.entry_point_name; reflector_options.shader_name = shader_name; Compiler compiler(source_file_mapping, options, reflector_options); if (!compiler.IsValid()) { std::cerr << "Compilation failed for bundled shader \"" << shader_name << "\"." << std::endl; std::cerr << compiler.GetErrorMessages() << std::endl; return nullptr; } auto reflector = compiler.GetReflector(); if (reflector == nullptr) { std::cerr << "Could not create reflector for bundled shader \"" << shader_name << "\"." << std::endl; return nullptr; } auto bundle_data = reflector->GetShaderBundleData(); if (!bundle_data) { std::cerr << "Bundled shader information was nil for \"" << shader_name << "\"." << std::endl; return nullptr; } result = bundle_data->CreateFlatbuffer(); if (!result) { std::cerr << "Failed to create flatbuffer for bundled shader \"" << shader_name << "\"." << std::endl; return nullptr; } return result; } static std::unique_ptr GenerateShaderFB( SourceOptions options, const std::string& shader_name, const ShaderConfig& shader_config) { auto result = std::make_unique(); result->name = shader_name; result->metal_ios = GenerateShaderBackendFB( TargetPlatform::kMetalIOS, options, shader_name, shader_config); if (!result->metal_ios) { return nullptr; } result->metal_desktop = GenerateShaderBackendFB( TargetPlatform::kMetalDesktop, options, shader_name, shader_config); if (!result->metal_desktop) { return nullptr; } result->opengl_es = GenerateShaderBackendFB( TargetPlatform::kOpenGLES, options, shader_name, shader_config); if (!result->opengl_es) { return nullptr; } result->opengl_desktop = GenerateShaderBackendFB( TargetPlatform::kOpenGLDesktop, options, shader_name, shader_config); if (!result->opengl_desktop) { return nullptr; } result->vulkan = GenerateShaderBackendFB(TargetPlatform::kVulkan, options, shader_name, shader_config); if (!result->vulkan) { return nullptr; } return result; } std::optional GenerateShaderBundleFlatbuffer( const std::string& bundle_config_json, const SourceOptions& options) { // -------------------------------------------------------------------------- /// 1. Parse the bundle configuration. /// std::optional bundle_config = ParseShaderBundleConfig(bundle_config_json, std::cerr); if (!bundle_config) { return std::nullopt; } // -------------------------------------------------------------------------- /// 2. Build the deserialized shader bundle. /// fb::shaderbundle::ShaderBundleT shader_bundle; for (const auto& [shader_name, shader_config] : bundle_config.value()) { std::unique_ptr shader = GenerateShaderFB(options, shader_name, shader_config); if (!shader) { return std::nullopt; } shader_bundle.shaders.push_back(std::move(shader)); } return shader_bundle; } bool GenerateShaderBundle(Switches& switches) { // -------------------------------------------------------------------------- /// 1. Parse the shader bundle and generate the flatbuffer result. /// auto shader_bundle = GenerateShaderBundleFlatbuffer( switches.shader_bundle, switches.CreateSourceOptions()); if (!shader_bundle.has_value()) { // Specific error messages are already handled by // GenerateShaderBundleFlatbuffer. return false; } // -------------------------------------------------------------------------- /// 2. Serialize the shader bundle and write to disk. /// auto builder = std::make_shared(); builder->Finish(fb::shaderbundle::ShaderBundle::Pack(*builder.get(), &shader_bundle.value()), fb::shaderbundle::ShaderBundleIdentifier()); auto mapping = std::make_shared( builder->GetBufferPointer(), builder->GetSize(), [builder](auto, auto) {}); auto sl_file_name = std::filesystem::absolute( std::filesystem::current_path() / switches.sl_file_name); if (!fml::WriteAtomically(*switches.working_directory, // Utf8FromPath(sl_file_name).c_str(), // *mapping // )) { std::cerr << "Could not write file to " << switches.sl_file_name << std::endl; return false; } // Tools that consume the runtime stage data expect the access mode to // be 0644. if (!SetPermissiveAccess(sl_file_name)) { return false; } return true; } } // namespace compiler } // namespace impeller