Flutter Engine Uber Docs
Docs for the entire Flutter Engine repo.
 
Loading...
Searching...
No Matches
shader_library.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 <memory>
8#include <optional>
9#include <utility>
10#include <vector>
11
16#include "fml/mapping.h"
17#include "fml/memory/ref_ptr.h"
21#include "impeller/shader_bundle/shader_bundle_flatbuffers.h"
22#include "lib/gpu/context.h"
24
25namespace flutter {
26namespace gpu {
27
29
30fml::RefPtr<ShaderLibrary> ShaderLibrary::override_shader_library_;
31
34 const std::string& name,
35 std::string& out_error) {
36 if (override_shader_library_) {
37 return override_shader_library_;
38 }
39
40 auto dart_state = UIDartState::Current();
41 std::shared_ptr<AssetManager> asset_manager =
42 dart_state->platform_configuration()->client()->GetAssetManager();
43
44 std::unique_ptr<fml::Mapping> data = asset_manager->GetAsMapping(name);
45 if (data == nullptr) {
46 out_error = std::string("Asset '") + name + std::string("' not found.");
47 return nullptr;
48 }
49
50 return MakeFromFlatbuffer(backend_type, std::move(data), name);
51}
52
54 return fml::MakeRefCounted<flutter::gpu::ShaderLibrary>(nullptr,
55 std::move(shaders));
56}
57
59 impeller::fb::shaderbundle::ShaderStage stage) {
60 switch (stage) {
61 case impeller::fb::shaderbundle::ShaderStage::kVertex:
63 case impeller::fb::shaderbundle::ShaderStage::kFragment:
65 case impeller::fb::shaderbundle::ShaderStage::kCompute:
67 }
69}
70
72 impeller::fb::shaderbundle::InputDataType input_type) {
73 switch (input_type) {
74 case impeller::fb::shaderbundle::InputDataType::kBoolean:
76 case impeller::fb::shaderbundle::InputDataType::kSignedByte:
78 case impeller::fb::shaderbundle::InputDataType::kUnsignedByte:
80 case impeller::fb::shaderbundle::InputDataType::kSignedShort:
82 case impeller::fb::shaderbundle::InputDataType::kUnsignedShort:
84 case impeller::fb::shaderbundle::InputDataType::kSignedInt:
86 case impeller::fb::shaderbundle::InputDataType::kUnsignedInt:
88 case impeller::fb::shaderbundle::InputDataType::kSignedInt64:
90 case impeller::fb::shaderbundle::InputDataType::kUnsignedInt64:
92 case impeller::fb::shaderbundle::InputDataType::kFloat:
94 case impeller::fb::shaderbundle::InputDataType::kDouble:
96 }
97}
98
100 impeller::fb::shaderbundle::UniformDataType uniform_type) {
101 switch (uniform_type) {
102 case impeller::fb::shaderbundle::UniformDataType::kBoolean:
104 case impeller::fb::shaderbundle::UniformDataType::kSignedByte:
106 case impeller::fb::shaderbundle::UniformDataType::kUnsignedByte:
108 case impeller::fb::shaderbundle::UniformDataType::kSignedShort:
110 case impeller::fb::shaderbundle::UniformDataType::kUnsignedShort:
112 case impeller::fb::shaderbundle::UniformDataType::kSignedInt:
114 case impeller::fb::shaderbundle::UniformDataType::kUnsignedInt:
116 case impeller::fb::shaderbundle::UniformDataType::kSignedInt64:
118 case impeller::fb::shaderbundle::UniformDataType::kUnsignedInt64:
120 case impeller::fb::shaderbundle::UniformDataType::kFloat:
122 case impeller::fb::shaderbundle::UniformDataType::kDouble:
124 case impeller::fb::shaderbundle::UniformDataType::kHalfFloat:
126 case impeller::fb::shaderbundle::UniformDataType::kSampledImage:
128 }
129}
130
131static size_t SizeOfInputType(
132 impeller::fb::shaderbundle::InputDataType input_type) {
133 switch (input_type) {
134 case impeller::fb::shaderbundle::InputDataType::kBoolean:
135 return 1;
136 case impeller::fb::shaderbundle::InputDataType::kSignedByte:
137 return 1;
138 case impeller::fb::shaderbundle::InputDataType::kUnsignedByte:
139 return 1;
140 case impeller::fb::shaderbundle::InputDataType::kSignedShort:
141 return 2;
142 case impeller::fb::shaderbundle::InputDataType::kUnsignedShort:
143 return 2;
144 case impeller::fb::shaderbundle::InputDataType::kSignedInt:
145 return 4;
146 case impeller::fb::shaderbundle::InputDataType::kUnsignedInt:
147 return 4;
148 case impeller::fb::shaderbundle::InputDataType::kSignedInt64:
149 return 8;
150 case impeller::fb::shaderbundle::InputDataType::kUnsignedInt64:
151 return 8;
152 case impeller::fb::shaderbundle::InputDataType::kFloat:
153 return 4;
154 case impeller::fb::shaderbundle::InputDataType::kDouble:
155 return 8;
156 }
157}
158
159static const impeller::fb::shaderbundle::BackendShader* GetShaderBackend(
161 const impeller::fb::shaderbundle::Shader* shader) {
162 switch (backend_type) {
164#ifdef FML_OS_IOS
165 return shader->metal_ios();
166#else
167 return shader->metal_desktop();
168#endif
170 return shader->opengl_es();
172 return shader->vulkan();
173 }
174}
175
178 const std::shared_ptr<fml::Mapping>& payload,
179 const std::string& library_id) {
180 ShaderLibrary::ShaderMap shader_map;
181 if (payload == nullptr || !payload->GetMapping()) {
182 return shader_map;
183 }
184 // `ShaderBundleBufferHasIdentifier` reads the file identifier at a fixed
185 // offset, so a buffer too small to hold the root offset plus the identifier
186 // would be read out of bounds. A `fromBytes` caller can pass arbitrary bytes,
187 // so reject undersized buffers before sniffing the identifier.
188 if (payload->GetSize() <
189 sizeof(flatbuffers::uoffset_t) + flatbuffers::kFileIdentifierLength) {
190 return shader_map;
191 }
192 if (!impeller::fb::shaderbundle::ShaderBundleBufferHasIdentifier(
193 payload->GetMapping())) {
194 return shader_map;
195 }
196 // Structurally verify the FlatBuffer before accessing any fields. A buffer
197 // with a valid "IPSB" identifier but corrupt internal offsets would otherwise
198 // be read out of bounds. This mirrors the verification added for the runtime
199 // stage and shader archive loaders.
200 flatbuffers::Verifier verifier(
201 reinterpret_cast<const uint8_t*>(payload->GetMapping()),
202 payload->GetSize());
203 if (!impeller::fb::shaderbundle::VerifyShaderBundleBuffer(verifier)) {
204 return shader_map;
205 }
206 auto* bundle =
207 impeller::fb::shaderbundle::GetShaderBundle(payload->GetMapping());
208 if (!bundle) {
209 return shader_map;
210 }
211
212 const auto version = bundle->format_version();
213 const auto expected = static_cast<uint32_t>(
214 impeller::fb::shaderbundle::ShaderBundleFormatVersion::kVersion);
215 if (version != expected) {
216 VALIDATION_LOG << "Unsupported shader bundle format version: " << version
217 << ", expected: " << expected
218 << ". This shader bundle was compiled with an incompatible "
219 "version of impellerc. Please rebuild the shader bundle "
220 "with the version of impellerc that ships with the "
221 "current Flutter SDK.";
222 return shader_map;
223 }
224
225 for (const auto* bundled_shader : *bundle->shaders()) {
226 const impeller::fb::shaderbundle::BackendShader* backend_shader =
227 GetShaderBackend(backend_type, bundled_shader);
228 if (!backend_shader) {
229 VALIDATION_LOG << "Failed to unpack shader \""
230 << bundled_shader->name()->c_str() << "\" from bundle.";
231 continue;
232 }
233
234 auto code_mapping = std::make_shared<fml::NonOwnedMapping>(
235 backend_shader->shader()->data(), //
236 backend_shader->shader()->size(), //
237 [payload = payload](auto, auto) {} //
238 );
239
240 std::vector<impeller::DescriptorSetLayout> descriptor_set_layouts;
241
242 std::unordered_map<std::string, Shader::UniformBinding> uniform_structs;
243 if (backend_shader->uniform_structs() != nullptr) {
244 for (const auto& uniform : *backend_shader->uniform_structs()) {
245 std::vector<impeller::ShaderStructMemberMetadata> members;
246 if (uniform->fields() != nullptr) {
247 for (const auto& struct_member : *uniform->fields()) {
249 FromUniformType(struct_member->type());
250 members.push_back(impeller::ShaderStructMemberMetadata{
251 .type = type,
252 .name = struct_member->name()->c_str(),
253 .offset = static_cast<size_t>(struct_member->offset_in_bytes()),
254 .size =
255 static_cast<size_t>(struct_member->element_size_in_bytes()),
256 .byte_length =
257 static_cast<size_t>(struct_member->total_size_in_bytes()),
258 .array_elements =
259 struct_member->array_elements() == 0
260 ? std::optional<size_t>(std::nullopt)
261 : static_cast<size_t>(struct_member->array_elements()),
263 type, static_cast<size_t>(struct_member->vec_size()),
264 static_cast<size_t>(struct_member->columns())),
265 });
266 }
267 }
268
269 uniform_structs[uniform->name()->str()] = Shader::UniformBinding{
270 .slot =
272 .name = uniform->name()->c_str(),
273 .ext_res_0 = static_cast<size_t>(uniform->ext_res_0()),
274 .set = static_cast<size_t>(uniform->set()),
275 .binding = static_cast<size_t>(uniform->binding()),
276 },
277 .metadata =
279 .name = uniform->name()->c_str(),
280 .members = members,
281 },
282 .size_in_bytes = static_cast<size_t>(uniform->size_in_bytes()),
283 };
284
285 descriptor_set_layouts.push_back(impeller::DescriptorSetLayout{
286 static_cast<uint32_t>(uniform->binding()),
288 ToShaderStage(backend_shader->stage()),
289 });
290 }
291 }
292
293 std::unordered_map<std::string, Shader::TextureBinding> uniform_textures;
294 if (backend_shader->uniform_textures() != nullptr) {
295 for (const auto& uniform : *backend_shader->uniform_textures()) {
296 Shader::TextureBinding texture_binding;
297 texture_binding.slot = impeller::SampledImageSlot{
298 .name = uniform->name()->c_str(),
299 .texture_index = static_cast<size_t>(uniform->ext_res_0()),
300 .set = static_cast<size_t>(uniform->set()),
301 .binding = static_cast<size_t>(uniform->binding()),
302 };
303 texture_binding.metadata = impeller::ShaderMetadata{
304 .name = uniform->name()->c_str(),
305 .members = {},
306 };
307
308 uniform_textures[uniform->name()->str()] = texture_binding;
309
310 descriptor_set_layouts.push_back(impeller::DescriptorSetLayout{
311 static_cast<uint32_t>(uniform->binding()),
313 ToShaderStage(backend_shader->stage()),
314 });
315 }
316 }
317
318 std::vector<impeller::ShaderStageIOSlot> inputs;
319 std::vector<impeller::ShaderStageBufferLayout> layouts;
320 if (backend_shader->stage() ==
321 impeller::fb::shaderbundle::ShaderStage::kVertex) {
322 auto inputs_fb = backend_shader->inputs();
323
324 inputs.reserve(inputs_fb->size());
325 size_t default_stride = 0;
326 for (const auto& input : *inputs_fb) {
328 slot.name = input->name()->c_str();
329 slot.location = input->location();
330 slot.set = input->set();
331 slot.binding = input->binding();
332 slot.type = FromInputType(input->type());
333 slot.bit_width = input->bit_width();
334 slot.vec_size = input->vec_size();
335 slot.columns = input->columns();
336 slot.offset = input->offset();
337 inputs.emplace_back(slot);
338
339 default_stride +=
340 SizeOfInputType(input->type()) * slot.vec_size * slot.columns;
341 }
343 .stride = default_stride,
344 .binding = 0u,
345 }};
346 }
347
348 auto shader = flutter::gpu::Shader::Make(
349 library_id, backend_shader->entrypoint()->str(),
350 ToShaderStage(backend_shader->stage()), std::move(code_mapping),
351 std::move(inputs), std::move(layouts), std::move(uniform_structs),
352 std::move(uniform_textures), std::move(descriptor_set_layouts));
353 shader_map[bundled_shader->name()->str()] = std::move(shader);
354 }
355
356 return shader_map;
357}
358
361 std::shared_ptr<fml::Mapping> payload,
362 std::string library_id) {
363 if (payload == nullptr || !payload->GetMapping()) {
364 return nullptr;
365 }
366 if (library_id.empty()) {
368 }
369 ShaderMap shader_map = ParseShaderBundle(backend_type, payload, library_id);
370 if (shader_map.empty()) {
371 return nullptr;
372 }
373 return fml::MakeRefCounted<flutter::gpu::ShaderLibrary>(
374 std::move(payload), std::move(shader_map), std::move(library_id));
375}
376
379 const std::string& name) {
380 auto dart_state = UIDartState::Current();
381 std::shared_ptr<AssetManager> asset_manager =
382 dart_state->platform_configuration()->client()->GetAssetManager();
383
384 std::unique_ptr<fml::Mapping> data = asset_manager->GetAsMapping(name);
385 if (data == nullptr) {
386 return "Asset '" + name + "' not found.";
387 }
388 return ReloadFromFlatbuffer(backend_type, std::move(data));
389}
390
393 std::shared_ptr<fml::Mapping> payload) {
394 if (payload == nullptr || !payload->GetMapping()) {
395 return "Empty shader bundle payload.";
396 }
397 ShaderMap new_shaders = ParseShaderBundle(backend_type, payload, library_id_);
398 if (new_shaders.empty()) {
399 return "Shader bundle could not be parsed.";
400 }
401
402 // Reuse the existing Shader instance for any name that survives the
403 // reload so user-held Dart wrappers stay attached to live data. Names
404 // that disappeared in the new bundle drop out of the map; names that
405 // are new get added. The eviction of old shader functions happens
406 // lazily in `Shader::RegisterSync` when the dirty bit is observed.
407 ShaderMap merged;
408 for (auto& [name, parsed] : new_shaders) {
409 auto it = shaders_.find(name);
410 if (it != shaders_.end()) {
411 it->second->ResetFrom(*parsed);
412 merged[name] = it->second;
413 } else {
414 merged[name] = std::move(parsed);
415 }
416 }
417 shaders_ = std::move(merged);
418 payload_ = std::move(payload);
419 return "";
420}
421
423 fml::RefPtr<ShaderLibrary> override_shader_library) {
424 override_shader_library_ = std::move(override_shader_library);
425}
426
427fml::RefPtr<Shader> ShaderLibrary::GetShader(const std::string& shader_name,
428 Dart_Handle shader_wrapper) {
429 auto it = shaders_.find(shader_name);
430 if (it == shaders_.end()) {
431 return nullptr; // No matching shaders.
432 }
433 auto shader = it->second;
434
435 if (shader->dart_wrapper() == nullptr) {
436 shader->AssociateWithDartWrapper(shader_wrapper);
437 }
438 return shader;
439}
440
441ShaderLibrary::ShaderLibrary(std::shared_ptr<fml::Mapping> payload,
442 ShaderMap shaders,
443 std::string library_id)
444 : payload_(std::move(payload)),
445 shaders_(std::move(shaders)),
446 library_id_(std::move(library_id)) {}
447
449
450} // namespace gpu
451} // namespace flutter
452
453//----------------------------------------------------------------------------
454/// Exports
455///
456
457namespace {
458
459// Copies the bytes of a Dart `ByteData` into an owned mapping, so the shader
460// bundle data outlives the (temporarily acquired) Dart buffer. Returns null if
461// the handle is not typed data.
462std::shared_ptr<fml::Mapping> ShaderBundleMappingFromByteData(
463 Dart_Handle byte_data) {
464 tonic::DartByteData data(byte_data);
465 if (!data.data()) {
466 return nullptr;
467 }
468 const auto* bytes = static_cast<const uint8_t*>(data.data());
469 return std::make_shared<fml::DataMapping>(
470 std::vector<uint8_t>(bytes, bytes + data.length_in_bytes()));
471}
472
473} // namespace
474
476 Dart_Handle wrapper,
477 Dart_Handle asset_name) {
478 if (!Dart_IsString(asset_name)) {
479 return tonic::ToDart("Asset name must be a string");
480 }
481
482 std::optional<std::string> out_error;
483 auto impeller_context = flutter::gpu::Context::GetDefaultContext(out_error);
484 if (out_error.has_value()) {
485 return tonic::ToDart(out_error.value());
486 }
487
488 std::string error;
490 impeller_context->GetBackendType(), tonic::StdStringFromDart(asset_name),
491 error);
492 if (!res) {
493 return tonic::ToDart(error);
494 }
495 res->AssociateWithDartWrapper(wrapper);
496 return Dart_Null();
497}
498
501 Dart_Handle asset_name) {
502 if (!Dart_IsString(asset_name)) {
503 return tonic::ToDart("Asset name must be a string");
504 }
505
506 std::optional<std::string> out_error;
507 auto impeller_context = flutter::gpu::Context::GetDefaultContext(out_error);
508 if (out_error.has_value()) {
509 return tonic::ToDart(out_error.value());
510 }
511
512 std::string error = wrapper->ReloadFromAsset(
513 impeller_context->GetBackendType(), tonic::StdStringFromDart(asset_name));
514 if (!error.empty()) {
515 return tonic::ToDart(error);
516 }
517 return Dart_Null();
518}
519
521 Dart_Handle wrapper,
522 Dart_Handle byte_data) {
523 std::optional<std::string> out_error;
524 auto impeller_context = flutter::gpu::Context::GetDefaultContext(out_error);
525 if (out_error.has_value()) {
526 return tonic::ToDart(out_error.value());
527 }
528
529 std::shared_ptr<fml::Mapping> payload =
530 ShaderBundleMappingFromByteData(byte_data);
531 if (payload == nullptr) {
532 return tonic::ToDart("Shader bundle bytes must be a ByteData.");
533 }
534
536 impeller_context->GetBackendType(), std::move(payload));
537 if (!res) {
538 return tonic::ToDart(
539 "Failed to parse the shader bundle bytes. The bytes must be a shader "
540 "bundle compiled by a compatible impellerc.");
541 }
542 res->AssociateWithDartWrapper(wrapper);
543 return Dart_Null();
544}
545
548 Dart_Handle byte_data) {
549 std::optional<std::string> out_error;
550 auto impeller_context = flutter::gpu::Context::GetDefaultContext(out_error);
551 if (out_error.has_value()) {
552 return tonic::ToDart(out_error.value());
553 }
554
555 std::shared_ptr<fml::Mapping> payload =
556 ShaderBundleMappingFromByteData(byte_data);
557 if (payload == nullptr) {
558 return tonic::ToDart("Shader bundle bytes must be a ByteData.");
559 }
560
561 std::string error = wrapper->ReloadFromFlatbuffer(
562 impeller_context->GetBackendType(), std::move(payload));
563 if (!error.empty()) {
564 return tonic::ToDart(error);
565 }
566 return Dart_Null();
567}
568
571 Dart_Handle shader_name,
572 Dart_Handle shader_wrapper) {
573 FML_DCHECK(Dart_IsString(shader_name));
574 auto shader =
575 wrapper->GetShader(tonic::StdStringFromDart(shader_name), shader_wrapper);
576 if (!shader) {
577 return Dart_Null();
578 }
579 return tonic::ToDart(shader.get());
580}
static UIDartState * Current()
static std::shared_ptr< impeller::Context > GetDefaultContext(std::optional< std::string > &out_error)
Definition context.cc:39
static fml::RefPtr< Shader > Make(std::string library_id, std::string entrypoint, impeller::ShaderStage stage, std::shared_ptr< fml::Mapping > code_mapping, std::vector< impeller::ShaderStageIOSlot > inputs, std::vector< impeller::ShaderStageBufferLayout > layouts, std::unordered_map< std::string, UniformBinding > uniform_structs, std::unordered_map< std::string, TextureBinding > uniform_textures, std::vector< impeller::DescriptorSetLayout > descriptor_set_layouts)
Definition shader.cc:41
An immutable collection of shaders loaded from a shader bundle asset.
static void SetOverride(fml::RefPtr< ShaderLibrary > override_shader_library)
Sets a return override for MakeFromAsset for testing purposes.
std::string ReloadFromFlatbuffer(impeller::Context::BackendType backend_type, std::shared_ptr< fml::Mapping > payload)
static fml::RefPtr< ShaderLibrary > MakeFromAsset(impeller::Context::BackendType backend_type, const std::string &name, std::string &out_error)
static fml::RefPtr< ShaderLibrary > MakeFromFlatbuffer(impeller::Context::BackendType backend_type, std::shared_ptr< fml::Mapping > payload, std::string library_id="")
static fml::RefPtr< ShaderLibrary > MakeFromShaders(ShaderMap shaders)
std::string ReloadFromAsset(impeller::Context::BackendType backend_type, const std::string &name)
std::unordered_map< std::string, fml::RefPtr< Shader > > ShaderMap
fml::RefPtr< Shader > GetShader(const std::string &shader_name, Dart_Handle shader_wrapper)
static int input(yyscan_t yyscanner)
#define IMPLEMENT_WRAPPERTYPEINFO(LibraryName, ClassName)
const uint8_t uint32_t uint32_t GError ** error
#define FML_UNREACHABLE()
Definition logging.h:128
#define FML_DCHECK(condition)
Definition logging.h:122
Dart_Handle InternalFlutterGpu_ShaderLibrary_InitializeWithAsset(Dart_Handle wrapper, Dart_Handle asset_name)
Dart_Handle InternalFlutterGpu_ShaderLibrary_ReinitializeWithBytes(flutter::gpu::ShaderLibrary *wrapper, Dart_Handle byte_data)
Dart_Handle InternalFlutterGpu_ShaderLibrary_GetShader(flutter::gpu::ShaderLibrary *wrapper, Dart_Handle shader_name, Dart_Handle shader_wrapper)
Dart_Handle InternalFlutterGpu_ShaderLibrary_InitializeWithBytes(Dart_Handle wrapper, Dart_Handle byte_data)
Dart_Handle InternalFlutterGpu_ShaderLibrary_ReinitializeWithAsset(flutter::gpu::ShaderLibrary *wrapper, Dart_Handle asset_name)
static const impeller::fb::shaderbundle::BackendShader * GetShaderBackend(impeller::Context::BackendType backend_type, const impeller::fb::shaderbundle::Shader *shader)
static impeller::ShaderType FromUniformType(impeller::fb::shaderbundle::UniformDataType uniform_type)
static impeller::ShaderType FromInputType(impeller::fb::shaderbundle::InputDataType input_type)
static ShaderLibrary::ShaderMap ParseShaderBundle(impeller::Context::BackendType backend_type, const std::shared_ptr< fml::Mapping > &payload, const std::string &library_id)
static impeller::ShaderStage ToShaderStage(impeller::fb::shaderbundle::ShaderStage stage)
static size_t SizeOfInputType(impeller::fb::shaderbundle::InputDataType input_type)
DEF_SWITCHES_START aot vmservice shared library name
Definition switch_defs.h:27
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot data
Definition switch_defs.h:36
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not set
Definition switch_defs.h:71
constexpr std::optional< ShaderFloatType > DeriveShaderFloatType(ShaderType type, size_t vec_size, size_t columns)
Derive the ShaderFloatType from the base ShaderType and the (vec_size, columns) dimensions reported b...
Definition ref_ptr.h:261
Dart_Handle ToDart(const T &object)
std::string StdStringFromDart(Dart_Handle handle)
impeller::ShaderType type
impeller::SampledImageSlot slot
Definition shader.h:38
impeller::ShaderMetadata metadata
Definition shader.h:39
impeller::ShaderUniformSlot slot
Definition shader.h:29
Metadata required to bind a combined texture and sampler.
const char * name
The name of the uniform slot.
static std::string MakeFallbackLibraryId()
Definition shader_key.cc:14
Metadata required to bind a buffer.
const char * name
The name of the uniform slot.
#define VALIDATION_LOG
Definition validation.h:91