Skip to content

Very large and very small scale transforms lead to significant rendering artifacts #152520

@Hixie

Description

@Hixie

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.

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-31 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 accurate2. 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 diamonds3, still with no text, eventually (4e-18 on my device) we see both a diamond and a circle4, eventually we see the circles (still with no text), then the text appears (around 1e-34), 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.7e235), then the diamonds come back, briefly transition through some other odd shapes (e.g. at 1.19e246), 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.

Footnotes

  1. android/impeller 1e-3

  2. android/impeller 1e3

  3. linux/skia 1e-20

  4. linux/skia 1e-3
    2

  5. linux/skia 1.7e23

  6. linux/skia 1e19e24

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Issues that are less important to the Flutter projectc: renderingUI glitches reported at the engine/skia or impeller rendering levelengineflutter/engine related. See also e: labels.found in release: 3.22Found to occur in 3.22found in release: 3.24Found to occur in 3.24has reproducible stepsThe issue has been confirmed reproducible and is ready to work onteam-engineOwned by Engine teamtriaged-engineTriaged by Engine team

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions