You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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
flutter::gpu::Shader gains IsDirty() / SetClean() mirroring impeller::RuntimeStage::is_dirty_. Freshly parsed shaders land with is_dirty_ = true.
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 replacespayload_ 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.
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.
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).
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.
What
flutter::gpu::ShaderLibraryis 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 intogpu::Shaderinstances 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 inRuntimeEffectContents::RegisterShader(UnregisterFunction+RemovePipelinesWithEntryPoint+ClearCachedRuntimeEffectPipeline), and aFragmentProgram._reinitializeShaderDart entry point bridged viaext.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
ShaderLibraryregistry and service extension.Required engine changes
flutter::gpu::ShadergainsIsDirty()/SetClean()mirroringimpeller::RuntimeStage::is_dirty_. Freshly parsed shaders land withis_dirty_ = true.Native in-place reload on
ShaderLibrary. Split today'sInternalFlutterGpu_ShaderLibrary_InitializeWithAssetinto "construct" and "reload into self." The reload re-fetches the asset viaAssetManager::GetAsMapping, reparses the flatbuffer withMakeFromFlatbuffer, and replacespayload_andshaders_in place (same Dart object identity, same scopedlibrary_idso registered names stay stable). MirrorsFragmentProgram::initFromAsset's in-place rebuild atengine/lib/ui/painting/fragment_program.cc:131.Per-shader pipeline cache eviction. In Flutter GPU's pipeline-registration path (the analog of
RuntimeEffectContents::RegisterShaderatimpeller/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 cachedRenderPipelinekeyed by name on theflutter::gpuside. Register the new code mapping with the same scoped name, thenSetClean(). Because [Impeller] Namespace user-supplied shaders to prevent entrypoint collisions #186332 keys bylibrary_id = asset_name, the new shader lands at the same scoped name and unregister-then-register is well-defined.Dart-side registry on
ShaderLibrary. Add a staticMap<String, ShaderLibrary> _registrytolib/gpu/lib/src/shader_library.dartkeyed by asset path.fromAssetbecomes "return cached or construct," matchingFragmentProgram._shaderRegistryatlib/ui/painting.dart. Hold weak refs to anyShaderinstances handed out viaoperator[]so the runtime can patch them in place on reload (the same weak-ref scaffoldingFragmentProgramuses for liveFragmentShaderinstances).Service extension
ext.ui.gpu.reinitializeShaderLibrary. Registered inlib/gpu/natives.dart(inside a debug-onlyassertblock, mirroringext.ui.window.reinitializeShaderatlib/ui/natives.dart:93). Bridges to a Dart-sideShaderLibrary._reinitialize(assetKey)that looks up the registry, calls the native reload, and walks any weak-ref liveShaderinstances.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
.shaderbundleassets 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 newAssetKindor pubspec block.References
engine/lib/ui/painting.dart(FragmentProgram._shaderRegistry,_reinitializeShader)engine/lib/ui/painting/fragment_program.cc:131(initFromAssetin-place reload)engine/lib/ui/natives.dart:93(service extension registration template)engine/impeller/entity/contents/runtime_effect_contents.cc:148(RegisterShadereviction 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)