Repository: flutter
Issue: Very large and very small scale transforms lead to significant rendering artifacts
Labels: engine,c: rendering,has reproducible steps,P3,team-engine,triaged-engine,found in release: 3.22,found in release: 3.24

The following program tries to render two circles and some text. Each frame it applies a different transform and scales the circles and text up or down appropriately to cancel out the transform.

```dart
import 'dart:ui' as ui;
import 'dart:math' as math;

import 'package:vector_math/vector_math_64.dart';

const int maxT = 10000;

void beginFrame(Duration timeStamp) {
  int t = timeStamp.inMilliseconds % maxT;
  double exponent = ui.lerpDouble(-40.0, 40.0, t / maxT)!;
  double zoom = math.pow(10.0, exponent).toDouble();
  
  final ui.FlutterView view = ui.PlatformDispatcher.instance.implicitView!;
  final ui.Rect paintBounds = ui.Offset.zero & view.physicalSize;
  final ui.PictureRecorder recorder = ui.PictureRecorder();
  final ui.Canvas canvas = ui.Canvas(recorder, paintBounds);
  canvas.drawPaint(ui.Paint());
  final Matrix4 matrix = Matrix4.diagonal3Values(zoom, zoom, zoom);
  canvas.transform(matrix.storage);
  canvas.drawPath(
    ui.Path()
      ..addOval(ui.Rect.fromCircle(
          center: paintBounds.center / zoom,
          radius: 300.0 / (zoom * 5.0),
        ))
      ..addOval(ui.Rect.fromCircle(
          center: paintBounds.center / zoom,
          radius: 400.0 / (zoom * 5.0),
        )),
    ui.Paint()
      ..color = const ui.Color(0xFF00CC99)
      ..strokeWidth = 10.0 / zoom
      ..style = ui.PaintingStyle.stroke,
  );
  final ui.ParagraphBuilder paragraphBuilder = ui.ParagraphBuilder(ui.ParagraphStyle());
  paragraphBuilder.pushStyle(ui.TextStyle(color: const ui.Color(0xFFCC0099), fontSize: 20.0 / zoom));
  paragraphBuilder.addText('zoom: ${zoom.toStringAsExponential(5)}');
  final ui.Paragraph paragraph = paragraphBuilder.build();
  paragraph.layout(const ui.ParagraphConstraints(width: double.infinity));
  canvas.drawParagraph(paragraph, paintBounds.center / (2 * zoom));
  paragraph.dispose();
  final ui.Picture picture = recorder.endRecording();
  final ui.SceneBuilder sceneBuilder = ui.SceneBuilder();
  sceneBuilder.addPicture(ui.Offset.zero, picture);
  picture.dispose();
  final ui.Scene scene = sceneBuilder.build();
  view.render(scene);
  scene.dispose();
  ui.PlatformDispatcher.instance.scheduleFrame();
}

void main() {
  ui.PlatformDispatcher.instance.onBeginFrame = beginFrame;
}
```

The effects seem to vary by platform and/or rendering backend.

I would chalk this up to "floating point issues" except even 32 bit floats (which appears to be what Skia uses, haven't checked Impeller) should have precision well below 1e-20 and well above 1e20. Most of the abnormalities reported below happen _well_ within the range of what a 32 bit float can handle.

On a Pixel 6A using Android with Impeller (I believe), very small zooms (1e-20) render nothing. Small zooms (1e-10) render the circles as full-screen fills -- well, based on what happens for zooms from 1e-8 to 1e-3[^1] I'm guessing the circles are just really really big. The text doesn't render at all until the zoom gets up to 1e-2 or so, and then from 1e1 to 1e4 the text rendering is progressively less accurate[^2]. 1e40 (which is subnormal for 32 bit floats) causes the application to crash, but that's probably a separate issue.

On Linux with Skia, very small zooms again render nothing (1e-36). Then as we continue up the logarithmic scale we see diamonds[^3], still with no text, eventually (4e-18 on my device) we see both a diamond and a circle[^4], eventually we see the circles (still with no text), then the text appears (around 1e-3[^4]), then the text moves up by about a line's worth of space (1e2), before disappearing again (around 1e4), then the circles transition through a number of different shapes like octagons and squircles (e.g. at 1.7e23[^5]), then the diamonds come back, briefly transition through some other odd shapes (e.g. at 1.19e24[^6]), and eventually at really large zooms (1e38..1e40, so in the subnormal range) the diamonds start shrinking to nothing, though at the very end before disappearing they do become circles again.

[^1]: android/impeller 1e-3
   ![](https://github.com/user-attachments/assets/5a7eee28-91fc-4c8b-b211-875523e93c11)
[^2]: android/impeller 1e3
    ![](https://github.com/user-attachments/assets/417dce8e-bedf-408a-8e64-c3590dab953b)
[^3]: linux/skia 1e-20
   ![](https://github.com/user-attachments/assets/3372b86b-82f6-41b5-8e56-606fc503c4d2)
[^4]: linux/skia 1e-3
   ![](https://github.com/user-attachments/assets/44cc9b44-7bd8-4c59-84b9-7bc11e480e56)
[^5]: linux/skia 1.7e23
   ![](https://github.com/user-attachments/assets/4d98de0a-3cea-4504-8ee9-261c5ebc19ab)
[^6]: linux/skia 1e19e24
   ![](https://github.com/user-attachments/assets/3f67a417-79db-40b2-8f92-b29abba86689)