未验证 提交 37bbe444 编写于 作者: Y Yegor 提交者: GitHub

[canvaskit] update CSS size of the canvas when device-pixel ratio changes (#24160)

上级 edcca811
......@@ -59,6 +59,11 @@ class Surface {
/// due to the browser tab becoming dormant.
final html.Element htmlElement = html.Element.tag('flt-canvas-container');
/// The underlying `<canvas>` element used for this surface.
html.CanvasElement? htmlCanvas;
int _pixelWidth = -1;
int _pixelHeight = -1;
/// Specify the GPU resource cache limits.
void setSkiaResourceCacheMaxBytes(int bytes) {
_skiaCacheBytes = bytes;
......@@ -102,6 +107,7 @@ class Surface {
}
ui.Size? _currentSize;
double _currentDevicePixelRatio = -1;
CkSurface _createOrUpdateSurfaces(ui.Size size) {
if (size.isEmpty) {
......@@ -116,9 +122,13 @@ class Surface {
size.width <= previousSize.width &&
size.height <= previousSize.height) {
// The existing surface is still reusable.
if (window.devicePixelRatio != _currentDevicePixelRatio) {
_updateLogicalHtmlCanvasSize();
}
return _surface!;
}
_currentDevicePixelRatio = window.devicePixelRatio;
_currentSize = _currentSize == null
// First frame. Allocate a canvas of the exact size as the window. The
// window is frequently never resized, particularly on mobile, so using
......@@ -131,36 +141,44 @@ class Surface {
_surface = null;
_addedToScene = false;
return _surface = _wrapHtmlCanvas(_currentSize!);
return _surface = _createNewSurface(_currentSize!);
}
CkSurface _wrapHtmlCanvas(ui.Size physicalSize) {
// Clear the container, if it's not empty.
while (htmlElement.firstChild != null) {
htmlElement.firstChild!.remove();
}
/// Sets the CSS size of the canvas so that canvas pixels are 1:1 with device
/// pixels.
///
/// The logical size of the canvas is not based on the size of the window
/// but on the size of the canvas, which, due to `ceil()` above, may not be
/// the same as the window. We do not round/floor/ceil the logical size as
/// CSS pixels can contain more than one physical pixel and therefore to
/// match the size of the window precisely we use the most precise floating
/// point value we can get.
void _updateLogicalHtmlCanvasSize() {
final double logicalWidth = _pixelWidth / ui.window.devicePixelRatio;
final double logicalHeight = _pixelHeight / ui.window.devicePixelRatio;
htmlCanvas!.style
..width = '${logicalWidth}px'
..height = '${logicalHeight}px';
}
/// This function is expensive.
///
/// It's better to reuse surface if possible.
CkSurface _createNewSurface(ui.Size physicalSize) {
// Clear the container, if it's not empty. We're going to create a new <canvas>.
this.htmlCanvas?.remove();
// If `physicalSize` is not precise, use a slightly bigger canvas. This way
// we ensure that the rendred picture covers the entire browser window.
final int pixelWidth = physicalSize.width.ceil();
final int pixelHeight = physicalSize.height.ceil();
_pixelWidth = physicalSize.width.ceil();
_pixelHeight = physicalSize.height.ceil();
final html.CanvasElement htmlCanvas = html.CanvasElement(
width: pixelWidth,
height: pixelHeight,
width: _pixelWidth,
height: _pixelHeight,
);
// The logical size of the canvas is not based on the size of the window
// but on the size of the canvas, which, due to `ceil()` above, may not be
// the same as the window. We do not round/floor/ceil the logical size as
// CSS pixels can contain more than one physical pixel and therefore to
// match the size of the window precisely we use the most precise floating
// point value we can get.
final double logicalWidth = pixelWidth / ui.window.devicePixelRatio;
final double logicalHeight = pixelHeight / ui.window.devicePixelRatio;
htmlCanvas.style
..position = 'absolute'
..width = '${logicalWidth}px'
..height = '${logicalHeight}px';
this.htmlCanvas = htmlCanvas;
htmlCanvas.style.position = 'absolute';
_updateLogicalHtmlCanvasSize();
// When the browser tab using WebGL goes dormant the browser and/or OS may
// decide to clear GPU resources to let other tabs/programs use the GPU.
......@@ -212,8 +230,8 @@ class Surface {
SkSurface? skSurface = canvasKit.MakeOnScreenGLSurface(
_grContext!,
pixelWidth,
pixelHeight,
_pixelWidth,
_pixelHeight,
SkColorSpaceSRGB,
);
......
......@@ -27,10 +27,14 @@ void testMain() {
// Expect exact requested dimensions.
expect(original.width(), 9);
expect(original.height(), 19);
expect(surface.htmlCanvas!.style.width, '9px');
expect(surface.htmlCanvas!.style.height, '19px');
// Shrinking reuses the existing surface straight-up.
final CkSurface shrunk = surface.acquireFrame(ui.Size(5, 15)).skiaSurface;
expect(shrunk, same(original));
expect(surface.htmlCanvas!.style.width, '9px');
expect(surface.htmlCanvas!.style.height, '19px');
// The first increase will allocate a new surface, but will overallocate
// by 40% to accommodate future increases.
......@@ -40,6 +44,8 @@ void testMain() {
// Expect overallocated dimensions
expect(firstIncrease.width(), 14);
expect(firstIncrease.height(), 28);
expect(surface.htmlCanvas!.style.width, '14px');
expect(surface.htmlCanvas!.style.height, '28px');
// Subsequent increases within 40% reuse the old surface.
final CkSurface secondIncrease = surface.acquireFrame(ui.Size(11, 22)).skiaSurface;
......@@ -52,6 +58,8 @@ void testMain() {
// Also over-allocated
expect(huge.width(), 28);
expect(huge.height(), 56);
expect(surface.htmlCanvas!.style.width, '28px');
expect(surface.htmlCanvas!.style.height, '56px');
// Shrink again. Reuse the last allocated surface.
final CkSurface shrunk2 = surface.acquireFrame(ui.Size(5, 15)).skiaSurface;
......@@ -88,5 +96,34 @@ void testMain() {
// Firefox doesn't have the WEBGL_lose_context extension.
skip: isFirefox || isIosSafari,
);
// Regression test for https://github.com/flutter/flutter/issues/75286
test('updates canvas logical size when device-pixel ratio changes', () {
final Surface surface = Surface(HtmlViewEmbedder());
final CkSurface original = surface.acquireFrame(ui.Size(10, 16)).skiaSurface;
expect(original.width(), 10);
expect(original.height(), 16);
expect(surface.htmlCanvas!.style.width, '10px');
expect(surface.htmlCanvas!.style.height, '16px');
// Increase device-pixel ratio: this makes CSS pixels bigger, so we need
// fewer of them to cover the browser window.
window.debugOverrideDevicePixelRatio(2.0);
final CkSurface highDpr = surface.acquireFrame(ui.Size(10, 16)).skiaSurface;
expect(highDpr.width(), 10);
expect(highDpr.height(), 16);
expect(surface.htmlCanvas!.style.width, '5px');
expect(surface.htmlCanvas!.style.height, '8px');
// Decrease device-pixel ratio: this makes CSS pixels smaller, so we need
// more of them to cover the browser window.
window.debugOverrideDevicePixelRatio(0.5);
final CkSurface lowDpr = surface.acquireFrame(ui.Size(10, 16)).skiaSurface;
expect(lowDpr.width(), 10);
expect(lowDpr.height(), 16);
expect(surface.htmlCanvas!.style.width, '20px');
expect(surface.htmlCanvas!.style.height, '32px');
});
}, skip: isIosSafari);
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册