未验证 提交 1ad67652 编写于 作者: F Ferhat 提交者: GitHub

[web] Fixes canvas pixelation and overallocation due to transforms. (#22160)

上级 37d766c0
...@@ -104,18 +104,23 @@ class BitmapCanvas extends EngineCanvas { ...@@ -104,18 +104,23 @@ class BitmapCanvas extends EngineCanvas {
/// can be constructed from contents. /// can be constructed from contents.
bool _preserveImageData = false; bool _preserveImageData = false;
/// Canvas pixel to screen pixel ratio. Similar to dpi but
/// uses global transform of canvas to compute ratio.
final double _density;
/// Allocates a canvas with enough memory to paint a picture within the given /// Allocates a canvas with enough memory to paint a picture within the given
/// [bounds]. /// [bounds].
/// ///
/// This canvas can be reused by pictures with different paint bounds as long /// This canvas can be reused by pictures with different paint bounds as long
/// as the [Rect.size] of the bounds fully fit within the size used to /// as the [Rect.size] of the bounds fully fit within the size used to
/// initialize this canvas. /// initialize this canvas.
BitmapCanvas(this._bounds) BitmapCanvas(this._bounds, {double density = 1.0})
: assert(_bounds != null), // ignore: unnecessary_null_comparison : assert(_bounds != null), // ignore: unnecessary_null_comparison
_density = density,
_widthInBitmapPixels = _widthToPhysical(_bounds.width), _widthInBitmapPixels = _widthToPhysical(_bounds.width),
_heightInBitmapPixels = _heightToPhysical(_bounds.height), _heightInBitmapPixels = _heightToPhysical(_bounds.height),
_canvasPool = _CanvasPool(_widthToPhysical(_bounds.width), _canvasPool = _CanvasPool(_widthToPhysical(_bounds.width),
_heightToPhysical(_bounds.height)) { _heightToPhysical(_bounds.height), density) {
rootElement.style.position = 'absolute'; rootElement.style.position = 'absolute';
// Adds one extra pixel to the requested size. This is to compensate for // Adds one extra pixel to the requested size. This is to compensate for
// _initializeViewport() snapping canvas position to 1 pixel, causing // _initializeViewport() snapping canvas position to 1 pixel, causing
...@@ -179,10 +184,11 @@ class BitmapCanvas extends EngineCanvas { ...@@ -179,10 +184,11 @@ class BitmapCanvas extends EngineCanvas {
} }
// Used by picture to assess if canvas is large enough to reuse as is. // Used by picture to assess if canvas is large enough to reuse as is.
bool doesFitBounds(ui.Rect newBounds) { bool doesFitBounds(ui.Rect newBounds, double newDensity) {
assert(newBounds != null); // ignore: unnecessary_null_comparison assert(newBounds != null); // ignore: unnecessary_null_comparison
return _widthInBitmapPixels >= _widthToPhysical(newBounds.width) && return _widthInBitmapPixels >= _widthToPhysical(newBounds.width) &&
_heightInBitmapPixels >= _heightToPhysical(newBounds.height); _heightInBitmapPixels >= _heightToPhysical(newBounds.height) &&
_density == newDensity;
} }
@override @override
......
...@@ -33,8 +33,10 @@ class _CanvasPool extends _SaveStackTracking { ...@@ -33,8 +33,10 @@ class _CanvasPool extends _SaveStackTracking {
html.HtmlElement? _rootElement; html.HtmlElement? _rootElement;
int _saveContextCount = 0; int _saveContextCount = 0;
final double _density;
_CanvasPool(this._widthInBitmapPixels, this._heightInBitmapPixels); _CanvasPool(this._widthInBitmapPixels, this._heightInBitmapPixels,
this._density);
html.CanvasRenderingContext2D get context { html.CanvasRenderingContext2D get context {
html.CanvasRenderingContext2D? ctx = _context; html.CanvasRenderingContext2D? ctx = _context;
...@@ -83,7 +85,12 @@ class _CanvasPool extends _SaveStackTracking { ...@@ -83,7 +85,12 @@ class _CanvasPool extends _SaveStackTracking {
void _createCanvas() { void _createCanvas() {
bool requiresClearRect = false; bool requiresClearRect = false;
bool reused = false; bool reused = false;
html.CanvasElement canvas; html.CanvasElement? canvas;
if (_canvas != null) {
_canvas!.width = 0;
_canvas!.height = 0;
_canvas = null;
}
if (_reusablePool != null && _reusablePool!.isNotEmpty) { if (_reusablePool != null && _reusablePool!.isNotEmpty) {
canvas = _canvas = _reusablePool!.removeAt(0); canvas = _canvas = _reusablePool!.removeAt(0);
requiresClearRect = true; requiresClearRect = true;
...@@ -99,10 +106,7 @@ class _CanvasPool extends _SaveStackTracking { ...@@ -99,10 +106,7 @@ class _CanvasPool extends _SaveStackTracking {
_widthInBitmapPixels / EnginePlatformDispatcher.browserDevicePixelRatio; _widthInBitmapPixels / EnginePlatformDispatcher.browserDevicePixelRatio;
final double cssHeight = final double cssHeight =
_heightInBitmapPixels / EnginePlatformDispatcher.browserDevicePixelRatio; _heightInBitmapPixels / EnginePlatformDispatcher.browserDevicePixelRatio;
canvas = html.CanvasElement( canvas = _allocCanvas(_widthInBitmapPixels, _heightInBitmapPixels);
width: _widthInBitmapPixels,
height: _heightInBitmapPixels,
);
_canvas = canvas; _canvas = canvas;
// Why is this null check here, even though we just allocated a canvas element above? // Why is this null check here, even though we just allocated a canvas element above?
...@@ -113,12 +117,9 @@ class _CanvasPool extends _SaveStackTracking { ...@@ -113,12 +117,9 @@ class _CanvasPool extends _SaveStackTracking {
if (_canvas == null) { if (_canvas == null) {
// Evict BitmapCanvas(s) and retry. // Evict BitmapCanvas(s) and retry.
_reduceCanvasMemoryUsage(); _reduceCanvasMemoryUsage();
canvas = html.CanvasElement( canvas = _allocCanvas(_widthInBitmapPixels, _heightInBitmapPixels);
width: _widthInBitmapPixels,
height: _heightInBitmapPixels,
);
} }
canvas.style canvas!.style
..position = 'absolute' ..position = 'absolute'
..width = '${cssWidth}px' ..width = '${cssWidth}px'
..height = '${cssHeight}px'; ..height = '${cssHeight}px';
...@@ -131,19 +132,55 @@ class _CanvasPool extends _SaveStackTracking { ...@@ -131,19 +132,55 @@ class _CanvasPool extends _SaveStackTracking {
_rootElement!.append(canvas); _rootElement!.append(canvas);
} }
if (reused) { try {
// If a canvas is the first element we set z-index = -1 in [BitmapCanvas] if (reused) {
// endOfPaint to workaround blink compositing bug. To make sure this // If a canvas is the first element we set z-index = -1 in [BitmapCanvas]
// does not leak when reused reset z-index. // endOfPaint to workaround blink compositing bug. To make sure this
canvas.style.removeProperty('z-index'); // does not leak when reused reset z-index.
canvas.style.removeProperty('z-index');
}
_context = canvas.context2D;
} catch (e) {
// Handle OOM.
} }
if (_context == null) {
final html.CanvasRenderingContext2D context = _context = canvas.context2D; _reduceCanvasMemoryUsage();
_contextHandle = ContextStateHandle(this, context); _context = canvas.context2D;
}
if (_context == null) {
/// Browser ran out of memory, try to recover current allocation
/// and bail.
_canvas?.width = 0;
_canvas?.height = 0;
_canvas = null;
return;
}
_contextHandle = ContextStateHandle(this, _context!, this._density);
_initializeViewport(requiresClearRect); _initializeViewport(requiresClearRect);
_replayClipStack(); _replayClipStack();
} }
html.CanvasElement? _allocCanvas(int width, int height) {
final dynamic canvas =
js_util.callMethod(html.document, 'createElement', <dynamic>['CANVAS']);
if (canvas != null) {
try {
canvas.width = (width * _density).ceil();
canvas.height = (height * _density).ceil();
} catch (e) {
return null;
}
return canvas as html.CanvasElement;
}
return null;
// !!! We don't use the code below since NNBD assumes it can never return
// null and optimizes out code.
// return canvas = html.CanvasElement(
// width: _widthInBitmapPixels,
// height: _heightInBitmapPixels,
// );
}
@override @override
void clear() { void clear() {
super.clear(); super.clear();
...@@ -188,7 +225,7 @@ class _CanvasPool extends _SaveStackTracking { ...@@ -188,7 +225,7 @@ class _CanvasPool extends _SaveStackTracking {
clipTimeTransform[5] != prevTransform[5] || clipTimeTransform[5] != prevTransform[5] ||
clipTimeTransform[12] != prevTransform[12] || clipTimeTransform[12] != prevTransform[12] ||
clipTimeTransform[13] != prevTransform[13]) { clipTimeTransform[13] != prevTransform[13]) {
final double ratio = EnginePlatformDispatcher.browserDevicePixelRatio; final double ratio = dpi;
ctx.setTransform(ratio, 0, 0, ratio, 0, 0); ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
ctx.transform( ctx.transform(
clipTimeTransform[0], clipTimeTransform[0],
...@@ -222,7 +259,7 @@ class _CanvasPool extends _SaveStackTracking { ...@@ -222,7 +259,7 @@ class _CanvasPool extends _SaveStackTracking {
transform[5] != prevTransform[5] || transform[5] != prevTransform[5] ||
transform[12] != prevTransform[12] || transform[12] != prevTransform[12] ||
transform[13] != prevTransform[13]) { transform[13] != prevTransform[13]) {
final double ratio = EnginePlatformDispatcher.browserDevicePixelRatio; final double ratio = dpi;
ctx.setTransform(ratio, 0, 0, ratio, 0, 0); ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
ctx.transform(transform[0], transform[1], transform[4], transform[5], ctx.transform(transform[0], transform[1], transform[4], transform[5],
transform[12], transform[13]); transform[12], transform[13]);
...@@ -300,15 +337,19 @@ class _CanvasPool extends _SaveStackTracking { ...@@ -300,15 +337,19 @@ class _CanvasPool extends _SaveStackTracking {
// is applied on the DOM elements. // is applied on the DOM elements.
ctx.setTransform(1, 0, 0, 1, 0, 0); ctx.setTransform(1, 0, 0, 1, 0, 0);
if (clearCanvas) { if (clearCanvas) {
ctx.clearRect(0, 0, _widthInBitmapPixels, _heightInBitmapPixels); ctx.clearRect(0, 0, _widthInBitmapPixels * _density,
_heightInBitmapPixels * _density);
} }
// This scale makes sure that 1 CSS pixel is translated to the correct // This scale makes sure that 1 CSS pixel is translated to the correct
// number of bitmap pixels. // number of bitmap pixels.
ctx.scale(EnginePlatformDispatcher.browserDevicePixelRatio, ctx.scale(dpi, dpi);
EnginePlatformDispatcher.browserDevicePixelRatio);
} }
/// Returns effective dpi (browser DPI and pixel density due to transform).
double get dpi =>
EnginePlatformDispatcher.browserDevicePixelRatio * _density;
void resetTransform() { void resetTransform() {
final html.CanvasElement? canvas = _canvas; final html.CanvasElement? canvas = _canvas;
if (canvas != null) { if (canvas != null) {
...@@ -688,8 +729,9 @@ class _CanvasPool extends _SaveStackTracking { ...@@ -688,8 +729,9 @@ class _CanvasPool extends _SaveStackTracking {
class ContextStateHandle { class ContextStateHandle {
final html.CanvasRenderingContext2D context; final html.CanvasRenderingContext2D context;
final _CanvasPool _canvasPool; final _CanvasPool _canvasPool;
final double density;
ContextStateHandle(this._canvasPool, this.context); ContextStateHandle(this._canvasPool, this.context, this.density);
ui.BlendMode? _currentBlendMode = ui.BlendMode.srcOver; ui.BlendMode? _currentBlendMode = ui.BlendMode.srcOver;
ui.StrokeCap? _currentStrokeCap = ui.StrokeCap.butt; ui.StrokeCap? _currentStrokeCap = ui.StrokeCap.butt;
ui.StrokeJoin? _currentStrokeJoin = ui.StrokeJoin.miter; ui.StrokeJoin? _currentStrokeJoin = ui.StrokeJoin.miter;
...@@ -778,7 +820,8 @@ class ContextStateHandle { ...@@ -778,7 +820,8 @@ class ContextStateHandle {
if (paint.shader != null) { if (paint.shader != null) {
final EngineGradient engineShader = paint.shader as EngineGradient; final EngineGradient engineShader = paint.shader as EngineGradient;
final Object paintStyle = final Object paintStyle =
engineShader.createPaintStyle(_canvasPool.context, shaderBounds); engineShader.createPaintStyle(_canvasPool.context, shaderBounds,
density);
fillStyle = paintStyle; fillStyle = paintStyle;
strokeStyle = paintStyle; strokeStyle = paintStyle;
} else if (paint.color != null) { } else if (paint.color != null) {
......
...@@ -90,6 +90,7 @@ class PersistedPicture extends PersistedLeafSurface { ...@@ -90,6 +90,7 @@ class PersistedPicture extends PersistedLeafSurface {
final EnginePicture picture; final EnginePicture picture;
final ui.Rect? localPaintBounds; final ui.Rect? localPaintBounds;
final int hints; final int hints;
double _density = 1.0;
/// Cache for reusing elements such as images across picture updates. /// Cache for reusing elements such as images across picture updates.
CrossFrameCache<html.HtmlElement>? _elementCache = CrossFrameCache<html.HtmlElement>? _elementCache =
...@@ -107,6 +108,23 @@ class PersistedPicture extends PersistedLeafSurface { ...@@ -107,6 +108,23 @@ class PersistedPicture extends PersistedLeafSurface {
_transform = _transform!.clone(); _transform = _transform!.clone();
_transform!.translate(dx, dy); _transform!.translate(dx, dy);
} }
final double paintWidth = localPaintBounds!.width;
final double paintHeight = localPaintBounds!.height;
final double newDensity = localPaintBounds == null || paintWidth == 0 || paintHeight == 0
? 1.0 : _computePixelDensity(_transform, paintWidth, paintHeight);
if (newDensity != _density) {
_density = newDensity;
if (_canvas != null) {
// If cull rect and density hasn't changed, this will only repaint.
// If density doesn't match canvas, a new canvas will be created
// and paint queued.
//
// Similar to preroll for transform where transform is updated, for
// picture this means we need to repaint so pixelation doesn't occur
// due to transform changing overall dpi.
applyPaint(_canvas);
}
}
_computeExactCullRects(); _computeExactCullRects();
} }
...@@ -296,7 +314,12 @@ class PersistedPicture extends PersistedLeafSurface { ...@@ -296,7 +314,12 @@ class PersistedPicture extends PersistedLeafSurface {
// painting. This removes all the setup work and scaffolding objects // painting. This removes all the setup work and scaffolding objects
// that won't be useful for anything anyway. // that won't be useful for anything anyway.
_recycleCanvas(oldCanvas); _recycleCanvas(oldCanvas);
domRenderer.clearDom(rootElement!); if (rootElement != null) {
domRenderer.clearDom(rootElement!);
}
if (_canvas != null) {
_recycleCanvas(_canvas);
}
_canvas = null; _canvas = null;
return; return;
} }
...@@ -339,7 +362,7 @@ class PersistedPicture extends PersistedLeafSurface { ...@@ -339,7 +362,7 @@ class PersistedPicture extends PersistedLeafSurface {
// We did not allocate a canvas last time. This can happen when the // We did not allocate a canvas last time. This can happen when the
// picture is completely clipped out of the view. // picture is completely clipped out of the view.
return 1.0; return 1.0;
} else if (!oldCanvas.doesFitBounds(_exactLocalCullRect!)) { } else if (!oldCanvas.doesFitBounds(_exactLocalCullRect!, _density)) {
// The canvas needs to be resized before painting. // The canvas needs to be resized before painting.
return 1.0; return 1.0;
} else { } else {
...@@ -382,7 +405,7 @@ class PersistedPicture extends PersistedLeafSurface { ...@@ -382,7 +405,7 @@ class PersistedPicture extends PersistedLeafSurface {
void _applyBitmapPaint(EngineCanvas? oldCanvas) { void _applyBitmapPaint(EngineCanvas? oldCanvas) {
if (oldCanvas is BitmapCanvas && if (oldCanvas is BitmapCanvas &&
oldCanvas.doesFitBounds(_optimalLocalCullRect!) && oldCanvas.doesFitBounds(_optimalLocalCullRect!, _density) &&
oldCanvas.isReusable()) { oldCanvas.isReusable()) {
if (_debugShowCanvasReuseStats) { if (_debugShowCanvasReuseStats) {
DebugCanvasReuseOverlay.instance.keptCount++; DebugCanvasReuseOverlay.instance.keptCount++;
...@@ -451,7 +474,7 @@ class PersistedPicture extends PersistedLeafSurface { ...@@ -451,7 +474,7 @@ class PersistedPicture extends PersistedLeafSurface {
final double candidatePixelCount = final double candidatePixelCount =
candidateSize.width * candidateSize.height; candidateSize.width * candidateSize.height;
final bool fits = candidate.doesFitBounds(bounds); final bool fits = candidate.doesFitBounds(bounds, _density);
final bool isSmaller = candidatePixelCount < lastPixelCount; final bool isSmaller = candidatePixelCount < lastPixelCount;
if (fits && isSmaller) { if (fits && isSmaller) {
// [isTooSmall] is used to make sure that a small picture doesn't // [isTooSmall] is used to make sure that a small picture doesn't
...@@ -493,7 +516,7 @@ class PersistedPicture extends PersistedLeafSurface { ...@@ -493,7 +516,7 @@ class PersistedPicture extends PersistedLeafSurface {
if (_debugShowCanvasReuseStats) { if (_debugShowCanvasReuseStats) {
DebugCanvasReuseOverlay.instance.createdCount++; DebugCanvasReuseOverlay.instance.createdCount++;
} }
final BitmapCanvas canvas = BitmapCanvas(bounds); final BitmapCanvas canvas = BitmapCanvas(bounds, density: _density);
canvas.setElementCache(_elementCache); canvas.setElementCache(_elementCache);
if (_debugExplainSurfaceStats) { if (_debugExplainSurfaceStats) {
_surfaceStatsFor(this) _surfaceStatsFor(this)
...@@ -536,8 +559,12 @@ class PersistedPicture extends PersistedLeafSurface { ...@@ -536,8 +559,12 @@ class PersistedPicture extends PersistedLeafSurface {
final bool cullRectChangeRequiresRepaint = final bool cullRectChangeRequiresRepaint =
_computeOptimalCullRect(oldSurface); _computeOptimalCullRect(oldSurface);
if (identical(picture, oldSurface.picture)) { if (identical(picture, oldSurface.picture)) {
bool densityChanged =
(_canvas is BitmapCanvas &&
_density != (_canvas as BitmapCanvas)._density);
// The picture is the same. Attempt to avoid repaint. // The picture is the same. Attempt to avoid repaint.
if (cullRectChangeRequiresRepaint) { if (cullRectChangeRequiresRepaint || densityChanged) {
// Cull rect changed such that a repaint is still necessary. // Cull rect changed such that a repaint is still necessary.
_applyPaint(oldSurface); _applyPaint(oldSurface);
} else { } else {
...@@ -603,3 +630,72 @@ class PersistedPicture extends PersistedLeafSurface { ...@@ -603,3 +630,72 @@ class PersistedPicture extends PersistedLeafSurface {
} }
} }
} }
/// Given size of a rectangle and transform, computes pixel density
/// (scale factor).
double _computePixelDensity(Matrix4? transform, double width, double height) {
if (transform == null || transform.isIdentity()) {
return 1.0;
}
final Float32List m = transform.storage;
// Apply perspective transform to all 4 corners. Can't use left,top, bottom,
// right since for example rotating 45 degrees would yield inaccurate size.
double minX = m[12] * m[15];
double minY = m[13] * m[15];
double maxX = minX;
double maxY = minY;
double x = width;
double y = height;
double wp = 1.0 / ((m[3] * x) + (m[7] * y) + m[15]);
double xp = ((m[0] * x) + (m[4] * y) + m[12]) * wp;
double yp = ((m[1] * x) + (m[5] * y) + m[13]) * wp;
print('$xp,$yp');
minX = math.min(minX, xp);
maxX = math.max(maxX, xp);
minY = math.min(minY, yp);
maxY = math.max(maxY, yp);
x = 0;
wp = 1.0 / ((m[3] * x) + (m[7] * y) + m[15]);
xp = ((m[0] * x) + (m[4] * y) + m[12]) * wp;
yp = ((m[1] * x) + (m[5] * y) + m[13]) * wp;
print('$xp,$yp');
minX = math.min(minX, xp);
maxX = math.max(maxX, xp);
minY = math.min(minY, yp);
maxY = math.max(maxY, yp);
x = width;
y = 0;
wp = 1.0 / ((m[3] * x) + (m[7] * y) + m[15]);
xp = ((m[0] * x) + (m[4] * y) + m[12]) * wp;
yp = ((m[1] * x) + (m[5] * y) + m[13]) * wp;
print('$xp,$yp');
minX = math.min(minX, xp);
maxX = math.max(maxX, xp);
minY = math.min(minY, yp);
maxY = math.max(maxY, yp);
double scaleX = (maxX - minX) / width;
double scaleY = (maxY - minY) / height;
double scale = math.min(scaleX, scaleY);
// kEpsilon guards against divide by zero below.
if (scale < kEpsilon || scale == 1) {
// Handle local paint bounds scaled to 0, typical when using
// transform animations and nothing is drawn.
return 1.0;
}
if (scale > 1) {
// Normalize scale to multiples of 2: 1x, 2x, 4x, 6x, 8x.
// This is to prevent frequent rescaling of canvas during animations.
//
// On a fullscreen high dpi device dpi*density*resolution will demand
// too much memory, so clamp at 4.
scale = math.min(4.0, ((scale / 2.0).ceil() * 2.0));
// Guard against webkit absolute limit.
const double kPixelLimit = 1024 * 1024 * 4;
if ((width * height * scale * scale) > kPixelLimit && scale > 2) {
scale = (kPixelLimit * 0.8) / (width * height);
}
} else {
scale = math.max(2.0 / (2.0 / scale).floor(), 0.0001);
}
return scale;
}
...@@ -11,7 +11,7 @@ abstract class EngineGradient implements ui.Gradient { ...@@ -11,7 +11,7 @@ abstract class EngineGradient implements ui.Gradient {
/// Creates a fill style to be used in painting. /// Creates a fill style to be used in painting.
Object createPaintStyle(html.CanvasRenderingContext2D? ctx, Object createPaintStyle(html.CanvasRenderingContext2D? ctx,
ui.Rect? shaderBounds); ui.Rect? shaderBounds, double density);
} }
class GradientSweep extends EngineGradient { class GradientSweep extends EngineGradient {
...@@ -29,7 +29,7 @@ class GradientSweep extends EngineGradient { ...@@ -29,7 +29,7 @@ class GradientSweep extends EngineGradient {
@override @override
Object createPaintStyle(html.CanvasRenderingContext2D? ctx, Object createPaintStyle(html.CanvasRenderingContext2D? ctx,
ui.Rect? shaderBounds) { ui.Rect? shaderBounds, double density) {
assert(shaderBounds != null); assert(shaderBounds != null);
int widthInPixels = shaderBounds!.right.ceil(); int widthInPixels = shaderBounds!.right.ceil();
int heightInPixels = shaderBounds.bottom.ceil(); int heightInPixels = shaderBounds.bottom.ceil();
...@@ -167,7 +167,7 @@ class GradientLinear extends EngineGradient { ...@@ -167,7 +167,7 @@ class GradientLinear extends EngineGradient {
@override @override
html.CanvasGradient createPaintStyle(html.CanvasRenderingContext2D? ctx, html.CanvasGradient createPaintStyle(html.CanvasRenderingContext2D? ctx,
ui.Rect? shaderBounds) { ui.Rect? shaderBounds, double density) {
_FastMatrix64? matrix4 = this.matrix4; _FastMatrix64? matrix4 = this.matrix4;
html.CanvasGradient gradient; html.CanvasGradient gradient;
if (matrix4 != null) { if (matrix4 != null) {
...@@ -215,7 +215,7 @@ class GradientRadial extends EngineGradient { ...@@ -215,7 +215,7 @@ class GradientRadial extends EngineGradient {
@override @override
Object createPaintStyle(html.CanvasRenderingContext2D? ctx, Object createPaintStyle(html.CanvasRenderingContext2D? ctx,
ui.Rect? shaderBounds) { ui.Rect? shaderBounds, double density) {
if (!useCanvasKit) { if (!useCanvasKit) {
if (tileMode != ui.TileMode.clamp) { if (tileMode != ui.TileMode.clamp) {
throw UnimplementedError( throw UnimplementedError(
...@@ -255,7 +255,7 @@ class GradientConical extends EngineGradient { ...@@ -255,7 +255,7 @@ class GradientConical extends EngineGradient {
@override @override
Object createPaintStyle(html.CanvasRenderingContext2D? ctx, Object createPaintStyle(html.CanvasRenderingContext2D? ctx,
ui.Rect? shaderBounds) { ui.Rect? shaderBounds, double density) {
throw UnimplementedError(); throw UnimplementedError();
} }
} }
......
...@@ -33,21 +33,24 @@ const double _kScreenPixelRatioWarningThreshold = 6.0; ...@@ -33,21 +33,24 @@ const double _kScreenPixelRatioWarningThreshold = 6.0;
/// Performs any outstanding painting work enqueued by [PersistedPicture]s. /// Performs any outstanding painting work enqueued by [PersistedPicture]s.
void commitScene(PersistedScene scene) { void commitScene(PersistedScene scene) {
if (_paintQueue.isNotEmpty) { if (_paintQueue.isNotEmpty) {
if (_paintQueue.length > 1) { try {
// Sort paint requests in decreasing canvas size order. Paint requests if (_paintQueue.length > 1) {
// attempt to reuse canvases. For efficiency we want the biggest pictures // Sort paint requests in decreasing canvas size order. Paint requests
// to find canvases before the smaller ones claim them. // attempt to reuse canvases. For efficiency we want the biggest pictures
_paintQueue.sort((_PaintRequest a, _PaintRequest b) { // to find canvases before the smaller ones claim them.
final double aSize = a.canvasSize.height * a.canvasSize.width; _paintQueue.sort((_PaintRequest a, _PaintRequest b) {
final double bSize = b.canvasSize.height * b.canvasSize.width; final double aSize = a.canvasSize.height * a.canvasSize.width;
return bSize.compareTo(aSize); final double bSize = b.canvasSize.height * b.canvasSize.width;
}); return bSize.compareTo(aSize);
} });
}
for (_PaintRequest request in _paintQueue) { for (_PaintRequest request in _paintQueue) {
request.paintCallback(); request.paintCallback();
}
} finally {
_paintQueue = <_PaintRequest>[];
} }
_paintQueue = <_PaintRequest>[];
} }
// After the update the retained surfaces are back to active. // After the update the retained surfaces are back to active.
...@@ -356,6 +359,7 @@ abstract class PersistedSurface implements ui.EngineLayer { ...@@ -356,6 +359,7 @@ abstract class PersistedSurface implements ui.EngineLayer {
assert(rootElement == null); assert(rootElement == null);
assert(debugAssertSurfaceState(this, PersistedSurfaceState.created)); assert(debugAssertSurfaceState(this, PersistedSurfaceState.created));
rootElement = createElement(); rootElement = createElement();
assert(rootElement != null);
applyWebkitClipFix(rootElement); applyWebkitClipFix(rootElement);
if (_debugExplainSurfaceStats) { if (_debugExplainSurfaceStats) {
_surfaceStatsFor(this).allocatedDomNodeCount++; _surfaceStatsFor(this).allocatedDomNodeCount++;
......
...@@ -525,6 +525,70 @@ void testMain() { ...@@ -525,6 +525,70 @@ void testMain() {
await testCase('be', 'remove in the middle', deletions: 2); await testCase('be', 'remove in the middle', deletions: 2);
await testCase('', 'remove all', deletions: 2); await testCase('', 'remove all', deletions: 2);
}); });
test('Canvas should allocate fewer pixels when zoomed out', () async {
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
final Picture picture1 = _drawPicture();
builder.pushClipRect(const Rect.fromLTRB(10, 10, 300, 300));
builder.addPicture(Offset.zero, picture1);
builder.pop();
html.HtmlElement content = builder.build().webOnlyRootElement;
html.CanvasElement canvas = content.querySelector('canvas');
final int unscaledWidth = canvas.width;
final int unscaledHeight = canvas.height;
// Force update to scene which will utilize reuse code path.
final SurfaceSceneBuilder builder2 = SurfaceSceneBuilder();
builder2.pushOffset(0, 0);
builder2.pushTransform(Matrix4.identity().scaled(0.5, 0.5).toFloat64());
builder2.pushClipRect(
const Rect.fromLTRB(10, 10, 300, 300),
);
builder2.addPicture(Offset.zero, picture1);
builder2.pop();
builder2.pop();
builder2.pop();
html.HtmlElement contentAfterScale = builder2.build().webOnlyRootElement;
html.CanvasElement canvas2 = contentAfterScale.querySelector('canvas');
// Although we are drawing same picture, due to scaling the new canvas
// should have fewer pixels.
expect(canvas2.width < unscaledWidth, true);
expect(canvas2.height < unscaledHeight, true);
});
test('Canvas should allocate more pixels when zoomed in', () async {
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
final Picture picture1 = _drawPicture();
builder.pushClipRect(const Rect.fromLTRB(10, 10, 300, 300));
builder.addPicture(Offset.zero, picture1);
builder.pop();
html.HtmlElement content = builder.build().webOnlyRootElement;
html.CanvasElement canvas = content.querySelector('canvas');
final int unscaledWidth = canvas.width;
final int unscaledHeight = canvas.height;
// Force update to scene which will utilize reuse code path.
final SurfaceSceneBuilder builder2 = SurfaceSceneBuilder();
builder2.pushOffset(0, 0);
builder2.pushTransform(Matrix4.identity().scaled(2, 2).toFloat64());
builder2.pushClipRect(
const Rect.fromLTRB(10, 10, 300, 300),
);
builder2.addPicture(Offset.zero, picture1);
builder2.pop();
builder2.pop();
builder2.pop();
html.HtmlElement contentAfterScale = builder2.build().webOnlyRootElement;
html.CanvasElement canvas2 = contentAfterScale.querySelector('canvas');
// Although we are drawing same picture, due to scaling the new canvas
// should have more pixels.
expect(canvas2.width > unscaledWidth, true);
expect(canvas2.height > unscaledHeight, true);
});
} }
typedef TestLayerBuilder = EngineLayer Function( typedef TestLayerBuilder = EngineLayer Function(
......
...@@ -163,7 +163,7 @@ void testMain() async { ...@@ -163,7 +163,7 @@ void testMain() async {
await matchGoldenFile( await matchGoldenFile(
'shadows.png', 'shadows.png',
region: region, region: region,
maxDiffRatePercent: 0.0, maxDiffRatePercent: 0.23,
pixelComparison: PixelComparison.precise, pixelComparison: PixelComparison.precise,
); );
}, },
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册