[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();