What
When a user authors a FragmentProgram-backed shader (.frag registered under the shaders: pubspec block) that #includes another file, editing the included file does not trigger hot reload. The user sees stale shader output until they edit the top-level .frag itself (which is in the asset watch set) or restart the app.
Why
packages/flutter_tools/lib/src/devfs.dart only watches files registered under flutter.assets: and flutter.shaders: in pubspec, derived from bundle.entries. A .glsl header that's #included from a .frag is unknown to the watcher, so no change event is generated when it's edited.
packages/flutter_tools/lib/src/build_system/tools/shader_compiler.dart invokes impellerc with --input, --sl, --include, and the target flags. It does not pass --depfile, so the dependency information impellerc would happily emit (via OutputDepfile at impeller/compiler/impellerc_main.cc:178) is discarded.
Repro
shaders/
main.frag # listed under flutter.shaders: in pubspec
helpers.glsl # #included by main.frag, not listed anywhere
- Run the app, observe the FragmentProgram output.
- Edit
helpers.glsl. Save.
- Hot reload is not triggered. The shader on screen continues to use the old
helpers.glsl.
- Edit
main.frag (anything, even a whitespace change). Save.
- Hot reload fires. Now the latest
helpers.glsl content takes effect.
Fix
This is pure flutter_tools work; the engine half (impellerc writing depfiles) already works for the single-shader compile path:
ShaderCompiler.compileShader (build_system/tools/shader_compiler.dart) passes --depfile=<adjacent path> to impellerc.
- After a successful compile, parse the depfile and feed the listed paths into the devfs watch set.
- When any watched include's mtime changes, re-trigger
recompileShader for every shader that depends on it.
The watch-set semantics need to become dynamic (today's watch set is static, derived from pubspec at startup). The depfile format is the Ninja-style single-line <target>: <dep1> <dep2> ... that Compiler::CreateDepfileContents emits.
Related
What
When a user authors a FragmentProgram-backed shader (
.fragregistered under theshaders:pubspec block) that#includes another file, editing the included file does not trigger hot reload. The user sees stale shader output until they edit the top-level.fragitself (which is in the asset watch set) or restart the app.Why
packages/flutter_tools/lib/src/devfs.dartonly watches files registered underflutter.assets:andflutter.shaders:in pubspec, derived frombundle.entries. A.glslheader that's#included from a.fragis unknown to the watcher, so no change event is generated when it's edited.packages/flutter_tools/lib/src/build_system/tools/shader_compiler.dartinvokesimpellercwith--input,--sl,--include, and the target flags. It does not pass--depfile, so the dependency informationimpellercwould happily emit (viaOutputDepfileatimpeller/compiler/impellerc_main.cc:178) is discarded.Repro
helpers.glsl. Save.helpers.glsl.main.frag(anything, even a whitespace change). Save.helpers.glslcontent takes effect.Fix
This is pure flutter_tools work; the engine half (
impellercwriting depfiles) already works for the single-shader compile path:ShaderCompiler.compileShader(build_system/tools/shader_compiler.dart) passes--depfile=<adjacent path>to impellerc.recompileShaderfor every shader that depends on it.The watch-set semantics need to become dynamic (today's watch set is static, derived from pubspec at startup). The depfile format is the Ninja-style single-line
<target>: <dep1> <dep2> ...thatCompiler::CreateDepfileContentsemits.Related
#includetracking gap, but for--shader-bundlemode (used by Flutter GPU). The engine side of that one merged in [ImpellerC] Write a depfile when --shader-bundle is in use #186341.