Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 98 additions & 15 deletions engine/src/flutter/impeller/compiler/shader_bundle.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
// found in the LICENSE file.

#include "impeller/compiler/shader_bundle.h"

#include <filesystem>
#include <sstream>

#include "flutter/fml/file.h"
#include "flutter/fml/mapping.h"
#include "impeller/compiler/compiler.h"
#include "impeller/compiler/reflector.h"
#include "impeller/compiler/source_options.h"
Expand Down Expand Up @@ -85,7 +91,8 @@ static std::unique_ptr<fb::shaderbundle::BackendShaderT>
GenerateShaderBackendFB(TargetPlatform target_platform,
SourceOptions& options,
const std::string& shader_name,
const ShaderConfig& shader_config) {
const ShaderConfig& shader_config,
std::set<std::string>* out_dependencies) {
auto result = std::make_unique<fb::shaderbundle::BackendShaderT>();

std::shared_ptr<fml::FileMapping> source_file_mapping =
Expand Down Expand Up @@ -118,6 +125,17 @@ GenerateShaderBackendFB(TargetPlatform target_platform,
return nullptr;
}

// Record dependencies so the caller can emit a depfile. The shader's
// source file plus every transitive `#include` that contributed to
// the compilation. The same source is compiled across multiple
// target platforms; the std::set dedupes naturally.
if (out_dependencies) {
out_dependencies->insert(shader_config.source_file_name);
for (const auto& included : compiler.GetIncludedFileNames()) {
out_dependencies->insert(included);
}
}

auto reflector = compiler.GetReflector();
if (reflector == nullptr) {
std::cerr << "Could not create reflector for bundled shader \""
Expand Down Expand Up @@ -145,31 +163,37 @@ GenerateShaderBackendFB(TargetPlatform target_platform,
static std::unique_ptr<fb::shaderbundle::ShaderT> GenerateShaderFB(
SourceOptions options,
const std::string& shader_name,
const ShaderConfig& shader_config) {
const ShaderConfig& shader_config,
std::set<std::string>* out_dependencies) {
auto result = std::make_unique<fb::shaderbundle::ShaderT>();
result->name = shader_name;
result->metal_ios = GenerateShaderBackendFB(
TargetPlatform::kMetalIOS, options, shader_name, shader_config);
result->metal_ios =
GenerateShaderBackendFB(TargetPlatform::kMetalIOS, options, shader_name,
shader_config, out_dependencies);
if (!result->metal_ios) {
return nullptr;
}
result->metal_desktop = GenerateShaderBackendFB(
TargetPlatform::kMetalDesktop, options, shader_name, shader_config);
result->metal_desktop =
GenerateShaderBackendFB(TargetPlatform::kMetalDesktop, options,
shader_name, shader_config, out_dependencies);
if (!result->metal_desktop) {
return nullptr;
}
result->opengl_es = GenerateShaderBackendFB(
TargetPlatform::kOpenGLES, options, shader_name, shader_config);
result->opengl_es =
GenerateShaderBackendFB(TargetPlatform::kOpenGLES, options, shader_name,
shader_config, out_dependencies);
if (!result->opengl_es) {
return nullptr;
}
result->opengl_desktop = GenerateShaderBackendFB(
TargetPlatform::kOpenGLDesktop, options, shader_name, shader_config);
result->opengl_desktop =
GenerateShaderBackendFB(TargetPlatform::kOpenGLDesktop, options,
shader_name, shader_config, out_dependencies);
if (!result->opengl_desktop) {
return nullptr;
}
result->vulkan = GenerateShaderBackendFB(TargetPlatform::kVulkan, options,
shader_name, shader_config);
result->vulkan =
GenerateShaderBackendFB(TargetPlatform::kVulkan, options, shader_name,
shader_config, out_dependencies);
if (!result->vulkan) {
return nullptr;
}
Expand All @@ -178,7 +202,8 @@ static std::unique_ptr<fb::shaderbundle::ShaderT> GenerateShaderFB(

std::optional<fb::shaderbundle::ShaderBundleT> GenerateShaderBundleFlatbuffer(
const std::string& bundle_config_json,
const SourceOptions& options) {
const SourceOptions& options,
std::set<std::string>* out_dependencies) {
// --------------------------------------------------------------------------
/// 1. Parse the bundle configuration.
///
Expand All @@ -199,7 +224,7 @@ std::optional<fb::shaderbundle::ShaderBundleT> GenerateShaderBundleFlatbuffer(

for (const auto& [shader_name, shader_config] : bundle_config.value()) {
std::unique_ptr<fb::shaderbundle::ShaderT> shader =
GenerateShaderFB(options, shader_name, shader_config);
GenerateShaderFB(options, shader_name, shader_config, out_dependencies);
if (!shader) {
return std::nullopt;
}
Expand All @@ -209,13 +234,56 @@ std::optional<fb::shaderbundle::ShaderBundleT> GenerateShaderBundleFlatbuffer(
return shader_bundle;
}

/// Write a Ninja-style depfile listing every source file (including
/// `#include`d headers) that contributed to the shader bundle at
/// `target`.
///
/// Format mirrors `Compiler::CreateDepfileContents` for single-shader
/// compiles: `<target>: <dep1> <dep2> ... <depN>\n`.
/// See
/// https://github.com/ninja-build/ninja/blob/master/src/depfile_parser.cc#L28
static bool OutputBundleDepfile(const Switches& switches,
const std::string& target,
const std::set<std::string>& dependencies) {
std::stringstream stream;
stream << target << ":";
for (const auto& dep : dependencies) {
stream << " " << dep;
}
stream << "\n";
const auto contents = std::make_shared<std::string>(stream.str());
const fml::NonOwnedMapping mapping(
reinterpret_cast<const uint8_t*>(contents->data()), contents->size(),
[contents](auto, auto) {});

// Pass the relative path straight through; fml::WriteAtomically
// resolves it against switches.working_directory (a directory fd
// representing the build system's intended working dir, which may
// differ from std::filesystem::current_path()).
if (!fml::WriteAtomically(*switches.working_directory,
Utf8FromPath(switches.depfile_path).c_str(),
mapping)) {
std::cerr << "Could not write depfile to " << switches.depfile_path
<< std::endl;
return false;
}
return true;
}

bool GenerateShaderBundle(Switches& switches) {
// --------------------------------------------------------------------------
/// 1. Parse the shader bundle and generate the flatbuffer result.
///
/// Collect dependencies along the way so a depfile can be emitted
/// after the bundle is written. The same source file is compiled
/// across multiple target platforms; the std::set dedupes naturally.
///

std::set<std::string> dependencies;
const bool want_depfile = !switches.depfile_path.empty();
auto shader_bundle = GenerateShaderBundleFlatbuffer(
switches.shader_bundle, switches.CreateSourceOptions());
switches.shader_bundle, switches.CreateSourceOptions(),
want_depfile ? &dependencies : nullptr);
if (!shader_bundle.has_value()) {
// Specific error messages are already handled by
// GenerateShaderBundleFlatbuffer.
Expand Down Expand Up @@ -251,6 +319,21 @@ bool GenerateShaderBundle(Switches& switches) {
return false;
}

// --------------------------------------------------------------------------
/// 3. Output a depfile if one was requested.
///
/// Lets build systems (notably Dart's `hooks` framework, which
/// `flutter_gpu_shaders`' `buildShaderBundleJson` consumer goes
/// through) rerun the bundle build when any contributing source file
/// or `#include`d header changes.

if (want_depfile) {
if (!OutputBundleDepfile(switches, Utf8FromPath(sl_file_name),
dependencies)) {
return false;
}
}

return true;
}

Expand Down
11 changes: 10 additions & 1 deletion engine/src/flutter/impeller/compiler/shader_bundle.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#ifndef FLUTTER_IMPELLER_COMPILER_SHADER_BUNDLE_H_
#define FLUTTER_IMPELLER_COMPILER_SHADER_BUNDLE_H_

#include <set>

#include "impeller/compiler/source_options.h"
#include "impeller/compiler/switches.h"
#include "impeller/shader_bundle/shader_bundle_flatbuffers.h"
Expand All @@ -25,9 +27,16 @@ std::optional<ShaderBundleConfig> ParseShaderBundleConfig(
///
/// @note Exposed only for testing purposes. Use `GenerateShaderBundle`
/// directly.
///
/// @param out_dependencies Optional. When non-null, populated with the
/// set of source files (including transitive
/// `#include`s) that contributed to the
/// generated bundle. Used by `GenerateShaderBundle`
/// to emit a depfile when `--depfile` is set.
std::optional<fb::shaderbundle::ShaderBundleT> GenerateShaderBundleFlatbuffer(
const std::string& bundle_config_json,
const SourceOptions& options);
const SourceOptions& options,
std::set<std::string>* out_dependencies = nullptr);

/// @brief Parses the JSON shader bundle configuration and invokes the
/// compiler multiple times to produce a shader bundle flatbuffer, which
Expand Down
51 changes: 51 additions & 0 deletions engine/src/flutter/impeller/compiler/shader_bundle_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,57 @@ TEST(ShaderBundleTest, GenerateShaderBundleFlatbufferProducesCorrectResult) {
ASSERT_EQ(fragment->metal_desktop->inputs.size(), 0u);
}

TEST(ShaderBundleTest,
GenerateShaderBundleFlatbufferReportsSourceFilesAsDependencies) {
std::string fixtures_path = flutter::testing::GetFixturesPath();
const std::string fragment_path = fixtures_path + "/flutter_gpu_unlit.frag";
const std::string vertex_path = fixtures_path + "/flutter_gpu_unlit.vert";
std::string config =
"{\"UnlitFragment\": {\"type\": \"fragment\", \"file\":\"" +
fragment_path +
"\"}, \"UnlitVertex\": {\"type\": \"vertex\", \"file\": \"" +
vertex_path + "\"}}";

SourceOptions options;
options.target_platform = TargetPlatform::kRuntimeStageMetal;
options.source_language = SourceLanguage::kGLSL;

std::set<std::string> dependencies;
std::optional<fb::shaderbundle::ShaderBundleT> bundle =
GenerateShaderBundleFlatbuffer(config, options, &dependencies);
ASSERT_TRUE(bundle.has_value());

// Every primary source file referenced by the bundle config should
// appear in the dependency set, deduplicated across the multiple
// target-platform compiles of each shader. The fixtures used here
// don't contain `#include` directives, so the dependency set
// contains exactly the two source files.
EXPECT_NE(dependencies.find(fragment_path), dependencies.end());
EXPECT_NE(dependencies.find(vertex_path), dependencies.end());
}

TEST(ShaderBundleTest,
GenerateShaderBundleFlatbufferIgnoresNullDependencyCollector) {
// Passing nullptr as the dependency collector is supported and is
// the default behaviour for callers that don't need a depfile.
std::string fixtures_path = flutter::testing::GetFixturesPath();
std::string config =
"{\"UnlitFragment\": {\"type\": \"fragment\", \"file\": \"" +
fixtures_path +
"/flutter_gpu_unlit.frag\"}, \"UnlitVertex\": {\"type\": \"vertex\", "
"\"file\": \"" +
fixtures_path + "/flutter_gpu_unlit.vert\"}}";

SourceOptions options;
options.target_platform = TargetPlatform::kRuntimeStageMetal;
options.source_language = SourceLanguage::kGLSL;

std::optional<fb::shaderbundle::ShaderBundleT> bundle =
GenerateShaderBundleFlatbuffer(config, options, /*out_dependencies=*/
nullptr);
ASSERT_TRUE(bundle.has_value());
}

TEST(ShaderBundleTest, DeriveShaderFloatTypeFromDimensions) {
// Non-float types always map to nullopt.
EXPECT_EQ(DeriveShaderFloatType(ShaderType::kSignedInt, 1, 1), std::nullopt);
Expand Down
Loading