Skip to content

Commit

Permalink
When picture is updated and bitmapcanvas reused, clear element cache (f…
Browse files Browse the repository at this point in the history
  • Loading branch information
ferhatb committed Oct 23, 2020
1 parent 9bddfc9 commit 6999c4d
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 4 deletions.
2 changes: 1 addition & 1 deletion lib/web_ui/lib/src/engine/bitmap_canvas.dart
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ class BitmapCanvas extends EngineCanvas {
}

/// Setup cache for reusing DOM elements across frames.
void setElementCache(CrossFrameCache<html.HtmlElement> cache) {
void setElementCache(CrossFrameCache<html.HtmlElement>? cache) {
_elementCache = cache;
}

Expand Down
16 changes: 13 additions & 3 deletions lib/web_ui/lib/src/engine/html/picture.dart
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ List<_PaintRequest> _paintQueue = <_PaintRequest>[];

void _recycleCanvas(EngineCanvas? canvas) {
if (canvas is BitmapCanvas) {
canvas.setElementCache(null);
if (canvas.isReusable()) {
_recycledCanvases.add(canvas);
if (_recycledCanvases.length > _kCanvasCacheSize) {
Expand Down Expand Up @@ -91,7 +92,7 @@ class PersistedPicture extends PersistedLeafSurface {
final int hints;

/// Cache for reusing elements such as images across picture updates.
CrossFrameCache<html.HtmlElement> _elementCache =
CrossFrameCache<html.HtmlElement>? _elementCache =
CrossFrameCache<html.HtmlElement>();

@override
Expand Down Expand Up @@ -386,6 +387,7 @@ class PersistedPicture extends PersistedLeafSurface {
if (_debugShowCanvasReuseStats) {
DebugCanvasReuseOverlay.instance.keptCount++;
}
// Re-use old bitmap canvas.
oldCanvas.bounds = _optimalLocalCullRect!;
_canvas = oldCanvas;
oldCanvas.setElementCache(_elementCache);
Expand All @@ -395,6 +397,10 @@ class PersistedPicture extends PersistedLeafSurface {
// We can't use the old canvas because the size has changed, so we put
// it in a cache for later reuse.
_recycleCanvas(oldCanvas);
if (_canvas is BitmapCanvas) {
(_canvas as BitmapCanvas).setElementCache(null);
}
_canvas = null;
// We cannot paint immediately because not all canvases that we may be
// able to reuse have been released yet. So instead we enqueue this
// picture to be painted after the update cycle is done syncing the layer
Expand All @@ -403,8 +409,9 @@ class PersistedPicture extends PersistedLeafSurface {
canvasSize: _optimalLocalCullRect!.size,
paintCallback: () {
_canvas = _findOrCreateCanvas(_optimalLocalCullRect!);
assert(_canvas is BitmapCanvas &&
(_canvas as BitmapCanvas?)!._elementCache == _elementCache);
if (_canvas is BitmapCanvas) {
(_canvas as BitmapCanvas).setElementCache(_elementCache);
}
if (_debugExplainSurfaceStats) {
final BitmapCanvas bitmapCanvas = _canvas as BitmapCanvas;
_surfaceStatsFor(this).paintPixelCount +=
Expand Down Expand Up @@ -518,6 +525,9 @@ class PersistedPicture extends PersistedLeafSurface {
super.update(oldSurface);
// Transfer element cache over.
_elementCache = oldSurface._elementCache;
if (oldSurface != this) {
oldSurface._elementCache = null;
}

if (dx != oldSurface.dx || dy != oldSurface.dy) {
_applyTranslate();
Expand Down
36 changes: 36 additions & 0 deletions lib/web_ui/test/engine/surface/scene_builder_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,42 @@ void testMain() {
});
});

/// Verify elementCache is passed during update to reuse existing
/// image elements.
test('Should retain same image element', () async {
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
final Picture picture1 = _drawPathImagePath();
EngineLayer oldLayer = builder.pushClipRect(
const Rect.fromLTRB(10, 10, 300, 300),
);
builder.addPicture(Offset.zero, picture1);
builder.pop();

html.HtmlElement content = builder.build().webOnlyRootElement;
html.document.body.append(content);
List<html.ImageElement> list = content.querySelectorAll('img');
for (html.ImageElement image in list) {
image.alt = 'marked';
}

// Force update to scene which will utilize reuse code path.
final SurfaceSceneBuilder builder2 = SurfaceSceneBuilder();
builder2.pushClipRect(
const Rect.fromLTRB(5, 10, 300, 300),
oldLayer: oldLayer
);
final Picture picture2 = _drawPathImagePath();
builder2.addPicture(Offset.zero, picture2);
builder2.pop();

html.HtmlElement contentAfterReuse = builder2.build().webOnlyRootElement;
list = contentAfterReuse.querySelectorAll('img');
for (html.ImageElement image in list) {
expect(image.alt, 'marked');
}
expect(list.length, 1);
});

PersistedPicture findPictureSurfaceChild(PersistedContainerSurface parent) {
PersistedPicture pictureSurface;
parent.visitChildren((PersistedSurface child) {
Expand Down

0 comments on commit 6999c4d

Please sign in to comment.