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
90std::vector<std::string_view> GetShaderBundleTargetPlatformDefines(
91 TargetPlatform platform) {
92 switch (platform) {
94 return {"IMPELLER_TARGET_METAL", "IMPELLER_TARGET_METAL_IOS"};
96 return {"IMPELLER_TARGET_METAL", "IMPELLER_TARGET_METAL_DESKTOP"};
98 return {"IMPELLER_TARGET_OPENGLES"};
100 return {"IMPELLER_TARGET_OPENGL"};
102 return {"IMPELLER_TARGET_VULKAN"};
109 return {};
110 }
111 return {};
112}
113
114static std::unique_ptr<fb::shaderbundle::BackendShaderT>
116 SourceOptions& options,
117 const std::string& shader_name,
118 const ShaderConfig& shader_config,
119 std::set<std::string>* out_dependencies) {
120 auto result = std::make_unique<fb::shaderbundle::BackendShaderT>();
121
122 std::shared_ptr<fml::FileMapping> source_file_mapping =
124 if (!source_file_mapping) {
125 std::cerr << "Could not open file for bundled shader \"" << shader_name
126 << "\"." << std::endl;
127 return nullptr;
128 }
129
130 /// Override options.
131 options.target_platform = target_platform;
132 options.file_name = shader_name; // This is just used for error messages.
133 options.type = shader_config.type;
134 options.source_language = shader_config.language;
136 shader_config.source_file_name, options.type, options.source_language,
137 shader_config.entry_point);
138
139 // Inject the platform-discriminating defines (e.g. IMPELLER_TARGET_METAL) so
140 // bundled shaders can specialize per backend. These are added to a local copy
141 // because `options` is shared across every backend compiled here; pushing
142 // onto it directly would accumulate defines from previously compiled
143 // backends.
144 SourceOptions backend_options = options;
145 backend_options.target_platform = target_platform;
146 for (const auto& define :
147 GetShaderBundleTargetPlatformDefines(target_platform)) {
148 backend_options.defines.emplace_back(define);
149 }
150
151 Reflector::Options reflector_options;
152 reflector_options.target_platform = target_platform;
153 reflector_options.entry_point_name = options.entry_point_name;
154 reflector_options.shader_name = shader_name;
155
156 Compiler compiler(source_file_mapping, backend_options, reflector_options);
157 if (!compiler.IsValid()) {
158 std::cerr << "Compilation failed for bundled shader \"" << shader_name
159 << "\"." << std::endl;
160 std::cerr << compiler.GetErrorMessages() << std::endl;
161 return nullptr;
162 }
163
164 // Record dependencies so the caller can emit a depfile. The shader's
165 // source file plus every transitive `#include` that contributed to
166 // the compilation. The same source is compiled across multiple
167 // target platforms; the std::set dedupes naturally.
168 if (out_dependencies) {
169 out_dependencies->insert(shader_config.source_file_name);
170 for (const auto& included : compiler.GetIncludedFileNames()) {
171 out_dependencies->insert(included);
172 }
173 }
174
175 auto reflector = compiler.GetReflector();
176 if (reflector == nullptr) {
177 std::cerr << "Could not create reflector for bundled shader \""
178 << shader_name << "\"." << std::endl;
179 return nullptr;
180 }
181
182 auto bundle_data = reflector->GetShaderBundleData();
183 if (!bundle_data) {
184 std::cerr << "Bundled shader information was nil for \"" << shader_name
185 << "\"." << std::endl;
186 return nullptr;
187 }
188
189 result = bundle_data->CreateFlatbuffer();
190 if (!result) {
191 std::cerr << "Failed to create flatbuffer for bundled shader \""
192 << shader_name << "\"." << std::endl;
193 return nullptr;
194 }
195
196 return result;
197}
198
199static std::unique_ptr<fb::shaderbundle::ShaderT> GenerateShaderFB(
200 SourceOptions options,
201 const std::string& shader_name,
202 const ShaderConfig& shader_config,
203 std::set<std::string>* out_dependencies) {
204 auto result = std::make_unique<fb::shaderbundle::ShaderT>();
205 result->name = shader_name;
206 result->metal_ios =
208 shader_config, out_dependencies);
209 if (!result->metal_ios) {
210 return nullptr;
211 }
212 result->metal_desktop =
214 shader_name, shader_config, out_dependencies);
215 if (!result->metal_desktop) {
216 return nullptr;
217 }
218 result->opengl_es =
220 shader_config, out_dependencies);
221 if (!result->opengl_es) {
222 return nullptr;
223 }
224 result->opengl_desktop =
226 shader_name, shader_config, out_dependencies);
227 if (!result->opengl_desktop) {
228 return nullptr;
229 }
230 result->vulkan =
232 shader_config, out_dependencies);
233 if (!result->vulkan) {
234 return nullptr;
235 }
236 return result;
237}
238
239std::optional<fb::shaderbundle::ShaderBundleT> GenerateShaderBundleFlatbuffer(
240 const std::string& bundle_config_json,
241 const SourceOptions& options,
242 std::set<std::string>* out_dependencies) {
243 // --------------------------------------------------------------------------
244 /// 1. Parse the bundle configuration.
245 ///
246
247 std::optional<ShaderBundleConfig> bundle_config =
248 ParseShaderBundleConfig(bundle_config_json, std::cerr);
249 if (!bundle_config) {
250 return std::nullopt;
251 }
252
253 // --------------------------------------------------------------------------
254 /// 2. Build the deserialized shader bundle.
255 ///
256
257 fb::shaderbundle::ShaderBundleT shader_bundle;
258 shader_bundle.format_version = static_cast<uint32_t>(
259 fb::shaderbundle::ShaderBundleFormatVersion::kVersion);
260
261 for (const auto& [shader_name, shader_config] : bundle_config.value()) {
262 std::unique_ptr<fb::shaderbundle::ShaderT> shader =
263 GenerateShaderFB(options, shader_name, shader_config, out_dependencies);
264 if (!shader) {
265 return std::nullopt;
266 }
267 shader_bundle.shaders.push_back(std::move(shader));
268 }
269
270 return shader_bundle;
271}
272
273/// Write a Ninja-style depfile listing every source file (including
274/// `#include`d headers) that contributed to the shader bundle at
275/// `target`.
276///
277/// Format mirrors `Compiler::CreateDepfileContents` for single-shader
278/// compiles: `<target>: <dep1> <dep2> ... <depN>\n`.
279/// See
280/// https://github.com/ninja-build/ninja/blob/master/src/depfile_parser.cc#L28
281static bool OutputBundleDepfile(const Switches& switches,
282 const std::string& target,
283 const std::set<std::string>& dependencies) {
284 std::stringstream stream;
285 stream << target << ":";
286 for (const auto& dep : dependencies) {
287 stream << " " << dep;
288 }
289 stream << "\n";
290 const auto contents = std::make_shared<std::string>(stream.str());
291 const fml::NonOwnedMapping mapping(
292 reinterpret_cast<const uint8_t*>(contents->data()), contents->size(),
293 [contents](auto, auto) {});
294
295 // Pass the relative path straight through; fml::WriteAtomically
296 // resolves it against switches.working_directory (a directory fd
297 // representing the build system's intended working dir, which may
298 // differ from std::filesystem::current_path()).
300 Utf8FromPath(switches.depfile_path).c_str(),
301 mapping)) {
302 std::cerr << "Could not write depfile to " << switches.depfile_path
303 << std::endl;
304 return false;
305 }
306 return true;
307}
308
310 // --------------------------------------------------------------------------
311 /// 1. Parse the shader bundle and generate the flatbuffer result.
312 ///
313 /// Collect dependencies along the way so a depfile can be emitted
314 /// after the bundle is written. The same source file is compiled
315 /// across multiple target platforms; the std::set dedupes naturally.
316 ///
317
318 std::set<std::string> dependencies;
319 const bool want_depfile = !switches.depfile_path.empty();
320 auto shader_bundle = GenerateShaderBundleFlatbuffer(
321 switches.shader_bundle, switches.CreateSourceOptions(),
322 want_depfile ? &dependencies : nullptr);
323 if (!shader_bundle.has_value()) {
324 // Specific error messages are already handled by
325 // GenerateShaderBundleFlatbuffer.
326 return false;
327 }
328
329 // --------------------------------------------------------------------------
330 /// 2. Serialize the shader bundle and write to disk.
331 ///
332
333 auto builder = std::make_shared<flatbuffers::FlatBufferBuilder>();
334 builder->Finish(fb::shaderbundle::ShaderBundle::Pack(*builder.get(),
335 &shader_bundle.value()),
336 fb::shaderbundle::ShaderBundleIdentifier());
337 auto mapping = std::make_shared<fml::NonOwnedMapping>(
338 builder->GetBufferPointer(), builder->GetSize(),
339 [builder](auto, auto) {});
340
341 auto sl_file_name = std::filesystem::absolute(
342 std::filesystem::current_path() / switches.sl_file_name);
343
344 if (!fml::WriteAtomically(*switches.working_directory, //
345 Utf8FromPath(sl_file_name).c_str(), //
346 *mapping //
347 )) {
348 std::cerr << "Could not write file to " << switches.sl_file_name
349 << std::endl;
350 return false;
351 }
352 // Tools that consume the runtime stage data expect the access mode to
353 // be 0644.
354 if (!SetPermissiveAccess(sl_file_name)) {
355 return false;
356 }
357
358 // --------------------------------------------------------------------------
359 /// 3. Output a depfile if one was requested.
360 ///
361 /// Lets build systems (notably Dart's `hooks` framework, which
362 /// `flutter_gpu_shaders`' `buildShaderBundleJson` consumer goes
363 /// through) rerun the bundle build when any contributing source file
364 /// or `#include`d header changes.
365
366 if (want_depfile) {
367 if (!OutputBundleDepfile(switches, Utf8FromPath(sl_file_name),
368 dependencies)) {
369 return false;
370 }
371 }
372
373 return true;
374}
375
376} // namespace compiler
377} // namespace impeller
static std::unique_ptr< FileMapping > CreateReadOnly(const std::string &path)
Definition mapping.cc:20
const Reflector * GetReflector() const
Definition compiler.cc:748
const std::vector< std::string > & GetIncludedFileNames() const
Definition compiler.cc:709
std::string GetErrorMessages() const
Definition compiler.cc:701
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
std::vector< std::string_view > GetShaderBundleTargetPlatformDefines(TargetPlatform platform)
The platform-discriminating preprocessor defines injected when compiling a bundled shader for platfor...
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
std::vector< std::string > defines