Import flutter_shaders package - #12583
Conversation
Add pixelation shader
switch to flutter runtime effect
Fix docs for fragment shader breaking changes
Add support for configuring inkwell shader
…y frame Call `AnimatedSampler.builder` from `Layer.addToScene` to update every frame
update changelog and version
fix offset transforming animated sampler children off the screen
Ready for publish
feat: add uniform setter
Publish prep.
Fix pixelation shader not compiling
update version and changelog to include location decoration removal.
…tests that were still failing
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
There was a problem hiding this comment.
Code Review
This pull request imports the flutter_shaders package into the repository, providing utilities like AnimatedSampler, ShaderInkFeature, SetUniforms, and ShaderBuilder to simplify working with the FragmentProgram API. Feedback on the implementation highlights a native memory leak in AnimatedSampler due to an undisposed ui.Scene, an incorrect radius calculation in ShaderInkFeature that ignores the tap position, and an optimization opportunity in ShaderBuilder to prevent redundant concurrent asset loading.
| return builder.build().toImageSync( | ||
| (pixelRatio * bounds.width).ceil(), | ||
| (pixelRatio * bounds.height).ceil(), | ||
| ); |
There was a problem hiding this comment.
The ui.Scene returned by builder.build() is a native resource that must be explicitly disposed to avoid memory leaks. Since _buildChildScene is called on every frame of an animation, this will cause a continuous native memory leak. Store the scene in a local variable and dispose it in a finally block.
| return builder.build().toImageSync( | |
| (pixelRatio * bounds.width).ceil(), | |
| (pixelRatio * bounds.height).ceil(), | |
| ); | |
| final ui.Scene scene = builder.build(); | |
| try { | |
| return scene.toImageSync( | |
| (pixelRatio * bounds.width).ceil(), | |
| (pixelRatio * bounds.height).ceil(), | |
| ); | |
| } finally { | |
| scene.dispose(); | |
| } |
| final Size size = rectCallback != null ? rectCallback().size : referenceBox.size; | ||
| final double d1 = size.bottomRight(Offset.zero).distance; | ||
| final double d2 = (size.topRight(Offset.zero) - size.bottomLeft(Offset.zero)).distance; | ||
| return math.max(d1, d2) / 2.0; |
There was a problem hiding this comment.
The _getTargetRadius function calculates the target radius as half of the diagonal of the bounding box, completely ignoring the tap position. If a user taps near a corner or edge, the splash will not expand enough to cover the entire widget. To ensure the splash covers the entire widget, the target radius should be the maximum distance from the tap position to the four corners of the bounding box.
final Size size = rectCallback != null ? rectCallback().size : referenceBox.size;
final double d1 = position.distance;
final double d2 = (position - size.topRight(Offset.zero)).distance;
final double d3 = (position - size.bottomLeft(Offset.zero)).distance;
final double d4 = (position - size.bottomRight(Offset.zero)).distance;
return math.max(math.max(d1, d2), math.max(d3, d4));| static final Map<String, ui.FragmentProgram> _shaderCache = <String, ui.FragmentProgram>{}; | ||
|
|
||
| @override | ||
| void initState() { | ||
| super.initState(); | ||
| _loadShader(widget.assetKey); | ||
| } | ||
|
|
||
| @override | ||
| void didUpdateWidget(covariant ShaderBuilder oldWidget) { | ||
| super.didUpdateWidget(oldWidget); | ||
| if (oldWidget.assetKey != widget.assetKey) { | ||
| _loadShader(widget.assetKey); | ||
| } | ||
| } | ||
|
|
||
| void _loadShader(String assetKey) { | ||
| if (_shaderCache.containsKey(assetKey)) { | ||
| program = _shaderCache[assetKey]; | ||
| shader = program!.fragmentShader(); | ||
| return; | ||
| } | ||
|
|
||
| ui.FragmentProgram.fromAsset(assetKey).then( | ||
| (ui.FragmentProgram program) { | ||
| if (!mounted) { | ||
| return; | ||
| } | ||
| setState(() { | ||
| this.program = program; | ||
| shader = program.fragmentShader(); | ||
| _shaderCache[assetKey] = program; | ||
| }); | ||
| }, | ||
| onError: (Object error, StackTrace stackTrace) { | ||
| FlutterError.reportError(FlutterErrorDetails(exception: error, stack: stackTrace)); | ||
| }, | ||
| ); | ||
| } |
There was a problem hiding this comment.
If multiple ShaderBuilder widgets with the same assetKey are initialized concurrently before the first one finishes loading, they will all trigger redundant ui.FragmentProgram.fromAsset calls. We can optimize this by keeping a map of pending futures to share the loading process, while still caching the completed FragmentProgram for synchronous access on subsequent builds.
static final Map<String, ui.FragmentProgram> _shaderCache = <String, ui.FragmentProgram>{};
static final Map<String, Future<ui.FragmentProgram>> _pendingLoads = <String, Future<ui.FragmentProgram>>{};
@override
void initState() {
super.initState();
_loadShader(widget.assetKey);
}
@override
void didUpdateWidget(covariant ShaderBuilder oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.assetKey != widget.assetKey) {
_loadShader(widget.assetKey);
}
}
void _loadShader(String assetKey) {
if (_shaderCache.containsKey(assetKey)) {
program = _shaderCache[assetKey];
shader = program!.fragmentShader();
return;
}
final Future<ui.FragmentProgram> future = _pendingLoads.putIfAbsent(
assetKey,
() => ui.FragmentProgram.fromAsset(assetKey),
);
future.then(
(ui.FragmentProgram loadedProgram) {
_shaderCache[assetKey] = loadedProgram;
_pendingLoads.remove(assetKey);
if (!mounted) {
return;
}
setState(() {
program = loadedProgram;
shader = loadedProgram.fragmentShader();
});
},
onError: (Object error, StackTrace stackTrace) {
_pendingLoads.remove(assetKey);
FlutterError.reportError(FlutterErrorDetails(exception: error, stack: stackTrace));
},
);
}|
The failing golden test was generated with material 2, the material 3 version is not very interesting. I can swap them, but it might be better to find something better maybe? |

