[Flutter GPU] Bind uniforms and textures by cached reflection index (#189820)
Fixes https://github.com/flutter/flutter/issues/189819.
`RenderPass.bindUniform` and `RenderPass.bindTexture` passed the uniform
name across the FFI boundary as a string on every call, and the native
side allocated a `std::string` and resolved the binding through a
string-keyed map lookup per bind, per draw.
`UniformSlot` now resolves the binding's index in the shader's
reflection data once, caches it, and binds through new index-taking
entry points. `Shader.getUniformSlot` memoizes slots by name so per-draw
lookups share the cache, and a reload epoch re-resolves cached indices
after a shader library hot reload. The name-keyed entry points remain,
and public API behavior is unchanged.
In a benchmark submitting ~10k draws per frame (flutter_scene's stress
harness, macOS/Metal, profile engine, A/B at the same revision), CPU
render time improves 7-12% at p50.
## Pre-launch Checklist
- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [AI contribution guidelines] and understand my
responsibilities, or I am not using AI tools.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.
<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[AI contribution guidelines]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
diff --git a/engine/src/flutter/lib/gpu/lib/src/buffer.dart b/engine/src/flutter/lib/gpu/lib/src/buffer.dart
index df25c61..e8f1984 100644
--- a/engine/src/flutter/lib/gpu/lib/src/buffer.dart
+++ b/engine/src/flutter/lib/gpu/lib/src/buffer.dart
@@ -84,13 +84,14 @@
bool _bindAsUniform(
RenderPass renderPass,
- UniformSlot slot,
+ Shader shader,
+ int uniformStructIndex,
int offsetInBytes,
int lengthInBytes,
) {
- return renderPass._bindUniformDevice(
- slot.shader,
- slot.uniformName,
+ return renderPass._bindUniformDeviceIndexed(
+ shader,
+ uniformStructIndex,
this,
offsetInBytes,
lengthInBytes,
diff --git a/engine/src/flutter/lib/gpu/lib/src/render_pass.dart b/engine/src/flutter/lib/gpu/lib/src/render_pass.dart
index a6f42d1..040231a 100644
--- a/engine/src/flutter/lib/gpu/lib/src/render_pass.dart
+++ b/engine/src/flutter/lib/gpu/lib/src/render_pass.dart
@@ -468,9 +468,18 @@
}
void bindUniform(UniformSlot slot, BufferView bufferView) {
+ // The slot's index is resolved once and cached, so steady-state binds
+ // pass an integer across the native boundary instead of the name.
+ int uniformStructIndex = slot._resolvedStructIndex;
+ if (uniformStructIndex < 0) {
+ throw Exception(
+ "Failed to bind uniform (no uniform struct named '${slot.uniformName}')",
+ );
+ }
bool success = bufferView.buffer._bindAsUniform(
this,
- slot,
+ slot.shader,
+ uniformStructIndex,
bufferView.offsetInBytes,
bufferView.lengthInBytes,
);
@@ -510,9 +519,15 @@
);
}
- bool success = _bindTexture(
+ int uniformTextureIndex = slot._resolvedTextureIndex;
+ if (uniformTextureIndex < 0) {
+ throw Exception(
+ "Failed to bind texture (no texture named '${slot.uniformName}')",
+ );
+ }
+ bool success = _bindTextureIndexed(
slot.shader,
- slot.uniformName,
+ uniformTextureIndex,
texture,
sampler.minFilter.index,
sampler.magFilter.index,
@@ -801,11 +816,11 @@
);
@Native<
- Bool Function(Pointer<Void>, Pointer<Void>, Handle, Pointer<Void>, Int, Int)
- >(symbol: 'InternalFlutterGpu_RenderPass_BindUniformDevice')
- external bool _bindUniformDevice(
+ Bool Function(Pointer<Void>, Pointer<Void>, Int, Pointer<Void>, Int, Int)
+ >(symbol: 'InternalFlutterGpu_RenderPass_BindUniformDeviceIndexed')
+ external bool _bindUniformDeviceIndexed(
Shader shader,
- String uniformName,
+ int uniformStructIndex,
DeviceBuffer buffer,
int offsetInBytes,
int lengthInBytes,
@@ -827,7 +842,7 @@
Bool Function(
Pointer<Void>,
Pointer<Void>,
- Handle,
+ Int,
Pointer<Void>,
Int,
Int,
@@ -836,10 +851,10 @@
Int,
Int,
)
- >(symbol: 'InternalFlutterGpu_RenderPass_BindTexture')
- external bool _bindTexture(
+ >(symbol: 'InternalFlutterGpu_RenderPass_BindTextureIndexed')
+ external bool _bindTextureIndexed(
Shader shader,
- String uniformName,
+ int uniformTextureIndex,
Texture texture,
int minFilter,
int magFilter,
diff --git a/engine/src/flutter/lib/gpu/lib/src/shader.dart b/engine/src/flutter/lib/gpu/lib/src/shader.dart
index 001bfc3..b568cd9 100644
--- a/engine/src/flutter/lib/gpu/lib/src/shader.dart
+++ b/engine/src/flutter/lib/gpu/lib/src/shader.dart
@@ -6,11 +6,49 @@
part of flutter_gpu;
+/// Bumped whenever any shader library hot reloads. [UniformSlot] caches
+/// reflection indices against this epoch, since a reload replaces the
+/// shaders' reflection data in place and invalidates cached indices.
+int _shaderReloadEpoch = 0;
+
+const int _kSlotIndexUnresolved = -2;
+
base class UniformSlot {
UniformSlot._(this.shader, this.uniformName);
final Shader shader;
final String uniformName;
+ // Reflection indices for the name-free bind path, resolved through one
+ // native call on first use and cached until a shader hot reload. -1
+ // means the shader has no struct/texture with this slot's name.
+ int _structIndex = _kSlotIndexUnresolved;
+ int _textureIndex = _kSlotIndexUnresolved;
+ int _epoch = _shaderReloadEpoch;
+
+ void _syncEpoch() {
+ if (_epoch != _shaderReloadEpoch) {
+ _structIndex = _kSlotIndexUnresolved;
+ _textureIndex = _kSlotIndexUnresolved;
+ _epoch = _shaderReloadEpoch;
+ }
+ }
+
+ int get _resolvedStructIndex {
+ _syncEpoch();
+ if (_structIndex == _kSlotIndexUnresolved) {
+ _structIndex = shader._getUniformStructIndex(uniformName);
+ }
+ return _structIndex;
+ }
+
+ int get _resolvedTextureIndex {
+ _syncEpoch();
+ if (_textureIndex == _kSlotIndexUnresolved) {
+ _textureIndex = shader._getUniformTextureIndex(uniformName);
+ }
+ return _textureIndex;
+ }
+
/// The reflected total size of a shader's uniform struct by name.
///
/// Returns [null] if the shader does not contain a uniform struct with the
@@ -35,8 +73,12 @@
// [Shader] handles are instantiated when interacting with a [ShaderLibrary].
Shader._();
+ // Memoized so per-draw lookups return the same slot instance, whose
+ // cached reflection indices make repeat binds name-free.
+ final Map<String, UniformSlot> _uniformSlots = <String, UniformSlot>{};
+
UniformSlot getUniformSlot(String uniformName) {
- return UniformSlot._(this, uniformName);
+ return _uniformSlots[uniformName] ??= UniformSlot._(this, uniformName);
}
@Native<Int Function(Pointer<Void>, Handle)>(
@@ -52,6 +94,16 @@
String memberName,
);
+ @Native<Int Function(Pointer<Void>, Handle)>(
+ symbol: 'InternalFlutterGpu_Shader_GetUniformStructIndex',
+ )
+ external int _getUniformStructIndex(String uniformStructName);
+
+ @Native<Int Function(Pointer<Void>, Handle)>(
+ symbol: 'InternalFlutterGpu_Shader_GetUniformTextureIndex',
+ )
+ external int _getUniformTextureIndex(String uniformTextureName);
+
/// Test-only. Whether this shader is currently marked dirty (will be
/// evicted and re-registered with the impeller shader library on next
/// pipeline build). Used by tests to assert that reload dedupe keeps
diff --git a/engine/src/flutter/lib/gpu/lib/src/shader_library.dart b/engine/src/flutter/lib/gpu/lib/src/shader_library.dart
index 22f1a1a..37d5a84 100644
--- a/engine/src/flutter/lib/gpu/lib/src/shader_library.dart
+++ b/engine/src/flutter/lib/gpu/lib/src/shader_library.dart
@@ -110,14 +110,22 @@
if (error != null) {
throw Exception("Failed to reinitialize ShaderLibrary: ${error}");
}
+ // The reload replaced the shaders' reflection data in place, so cached
+ // uniform slot indices must re-resolve.
+ _shaderReloadEpoch++;
}
/// Test-only. Reloads this library from `assetName`'s bytes while keeping
/// this library's identity and registry key. Production hot reload always
/// re-fetches the original asset path via [reinitialize]; this hook lets
/// tests simulate an edited bundle by swapping in a different fixture.
- String? debugReinitializeFromAsset(String assetName) =>
- _reinitializeWithAsset(assetName);
+ String? debugReinitializeFromAsset(String assetName) {
+ final String? error = _reinitializeWithAsset(assetName);
+ if (error == null) {
+ _shaderReloadEpoch++;
+ }
+ return error;
+ }
/// Reparses [bytes] into this library in place, preserving its identity so
/// any [Shader]s already handed out keep working (they are mutated and
@@ -127,8 +135,13 @@
///
/// Returns null on success, or an error message if [bytes] could not be
/// parsed (the live shaders are left unchanged in that case).
- String? reinitializeFromBytes(ByteData bytes) =>
- _reinitializeWithBytes(bytes);
+ String? reinitializeFromBytes(ByteData bytes) {
+ final String? error = _reinitializeWithBytes(bytes);
+ if (error == null) {
+ _shaderReloadEpoch++;
+ }
+ return error;
+ }
@Native<Handle Function(Handle, Handle)>(
symbol: 'InternalFlutterGpu_ShaderLibrary_InitializeWithAsset',
diff --git a/engine/src/flutter/lib/gpu/render_pass.cc b/engine/src/flutter/lib/gpu/render_pass.cc
index 3f2ec01..cbe5bbc 100644
--- a/engine/src/flutter/lib/gpu/render_pass.cc
+++ b/engine/src/flutter/lib/gpu/render_pass.cc
@@ -405,18 +405,13 @@
length_in_bytes, index_type);
}
-static bool BindUniform(
+static bool BindUniformStruct(
flutter::gpu::RenderPass* wrapper,
flutter::gpu::Shader* shader,
- Dart_Handle uniform_name_handle,
+ const flutter::gpu::Shader::UniformBinding* uniform_struct,
const std::shared_ptr<const impeller::DeviceBuffer>& buffer,
int offset_in_bytes,
int length_in_bytes) {
- auto uniform_name = tonic::StdStringFromDart(uniform_name_handle);
- const flutter::gpu::Shader::UniformBinding* uniform_struct =
- shader->GetUniformStruct(uniform_name);
- // TODO(bdero): Return an error string stating that no uniform struct with
- // this name exists and throw an exception.
if (!uniform_struct) {
return false;
}
@@ -458,15 +453,28 @@
flutter::gpu::DeviceBuffer* device_buffer,
int offset_in_bytes,
int length_in_bytes) {
- return BindUniform(wrapper, shader, uniform_name_handle,
- device_buffer->GetBuffer(), offset_in_bytes,
- length_in_bytes);
+ auto uniform_name = tonic::StdStringFromDart(uniform_name_handle);
+ return BindUniformStruct(
+ wrapper, shader, shader->GetUniformStruct(uniform_name),
+ device_buffer->GetBuffer(), offset_in_bytes, length_in_bytes);
}
-bool InternalFlutterGpu_RenderPass_BindTexture(
+bool InternalFlutterGpu_RenderPass_BindUniformDeviceIndexed(
flutter::gpu::RenderPass* wrapper,
flutter::gpu::Shader* shader,
- Dart_Handle uniform_name_handle,
+ int uniform_struct_index,
+ flutter::gpu::DeviceBuffer* device_buffer,
+ int offset_in_bytes,
+ int length_in_bytes) {
+ return BindUniformStruct(
+ wrapper, shader, shader->GetUniformStructAt(uniform_struct_index),
+ device_buffer->GetBuffer(), offset_in_bytes, length_in_bytes);
+}
+
+static bool BindTextureBinding(
+ flutter::gpu::RenderPass* wrapper,
+ flutter::gpu::Shader* shader,
+ const flutter::gpu::Shader::TextureBinding* texture_binding,
flutter::gpu::Texture* texture,
int min_filter,
int mag_filter,
@@ -474,11 +482,6 @@
int width_address_mode,
int height_address_mode,
int max_anisotropy) {
- auto uniform_name = tonic::StdStringFromDart(uniform_name_handle);
- const flutter::gpu::Shader::TextureBinding* texture_binding =
- shader->GetUniformTexture(uniform_name);
- // TODO(bdero): Return an error string stating that no uniform texture with
- // this name exists and throw an exception.
if (!texture_binding) {
return false;
}
@@ -520,6 +523,41 @@
return true;
}
+bool InternalFlutterGpu_RenderPass_BindTexture(
+ flutter::gpu::RenderPass* wrapper,
+ flutter::gpu::Shader* shader,
+ Dart_Handle uniform_name_handle,
+ flutter::gpu::Texture* texture,
+ int min_filter,
+ int mag_filter,
+ int mip_filter,
+ int width_address_mode,
+ int height_address_mode,
+ int max_anisotropy) {
+ auto uniform_name = tonic::StdStringFromDart(uniform_name_handle);
+ return BindTextureBinding(
+ wrapper, shader, shader->GetUniformTexture(uniform_name), texture,
+ min_filter, mag_filter, mip_filter, width_address_mode,
+ height_address_mode, max_anisotropy);
+}
+
+bool InternalFlutterGpu_RenderPass_BindTextureIndexed(
+ flutter::gpu::RenderPass* wrapper,
+ flutter::gpu::Shader* shader,
+ int uniform_texture_index,
+ flutter::gpu::Texture* texture,
+ int min_filter,
+ int mag_filter,
+ int mip_filter,
+ int width_address_mode,
+ int height_address_mode,
+ int max_anisotropy) {
+ return BindTextureBinding(
+ wrapper, shader, shader->GetUniformTextureAt(uniform_texture_index),
+ texture, min_filter, mag_filter, mip_filter, width_address_mode,
+ height_address_mode, max_anisotropy);
+}
+
void InternalFlutterGpu_RenderPass_ClearBindings(
flutter::gpu::RenderPass* wrapper) {
wrapper->ClearBindings();
diff --git a/engine/src/flutter/lib/gpu/render_pass.h b/engine/src/flutter/lib/gpu/render_pass.h
index 2a40293..ae72c0b 100644
--- a/engine/src/flutter/lib/gpu/render_pass.h
+++ b/engine/src/flutter/lib/gpu/render_pass.h
@@ -195,6 +195,15 @@
int length_in_bytes);
FLUTTER_GPU_EXPORT
+extern bool InternalFlutterGpu_RenderPass_BindUniformDeviceIndexed(
+ flutter::gpu::RenderPass* wrapper,
+ flutter::gpu::Shader* shader,
+ int uniform_struct_index,
+ flutter::gpu::DeviceBuffer* device_buffer,
+ int offset_in_bytes,
+ int length_in_bytes);
+
+FLUTTER_GPU_EXPORT
extern bool InternalFlutterGpu_RenderPass_BindTexture(
flutter::gpu::RenderPass* wrapper,
flutter::gpu::Shader* shader,
@@ -208,6 +217,19 @@
int max_anisotropy);
FLUTTER_GPU_EXPORT
+extern bool InternalFlutterGpu_RenderPass_BindTextureIndexed(
+ flutter::gpu::RenderPass* wrapper,
+ flutter::gpu::Shader* shader,
+ int uniform_texture_index,
+ flutter::gpu::Texture* texture,
+ int min_filter,
+ int mag_filter,
+ int mip_filter,
+ int width_address_mode,
+ int height_address_mode,
+ int max_anisotropy);
+
+FLUTTER_GPU_EXPORT
extern void InternalFlutterGpu_RenderPass_ClearBindings(
flutter::gpu::RenderPass* wrapper);
diff --git a/engine/src/flutter/lib/gpu/shader.cc b/engine/src/flutter/lib/gpu/shader.cc
index 31c9470..c2ff995 100644
--- a/engine/src/flutter/lib/gpu/shader.cc
+++ b/engine/src/flutter/lib/gpu/shader.cc
@@ -58,6 +58,7 @@
shader->uniform_structs_ = std::move(uniform_structs);
shader->uniform_textures_ = std::move(uniform_textures);
shader->descriptor_set_layouts_ = std::move(descriptor_set_layouts);
+ shader->RebuildBindingOrder();
return shader;
}
@@ -110,6 +111,7 @@
uniform_structs_ = std::move(other.uniform_structs_);
uniform_textures_ = std::move(other.uniform_textures_);
descriptor_set_layouts_ = std::move(other.descriptor_set_layouts_);
+ RebuildBindingOrder();
if (code_changed) {
is_dirty_ = true;
}
@@ -191,6 +193,60 @@
return &uniform->second;
}
+int Shader::GetUniformStructIndex(const std::string& name) const {
+ const UniformBinding* binding = GetUniformStruct(name);
+ if (binding == nullptr) {
+ return -1;
+ }
+ for (size_t i = 0; i < uniform_struct_order_.size(); i++) {
+ if (uniform_struct_order_[i] == binding) {
+ return static_cast<int>(i);
+ }
+ }
+ return -1;
+}
+
+const Shader::UniformBinding* Shader::GetUniformStructAt(int index) const {
+ if (index < 0 || static_cast<size_t>(index) >= uniform_struct_order_.size()) {
+ return nullptr;
+ }
+ return uniform_struct_order_[index];
+}
+
+int Shader::GetUniformTextureIndex(const std::string& name) const {
+ const TextureBinding* binding = GetUniformTexture(name);
+ if (binding == nullptr) {
+ return -1;
+ }
+ for (size_t i = 0; i < uniform_texture_order_.size(); i++) {
+ if (uniform_texture_order_[i] == binding) {
+ return static_cast<int>(i);
+ }
+ }
+ return -1;
+}
+
+const Shader::TextureBinding* Shader::GetUniformTextureAt(int index) const {
+ if (index < 0 ||
+ static_cast<size_t>(index) >= uniform_texture_order_.size()) {
+ return nullptr;
+ }
+ return uniform_texture_order_[index];
+}
+
+void Shader::RebuildBindingOrder() {
+ uniform_struct_order_.clear();
+ uniform_struct_order_.reserve(uniform_structs_.size());
+ for (const auto& entry : uniform_structs_) {
+ uniform_struct_order_.push_back(&entry.second);
+ }
+ uniform_texture_order_.clear();
+ uniform_texture_order_.reserve(uniform_textures_.size());
+ for (const auto& entry : uniform_textures_) {
+ uniform_texture_order_.push_back(&entry.second);
+ }
+}
+
} // namespace gpu
} // namespace flutter
@@ -210,6 +266,20 @@
return uniform->size_in_bytes;
}
+int InternalFlutterGpu_Shader_GetUniformStructIndex(
+ flutter::gpu::Shader* wrapper,
+ Dart_Handle struct_name_handle) {
+ auto name = tonic::StdStringFromDart(struct_name_handle);
+ return wrapper->GetUniformStructIndex(name);
+}
+
+int InternalFlutterGpu_Shader_GetUniformTextureIndex(
+ flutter::gpu::Shader* wrapper,
+ Dart_Handle texture_name_handle) {
+ auto name = tonic::StdStringFromDart(texture_name_handle);
+ return wrapper->GetUniformTextureIndex(name);
+}
+
int InternalFlutterGpu_Shader_GetUniformMemberOffset(
flutter::gpu::Shader* wrapper,
Dart_Handle struct_name_handle,
diff --git a/engine/src/flutter/lib/gpu/shader.h b/engine/src/flutter/lib/gpu/shader.h
index feae282..b9429bc 100644
--- a/engine/src/flutter/lib/gpu/shader.h
+++ b/engine/src/flutter/lib/gpu/shader.h
@@ -90,6 +90,22 @@
const Shader::TextureBinding* GetUniformTexture(
const std::string& name) const;
+ /// The position of the named uniform struct in this shader's stable
+ /// binding order, or -1. Indices stay valid until the shader's payload
+ /// is replaced by a reload (`ResetFrom`); callers cache them to bind
+ /// without passing the name across the FFI boundary on every draw.
+ int GetUniformStructIndex(const std::string& name) const;
+
+ /// The uniform struct at `index` in the stable binding order, or nullptr
+ /// when the index is out of range.
+ const Shader::UniformBinding* GetUniformStructAt(int index) const;
+
+ /// The texture counterpart to `GetUniformStructIndex`.
+ int GetUniformTextureIndex(const std::string& name) const;
+
+ /// The texture counterpart to `GetUniformStructAt`.
+ const Shader::TextureBinding* GetUniformTextureAt(int index) const;
+
private:
Shader();
@@ -105,9 +121,16 @@
std::vector<impeller::ShaderStageBufferLayout> layouts_;
std::unordered_map<std::string, UniformBinding> uniform_structs_;
std::unordered_map<std::string, TextureBinding> uniform_textures_;
+ // The maps' entries in a stable order for index-based lookup. Entry
+ // pointers stay valid for the maps' lifetime (node-based containers);
+ // rebuilt whenever the maps are replaced (`Make`, `ResetFrom`).
+ std::vector<const UniformBinding*> uniform_struct_order_;
+ std::vector<const TextureBinding*> uniform_texture_order_;
std::vector<impeller::DescriptorSetLayout> descriptor_set_layouts_;
bool is_dirty_ = true;
+ void RebuildBindingOrder();
+
// Returns the scoped name to use when registering or looking up this
// shader's function in a shared impeller::ShaderLibrary.
std::string GetScopedName() const;
@@ -135,6 +158,16 @@
Dart_Handle struct_name_handle,
Dart_Handle member_name_handle);
+FLUTTER_GPU_EXPORT
+extern int InternalFlutterGpu_Shader_GetUniformStructIndex(
+ flutter::gpu::Shader* wrapper,
+ Dart_Handle struct_name_handle);
+
+FLUTTER_GPU_EXPORT
+extern int InternalFlutterGpu_Shader_GetUniformTextureIndex(
+ flutter::gpu::Shader* wrapper,
+ Dart_Handle texture_name_handle);
+
// Test-only: exposes the per-shader dirty bit so tests can assert that
// reload deduplication keeps unchanged shaders clean.
FLUTTER_GPU_EXPORT
diff --git a/engine/src/flutter/testing/dart/gpu_test.dart b/engine/src/flutter/testing/dart/gpu_test.dart
index 652c337..b31d0ce 100644
--- a/engine/src/flutter/testing/dart/gpu_test.dart
+++ b/engine/src/flutter/testing/dart/gpu_test.dart
@@ -1410,6 +1410,34 @@
}
}, skip: !(impellerEnabled && flutterGpuEnabled));
+ test('Shader.getUniformSlot returns the same slot for repeat lookups', () async {
+ final gpu.RenderPipeline pipeline = await createUnlitRenderPipeline();
+ final gpu.UniformSlot first = pipeline.vertexShader.getUniformSlot('VertInfo');
+ final gpu.UniformSlot second = pipeline.vertexShader.getUniformSlot('VertInfo');
+ expect(identical(first, second), isTrue);
+ }, skip: !(impellerEnabled && flutterGpuEnabled));
+
+ test('RenderPass.bindUniform throws for an unknown uniform name', () async {
+ final RenderPassState state = createSimpleRenderPass();
+
+ final gpu.RenderPipeline pipeline = await createUnlitRenderPipeline();
+ final gpu.DeviceBuffer uniformBuffer = gpu.gpuContext.createDeviceBufferWithCopy(
+ float32(<double>[1, 2, 3, 4]),
+ );
+ final uniformBufferView = gpu.BufferView(
+ uniformBuffer,
+ offsetInBytes: 0,
+ lengthInBytes: uniformBuffer.sizeInBytes,
+ );
+ final gpu.UniformSlot unknownSlot = pipeline.vertexShader.getUniformSlot('DoesNotExist');
+ try {
+ state.renderPass.bindUniform(unknownSlot, uniformBufferView);
+ fail('Exception not thrown for an unknown uniform name.');
+ } catch (e) {
+ expect(e.toString(), contains('Failed to bind uniform'));
+ }
+ }, skip: !(impellerEnabled && flutterGpuEnabled));
+
// Renders a green triangle pointing downwards.
test('Can render triangle', () async {
final RenderPassState state = createSimpleRenderPass();