Flutter Engine Uber Docs
Docs for the entire Flutter Engine repo.
 
Loading...
Searching...
No Matches
shader_bundle.cc
Go to the documentation of this file.
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
6
7#include <filesystem>
8#include <sstream>
9
10#include "flutter/fml/file.h"
11#include "flutter/fml/mapping.h"
16
19#include "impeller/shader_bundle/shader_bundle_flatbuffers.h"
20#include "third_party/json/include/nlohmann/json.hpp"
21
22namespace impeller {
23namespace compiler {
24
25std::optional<ShaderBundleConfig> ParseShaderBundleConfig(
26 const std::string& bundle_config_json,
27 std::ostream& error_stream) {
28 auto json = nlohmann::json::parse(bundle_config_json, nullptr, false);
29 if (json.is_discarded() || !json.is_object()) {
30 error_stream << "The shader bundle is not a valid JSON object."
31 << std::endl;
32 return std::nullopt;
33 }
34
35 ShaderBundleConfig bundle;
36 for (auto& [shader_name, shader_value] : json.items()) {
37 if (bundle.find(shader_name) != bundle.end()) {
38 error_stream << "Duplicate shader \"" << shader_name << "\"."
39 << std::endl;
40 return std::nullopt;
41 }
42 if (!shader_value.is_object()) {
43 error_stream << "Invalid shader entry \"" << shader_name
44 << "\": Entry is not a JSON object." << std::endl;
45 return std::nullopt;
46 }
47
48 ShaderConfig shader;
49
50 if (!shader_value.contains("file")) {
51 error_stream << "Invalid shader entry \"" << shader_name
52 << "\": Missing required \"file\" field." << std::endl;
53 return std::nullopt;
54 }
55 shader.source_file_name = shader_value["file"];
56
57 if (!shader_value.contains("type")) {
58 error_stream << "Invalid shader entry \"" << shader_name
59 << "\": Missing required \"type\" field." << std::endl;
60 return std::nullopt;
61 }
62 shader.type = SourceTypeFromString(shader_value["type"]);
63 if (shader.type == SourceType::kUnknown) {
64 error_stream << "Invalid shader entry \"" << shader_name
65 << "\": Shader type " << shader_value["type"]
66 << " is unknown." << std::endl;
67 return std::nullopt;
68 }
69
70 shader.language = shader_value.contains("language")
71 ? ToSourceLanguage(shader_value["language"])
73 if (shader.language == SourceLanguage::kUnknown) {
74 error_stream << "Invalid shader entry \"" << shader_name
75 << "\": Unknown language type " << shader_value["language"]
76 << "." << std::endl;
77 return std::nullopt;
78 }
79
80 shader.entry_point = shader_value.contains("entry_point")
81 ? shader_value["entry_point"]
82 : "main";
83
84 bundle[shader_name] = shader;
85 }
86
87 return bundle;
88}
89
90static std::unique_ptr<fb::shaderbundle::BackendShaderT>
92 SourceOptions& options,
93 const std::string& shader_name,
94 const ShaderConfig& shader_config,
95 std::set<std::string>* out_dependencies) {
96 auto result = std::make_unique<fb::shaderbundle::BackendShaderT>();
97
98 std::shared_ptr<fml::FileMapping> source_file_mapping =
100 if (!source_file_mapping) {
101 std::cerr << "Could not open file for bundled shader \"" << shader_name
102 << "\"." << std::endl;
103 return nullptr;
104 }
105
106 /// Override options.
107 options.target_platform = target_platform;
108 options.file_name = shader_name; // This is just used for error messages.
109 options.type = shader_config.type;
110 options.source_language = shader_config.language;
112 shader_config.source_file_name, options.type, options.source_language,
113 shader_config.entry_point);
114
115 Reflector::Options reflector_options;
116 reflector_options.target_platform = options.target_platform;
117 reflector_options.entry_point_name = options.entry_point_name;
118 reflector_options.shader_name = shader_name;
119
120 Compiler compiler(source_file_mapping, options, reflector_options);
121 if (!compiler.IsValid()) {
122 std::cerr << "Compilation failed for bundled shader \"" << shader_name
123 << "\"." << std::endl;
124 std::cerr << compiler.GetErrorMessages() << std::endl;
125 return nullptr;
126 }
127
128 // Record dependencies so the caller can emit a depfile. The shader's
129 // source file plus every transitive `#include` that contributed to
130 // the compilation. The same source is compiled across multiple
131 // target platforms; the std::set dedupes naturally.
132 if (out_dependencies) {
133 out_dependencies->insert(shader_config.source_file_name);
134 for (const auto& included : compiler.GetIncludedFileNames()) {
135 out_dependencies->insert(included);
136 }
137 }
138
139 auto reflector = compiler.GetReflector();
140 if (reflector == nullptr) {
141 std::cerr << "Could not create reflector for bundled shader \""
142 << shader_name << "\"." << std::endl;
143 return nullptr;
144 }
145
146 auto bundle_data = reflector->GetShaderBundleData();
147 if (!bundle_data) {
148 std::cerr << "Bundled shader information was nil for \"" << shader_name
149 << "\"." << std::endl;
150 return nullptr;
151 }
152
153 result = bundle_data->CreateFlatbuffer();
154 if (!result) {
155 std::cerr << "Failed to create flatbuffer for bundled shader \""
156 << shader_name << "\"." << std::endl;
157 return nullptr;
158 }
159
160 return result;
161}
162
163static std::unique_ptr<fb::shaderbundle::ShaderT> GenerateShaderFB(
164 SourceOptions options,
165 const std::string& shader_name,
166 const ShaderConfig& shader_config,
167 std::set<std::string>* out_dependencies) {
168 auto result = std::make_unique<fb::shaderbundle::ShaderT>();
169 result->name = shader_name;
170 result->metal_ios =
172 shader_config, out_dependencies);
173 if (!result->metal_ios) {
174 return nullptr;
175 }
176 result->metal_desktop =
178 shader_name, shader_config, out_dependencies);
179 if (!result->metal_desktop) {
180 return nullptr;
181 }
182 result->opengl_es =
184 shader_config, out_dependencies);
185 if (!result->opengl_es) {
186 return nullptr;
187 }
188 result->opengl_desktop =
190 shader_name, shader_config, out_dependencies);
191 if (!result->opengl_desktop) {
192 return nullptr;
193 }
194 result->vulkan =
196 shader_config, out_dependencies);
197 if (!result->vulkan) {
198 return nullptr;
199 }
200 return result;
201}
202
203std::optional<fb::shaderbundle::ShaderBundleT> GenerateShaderBundleFlatbuffer(
204 const std::string& bundle_config_json,
205 const SourceOptions& options,
206 std::set<std::string>* out_dependencies) {
207 // --------------------------------------------------------------------------
208 /// 1. Parse the bundle configuration.
209 ///
210
211 std::optional<ShaderBundleConfig> bundle_config =
212 ParseShaderBundleConfig(bundle_config_json, std::cerr);
213 if (!bundle_config) {
214 return std::nullopt;
215 }
216
217 // --------------------------------------------------------------------------
218 /// 2. Build the deserialized shader bundle.
219 ///
220
221 fb::shaderbundle::ShaderBundleT shader_bundle;
222 shader_bundle.format_version = static_cast<uint32_t>(
223 fb::shaderbundle::ShaderBundleFormatVersion::kVersion);
224
225 for (const auto& [shader_name, shader_config] : bundle_config.value()) {
226 std::unique_ptr<fb::shaderbundle::ShaderT> shader =
227 GenerateShaderFB(options, shader_name, shader_config, out_dependencies);
228 if (!shader) {
229 return std::nullopt;
230 }
231 shader_bundle.shaders.push_back(std::move(shader));
232 }
233
234 return shader_bundle;
235}
236
237/// Write a Ninja-style depfile listing every source file (including
238/// `#include`d headers) that contributed to the shader bundle at
239/// `target`.
240///
241/// Format mirrors `Compiler::CreateDepfileContents` for single-shader
242/// compiles: `<target>: <dep1> <dep2> ... <depN>\n`.
243/// See
244/// https://github.com/ninja-build/ninja/blob/master/src/depfile_parser.cc#L28
245static bool OutputBundleDepfile(const Switches& switches,
246 const std::string& target,
247 const std::set<std::string>& dependencies) {
248 std::stringstream stream;
249 stream << target << ":";
250 for (const auto& dep : dependencies) {
251 stream << " " << dep;
252 }
253 stream << "\n";
254 const auto contents = std::make_shared<std::string>(stream.str());
255 const fml::NonOwnedMapping mapping(
256 reinterpret_cast<const uint8_t*>(contents->data()), contents->size(),
257 [contents](auto, auto) {});
258
259 // Pass the relative path straight through; fml::WriteAtomically
260 // resolves it against switches.working_directory (a directory fd
261 // representing the build system's intended working dir, which may
262 // differ from std::filesystem::current_path()).
264 Utf8FromPath(switches.depfile_path).c_str(),
265 mapping)) {
266 std::cerr << "Could not write depfile to " << switches.depfile_path
267 << std::endl;
268 return false;
269 }
270 return true;
271}
272
274 // --------------------------------------------------------------------------
275 /// 1. Parse the shader bundle and generate the flatbuffer result.
276 ///
277 /// Collect dependencies along the way so a depfile can be emitted
278 /// after the bundle is written. The same source file is compiled
279 /// across multiple target platforms; the std::set dedupes naturally.
280 ///
281
282 std::set<std::string> dependencies;
283 const bool want_depfile = !switches.depfile_path.empty();
284 auto shader_bundle = GenerateShaderBundleFlatbuffer(
285 switches.shader_bundle, switches.CreateSourceOptions(),
286 want_depfile ? &dependencies : nullptr);
287 if (!shader_bundle.has_value()) {
288 // Specific error messages are already handled by
289 // GenerateShaderBundleFlatbuffer.
290 return false;
291 }
292
293 // --------------------------------------------------------------------------
294 /// 2. Serialize the shader bundle and write to disk.
295 ///
296
297 auto builder = std::make_shared<flatbuffers::FlatBufferBuilder>();
298 builder->Finish(fb::shaderbundle::ShaderBundle::Pack(*builder.get(),
299 &shader_bundle.value()),
300 fb::shaderbundle::ShaderBundleIdentifier());
301 auto mapping = std::make_shared<fml::NonOwnedMapping>(
302 builder->GetBufferPointer(), builder->GetSize(),
303 [builder](auto, auto) {});
304
305 auto sl_file_name = std::filesystem::absolute(
306 std::filesystem::current_path() / switches.sl_file_name);
307
308 if (!fml::WriteAtomically(*switches.working_directory, //
309 Utf8FromPath(sl_file_name).c_str(), //
310 *mapping //
311 )) {
312 std::cerr << "Could not write file to " << switches.sl_file_name
313 << std::endl;
314 return false;
315 }
316 // Tools that consume the runtime stage data expect the access mode to
317 // be 0644.
318 if (!SetPermissiveAccess(sl_file_name)) {
319 return false;
320 }
321
322 // --------------------------------------------------------------------------
323 /// 3. Output a depfile if one was requested.
324 ///
325 /// Lets build systems (notably Dart's `hooks` framework, which
326 /// `flutter_gpu_shaders`' `buildShaderBundleJson` consumer goes
327 /// through) rerun the bundle build when any contributing source file
328 /// or `#include`d header changes.
329
330 if (want_depfile) {
331 if (!OutputBundleDepfile(switches, Utf8FromPath(sl_file_name),
332 dependencies)) {
333 return false;
334 }
335 }
336
337 return true;
338}
339
340} // namespace compiler
341} // namespace impeller
static std::unique_ptr< FileMapping > CreateReadOnly(const std::string &path)
Definition mapping.cc:20
const Reflector * GetReflector() const
Definition compiler.cc:663
const std::vector< std::string > & GetIncludedFileNames() const
Definition compiler.cc:624
std::string GetErrorMessages() const
Definition compiler.cc:616
std::shared_ptr< ShaderBundleData > GetShaderBundleData() const
Definition reflector.cc:136
std::filesystem::path sl_file_name
Definition switches.h:31
std::shared_ptr< fml::UniqueFD > working_directory
Definition switches.h:24
SourceOptions CreateSourceOptions() const
Definition switches.cc:319
std::filesystem::path depfile_path
Definition switches.h:38
uint32_t * target
bool WriteAtomically(const fml::UniqueFD &base_directory, const char *file_name, const Mapping &mapping)
bool SetPermissiveAccess(const std::filesystem::path &p)
Sets the file access mode of the file at path 'p' to 0644.
Definition utilities.cc:16
static std::unique_ptr< fb::shaderbundle::ShaderT > GenerateShaderFB(SourceOptions options, const std::string &shader_name, const ShaderConfig &shader_config, std::set< std::string > *out_dependencies)
static std::unique_ptr< fb::shaderbundle::BackendShaderT > GenerateShaderBackendFB(TargetPlatform target_platform, SourceOptions &options, const std::string &shader_name, const ShaderConfig &shader_config, std::set< std::string > *out_dependencies)
std::optional< fb::shaderbundle::ShaderBundleT > GenerateShaderBundleFlatbuffer(const std::string &bundle_config_json, const SourceOptions &options, std::set< std::string > *out_dependencies)
Parses the JSON shader bundle configuration and invokes the compiler multiple times to produce a shad...
bool GenerateShaderBundle(Switches &switches)
Parses the JSON shader bundle configuration and invokes the compiler multiple times to produce a shad...
std::unordered_map< std::string, ShaderConfig > ShaderBundleConfig
Definition types.h:97
std::string EntryPointFunctionNameFromSourceName(const std::filesystem::path &file_name, SourceType type, SourceLanguage source_language, const std::string &entry_point_name)
Definition types.cc:101
SourceType SourceTypeFromString(std::string name)
Definition types.cc:34
std::optional< ShaderBundleConfig > ParseShaderBundleConfig(const std::string &bundle_config_json, std::ostream &error_stream)
Parse a shader bundle configuration from a given JSON string.
static bool OutputBundleDepfile(const Switches &switches, const std::string &target, const std::set< std::string > &dependencies)
std::string Utf8FromPath(const std::filesystem::path &path)
Converts a native format path to a utf8 string.
Definition utilities.cc:30
SourceLanguage ToSourceLanguage(const std::string &source_language)
Definition types.cc:52
A shader config parsed as part of a ShaderBundleConfig.
Definition types.h:90
std::filesystem::path file_name