autosubmitReviewers: Please DO NOT add the
autosubmitlabel to this PR. This is an initial repository migration and must be landed manually following the repository transfer process.Description
This PR imports
flutter_shadersintoflutter/packagesfrom its original repository:Source Repository: https://github.com/jonahwilliams/flutter_shaders
The commit history from the original repository was preserved via git subtree/merge up to commit
2802b9e47b.Reviewer Guide
Summary of Changes Relative to Initial Import
pubspec.yamlwith theflutter/packagesmono-reporepositoryandissue_trackerURLs, and addedtopics: [shaders, graphics].p: flutter_shadersto.github/labeler.yml.flutter_shadersto the packages table in the rootREADME.md.// Copyright 2013 The Flutter Authors).pubspec.yamlandexample/pubspec.yamlto Flutter>=3.38.0/ Dart^3.10.0.set_uniforms: ExtractedFloatUniformsSetteras an abstract base class/interface so uniform conversion logic can be tested and extended without implementingui.FragmentShader(which is abase classin Dart 3 and cannot be implemented outsidedart:ui). KeptUniformsSetternon-nullable and backwards-compatible.animated_sampler_test: Updated test callbacks from 4 arguments to 3 arguments (expectAsync3) to match theAnimatedSamplerBuildersignature (theoffsetparameter was removed in0.0.6).setColors w/ premultiplywhere a zero-alpha color was expected to have non-zero alpha.UniformsSetterandShaderInkFeatureFactory.prefer_foreach, control body newlines) and formatted all files withdart format.0.1.4inpubspec.yaml.CHANGELOG.mdentry describing the package transfer and changes.Verification
dart run script/tool/bin/flutter_plugin_tools.dart validate --packages flutter_shaderspasses.dart run script/tool/bin/flutter_plugin_tools.dart analyze --packages flutter_shaderspasses with 0 issues.dart run script/tool/bin/flutter_plugin_tools.dart dart-test --packages flutter_shaderspasses (all unit and example tests pass).dart run script/tool/bin/flutter_plugin_tools.dart license-checkpasses.Reminder: DO NOT ADD
autosubmit.