blob: 155029148cc51c73e8d55d346beb9b509cc9e0a9 [file] [log] [blame]
// 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<ShaderBundleConfig> 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<fb::shaderbundle::BackendShaderT>
GenerateShaderBackendFB(TargetPlatform target_platform,
SourceOptions& options,
const std::string& shader_name,
const ShaderConfig& shader_config) {
auto result = std::make_unique<fb::shaderbundle::BackendShaderT>();
std::shared_ptr<fml::FileMapping> 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<fb::shaderbundle::ShaderT> GenerateShaderFB(
SourceOptions options,
const std::string& shader_name,
const ShaderConfig& shader_config) {
auto result = std::make_unique<fb::shaderbundle::ShaderT>();
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<fb::shaderbundle::ShaderBundleT> GenerateShaderBundleFlatbuffer(
const std::string& bundle_config_json,
const SourceOptions& options) {
// --------------------------------------------------------------------------
/// 1. Parse the bundle configuration.
///
std::optional<ShaderBundleConfig> 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<fb::shaderbundle::ShaderT> 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<flatbuffers::FlatBufferBuilder>();
builder->Finish(fb::shaderbundle::ShaderBundle::Pack(*builder.get(),
&shader_bundle.value()),
fb::shaderbundle::ShaderBundleIdentifier());
auto mapping = std::make_shared<fml::NonOwnedMapping>(
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