From 0d0445286baeccf630b84d8434700fcdcd0a67a0 Mon Sep 17 00:00:00 2001 From: DevKage Date: Thu, 29 Oct 2026 10:36:27 +0530 Subject: [PATCH 1/2] fix: Scale the subtree to the pixel ratio in `rasterizeSubtree` `PostProcess.rasterizeSubtree` created an image of `pixelRatio * size` pixels but recorded the subtree at logical size, so the subtree only filled the top left corner of the image. Shaders sampling it with coordinates from 0 to 1 showed the subtree shrunk by the pixel ratio on high density screens. The recording canvas is now scaled by the pixel ratio, so the subtree fills the whole image. Shaders that worked around this by dividing the texture coordinates by the pixel ratio, like the crystal ball ground shader, must drop that workaround. Fixes #3766 --- doc/flame/rendering/post_processing.md | 4 +- examples/assets/images/curved_screen.png | Bin 0 -> 27753 bytes .../lib/src/game/post_processes/water.dart | 1 - .../games/crystal_ball/shaders/ground.frag | 3 +- examples/pubspec.yaml | 5 + examples/shaders/curved_screen.frag | 74 +++++++++ .../curved_screen_flame_pixel_ratio_1.png | Bin 0 -> 1719 bytes .../curved_screen_flame_pixel_ratio_2.png | Bin 0 -> 1910 bytes .../curved_screen_flame_pixel_ratio_3.png | Bin 0 -> 1978 bytes .../test/_goldens/curved_screen_flutter.png | Bin 0 -> 2196 bytes .../curved_screen_shader_test.dart | 137 ++++++++++++++++ .../lib/src/post_process/post_process.dart | 11 +- .../post_process_rasterize_subtree.png | Bin 0 -> 506 bytes .../post_process/rasterize_subtree_test.dart | 151 ++++++++++++++++++ 14 files changed, 379 insertions(+), 7 deletions(-) create mode 100644 examples/assets/images/curved_screen.png create mode 100644 examples/shaders/curved_screen.frag create mode 100644 examples/test/_goldens/curved_screen_flame_pixel_ratio_1.png create mode 100644 examples/test/_goldens/curved_screen_flame_pixel_ratio_2.png create mode 100644 examples/test/_goldens/curved_screen_flame_pixel_ratio_3.png create mode 100644 examples/test/_goldens/curved_screen_flutter.png create mode 100644 examples/test/post_process/curved_screen_shader_test.dart create mode 100644 packages/flame/test/_goldens/post_process_rasterize_subtree.png create mode 100644 packages/flame/test/post_process/rasterize_subtree_test.dart diff --git a/doc/flame/rendering/post_processing.md b/doc/flame/rendering/post_processing.md index 8964f8c70cc..67cfe446ace 100644 --- a/doc/flame/rendering/post_processing.md +++ b/doc/flame/rendering/post_processing.md @@ -124,7 +124,9 @@ In this example: - A fragment shader (`pixelation.frag`) is loaded and used to apply a pixelation effect. - The `rasterizeSubtree` method captures the component tree rendering as a texture, which the - shader uses to generate the pixelated output. + shader uses to generate the pixelated output. The texture is rendered at the post process's + `pixelRatio` and covers the whole subtree, so the shader can sample it with coordinates from 0 + to 1 across `size`. - The effect dynamically changes over time, creating an animated pixelation effect. diff --git a/examples/assets/images/curved_screen.png b/examples/assets/images/curved_screen.png new file mode 100644 index 0000000000000000000000000000000000000000..bfc0e6ddaffa5ff01d078d6f0701e381e6b043d7 GIT binary patch literal 27753 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 literal 0 HcmV?d00001 diff --git a/examples/games/crystal_ball/lib/src/game/post_processes/water.dart b/examples/games/crystal_ball/lib/src/game/post_processes/water.dart index 97046e113ea..5e1d3d159d8 100644 --- a/examples/games/crystal_ball/lib/src/game/post_processes/water.dart +++ b/examples/games/crystal_ball/lib/src/game/post_processes/water.dart @@ -33,7 +33,6 @@ class WaterPostProcess({ ..setFloatUniforms((value) { value ..setVector(size) - ..setFloat(pixelRatio) ..setFloat(uvGround) ..setFloat(time); }) diff --git a/examples/games/crystal_ball/shaders/ground.frag b/examples/games/crystal_ball/shaders/ground.frag index 91d7879b940..4fe44944ef9 100644 --- a/examples/games/crystal_ball/shaders/ground.frag +++ b/examples/games/crystal_ball/shaders/ground.frag @@ -5,7 +5,6 @@ precision highp float; #include uniform vec2 uSize; -uniform float uPixelSize; uniform float uWaterLevel; uniform float uTime; @@ -45,7 +44,7 @@ vec4 fragment(vec2 uv) { fogIntensity = waterProximity * 0.1; } - vec4 baseColor = texture(tGameCanvas, reflectedUv / uPixelSize) * waterColor; + vec4 baseColor = texture(tGameCanvas, reflectedUv) * waterColor; return mix(baseColor, fogColor, fogIntensity); } diff --git a/examples/pubspec.yaml b/examples/pubspec.yaml index 8e24e82cf1e..47ddd60cb56 100644 --- a/examples/pubspec.yaml +++ b/examples/pubspec.yaml @@ -40,11 +40,16 @@ dependencies: dev_dependencies: flame_lint: ^1.5.0-dev.1 + flutter_test: + sdk: flutter test: any flutter: uses-material-design: true + shaders: + - shaders/curved_screen.frag + assets: - assets/images/animations/ - assets/images/ diff --git a/examples/shaders/curved_screen.frag b/examples/shaders/curved_screen.frag new file mode 100644 index 00000000000..d62d0ceea43 --- /dev/null +++ b/examples/shaders/curved_screen.frag @@ -0,0 +1,74 @@ +#version 460 core +// The curved screen shader from https://github.com/stilnat/shader-flame-test, +// the reproduction for https://github.com/flame-engine/flame/issues/3766. +// It is used by the post process golden tests, so keep it unchanged. +#include + +precision highp float; + +uniform vec2 uResolution; +uniform sampler2D uTexture; + +out vec4 fragColor; + +// you must pass uv in range [-1, 1] for it to work +// you should see a radius black point middle and going towards white +void lenghtDebug(vec2 uv) +{ + float r = length(uv); + fragColor = vec4(vec3(r), 1.0); +} + +// you must pass uv in range [-1, 1] for it to work +void angleDebug(vec2 uv) +{ + float theta = atan(uv.y, uv.x); // -π to +π + // normalize to [0,1] + float normalized = (theta + 3.14159265) / (2.0 * 3.14159265); + + fragColor = vec4(vec3(normalized), 1.0); +} + + +// create a regular grid to help visualize deformation and such +vec3 debugGrid(vec2 uv) { + float lineX = smoothstep(0.0, 0.05, abs(fract(uv.x * 10.0) - 0.5)); + float lineY = smoothstep(0.0, 0.05, abs(fract(uv.y * 10.0) - 0.5)); + float grid = 1.0 - min(lineX, lineY); + return vec3(grid); +} + +// the actual coordinate deformation happens here, +// you must pass uv in range [-1, 1] for it to work +vec2 crt(vec2 uv) +{ + float theta = atan(uv.y, uv.x); + float r = length(uv); + r /= (1.0 - 0.1 * r * r); + uv.x = r * cos(theta); + uv.y = r * sin(theta); + return 0.5 * (uv + 1.0); +} + +void draw_crt_grid(vec2 uv){ + uv = crt(2*uv-1); + vec3 grid = debugGrid(uv); + fragColor = vec4(grid, 1.0); +} + + +void main() { + vec2 fragCoord = FlutterFragCoord().xy; + + // Convert to [0,1] UV, invert Y to match texture coordinates + vec2 uv = fragCoord / uResolution.xy; + uv.y = 1.0 - uv.y; + + // draw_crt_grid(uv); + + uv = crt(2*uv-1); + vec3 col = texture(uTexture, uv).rgb; + fragColor = vec4(col, 1.0); +} + + diff --git a/examples/test/_goldens/curved_screen_flame_pixel_ratio_1.png b/examples/test/_goldens/curved_screen_flame_pixel_ratio_1.png new file mode 100644 index 0000000000000000000000000000000000000000..88790a7ca696fda737013d388c719991012f9ead GIT binary patch literal 1719 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 I35|a4KdaHqasU7T literal 0 HcmV?d00001 diff --git a/examples/test/post_process/curved_screen_shader_test.dart b/examples/test/post_process/curved_screen_shader_test.dart new file mode 100644 index 00000000000..800b63214a7 --- /dev/null +++ b/examples/test/post_process/curved_screen_shader_test.dart @@ -0,0 +1,137 @@ +import 'dart:ui' as ui show Image; +import 'dart:ui' hide Image; + +import 'package:flame/components.dart'; +import 'package:flame/palette.dart'; +import 'package:flame/post_process.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; + +/// Golden tests for https://github.com/flame-engine/flame/issues/3766, using +/// the shader and image from the reporter's reproduction. +/// +/// The Flame goldens should look like the pure Flutter reference golden. Before +/// the fix, the image passed to the shader by `rasterizeSubtree` only had the +/// subtree in its top left corner, so the screen was shrunk by the pixel ratio. +void main() { + const shaderAsset = 'shaders/curved_screen.frag'; + const imageAsset = 'assets/images/curved_screen.png'; + const screenWidth = 200.0; + final goldenSize = Vector2(240, 220); + final backgroundColor = BasicPalette.brown.color; + + group('curved screen shader from issue #3766', () { + testWidgets('renders in pure Flutter', (tester) async { + const key = ValueKey('reference'); + await tester.runAsync(() async { + final data = await rootBundle.load(imageAsset); + final codec = await instantiateImageCodec(data.buffer.asUint8List()); + final image = (await codec.getNextFrame()).image; + final program = await FragmentProgram.fromAsset(shaderAsset); + + await tester.pumpWidget( + Center( + child: RepaintBoundary( + key: key, + child: SizedBox( + width: goldenSize.x, + height: goldenSize.y, + child: ColoredBox( + color: backgroundColor, + child: Center( + child: CustomPaint( + painter: _CurvedScreenPainter( + program.fragmentShader(), + image, + ), + size: Size( + screenWidth, + screenWidth * image.height / image.width, + ), + ), + ), + ), + ), + ), + ), + ); + }); + + await expectLater( + find.byKey(key), + matchesGoldenFile('../_goldens/curved_screen_flutter.png'), + ); + }); + + for (final pixelRatio in [1, 2, 3]) { + testGolden( + 'renders in a PostProcessComponent at pixel ratio $pixelRatio', + (game, tester) async { + final image = await game.images.load(imageAsset); + final program = await FragmentProgram.fromAsset(shaderAsset); + final screen = SpriteComponent.fromImage( + image, + size: Vector2( + screenWidth, + screenWidth * image.height / image.width, + ), + ); + + await game.world.ensureAdd( + PostProcessComponent( + postProcess: _CurvedScreenPostProcess( + program.fragmentShader(), + pixelRatio: pixelRatio.toDouble(), + ), + anchor: Anchor.center, + size: screen.size, + children: [screen], + ), + ); + }, + goldenFile: + '../_goldens/curved_screen_flame_pixel_ratio_$pixelRatio.png', + size: goldenSize, + backgroundColor: backgroundColor, + ); + } + }); +} + +/// The post process from the reporter's reproduction. +class _CurvedScreenPostProcess( + final FragmentShader _shader, { + super.pixelRatio, +}) extends PostProcess { + late final _paint = Paint()..shader = _shader; + + @override + void postProcess(Vector2 size, Canvas canvas) { + final subtree = rasterizeSubtree(); + _shader + ..setFloat(0, size.x) + ..setFloat(1, size.y) + ..setImageSampler(0, subtree); + canvas.drawRect(Offset.zero & size.toSize(), _paint); + } +} + +/// The custom painter from the reporter's reproduction. +class _CurvedScreenPainter( + final FragmentShader _shader, + final ui.Image _image, +) extends CustomPainter { + @override + void paint(Canvas canvas, Size size) { + _shader + ..setFloat(0, size.width) + ..setFloat(1, size.height) + ..setImageSampler(0, _image); + canvas.drawRect(Offset.zero & size, Paint()..shader = _shader); + } + + @override + bool shouldRepaint(_CurvedScreenPainter oldDelegate) => false; +} diff --git a/packages/flame/lib/src/post_process/post_process.dart b/packages/flame/lib/src/post_process/post_process.dart index a9cc45a5966..1b8e7875661 100644 --- a/packages/flame/lib/src/post_process/post_process.dart +++ b/packages/flame/lib/src/post_process/post_process.dart @@ -52,8 +52,9 @@ import 'package:flutter/foundation.dart'; /// - [PostProcessingContextFinder] for a way to access the post process /// context from within a component during rendering abstract class PostProcess({double? pixelRatio}) { - /// The pixel ratio of the screen. This is used to scale the image generated - /// by [rasterizeSubtree] to the correct size. + /// The pixel ratio of the screen. The image generated by [rasterizeSubtree] + /// is rendered at this many pixels per logical pixel, so that it stays sharp + /// on high density screens. /// /// Defaults to [FlutterView.devicePixelRatio]. double pixelRatio = @@ -111,13 +112,17 @@ abstract class PostProcess({double? pixelRatio}) { /// subtree to an image, which can be used as a texture in a shader. This /// is an expensive operation, so it should be used sparingly. /// + /// The image is `pixelRatio` times the size of the post process and the + /// subtree fills all of it, so a shader can sample it with texture + /// coordinates going from 0 to 1 across the size of the post process. + /// /// This method will set the context of the post process, so that /// components can know they are being rendered within a post process. @nonVirtual @protected ui.Image rasterizeSubtree() { final recorder = PictureRecorder(); - final innerCanvas = Canvas(recorder); + final innerCanvas = Canvas(recorder)..scale(pixelRatio); renderSubtree(innerCanvas); diff --git a/packages/flame/test/_goldens/post_process_rasterize_subtree.png b/packages/flame/test/_goldens/post_process_rasterize_subtree.png new file mode 100644 index 0000000000000000000000000000000000000000..fe531e17dde84841a55215d89401167e042c237f GIT binary patch literal 506 zcmeAS@N?(olHy`uVBq!ia0vp^CxAGGgAGU?ZmZ`8QY^(zo*^7SP{WbZ0pv3l2e~^j ztUD+363AZS>EaktG3V{wjlPEiL>vOy4H*|LUX;L*#l^N@K{EHU1STt&<2*bQniL}# zHae|nED+_mVQ95{OVFNYuU^(ipGuNwyUMU;tNB-k4X0U{92Ep8CcZ6~O9-EPcW(Zd z-M?>Nx1XQN<9>SiO7|O{W*jVy4gwr3=@WG4%EkHF9_rVLms=*6D3&9@!PKY#CGs}z z`|y$XXH5R;8-2VG2OX45Q8{nE|M#_UANmW#dps>qR;sJog2trypxohvF`u!k1w}%)IBMwQs{1-bc7*ry{00f?{elF{r5}E*g-;)>s literal 0 HcmV?d00001 diff --git a/packages/flame/test/post_process/rasterize_subtree_test.dart b/packages/flame/test/post_process/rasterize_subtree_test.dart new file mode 100644 index 00000000000..6d078ca3431 --- /dev/null +++ b/packages/flame/test/post_process/rasterize_subtree_test.dart @@ -0,0 +1,151 @@ +import 'dart:typed_data'; +import 'dart:ui'; + +import 'package:flame/components.dart'; +import 'package:flame/post_process.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('rasterizeSubtree', () { + const red = Color(0xFFFF0000); + const blue = Color(0xFF0000FF); + + for (final pixelRatio in [1.0, 1.5, 2.0, 3.0]) { + test('fills the whole image at pixel ratio $pixelRatio', () async { + final size = Vector2(40, 20); + final postProcess = _CapturingPostProcess(pixelRatio: pixelRatio); + + postProcess.render( + Canvas(PictureRecorder()), + size, + (canvas) { + // Red on the left half, blue on the right half. + canvas.drawRect( + Rect.fromLTWH(0, 0, size.x / 2, size.y), + Paint()..color = red, + ); + canvas.drawRect( + Rect.fromLTWH(size.x / 2, 0, size.x / 2, size.y), + Paint()..color = blue, + ); + }, + (_) {}, + ); + + final image = postProcess.image!; + expect(image.width, (size.x * pixelRatio).ceil()); + expect(image.height, (size.y * pixelRatio).ceil()); + + final pixels = await _Pixels.of(image); + expect(pixels.at(0, 0), red); + expect(pixels.at(image.width ~/ 4, image.height ~/ 2), red); + expect(pixels.at(image.width * 3 ~/ 4, image.height ~/ 2), blue); + expect(pixels.at(image.width - 1, image.height - 1), blue); + }); + } + }); + + group('PostProcessComponent with rasterizeSubtree', () { + const goldenFile = '../_goldens/post_process_rasterize_subtree.png'; + final goldenSize = Vector2(200, 100); + final cardPosition = Vector2(-30, -20); + + testGolden( + 'renders the subtree without a post process', + (game, tester) async { + game.world.add(_TestCard()..position = cardPosition); + }, + goldenFile: goldenFile, + size: goldenSize, + ); + + for (final pixelRatio in [1.0, 2.0, 3.0]) { + testGolden( + 'renders the rasterized subtree at its full size at pixel ratio ' + '$pixelRatio', + (game, tester) async { + game.world.add( + PostProcessComponent( + postProcess: _DrawImagePostProcess(pixelRatio: pixelRatio), + position: cardPosition, + size: _TestCard.cardSize, + children: [_TestCard()], + ), + ); + }, + goldenFile: goldenFile, + size: goldenSize, + ); + } + }); +} + +class _CapturingPostProcess({super.pixelRatio}) extends PostProcess { + Image? image; + + @override + void postProcess(Vector2 size, Canvas canvas) { + image = rasterizeSubtree(); + } +} + +/// Draws the rasterized subtree stretched over the size of the post process, +/// which should look exactly like rendering the subtree directly. +class _DrawImagePostProcess({super.pixelRatio}) extends PostProcess { + final _paint = Paint()..filterQuality = FilterQuality.none; + + @override + void postProcess(Vector2 size, Canvas canvas) { + final image = rasterizeSubtree(); + canvas.drawImageRect( + image, + Rect.fromLTWH(0, 0, image.width.toDouble(), image.height.toDouble()), + Offset.zero & size.toSize(), + _paint, + ); + } +} + +/// A card made only of pixel aligned rectangles, so that downscaling a +/// rasterized copy of it with nearest sampling gives back the same pixels. +class _TestCard() extends PositionComponent { + this : super(size: cardSize); + + static Vector2 get cardSize => Vector2(60, 40); + + static final _fill = Paint()..color = const Color(0xFF1E4D8C); + static final _border = Paint()..color = const Color(0xFFE53935); + static final _center = Paint()..color = const Color(0xFFFDD835); + static final _corner = Paint()..color = const Color(0xFF43A047); + + @override + void render(Canvas canvas) { + canvas + ..drawRect(const Rect.fromLTWH(0, 0, 60, 40), _fill) + ..drawRect(const Rect.fromLTWH(0, 0, 60, 4), _border) + ..drawRect(const Rect.fromLTWH(0, 36, 60, 4), _border) + ..drawRect(const Rect.fromLTWH(0, 0, 4, 40), _border) + ..drawRect(const Rect.fromLTWH(56, 0, 4, 40), _border) + ..drawRect(const Rect.fromLTWH(20, 14, 20, 12), _center) + ..drawRect(const Rect.fromLTWH(0, 0, 10, 10), _corner) + ..drawRect(const Rect.fromLTWH(50, 30, 10, 10), _corner); + } +} + +class _Pixels(final ByteData _bytes, final int _width) { + static Future<_Pixels> of(Image image) async { + final data = await image.toByteData(); + return _Pixels(data!, image.width); + } + + Color at(int x, int y) { + final offset = (y * _width + x) * 4; + return Color.fromARGB( + _bytes.getUint8(offset + 3), + _bytes.getUint8(offset), + _bytes.getUint8(offset + 1), + _bytes.getUint8(offset + 2), + ); + } +} From 0ea90fe0640cfb7bc3f5bd9acae3d35cf6f40c73 Mon Sep 17 00:00:00 2001 From: DevKage Date: Thu, 29 Oct 2026 13:25:05 +0530 Subject: [PATCH 2/2] test: Remove the curved screen shader test from the examples The flame package tests already cover the bug from #3766, so the examples don't need their own copy of the reporter's shader and image. --- examples/assets/images/curved_screen.png | Bin 27753 -> 0 bytes examples/pubspec.yaml | 5 - examples/shaders/curved_screen.frag | 74 ---------- .../curved_screen_flame_pixel_ratio_1.png | Bin 1719 -> 0 bytes .../curved_screen_flame_pixel_ratio_2.png | Bin 1910 -> 0 bytes .../curved_screen_flame_pixel_ratio_3.png | Bin 1978 -> 0 bytes .../test/_goldens/curved_screen_flutter.png | Bin 2196 -> 0 bytes .../curved_screen_shader_test.dart | 137 ------------------ 8 files changed, 216 deletions(-) delete mode 100644 examples/assets/images/curved_screen.png delete mode 100644 examples/shaders/curved_screen.frag delete mode 100644 examples/test/_goldens/curved_screen_flame_pixel_ratio_1.png delete mode 100644 examples/test/_goldens/curved_screen_flame_pixel_ratio_2.png delete mode 100644 examples/test/_goldens/curved_screen_flame_pixel_ratio_3.png delete mode 100644 examples/test/_goldens/curved_screen_flutter.png delete mode 100644 examples/test/post_process/curved_screen_shader_test.dart diff --git a/examples/assets/images/curved_screen.png b/examples/assets/images/curved_screen.png deleted file mode 100644 index bfc0e6ddaffa5ff01d078d6f0701e381e6b043d7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27753 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 diff --git a/examples/pubspec.yaml b/examples/pubspec.yaml index 47ddd60cb56..8e24e82cf1e 100644 --- a/examples/pubspec.yaml +++ b/examples/pubspec.yaml @@ -40,16 +40,11 @@ dependencies: dev_dependencies: flame_lint: ^1.5.0-dev.1 - flutter_test: - sdk: flutter test: any flutter: uses-material-design: true - shaders: - - shaders/curved_screen.frag - assets: - assets/images/animations/ - assets/images/ diff --git a/examples/shaders/curved_screen.frag b/examples/shaders/curved_screen.frag deleted file mode 100644 index d62d0ceea43..00000000000 --- a/examples/shaders/curved_screen.frag +++ /dev/null @@ -1,74 +0,0 @@ -#version 460 core -// The curved screen shader from https://github.com/stilnat/shader-flame-test, -// the reproduction for https://github.com/flame-engine/flame/issues/3766. -// It is used by the post process golden tests, so keep it unchanged. -#include - -precision highp float; - -uniform vec2 uResolution; -uniform sampler2D uTexture; - -out vec4 fragColor; - -// you must pass uv in range [-1, 1] for it to work -// you should see a radius black point middle and going towards white -void lenghtDebug(vec2 uv) -{ - float r = length(uv); - fragColor = vec4(vec3(r), 1.0); -} - -// you must pass uv in range [-1, 1] for it to work -void angleDebug(vec2 uv) -{ - float theta = atan(uv.y, uv.x); // -π to +π - // normalize to [0,1] - float normalized = (theta + 3.14159265) / (2.0 * 3.14159265); - - fragColor = vec4(vec3(normalized), 1.0); -} - - -// create a regular grid to help visualize deformation and such -vec3 debugGrid(vec2 uv) { - float lineX = smoothstep(0.0, 0.05, abs(fract(uv.x * 10.0) - 0.5)); - float lineY = smoothstep(0.0, 0.05, abs(fract(uv.y * 10.0) - 0.5)); - float grid = 1.0 - min(lineX, lineY); - return vec3(grid); -} - -// the actual coordinate deformation happens here, -// you must pass uv in range [-1, 1] for it to work -vec2 crt(vec2 uv) -{ - float theta = atan(uv.y, uv.x); - float r = length(uv); - r /= (1.0 - 0.1 * r * r); - uv.x = r * cos(theta); - uv.y = r * sin(theta); - return 0.5 * (uv + 1.0); -} - -void draw_crt_grid(vec2 uv){ - uv = crt(2*uv-1); - vec3 grid = debugGrid(uv); - fragColor = vec4(grid, 1.0); -} - - -void main() { - vec2 fragCoord = FlutterFragCoord().xy; - - // Convert to [0,1] UV, invert Y to match texture coordinates - vec2 uv = fragCoord / uResolution.xy; - uv.y = 1.0 - uv.y; - - // draw_crt_grid(uv); - - uv = crt(2*uv-1); - vec3 col = texture(uTexture, uv).rgb; - fragColor = vec4(col, 1.0); -} - - diff --git a/examples/test/_goldens/curved_screen_flame_pixel_ratio_1.png b/examples/test/_goldens/curved_screen_flame_pixel_ratio_1.png deleted file mode 100644 index 88790a7ca696fda737013d388c719991012f9ead..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1719 zcmb7F3pCVO7@uL7!AvunjPW)KDQZe4$)*_3!6}icq_N~6h9cy#ra@_@%=YkDk`+m& z$hIP{`IAaZh@xWTT}X`AU<~`mX;1q&yJzqD-S7MEckcP_?>qPV?!CC*(HbSIEDM1^ zP_{N?Cs2=rvlAf=O2^&$A3y~QHn(*~fFlawl>n}x!A{oZkb*|lAqYg;!b zzlg2s0(mZY_njOY=Db#Gqt{sNAYeWTN1P**5iA@tJ|1_7U}Hz2uoon8dQNsV#mpGS zWhQNX|DkNnjaGzsOx=-$I~U6%AMSoA>IxK2-rdqq9~=m+5_yMp`AhGd5$d8azSIZ^ zFabkW|6iz&M8<0HtN21456`HIlJ=3A2=wfDlbNV%12OiX4?>6bSG0-F(9*n(KQEmX zw5X&+d>Ax^??i(t7QQSWVaJhda_N`SHV?I>T+5pqYR&jJmImmr#Ac`FU)&On<&U1)zIp2X`ob;UF-!-U zUpc@JqPceWXTQi!KAU;nddDP?m#mx6msk>;4tIIxb__@{tLVIhetwewQA@r}wWDlW zP#zwl!Zy{rXSi6e;Z9G>^(}}#9S|V$&n{!6=lWWeXAg=5!k1p#4@amxx74dOF_m9iOEH&?z^6I_7<=FWLueMxo5HAT(NcTa zu}u_oR+68@;+Xb^ymv3I=~B==Hb&5fk9Se1}IL{OC{HvAYQmBV5jlhk>6x0P>pIS1_mGo{oRYIgV<=dI5$v4VOW67-1c z^E}sKyovWbw_RX($T(&Zs0Q_VQbxpVbawdZ_ac&{g|)V;rwra?(_>`{&|K4*J}>7k zha-mcuTy5q;F)Jp$Z9Z#f2pVRo?gjO8&8n1yQBrN{Tv~6wX8|15vY}O?3eEa(_p8M z@Fu`v!Lq38d$mlO_e$Xpf_w&zh%4S3<`zUlB5YC_&m2XDu||&q%~zE0RMuC5t29AL zJp;|;g=9@(G_B4+B}YLv-{45z^l-x0gbJfi-+x_@<*Lm&0Y=9DE4bUcj|B3W@12#1 z&54X2yiuk+4)xX`K4)_@G9Aq7Z#0&f6hqr9=B4}&1OR}51$T?kQ*JOEvwv{jvm_k& zr}rX%&09bH)It{HWU1yFcHThheJxtvff}Ws{~Vn<4b`o{B5pflgACf=bQ>F~ao@m@ zMz*^q|Hy%jm>pF2w|0RA)Yb33s=P4 z_e$YO0+O0~P)}joCUdtouL)u2Oe@FCsL51hKh$IIatsI?>k3$$6?0mir1~nrsl(UI z9g6O^h(*G5K~L$f$*G@OEaozYC8`#OrY|fm%%>MBs??YHQxF&F(V^Ta%wYbOZtLPX zDY`}fRN;(qN8X-p61g#(L}()uP6w9d~Q6U%wk!OGl4|#-VNGOz03r55Q3=$KG6r>ie zpfFgVfyyHks3K?sga`_Xlz;^T1Wckp1p`41ZvxG<(;H`c*Xi6p^Zz^l?CwAR?0&nO z6X1u_*EQA!008>lUhaV!xuDtOFsSDAJ7z7_2uO;ncQ8z|F2LflHFsc2AkG!g&~G{e z0BG;@c6SL*`(|#!Kf&~ue+cDN4|J_>EF;1D%qNX-_RdH5edj-BN4dSistP#)po8D_ zpi<`ULmnQnz(St7`R&0%eJsQ+$A=~Ze;}-ao6~DDukMk2f31LXN0C5D#jP`yv*-Q3 z{x>~Z*+-?@9p1lqoD?b9d%|b`AQ;eOh_?Kg?1dNR7qcu50^L3VWj%nvb99i~|4OkG zCPu7On>g^9USY<{a%btxOB!mSdxHjzXQj&I6sh(|A@$75SqR4F2`eoua zO!eB@=UZ*`xTXQTFeu^5>A_^1&O2Mx;boasrJPci^d5{X`QU-rZ2Qj{0;~EjlI7;& zS49V8gJ_Q*FLc-cT(#(Eyng#~**=*%S+@Y=dA9(=rCe#_vr=~xvrbSq)}04;2g^6! zi9GYrgPU_n<1KfwBi74GsjW16i+;brtbxb!FFhJ^KydCB90MJ!%x-ieIu;EcW9Qlu zt#5t^G>@lHstW^sE;f^x8}gpF3-YdhRAl~-zO9>lxS(T9skp@_gb1l>d3}4H$T6cz zp-|x4P%$EW^|*GQzz<=Z0aA10@m&#i&-(45XksBBSMzb zPIul>vg}Lm%bslZ4I6 z)o-E0P&j@EYQeC&JR1d!5A2!66{A~$f+^+ zqj>Op(@v`WMB?1zubfH}28| zFtVc!#oriUwR3^fhUu`0fkslI_FS4)7S>&0bdRI0aJp<6@XjD6L6~{fW%5PLX#~m< zMie~p`cnhYL#*U>Trh9~^-cC_hgRUS5hD{}aUW$E5o)or5+#!z5p04+ho=c0sL#Jd z0gcV*miOA*+XuqCbr8tn()Zg*h`|D-oVmjZgrBNJIA1|=3nM^u8)SSCO*r3usY|1t zliaPuv1-R#Q*9ZXz*9QNQ-yL^z<;{ZFl6J+wLFal;nWzRPG_WOkgVVxm%%9@ z3m4^Thep6|cR9@-2^bqu0yetnD=P1cgME2W*$xN?jUW>s@S57Y8ADc zIk7CwAeCX)`z}?L5JP~7FS@|x2n^J)gK|#GuKokA?cJ7`7JJiePsp(W1OlOVG=;Lf zsqQa5mg00;%+pQlz`XIROnTMonJL|~QE6}dqTzhzH^Y^m!n|D=Z0;$Soi&BrJHS^bQ-xVm)2_UXMT=>yu6JlB)?(8j%ij8bRXfqO)VKYDh4TS2TGzn4#8N()9pb!Lu;L$Kj ziw#?2j35YzfIxvHKnbX93SpT@p`yr6B9SE_VSdzq=A3rMd(PYMd+)yUzVCiHC;ahR za05691k&>M@(2K02~b|bG=SP4XE_Np>Tzz~L>OSHu&}GZ7#tUXcLP=T8_t11&@gWg zS7K7p(nR~UqQuVx(n(9L;l-~SUJ0*;o>L>h@?Wt<93p%dS8HuBw96gsdKbD4VwP~T zP&Z!~LNsWEV5#{JThM@>W*it<_31wE9_x(0q`Z)G*p*(M-RilLNjt@6(ane>D?`QW zxRtQUNd}bk}`|0 zlnqsv>ij5N=`Wg-Z8{*6;!;``%G8AtZG*o$$-xELyIu9bQU2S;%k|R8t-3NrnLdWF zPDN6&&Z;La&dIN9vysNu?O1RWsPBfafa985 z>(8n26pwy)MutDQ!G$GZ`zJ7-$(MX}mDI%+wvGF15j1spx^=ayfGyshm)o($D=(IL zb`i!;^^)VkrRhtlGFV^)e9@&;i3|I&oBeFNpr%+ZH)D#{G;GoDL=B?YY58^fePQL{ zDUnF@Vsf^ieTK{yJE49*vg_2Xdo>I8q=n2V(6wCt=t7X zxUnCn=L9jF-;hoNUV(msma@E`HL%j4k+0IX6xQ*7WpE`9IfjdzRB*YnY&F0-GVNbMmQ!c$&jWJHtmNW&O>IZj*1*!jY zzx{ZTL;PV{R=|bbs7G^EvOD-MJO!A-(R39}2IIm|vAP)F6N=D1AE48rmZmrvg=d1! zig*SVj#IDv9WZqg`e?}o-avKGso~|t#DQbXy~da@(L$Ha%w9}yIU~iR`#8wEZR=J6 z2fr5+_6HiHud)gP;eVi0&p=YhPqm9(&HFZ7kY;M~*>J1TzwzN-miAM(CjU0R(G))z zCf!1oASZZcm|hdwQ7)ab=efQ;3^g+a)68j;bn3e`+WZOPR>L&Ehjf$Lhjfy1ypmAJ z;A$tTCmb~CwX!6SLOQq9H31I-A0Wl#6~?cI89SZ>X*MdnotwVt@fpNnI zfEtefs_lm+@jOu<6E+Q8c@*Lv`me-$pI~~?@A~s0jH5df0O3+~5jv+yB&7K9N2vC3 za#mVT>64Ptz%O%tp=YdhKzN(dzH&eJGHaD8bnI~_#Ar|U&YMigUcLMb6I(nv{CB*I zQaZMs@Oioen@&lKF=oF<#32wHBUPsXPyeOG#jJ~BZ0!Lc2_P&i;_n5#CH~#we@4Ov zr_0_l4U7}d;vO}nN@Hi&&N2nnUYDD*G!|LpBeyTvrq>VBp_#6)XDC#pkt1w^7ZvLI zA?;+BzYm@=CHMIKchc!lQ%y0A?VYJk_6;qub3QVd6pXaG%J}IDbZhf`=?Ug#aWjK&s!g8W_$fEmrW%hX9R= z5sbMYUCGGEH!|4Z)3%g#fJy-EPTbObOTR`p!Ghg0KlBBi#J*XzS}$5)gzrT9>{ zs=0mUXBk$GU|mQE+SOWDSGTgB+2>lx$I9J&X@uOdf&lRsWnYdN<_{@{xT6b8P6_MF z6a6K8M9?nBii@p7Ec3P40~@CDZmPZTMND2@lT%ZO4_ah&y%LcW8Ch9TIuacXWyNPZ zY#hTJUMTEnA3NrmJ=(BytKz-qnGnIr?dS%nbhW0Kv|$;-s&}^$_wzKJw}!Hxo7D$| z2&U^dFP~!;b@k|+-xmR)a+VNgvN!dI|_xZl>;_UQr z#;EG60sw$<*}io*v`#{27YYeoJvI}i(1M7dx$HqfM=UBJ1-b_#c01F6l2-gM0H7RP zw*F%eD|@WFEu@`^Z=BlEr~W~`n;7hk4Ovr^Ow;r^>%Q*D5hITarHL{>6?KK>nJz(T z;uDO;VPq{9uBS+dD2LnpI~sfV#D)u8-s4~WFO?iizDDFN6i7{_1B>x-GqKs^l7*^_ z+045wg9`U!!K?}_u>HkBbs#SsfkqMNR4fRuMPW6ZglrNFnWRZFN@at7|NBvA=B!Tq zsX4~Wear7YFHBZn6&=S>u@^i}pY?*J2wj7|ugMXpr!ECNT5_~Dpqz=U6JYw0X!DNv z^Ra$)(!j8m^rg>z4Qh#;msi+?J!&Lgui4R=vG$=cuV^C~NDm3sIcc3`<^m8 zH+(vCk)i`?nfE#6J{EVYxMdp?*AX*|6(co^=A(>4+kDGy<5sdqhIh&h@~v)$Q}}`) zJJ*+wN0$lA8F56tOFxJ)<#5x~dDb)jwWZ>r<)i-(fP# zowhES`)Ji5eKEP&&W;x58`dl-N-knIu4>%EW%W0=4dVD#=iCZkF6zz2hg#2f(M$3l zgy`%8!{P;Gn_ETtQ`5C8{7lj-YUXBFzR`$s4s;$GlP?r||L!Gx-H(juRXpIA8^A5* z=9PUHnxqRdN_$kFzoi+p2MKnOOs4mhQV@iDZLMC4QT{r!Huv6-HsC}@d2=*^?wXU6 zBO#oSnaid);iH=DGDCr~H9Bd37sB3p(_Z=Xy@tlMxyyF;n$rP!gyAG_*q7%Fbe%FJ z$y)eviel&01bUu_t^=P=q3L}kAVs!FSIW#}e>~g;$z`mr(*0l}`~AiZ2?4l%2!TKS z5n~+=ls`q;1{;zmz`7zg^CJ`*>M#uXYF0jX;EDzegr{v{DzoJ%V+~SX+E<8P$>KI9 zM+pf80;F=SDr|tH&g$z=lB3wf8TOX?Roqez{LSqsVRuYiPJvcRNaN*|62co5{CS`K zLP-}A&9aFMXo^O_kou=b7Cj{dNcbC_8!@b)CS?X7+-o0JNXFQXxZ#1m^%zzkqH}d` z?%<->9uBWPah9`9K=pKkJG^3YN8aO`0G&$agtbrZlWTyV6?}k-s@$Z^&dtKq2k)a`ycUBsnO=5x(b}Nk>m>@iOLuTsRj<2cm zTngK=t-Z6@3Fb%3s*;caQyU>82Fg^kwe*9_^eR)kmZ7=5W@IDLbpJR4P38nb#ue1V zlH{|qw|MUV&%3w(rEGilB^8-dEoPH0JHa5MvoBPr&A;K;KwJb=s9~wGe}43>1H!gk z$0rB%x4(Mtn$aS$R;$=gJh&EPdVj6d$%KA)Tg-U<#UbVrSh?{tm=@H0Wv_*k4m#y{ zbB4Ix(cK#g9O@ddEVWOR1G^OY;6#aUC4+J zg24NSTNy;wX?a$WgpdT8kK5i{mI>#!Y!fy&H$yLSI_lHxZ;+!{%dwM93iHcUA(cFl zWea(&1M1lW=CY^Ku{H)!KNU1}O^!OsfDKgBxfRpzbw)K`bRk5}X)t+&*5t@Y@ayKc zA~5WZeUv|imYKx{cWC#(e?Xvvpe~?#-W^}#EwnlN^f(2!v%TZ~BIm=Jr>eK4TkAH5 zwTU+KRJi1bBj%~@mqROx_Y*JF5w`p!VDvsnHcrymo38AUEL{B|W=^nzhU%4tB%3T{ z(fAt3Q}A^@@xwE&op#>Uhn7|G`TX9Ag@xGS2bSHM<>ObWL%->+5>;Gm@Na4xtfMlF z0ybn$zqhosboXUmKeX(S1*1xyf{VVldp2RxyFlB2`|9V%{^^l(11HLC)okz)78Mnh zIPmhPaoo(Xf)^_1uGXp~z8E@}IC(uiw=MZlt8e`{j6eUjA;SuXWl@qeY9Idb4yk2! zWX2}4gJ09$FKLm|P{!J-XAfQ}Gh>4LJf9hdbmtvU!FTSkXtgzkT_$++Cx|^QL;arL zyv=@h5f^RtOBnA=+C$Ay-ID};4uAJBm}kmUN8FqI8Ff;jc~b3!(rEyPbD1h%ho<^` zZ=Sk-j+50;Vy^Rl1~fN#*41S_ I35|a4KdaHqasU7T diff --git a/examples/test/post_process/curved_screen_shader_test.dart b/examples/test/post_process/curved_screen_shader_test.dart deleted file mode 100644 index 800b63214a7..00000000000 --- a/examples/test/post_process/curved_screen_shader_test.dart +++ /dev/null @@ -1,137 +0,0 @@ -import 'dart:ui' as ui show Image; -import 'dart:ui' hide Image; - -import 'package:flame/components.dart'; -import 'package:flame/palette.dart'; -import 'package:flame/post_process.dart'; -import 'package:flame_test/flame_test.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter/widgets.dart'; -import 'package:flutter_test/flutter_test.dart'; - -/// Golden tests for https://github.com/flame-engine/flame/issues/3766, using -/// the shader and image from the reporter's reproduction. -/// -/// The Flame goldens should look like the pure Flutter reference golden. Before -/// the fix, the image passed to the shader by `rasterizeSubtree` only had the -/// subtree in its top left corner, so the screen was shrunk by the pixel ratio. -void main() { - const shaderAsset = 'shaders/curved_screen.frag'; - const imageAsset = 'assets/images/curved_screen.png'; - const screenWidth = 200.0; - final goldenSize = Vector2(240, 220); - final backgroundColor = BasicPalette.brown.color; - - group('curved screen shader from issue #3766', () { - testWidgets('renders in pure Flutter', (tester) async { - const key = ValueKey('reference'); - await tester.runAsync(() async { - final data = await rootBundle.load(imageAsset); - final codec = await instantiateImageCodec(data.buffer.asUint8List()); - final image = (await codec.getNextFrame()).image; - final program = await FragmentProgram.fromAsset(shaderAsset); - - await tester.pumpWidget( - Center( - child: RepaintBoundary( - key: key, - child: SizedBox( - width: goldenSize.x, - height: goldenSize.y, - child: ColoredBox( - color: backgroundColor, - child: Center( - child: CustomPaint( - painter: _CurvedScreenPainter( - program.fragmentShader(), - image, - ), - size: Size( - screenWidth, - screenWidth * image.height / image.width, - ), - ), - ), - ), - ), - ), - ), - ); - }); - - await expectLater( - find.byKey(key), - matchesGoldenFile('../_goldens/curved_screen_flutter.png'), - ); - }); - - for (final pixelRatio in [1, 2, 3]) { - testGolden( - 'renders in a PostProcessComponent at pixel ratio $pixelRatio', - (game, tester) async { - final image = await game.images.load(imageAsset); - final program = await FragmentProgram.fromAsset(shaderAsset); - final screen = SpriteComponent.fromImage( - image, - size: Vector2( - screenWidth, - screenWidth * image.height / image.width, - ), - ); - - await game.world.ensureAdd( - PostProcessComponent( - postProcess: _CurvedScreenPostProcess( - program.fragmentShader(), - pixelRatio: pixelRatio.toDouble(), - ), - anchor: Anchor.center, - size: screen.size, - children: [screen], - ), - ); - }, - goldenFile: - '../_goldens/curved_screen_flame_pixel_ratio_$pixelRatio.png', - size: goldenSize, - backgroundColor: backgroundColor, - ); - } - }); -} - -/// The post process from the reporter's reproduction. -class _CurvedScreenPostProcess( - final FragmentShader _shader, { - super.pixelRatio, -}) extends PostProcess { - late final _paint = Paint()..shader = _shader; - - @override - void postProcess(Vector2 size, Canvas canvas) { - final subtree = rasterizeSubtree(); - _shader - ..setFloat(0, size.x) - ..setFloat(1, size.y) - ..setImageSampler(0, subtree); - canvas.drawRect(Offset.zero & size.toSize(), _paint); - } -} - -/// The custom painter from the reporter's reproduction. -class _CurvedScreenPainter( - final FragmentShader _shader, - final ui.Image _image, -) extends CustomPainter { - @override - void paint(Canvas canvas, Size size) { - _shader - ..setFloat(0, size.width) - ..setFloat(1, size.height) - ..setImageSampler(0, _image); - canvas.drawRect(Offset.zero & size, Paint()..shader = _shader); - } - - @override - bool shouldRepaint(_CurvedScreenPainter oldDelegate) => false; -}