Skip to content

Commit

Permalink
fix: web canvaskit fragment shaders were not using updated uniform va…
Browse files Browse the repository at this point in the history
…lues (flutter#53246)

This fixes an issue where in CanvasKit builds, uniforms set in setFloat function in the Paint class don't get updated after the initial render.
For example, like in the issue linked below, having a shader that animates a value over time and you want to reuse the Paint object that the shader is set to.

I'm no expert with CanvasKit nor with the Flutter Engine, but from what I could tell it seemed that the uniforms were only being sent to Skia on creation of the shader via _makeEffect.

However, any uniforms set afterwords were just stored in a local dart-side List<double> and forgotten about.

This PR changes the List<double> to use a WASM backed SkFloat32List to keep the references to the uniforms linked to dart-side.

fixes flutter/flutter#149800
  • Loading branch information
Moncader authored Jun 25, 2024
1 parent 0e9f516 commit ee1884c
Show file tree
Hide file tree
Showing 5 changed files with 42 additions and 10 deletions.
4 changes: 2 additions & 2 deletions lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3617,13 +3617,13 @@ SkRuntimeEffect? MakeRuntimeEffect(String program) =>
extension SkSkRuntimeEffectExtension on SkRuntimeEffect {
@JS('makeShader')
external SkShader? _makeShader(JSAny uniforms);
SkShader? makeShader(List<Object> uniforms) =>
SkShader? makeShader(SkFloat32List uniforms) =>
_makeShader(uniforms.toJSAnyShallow);

@JS('makeShaderWithChildren')
external SkShader? _makeShaderWithChildren(JSAny uniforms, JSAny children);
SkShader? makeShaderWithChildren(
List<Object> uniforms, List<Object?> children) =>
SkFloat32List uniforms, List<Object?> children) =>
_makeShaderWithChildren(uniforms.toJSAnyShallow,
children.toJSAnyShallow);
}
Expand Down
7 changes: 4 additions & 3 deletions lib/web_ui/lib/src/engine/canvaskit/painting.dart
Original file line number Diff line number Diff line change
Expand Up @@ -420,14 +420,14 @@ class CkFragmentProgram implements ui.FragmentProgram {

class CkFragmentShader implements ui.FragmentShader, CkShader {
CkFragmentShader(this.name, this.effect, int floatCount, int textureCount)
: floats = List<double>.filled(floatCount + textureCount * 2, 0),
: floats = mallocFloat32List(floatCount + textureCount * 2),
samplers = List<SkShader?>.filled(textureCount, null),
lastFloatIndex = floatCount;

final String name;
final SkRuntimeEffect effect;
final int lastFloatIndex;
final List<double> floats;
final SkFloat32List floats;
final List<SkShader?> samplers;

@visibleForTesting
Expand All @@ -454,7 +454,7 @@ class CkFragmentShader implements ui.FragmentShader, CkShader {
@override
void setFloat(int index, double value) {
assert(!_debugDisposed, 'FragmentShader has been disposed of.');
floats[index] = value;
floats.toTypedArray()[index] = value;
}

@override
Expand All @@ -476,6 +476,7 @@ class CkFragmentShader implements ui.FragmentShader, CkShader {
}());
ref?.dispose();
ref = null;
free(floats);
}

bool _debugDisposed = false;
Expand Down
5 changes: 3 additions & 2 deletions lib/web_ui/skwasm/shaders.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,10 @@ SKWASM_EXPORT SkShader* shader_createRuntimeEffectShader(
for (size_t i = 0; i < childCount; i++) {
childPointers.emplace_back(sk_ref_sp<SkShader>(children[i]));
}

return runtimeEffect
->makeShader(SkData::MakeWithCopy(uniforms->data(), uniforms->size()),
childPointers.data(), childCount, nullptr)
->makeShader(sk_ref_sp<SkData>(uniforms), childPointers.data(),
childCount, nullptr)
.release();
}

Expand Down
16 changes: 13 additions & 3 deletions lib/web_ui/test/canvaskit/canvaskit_api_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -366,11 +366,13 @@ half4 main(vec2 fragCoord) {
final SkRuntimeEffect? invalidEffect = MakeRuntimeEffect(kInvalidSkSlProgram);
expect(invalidEffect, isNull);

final SkShader? shader = effect!.makeShader(<double>[]);
final SkFloat32List emptyUniforms = mallocFloat32List(0);
final SkShader? shader = effect!.makeShader(emptyUniforms);
expect(shader, isNotNull);

// mismatched uniforms returns null.
final SkShader? invalidShader = effect.makeShader(<double>[1]);
final SkFloat32List mismatchedUniforms = mallocFloat32List(1);
final SkShader? invalidShader = effect.makeShader(mismatchedUniforms);

expect(invalidShader, isNull);

Expand All @@ -382,8 +384,16 @@ return u_color;
}
''';

final SkFloat32List uniforms = mallocFloat32List(4);
final uniformData = uniforms.toTypedArray();

uniformData[0] = 1.0;
uniformData[1] = 0.0;
uniformData[2] = 0.0;
uniformData[3] = 1.0;

final SkShader? shaderWithUniform = MakeRuntimeEffect(kSkSlProgramWithUniforms)
!.makeShader(<double>[1.0, 0.0, 0.0, 1.0]);
!.makeShader(uniforms);

expect(shaderWithUniform, isNotNull);
});
Expand Down
20 changes: 20 additions & 0 deletions lib/web_ui/test/ui/fragment_shader_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -82,5 +82,25 @@ Future<void> testMain() async {
// Make sure we can reuse the shader object with a new uniform value.
shader.setFloat(0, 25.0);
await drawCircle('fragment_shader_voronoi_tile25px.png');

// Test reusing a Paint object with the same shader.
final ui.Paint reusablePaint = ui.Paint()..shader = shader;

Future<void> drawCircleReusePaint(String goldenFilename) async {
final ui.PictureRecorder recorder = ui.PictureRecorder();
final ui.Canvas canvas = ui.Canvas(recorder, region);
canvas.drawCircle(const ui.Offset(150, 150), 100, reusablePaint);

await drawPictureUsingCurrentRenderer(recorder.endRecording());

await matchGoldenFile(goldenFilename, region: region);
}

shader.setFloat(0, 10.0);
await drawCircleReusePaint('fragment_shader_voronoi_tile10px_reuse_paint.png');

// Make sure we can reuse the shader object with a new uniform value and the same Paint object.
shader.setFloat(0, 25.0);
await drawCircleReusePaint('fragment_shader_voronoi_tile25px_reuse_paint.png');
}, skip: isHtml); // Fragment shaders are not supported by the HTML renderer.
}

0 comments on commit ee1884c

Please sign in to comment.