Skip to content

[Impeller] Flutter GPU ShaderLibrary supports in-place reload for shader hot reload #186344

Description

@bdero

What

flutter::gpu::ShaderLibrary is documented as "An immutable collection of shaders loaded from a shader bundle asset." That immutability is what blocks shader hot reload for Flutter GPU: once a bundle has been parsed into gpu::Shader instances and the Impeller pipeline cache has registered functions keyed by entrypoint name, there is no mechanism to evict and replace them when the underlying asset changes.

FragmentProgram (RuntimeEffect) already solves the same problem on its half of the engine: a per-stage is_dirty_ bit, a frame-level eviction triple-call in RuntimeEffectContents::RegisterShader (UnregisterFunction + RemovePipelinesWithEntryPoint + ClearCachedRuntimeEffectPipeline), and a FragmentProgram._reinitializeShader Dart entry point bridged via ext.ui.window.reinitializeShader. The shape needed for Flutter GPU is a direct adaptation of that protocol.

This issue tracks the engine half: native in-place reload, per-shader dirty bit, pipeline cache eviction, plus the matching Dart-side ShaderLibrary registry and service extension.

Required engine changes

  1. flutter::gpu::Shader gains IsDirty() / SetClean() mirroring impeller::RuntimeStage::is_dirty_. Freshly parsed shaders land with is_dirty_ = true.

  2. Native in-place reload on ShaderLibrary. Split today's InternalFlutterGpu_ShaderLibrary_InitializeWithAsset into "construct" and "reload into self." The reload re-fetches the asset via AssetManager::GetAsMapping, reparses the flatbuffer with MakeFromFlatbuffer, and replaces payload_ and shaders_ in place (same Dart object identity, same scoped library_id so registered names stay stable). Mirrors FragmentProgram::initFromAsset's in-place rebuild at engine/lib/ui/painting/fragment_program.cc:131.

  3. Per-shader pipeline cache eviction. In Flutter GPU's pipeline-registration path (the analog of RuntimeEffectContents::RegisterShader at impeller/entity/contents/runtime_effect_contents.cc:148), consult the per-shader dirty bit and run the triple-eviction on a dirty shader: library->UnregisterFunction(scoped_name, stage), pipeline_library->RemovePipelinesWithEntryPoint(function), and drop any cached RenderPipeline keyed by name on the flutter::gpu side. Register the new code mapping with the same scoped name, then SetClean(). Because [Impeller] Namespace user-supplied shaders to prevent entrypoint collisions #186332 keys by library_id = asset_name, the new shader lands at the same scoped name and unregister-then-register is well-defined.

  4. Dart-side registry on ShaderLibrary. Add a static Map<String, ShaderLibrary> _registry to lib/gpu/lib/src/shader_library.dart keyed by asset path. fromAsset becomes "return cached or construct," matching FragmentProgram._shaderRegistry at lib/ui/painting.dart. Hold weak refs to any Shader instances handed out via operator[] so the runtime can patch them in place on reload (the same weak-ref scaffolding FragmentProgram uses for live FragmentShader instances).

  5. Service extension ext.ui.gpu.reinitializeShaderLibrary. Registered in lib/gpu/natives.dart (inside a debug-only assert block, mirroring ext.ui.window.reinitializeShader at lib/ui/natives.dart:93). Bridges to a Dart-side ShaderLibrary._reinitialize(assetKey) that looks up the registry, calls the native reload, and walks any weak-ref live Shader instances.

Sequencing

PR #186332 (merged) is a prerequisite: it anchors user-shader scoped names to a stable per-asset library_id, which is exactly the key the eviction triple-call needs to land at.

This issue is the engine half of the wider shader hot-reload story. The follow-on flutter_tools work (dispatch this service extension for changed .shaderbundle assets after DevFS sync) is tracked in #186345 and can be developed independently once this lands. That dispatch is producer-agnostic: because the reload no-ops on a registry miss, the tool keys on the asset path and needs no new AssetKind or pubspec block.

References

  • engine/lib/ui/painting.dart (FragmentProgram._shaderRegistry, _reinitializeShader)
  • engine/lib/ui/painting/fragment_program.cc:131 (initFromAsset in-place reload)
  • engine/lib/ui/natives.dart:93 (service extension registration template)
  • engine/impeller/entity/contents/runtime_effect_contents.cc:148 (RegisterShader eviction template)
  • engine/impeller/runtime_stage/runtime_stage.h:49 / .cc:232 (is_dirty_ template)
  • engine/lib/gpu/shader_library.{h,cc,dart} (surfaces to be extended)
  • [Impeller] Namespace user-supplied shaders to prevent entrypoint collisions #186332 (scoped naming prerequisite, merged)

Metadata

Metadata

Assignees

Labels

c: new featureNothing broken; request for a new capabilitye: impellerImpeller rendering backend issues and features requestsengineflutter/engine related. See also e: labels.flutter-gputeam-fluttergpuOwned by Flutter GPU team

Type

No type
No fields configured for issues without a type.

Projects

Status
✅ Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions