diff --git a/lib/web_ui/lib/src/engine/layers.dart b/lib/web_ui/lib/src/engine/layers.dart index 6f7a67450f1f2..f3658ddee3c4c 100644 --- a/lib/web_ui/lib/src/engine/layers.dart +++ b/lib/web_ui/lib/src/engine/layers.dart @@ -35,6 +35,11 @@ class BackdropFilterOperation implements LayerOperation { @override PlatformViewStyling createPlatformViewStyling() => const PlatformViewStyling(); + + // The backdrop filter actually has an effect on the scene even if it contains + // no pictures, so we return true here. + @override + bool get shouldDrawIfEmpty => true; } class ClipPathLayer @@ -70,6 +75,9 @@ class ClipPathOperation implements LayerOperation { PlatformViewStyling createPlatformViewStyling() { return PlatformViewStyling(clip: PlatformViewPathClip(path)); } + + @override + bool get shouldDrawIfEmpty => false; } class ClipRectLayer @@ -105,6 +113,9 @@ class ClipRectOperation implements LayerOperation { PlatformViewStyling createPlatformViewStyling() { return PlatformViewStyling(clip: PlatformViewRectClip(rect)); } + + @override + bool get shouldDrawIfEmpty => false; } class ClipRRectLayer @@ -140,6 +151,9 @@ class ClipRRectOperation implements LayerOperation { PlatformViewStyling createPlatformViewStyling() { return PlatformViewStyling(clip: PlatformViewRRectClip(rrect)); } + + @override + bool get shouldDrawIfEmpty => false; } class ColorFilterLayer @@ -165,6 +179,9 @@ class ColorFilterOperation implements LayerOperation { @override PlatformViewStyling createPlatformViewStyling() => const PlatformViewStyling(); + + @override + bool get shouldDrawIfEmpty => false; } class ImageFilterLayer @@ -207,6 +224,9 @@ class ImageFilterOperation implements LayerOperation { return const PlatformViewStyling(); } } + + @override + bool get shouldDrawIfEmpty => false; } class OffsetLayer @@ -236,6 +256,9 @@ class OffsetOperation implements LayerOperation { PlatformViewStyling createPlatformViewStyling() => PlatformViewStyling( position: PlatformViewPosition.offset(ui.Offset(dx, dy)) ); + + @override + bool get shouldDrawIfEmpty => false; } class OpacityLayer @@ -276,6 +299,9 @@ class OpacityOperation implements LayerOperation { position: offset != ui.Offset.zero ? PlatformViewPosition.offset(offset) : const PlatformViewPosition.zero(), opacity: alpha.toDouble() / 255.0, ); + + @override + bool get shouldDrawIfEmpty => false; } class TransformLayer @@ -307,6 +333,9 @@ class TransformOperation implements LayerOperation { PlatformViewStyling createPlatformViewStyling() => PlatformViewStyling( position: PlatformViewPosition.transform(matrix), ); + + @override + bool get shouldDrawIfEmpty => false; } class ShaderMaskLayer @@ -346,6 +375,9 @@ class ShaderMaskOperation implements LayerOperation { @override PlatformViewStyling createPlatformViewStyling() => const PlatformViewStyling(); + + @override + bool get shouldDrawIfEmpty => false; } class PlatformView { @@ -414,6 +446,11 @@ abstract class LayerOperation { void post(SceneCanvas canvas, ui.Rect contentRect); PlatformViewStyling createPlatformViewStyling(); + + /// Indicates whether this operation's `pre` and `post` methods should be + /// invoked even if it contains no pictures. (Most operations don't need to + /// actually be performed at all if they don't contain any pictures.) + bool get shouldDrawIfEmpty; } class PictureDrawCommand { @@ -771,7 +808,7 @@ class LayerBuilder { } void flushSlices() { - if (pendingPictures.isNotEmpty) { + if (pendingPictures.isNotEmpty || (operation?.shouldDrawIfEmpty ?? false)) { // Merge the existing draw commands into a single picture and add a slice // with that picture to the slice list. final ui.Rect drawnRect = picturesRect ?? ui.Rect.zero; diff --git a/lib/web_ui/test/ui/scene_builder_test.dart b/lib/web_ui/test/ui/scene_builder_test.dart index fee4e87e6b74a..fc77fd98c2d39 100644 --- a/lib/web_ui/test/ui/scene_builder_test.dart +++ b/lib/web_ui/test/ui/scene_builder_test.dart @@ -215,6 +215,34 @@ Future testMain() async { await matchGoldenFile('scene_builder_backdrop_filter.png', region: region); }); + test('empty backdrop filter layer with clip', () async { + // Note that this test does not actually render properly in skwasm due to + // a Skia bug. See https://g-issues.skia.org/issues/362552959 and + // https://github.com/flutter/flutter/issues/152026 + final ui.SceneBuilder sceneBuilder = ui.SceneBuilder(); + + sceneBuilder.addPicture(ui.Offset.zero, drawPicture((ui.Canvas canvas) { + // Create a red and blue checkerboard pattern + final ui.Paint redPaint = ui.Paint()..color = const ui.Color(0xFFFF0000); + final ui.Paint bluePaint = ui.Paint()..color = const ui.Color(0xFF0000FF); + for (double y = 0; y < 300; y += 10) { + for (double x = 0; x < 300; x += 10) { + final ui.Paint paint = ((x + y) % 20 == 0) ? redPaint : bluePaint; + canvas.drawRect(ui.Rect.fromLTWH(x, y, 10, 10), paint); + } + } + })); + + sceneBuilder.pushClipRect(const ui.Rect.fromLTRB(100, 100, 200, 200)); + + sceneBuilder.pushBackdropFilter(ui.ImageFilter.blur( + sigmaX: 3.0, + sigmaY: 3.0, + )); + await renderScene(sceneBuilder.build()); + await matchGoldenFile('scene_builder_empty_backdrop_filter_with_clip.png', region: region); + }); + test('image filter layer', () async { final ui.SceneBuilder sceneBuilder = ui.SceneBuilder(); sceneBuilder.pushImageFilter(ui.ImageFilter.blur(