[web] Cleanup. Split path and surface stats code into separate files (flutter/engine#17796)

* Split path and surface stats code into separate files

* Update licenses
This commit is contained in:
Ferhat 2020-04-17 17:47:22 -07:00 committed by GitHub
parent 38c531de4a
commit fbc9a08576
6 changed files with 1529 additions and 1505 deletions

View File

@ -475,6 +475,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/image_filter.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/offset.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/opacity.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/painting.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/path.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/path_metrics.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/picture.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/platform_view.dart
@ -482,6 +483,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/recording_canvas.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/scene.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/scene_builder.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/surface.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/surface_stats.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/transform.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/test_embedding.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/text/font_collection.dart

View File

@ -103,6 +103,8 @@ part 'engine/surface/recording_canvas.dart';
part 'engine/surface/scene.dart';
part 'engine/surface/scene_builder.dart';
part 'engine/surface/surface.dart';
part 'engine/surface/path.dart';
part 'engine/surface/surface_stats.dart';
part 'engine/surface/transform.dart';
part 'engine/test_embedding.dart';
part 'engine/text/font_collection.dart';
@ -187,8 +189,8 @@ void webOnlyInitializeEngine() {
final int highResTimeMicroseconds = (1000 * highResTime).toInt();
if (window._onBeginFrame != null) {
window
.invokeOnBeginFrame(Duration(microseconds: highResTimeMicroseconds));
window.invokeOnBeginFrame(
Duration(microseconds: highResTimeMicroseconds));
}
if (window._onDrawFrame != null) {

File diff suppressed because it is too large Load Diff

View File

@ -78,7 +78,8 @@ void commitScene(PersistedScene scene) {
if (_retainedSurfaces.isNotEmpty) {
for (int i = 0; i < _retainedSurfaces.length; i++) {
final PersistedSurface retainedSurface = _retainedSurfaces[i];
assert(debugAssertSurfaceState(retainedSurface, PersistedSurfaceState.pendingRetention));
assert(debugAssertSurfaceState(
retainedSurface, PersistedSurfaceState.pendingRetention));
retainedSurface.state = PersistedSurfaceState.active;
}
_retainedSurfaces = <PersistedSurface>[];
@ -112,322 +113,6 @@ void commitScene(PersistedScene scene) {
}());
}
/// Surfaces that were retained this frame.
///
/// Surfaces should be added to this list directly. Instead, if a surface needs
/// to be retained call [_retainSurface].
List<PersistedSurface> get debugRetainedSurfaces => _retainedSurfaces;
List<PersistedSurface> _retainedSurfaces = <PersistedSurface>[];
/// Maps every surface currently active on the screen to debug statistics.
Map<PersistedSurface, _DebugSurfaceStats> _surfaceStats =
<PersistedSurface, _DebugSurfaceStats>{};
List<Map<PersistedSurface, _DebugSurfaceStats>> _surfaceStatsTimeline =
<Map<PersistedSurface, _DebugSurfaceStats>>[];
/// Returns debug statistics for the given [surface].
_DebugSurfaceStats _surfaceStatsFor(PersistedSurface surface) {
if (!_debugExplainSurfaceStats) {
throw Exception(
'_surfaceStatsFor is only available when _debugExplainSurfaceStats is set to true.');
}
return _surfaceStats.putIfAbsent(surface, () => _DebugSurfaceStats(surface));
}
/// Compositor information collected for one frame useful for assessing the
/// efficiency of constructing the frame.
///
/// This information is only available in debug mode.
///
/// For stats pertaining to a single surface the numeric counter fields are
/// typically either 0 or 1. For aggregated stats, the numbers can be >1.
class _DebugSurfaceStats {
_DebugSurfaceStats(this.surface);
/// The surface these stats are for, or `null` if these are aggregated stats.
final PersistedSurface surface;
/// How many times a surface was retained from a previously rendered frame.
int retainSurfaceCount = 0;
/// How many times a surface reused an HTML element from a previously rendered
/// surface.
int reuseElementCount = 0;
/// If a surface is a [PersistedPicture], how many times it painted.
int paintCount = 0;
/// If a surface is a [PersistedPicture], how many pixels it painted.
int paintPixelCount = 0;
/// If a surface is a [PersistedPicture], how many times it reused a
/// previously allocated `<canvas>` element when it painted.
int reuseCanvasCount = 0;
/// If a surface is a [PersistedPicture], how many times it allocated a new
/// bitmap canvas.
int allocateBitmapCanvasCount = 0;
/// If a surface is a [PersistedPicture], how many pixels it allocated for
/// the bitmap.
///
/// For aggregated stats, this is the total sum of all pixels across all
/// canvases.
int allocatedBitmapSizeInPixels = 0;
/// The number of HTML DOM nodes a surface allocated.
///
/// For aggregated stats, this is the total sum of all DOM nodes across all
/// surfaces.
int allocatedDomNodeCount = 0;
/// Adds all counters of [oneSurfaceStats] into this object.
void aggregate(_DebugSurfaceStats oneSurfaceStats) {
retainSurfaceCount += oneSurfaceStats.retainSurfaceCount;
reuseElementCount += oneSurfaceStats.reuseElementCount;
paintCount += oneSurfaceStats.paintCount;
paintPixelCount += oneSurfaceStats.paintPixelCount;
reuseCanvasCount += oneSurfaceStats.reuseCanvasCount;
allocateBitmapCanvasCount += oneSurfaceStats.allocateBitmapCanvasCount;
allocatedBitmapSizeInPixels += oneSurfaceStats.allocatedBitmapSizeInPixels;
allocatedDomNodeCount += oneSurfaceStats.allocatedDomNodeCount;
}
}
html.CanvasRenderingContext2D _debugSurfaceStatsOverlayCtx;
void _debugRepaintSurfaceStatsOverlay(PersistedScene scene) {
final int overlayWidth = html.window.innerWidth;
const int rowHeight = 30;
const int rowCount = 4;
const int overlayHeight = rowHeight * rowCount;
const int strokeWidth = 2;
_surfaceStatsTimeline.add(_surfaceStats);
while (_surfaceStatsTimeline.length > (overlayWidth / strokeWidth)) {
_surfaceStatsTimeline.removeAt(0);
}
if (_debugSurfaceStatsOverlayCtx == null) {
final html.CanvasElement _debugSurfaceStatsOverlay = html.CanvasElement(
width: overlayWidth,
height: overlayHeight,
);
_debugSurfaceStatsOverlay.style
..position = 'fixed'
..left = '0'
..top = '0'
..zIndex = '1000'
..opacity = '0.8';
_debugSurfaceStatsOverlayCtx = _debugSurfaceStatsOverlay.context2D;
html.document.body.append(_debugSurfaceStatsOverlay);
}
_debugSurfaceStatsOverlayCtx
..fillStyle = 'black'
..beginPath()
..rect(0, 0, overlayWidth, overlayHeight)
..fill();
final double physicalScreenWidth =
html.window.innerWidth * EngineWindow.browserDevicePixelRatio;
final double physicalScreenHeight =
html.window.innerHeight * EngineWindow.browserDevicePixelRatio;
final double physicsScreenPixelCount =
physicalScreenWidth * physicalScreenHeight;
final int totalDomNodeCount = scene.rootElement.querySelectorAll('*').length;
for (int i = 0; i < _surfaceStatsTimeline.length; i++) {
final Map<PersistedSurface, _DebugSurfaceStats> statsMap =
_surfaceStatsTimeline[i];
final _DebugSurfaceStats totals = _DebugSurfaceStats(null);
int pixelCount = 0;
for (_DebugSurfaceStats oneSurfaceStats in statsMap.values) {
totals.aggregate(oneSurfaceStats);
if (oneSurfaceStats.surface is PersistedPicture) {
final PersistedPicture picture = oneSurfaceStats.surface;
pixelCount += picture.bitmapPixelCount;
}
}
final double repaintRate = totals.paintPixelCount / pixelCount;
final double domAllocationRate =
totals.allocatedDomNodeCount / totalDomNodeCount;
final double bitmapAllocationRate =
totals.allocatedBitmapSizeInPixels / physicsScreenPixelCount;
final double surfaceRetainRate =
totals.retainSurfaceCount / _surfaceStatsTimeline[i].length;
// Repaints
_debugSurfaceStatsOverlayCtx
..lineWidth = strokeWidth
..strokeStyle = 'red'
..beginPath()
..moveTo(strokeWidth * i, rowHeight)
..lineTo(strokeWidth * i, rowHeight * (1 - repaintRate))
..stroke();
// DOM allocations
_debugSurfaceStatsOverlayCtx
..lineWidth = strokeWidth
..strokeStyle = 'red'
..beginPath()
..moveTo(strokeWidth * i, 2 * rowHeight)
..lineTo(strokeWidth * i, rowHeight * (2 - domAllocationRate))
..stroke();
// Bitmap allocations
_debugSurfaceStatsOverlayCtx
..lineWidth = strokeWidth
..strokeStyle = 'red'
..beginPath()
..moveTo(strokeWidth * i, 3 * rowHeight)
..lineTo(strokeWidth * i, rowHeight * (3 - bitmapAllocationRate))
..stroke();
// Surface retentions
_debugSurfaceStatsOverlayCtx
..lineWidth = strokeWidth
..strokeStyle = 'green'
..beginPath()
..moveTo(strokeWidth * i, 4 * rowHeight)
..lineTo(strokeWidth * i, rowHeight * (4 - surfaceRetainRate))
..stroke();
}
_debugSurfaceStatsOverlayCtx
..font = 'normal normal 14px sans-serif'
..fillStyle = 'white'
..fillText('Repaint rate', 5, rowHeight - 5)
..fillText('DOM alloc rate', 5, 2 * rowHeight - 5)
..fillText('Bitmap alloc rate', 5, 3 * rowHeight - 5)
..fillText('Retain rate', 5, 4 * rowHeight - 5);
for (int i = 1; i <= rowCount; i++) {
_debugSurfaceStatsOverlayCtx
..lineWidth = 1
..strokeStyle = 'blue'
..beginPath()
..moveTo(0, overlayHeight - rowHeight * i)
..lineTo(overlayWidth, overlayHeight - rowHeight * i)
..stroke();
}
}
/// Prints debug statistics for the current frame to the console.
void _debugPrintSurfaceStats(PersistedScene scene, int frameNumber) {
int pictureCount = 0;
int paintCount = 0;
int bitmapCanvasCount = 0;
int bitmapReuseCount = 0;
int bitmapAllocationCount = 0;
int bitmapPaintCount = 0;
int bitmapPixelsAllocated = 0;
int domCanvasCount = 0;
int domPaintCount = 0;
int surfaceRetainCount = 0;
int elementReuseCount = 0;
int totalAllocatedDomNodeCount = 0;
void countReusesRecursively(PersistedSurface surface) {
final _DebugSurfaceStats stats = _surfaceStatsFor(surface);
assert(stats != null);
surfaceRetainCount += stats.retainSurfaceCount;
elementReuseCount += stats.reuseElementCount;
totalAllocatedDomNodeCount += stats.allocatedDomNodeCount;
if (surface is PersistedStandardPicture) {
pictureCount += 1;
paintCount += stats.paintCount;
if (surface._canvas is DomCanvas) {
domCanvasCount++;
domPaintCount += stats.paintCount;
}
if (surface._canvas is BitmapCanvas) {
bitmapCanvasCount++;
bitmapPaintCount += stats.paintCount;
}
bitmapReuseCount += stats.reuseCanvasCount;
bitmapAllocationCount += stats.allocateBitmapCanvasCount;
bitmapPixelsAllocated += stats.allocatedBitmapSizeInPixels;
}
surface.visitChildren(countReusesRecursively);
}
scene.visitChildren(countReusesRecursively);
final StringBuffer buf = StringBuffer();
buf
..writeln(
'---------------------- FRAME #$frameNumber -------------------------')
..writeln('Surfaces retained: $surfaceRetainCount')
..writeln('Elements reused: $elementReuseCount')
..writeln('Elements allocated: $totalAllocatedDomNodeCount')
..writeln('Pictures: $pictureCount')
..writeln(' Painted: $paintCount')
..writeln(' Skipped painting: ${pictureCount - paintCount}')
..writeln('DOM canvases:')
..writeln(' Painted: $domPaintCount')
..writeln(' Skipped painting: ${domCanvasCount - domPaintCount}')
..writeln('Bitmap canvases: $bitmapCanvasCount')
..writeln(' Painted: $bitmapPaintCount')
..writeln(' Skipped painting: ${bitmapCanvasCount - bitmapPaintCount}')
..writeln(' Reused: $bitmapReuseCount')
..writeln(' Allocated: $bitmapAllocationCount')
..writeln(' Allocated pixels: $bitmapPixelsAllocated')
..writeln(' Available for reuse: ${_recycledCanvases.length}');
// A microtask will fire after the DOM is flushed, letting us probe into
// actual <canvas> tags.
scheduleMicrotask(() {
final List<html.Element> canvasElements =
html.document.querySelectorAll('canvas');
final StringBuffer canvasInfo = StringBuffer();
final int pixelCount = canvasElements
.cast<html.CanvasElement>()
.map<int>((html.CanvasElement e) {
final int pixels = e.width * e.height;
canvasInfo.writeln(' - ${e.width} x ${e.height} = $pixels pixels');
return pixels;
}).fold(0, (int total, int pixels) => total + pixels);
final double physicalScreenWidth =
html.window.innerWidth * EngineWindow.browserDevicePixelRatio;
final double physicalScreenHeight =
html.window.innerHeight * EngineWindow.browserDevicePixelRatio;
final double physicsScreenPixelCount =
physicalScreenWidth * physicalScreenHeight;
final double screenPixelRatio = pixelCount / physicsScreenPixelCount;
final String screenDescription =
'1 screen is $physicalScreenWidth x $physicalScreenHeight = $physicsScreenPixelCount pixels';
final String canvasPixelDescription =
'$pixelCount (${screenPixelRatio.toStringAsFixed(2)} x screens';
buf
..writeln(' Elements: ${canvasElements.length}')
..writeln(canvasInfo)
..writeln(' Pixels: $canvasPixelDescription; $screenDescription)')
..writeln('-----------------------------------------------------------');
final bool screenPixelRatioTooHigh =
screenPixelRatio > _kScreenPixelRatioWarningThreshold;
if (screenPixelRatioTooHigh) {
print(
'WARNING: pixel/screen ratio too high (${screenPixelRatio.toStringAsFixed(2)}x)');
}
print(buf);
});
}
/// Signature of a function that receives a [PersistedSurface].
///
/// This is used to traverse surfaces using [PersistedSurface.visitChildren].
@ -502,8 +187,10 @@ class PersistedSurfaceException implements Exception {
/// Verifies that the [surface] is in one of the valid states.
///
/// This function should be used inside an assertion expression.
bool debugAssertSurfaceState(PersistedSurface surface, PersistedSurfaceState state1, [PersistedSurfaceState state2, PersistedSurfaceState state3]) {
final List<PersistedSurfaceState> validStates = [ state1, state2, state3 ];
bool debugAssertSurfaceState(
PersistedSurface surface, PersistedSurfaceState state1,
[PersistedSurfaceState state2, PersistedSurfaceState state3]) {
final List<PersistedSurfaceState> validStates = [state1, state2, state3];
if (validStates.contains(surface.state)) {
return true;
@ -571,7 +258,8 @@ abstract class PersistedSurface implements ui.EngineLayer {
/// reused before the request to retain it came in. In this case, the surface
/// is [revive]d and rebuilt from scratch.
void tryRetain() {
assert(debugAssertSurfaceState(this, PersistedSurfaceState.active, PersistedSurfaceState.released));
assert(debugAssertSurfaceState(
this, PersistedSurfaceState.active, PersistedSurfaceState.released));
// Request that the layer is retained, but only if it's still active. It
// could have been released.
if (isActive) {
@ -664,8 +352,9 @@ abstract class PersistedSurface implements ui.EngineLayer {
if (rootElement != null) {
try {
throw null;
} catch(_, stack) {
print('Attempted to build a $runtimeType, but it already has an HTML element ${rootElement.tagName}.');
} catch (_, stack) {
print(
'Attempted to build a $runtimeType, but it already has an HTML element ${rootElement.tagName}.');
print(stack.toString().split('\n').take(20).join('\n'));
}
}
@ -690,7 +379,8 @@ abstract class PersistedSurface implements ui.EngineLayer {
@mustCallSuper
void adoptElements(covariant PersistedSurface oldSurface) {
assert(oldSurface.rootElement != null);
assert(debugAssertSurfaceState(oldSurface, PersistedSurfaceState.active, PersistedSurfaceState.pendingUpdate));
assert(debugAssertSurfaceState(oldSurface, PersistedSurfaceState.active,
PersistedSurfaceState.pendingUpdate));
assert(() {
if (oldSurface.isPendingUpdate) {
final PersistedContainerSurface self = this;
@ -719,7 +409,8 @@ abstract class PersistedSurface implements ui.EngineLayer {
assert(oldSurface != null);
assert(!identical(oldSurface, this));
assert(debugAssertSurfaceState(this, PersistedSurfaceState.created));
assert(debugAssertSurfaceState(oldSurface, PersistedSurfaceState.active, PersistedSurfaceState.pendingUpdate));
assert(debugAssertSurfaceState(oldSurface, PersistedSurfaceState.active,
PersistedSurfaceState.pendingUpdate));
adoptElements(oldSurface);
@ -954,7 +645,11 @@ abstract class PersistedContainerSurface extends PersistedSurface {
/// Adds a child to this container.
void appendChild(PersistedSurface child) {
assert(debugAssertSurfaceState(child, PersistedSurfaceState.created, PersistedSurfaceState.pendingRetention, PersistedSurfaceState.pendingUpdate));
assert(debugAssertSurfaceState(
child,
PersistedSurfaceState.created,
PersistedSurfaceState.pendingRetention,
PersistedSurfaceState.pendingUpdate));
_children.add(child);
child.parent = this;
}
@ -991,7 +686,8 @@ abstract class PersistedContainerSurface extends PersistedSurface {
} else if (child is PersistedContainerSurface && child.oldLayer != null) {
final PersistedSurface oldLayer = child.oldLayer;
assert(oldLayer.rootElement != null);
assert(debugAssertSurfaceState(oldLayer, PersistedSurfaceState.pendingUpdate));
assert(debugAssertSurfaceState(
oldLayer, PersistedSurfaceState.pendingUpdate));
child.update(child.oldLayer);
} else {
assert(debugAssertSurfaceState(child, PersistedSurfaceState.created));
@ -1018,7 +714,8 @@ abstract class PersistedContainerSurface extends PersistedSurface {
@override
void update(PersistedContainerSurface oldSurface) {
assert(debugAssertSurfaceState(oldSurface, PersistedSurfaceState.active, PersistedSurfaceState.pendingUpdate));
assert(debugAssertSurfaceState(oldSurface, PersistedSurfaceState.active,
PersistedSurfaceState.pendingUpdate));
assert(runtimeType == oldSurface.runtimeType);
super.update(oldSurface);
assert(debugAssertSurfaceState(oldSurface, PersistedSurfaceState.released));
@ -1053,7 +750,8 @@ abstract class PersistedContainerSurface extends PersistedSurface {
}
for (int i = 0; i < _children.length; i++) {
final PersistedSurface newChild = _children[i];
assert(debugAssertSurfaceState(newChild, PersistedSurfaceState.active, PersistedSurfaceState.pendingRetention));
assert(debugAssertSurfaceState(newChild, PersistedSurfaceState.active,
PersistedSurfaceState.pendingRetention));
assert(newChild.rootElement != null);
assert(newChild.rootElement.parent == childContainer);
}
@ -1076,13 +774,16 @@ abstract class PersistedContainerSurface extends PersistedSurface {
final PersistedSurface newChild = _children[i];
if (newChild.isPendingRetention) {
newChild.retain();
assert(debugAssertSurfaceState(newChild, PersistedSurfaceState.pendingRetention));
assert(debugAssertSurfaceState(
newChild, PersistedSurfaceState.pendingRetention));
} else if (newChild is PersistedContainerSurface &&
newChild.oldLayer != null) {
final PersistedContainerSurface oldLayer = newChild.oldLayer;
assert(debugAssertSurfaceState(oldLayer, PersistedSurfaceState.pendingUpdate));
assert(debugAssertSurfaceState(
oldLayer, PersistedSurfaceState.pendingUpdate));
newChild.update(oldLayer);
assert(debugAssertSurfaceState(oldLayer, PersistedSurfaceState.released));
assert(
debugAssertSurfaceState(oldLayer, PersistedSurfaceState.released));
assert(debugAssertSurfaceState(newChild, PersistedSurfaceState.active));
} else {
newChild.build();
@ -1126,14 +827,16 @@ abstract class PersistedContainerSurface extends PersistedSurface {
newChild.retain();
_discardActiveChildren(oldSurface);
assert(debugAssertSurfaceState(newChild, PersistedSurfaceState.pendingRetention));
assert(debugAssertSurfaceState(
newChild, PersistedSurfaceState.pendingRetention));
return;
}
// Updated child is moved to the correct location in the tree; all others
// are released.
if (newChild is PersistedContainerSurface && newChild.oldLayer != null) {
assert(debugAssertSurfaceState(newChild.oldLayer, PersistedSurfaceState.pendingUpdate));
assert(debugAssertSurfaceState(
newChild.oldLayer, PersistedSurfaceState.pendingUpdate));
assert(newChild.rootElement == null);
assert(newChild.oldLayer.rootElement != null);
@ -1176,7 +879,8 @@ abstract class PersistedContainerSurface extends PersistedSurface {
childContainer.append(newChild.rootElement);
}
assert(debugAssertSurfaceState(bestMatch, PersistedSurfaceState.released));
assert(
debugAssertSurfaceState(bestMatch, PersistedSurfaceState.released));
} else {
newChild.build();
childContainer.append(newChild.rootElement);
@ -1235,29 +939,37 @@ abstract class PersistedContainerSurface extends PersistedSurface {
final PersistedSurface newChild = _children[bottomInNew];
if (newChild.isPendingRetention) {
newChild.retain();
assert(debugAssertSurfaceState(newChild, PersistedSurfaceState.pendingRetention));
assert(debugAssertSurfaceState(
newChild, PersistedSurfaceState.pendingRetention));
} else if (newChild is PersistedContainerSurface &&
newChild.oldLayer != null) {
final PersistedContainerSurface oldLayer = newChild.oldLayer;
assert(debugAssertSurfaceState(oldLayer, PersistedSurfaceState.pendingUpdate));
assert(debugAssertSurfaceState(
oldLayer, PersistedSurfaceState.pendingUpdate));
newChild.update(oldLayer);
assert(debugAssertSurfaceState(oldLayer, PersistedSurfaceState.released));
assert(
debugAssertSurfaceState(oldLayer, PersistedSurfaceState.released));
assert(debugAssertSurfaceState(newChild, PersistedSurfaceState.active));
} else {
final PersistedSurface matchedOldChild = matches[newChild];
if (matchedOldChild != null) {
assert(debugAssertSurfaceState(matchedOldChild, PersistedSurfaceState.active));
assert(debugAssertSurfaceState(
matchedOldChild, PersistedSurfaceState.active));
newChild.update(matchedOldChild);
assert(debugAssertSurfaceState(matchedOldChild, PersistedSurfaceState.released));
assert(debugAssertSurfaceState(newChild, PersistedSurfaceState.active));
assert(debugAssertSurfaceState(
matchedOldChild, PersistedSurfaceState.released));
assert(
debugAssertSurfaceState(newChild, PersistedSurfaceState.active));
} else {
newChild.build();
assert(debugAssertSurfaceState(newChild, PersistedSurfaceState.active));
assert(
debugAssertSurfaceState(newChild, PersistedSurfaceState.active));
}
}
insertDomNodeIfMoved(newChild);
assert(newChild.rootElement != null);
assert(debugAssertSurfaceState(newChild, PersistedSurfaceState.active, PersistedSurfaceState.pendingRetention));
assert(debugAssertSurfaceState(newChild, PersistedSurfaceState.active,
PersistedSurfaceState.pendingRetention));
nextSibling = newChild;
}

View File

@ -0,0 +1,322 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.6
part of engine;
/// Surfaces that were retained this frame.
///
/// Surfaces should be added to this list directly. Instead, if a surface needs
/// to be retained call [_retainSurface].
List<PersistedSurface> get debugRetainedSurfaces => _retainedSurfaces;
List<PersistedSurface> _retainedSurfaces = <PersistedSurface>[];
/// Maps every surface currently active on the screen to debug statistics.
Map<PersistedSurface, _DebugSurfaceStats> _surfaceStats =
<PersistedSurface, _DebugSurfaceStats>{};
List<Map<PersistedSurface, _DebugSurfaceStats>> _surfaceStatsTimeline =
<Map<PersistedSurface, _DebugSurfaceStats>>[];
/// Returns debug statistics for the given [surface].
_DebugSurfaceStats _surfaceStatsFor(PersistedSurface surface) {
if (!_debugExplainSurfaceStats) {
throw Exception(
'_surfaceStatsFor is only available when _debugExplainSurfaceStats is set to true.');
}
return _surfaceStats.putIfAbsent(surface, () => _DebugSurfaceStats(surface));
}
/// Compositor information collected for one frame useful for assessing the
/// efficiency of constructing the frame.
///
/// This information is only available in debug mode.
///
/// For stats pertaining to a single surface the numeric counter fields are
/// typically either 0 or 1. For aggregated stats, the numbers can be >1.
class _DebugSurfaceStats {
_DebugSurfaceStats(this.surface);
/// The surface these stats are for, or `null` if these are aggregated stats.
final PersistedSurface surface;
/// How many times a surface was retained from a previously rendered frame.
int retainSurfaceCount = 0;
/// How many times a surface reused an HTML element from a previously rendered
/// surface.
int reuseElementCount = 0;
/// If a surface is a [PersistedPicture], how many times it painted.
int paintCount = 0;
/// If a surface is a [PersistedPicture], how many pixels it painted.
int paintPixelCount = 0;
/// If a surface is a [PersistedPicture], how many times it reused a
/// previously allocated `<canvas>` element when it painted.
int reuseCanvasCount = 0;
/// If a surface is a [PersistedPicture], how many times it allocated a new
/// bitmap canvas.
int allocateBitmapCanvasCount = 0;
/// If a surface is a [PersistedPicture], how many pixels it allocated for
/// the bitmap.
///
/// For aggregated stats, this is the total sum of all pixels across all
/// canvases.
int allocatedBitmapSizeInPixels = 0;
/// The number of HTML DOM nodes a surface allocated.
///
/// For aggregated stats, this is the total sum of all DOM nodes across all
/// surfaces.
int allocatedDomNodeCount = 0;
/// Adds all counters of [oneSurfaceStats] into this object.
void aggregate(_DebugSurfaceStats oneSurfaceStats) {
retainSurfaceCount += oneSurfaceStats.retainSurfaceCount;
reuseElementCount += oneSurfaceStats.reuseElementCount;
paintCount += oneSurfaceStats.paintCount;
paintPixelCount += oneSurfaceStats.paintPixelCount;
reuseCanvasCount += oneSurfaceStats.reuseCanvasCount;
allocateBitmapCanvasCount += oneSurfaceStats.allocateBitmapCanvasCount;
allocatedBitmapSizeInPixels += oneSurfaceStats.allocatedBitmapSizeInPixels;
allocatedDomNodeCount += oneSurfaceStats.allocatedDomNodeCount;
}
}
html.CanvasRenderingContext2D _debugSurfaceStatsOverlayCtx;
void _debugRepaintSurfaceStatsOverlay(PersistedScene scene) {
final int overlayWidth = html.window.innerWidth;
const int rowHeight = 30;
const int rowCount = 4;
const int overlayHeight = rowHeight * rowCount;
const int strokeWidth = 2;
_surfaceStatsTimeline.add(_surfaceStats);
while (_surfaceStatsTimeline.length > (overlayWidth / strokeWidth)) {
_surfaceStatsTimeline.removeAt(0);
}
if (_debugSurfaceStatsOverlayCtx == null) {
final html.CanvasElement _debugSurfaceStatsOverlay = html.CanvasElement(
width: overlayWidth,
height: overlayHeight,
);
_debugSurfaceStatsOverlay.style
..position = 'fixed'
..left = '0'
..top = '0'
..zIndex = '1000'
..opacity = '0.8';
_debugSurfaceStatsOverlayCtx = _debugSurfaceStatsOverlay.context2D;
html.document.body.append(_debugSurfaceStatsOverlay);
}
_debugSurfaceStatsOverlayCtx
..fillStyle = 'black'
..beginPath()
..rect(0, 0, overlayWidth, overlayHeight)
..fill();
final double physicalScreenWidth =
html.window.innerWidth * EngineWindow.browserDevicePixelRatio;
final double physicalScreenHeight =
html.window.innerHeight * EngineWindow.browserDevicePixelRatio;
final double physicsScreenPixelCount =
physicalScreenWidth * physicalScreenHeight;
final int totalDomNodeCount = scene.rootElement.querySelectorAll('*').length;
for (int i = 0; i < _surfaceStatsTimeline.length; i++) {
final Map<PersistedSurface, _DebugSurfaceStats> statsMap =
_surfaceStatsTimeline[i];
final _DebugSurfaceStats totals = _DebugSurfaceStats(null);
int pixelCount = 0;
for (_DebugSurfaceStats oneSurfaceStats in statsMap.values) {
totals.aggregate(oneSurfaceStats);
if (oneSurfaceStats.surface is PersistedPicture) {
final PersistedPicture picture = oneSurfaceStats.surface;
pixelCount += picture.bitmapPixelCount;
}
}
final double repaintRate = totals.paintPixelCount / pixelCount;
final double domAllocationRate =
totals.allocatedDomNodeCount / totalDomNodeCount;
final double bitmapAllocationRate =
totals.allocatedBitmapSizeInPixels / physicsScreenPixelCount;
final double surfaceRetainRate =
totals.retainSurfaceCount / _surfaceStatsTimeline[i].length;
// Repaints
_debugSurfaceStatsOverlayCtx
..lineWidth = strokeWidth
..strokeStyle = 'red'
..beginPath()
..moveTo(strokeWidth * i, rowHeight)
..lineTo(strokeWidth * i, rowHeight * (1 - repaintRate))
..stroke();
// DOM allocations
_debugSurfaceStatsOverlayCtx
..lineWidth = strokeWidth
..strokeStyle = 'red'
..beginPath()
..moveTo(strokeWidth * i, 2 * rowHeight)
..lineTo(strokeWidth * i, rowHeight * (2 - domAllocationRate))
..stroke();
// Bitmap allocations
_debugSurfaceStatsOverlayCtx
..lineWidth = strokeWidth
..strokeStyle = 'red'
..beginPath()
..moveTo(strokeWidth * i, 3 * rowHeight)
..lineTo(strokeWidth * i, rowHeight * (3 - bitmapAllocationRate))
..stroke();
// Surface retentions
_debugSurfaceStatsOverlayCtx
..lineWidth = strokeWidth
..strokeStyle = 'green'
..beginPath()
..moveTo(strokeWidth * i, 4 * rowHeight)
..lineTo(strokeWidth * i, rowHeight * (4 - surfaceRetainRate))
..stroke();
}
_debugSurfaceStatsOverlayCtx
..font = 'normal normal 14px sans-serif'
..fillStyle = 'white'
..fillText('Repaint rate', 5, rowHeight - 5)
..fillText('DOM alloc rate', 5, 2 * rowHeight - 5)
..fillText('Bitmap alloc rate', 5, 3 * rowHeight - 5)
..fillText('Retain rate', 5, 4 * rowHeight - 5);
for (int i = 1; i <= rowCount; i++) {
_debugSurfaceStatsOverlayCtx
..lineWidth = 1
..strokeStyle = 'blue'
..beginPath()
..moveTo(0, overlayHeight - rowHeight * i)
..lineTo(overlayWidth, overlayHeight - rowHeight * i)
..stroke();
}
}
/// Prints debug statistics for the current frame to the console.
void _debugPrintSurfaceStats(PersistedScene scene, int frameNumber) {
int pictureCount = 0;
int paintCount = 0;
int bitmapCanvasCount = 0;
int bitmapReuseCount = 0;
int bitmapAllocationCount = 0;
int bitmapPaintCount = 0;
int bitmapPixelsAllocated = 0;
int domCanvasCount = 0;
int domPaintCount = 0;
int surfaceRetainCount = 0;
int elementReuseCount = 0;
int totalAllocatedDomNodeCount = 0;
void countReusesRecursively(PersistedSurface surface) {
final _DebugSurfaceStats stats = _surfaceStatsFor(surface);
assert(stats != null);
surfaceRetainCount += stats.retainSurfaceCount;
elementReuseCount += stats.reuseElementCount;
totalAllocatedDomNodeCount += stats.allocatedDomNodeCount;
if (surface is PersistedStandardPicture) {
pictureCount += 1;
paintCount += stats.paintCount;
if (surface._canvas is DomCanvas) {
domCanvasCount++;
domPaintCount += stats.paintCount;
}
if (surface._canvas is BitmapCanvas) {
bitmapCanvasCount++;
bitmapPaintCount += stats.paintCount;
}
bitmapReuseCount += stats.reuseCanvasCount;
bitmapAllocationCount += stats.allocateBitmapCanvasCount;
bitmapPixelsAllocated += stats.allocatedBitmapSizeInPixels;
}
surface.visitChildren(countReusesRecursively);
}
scene.visitChildren(countReusesRecursively);
final StringBuffer buf = StringBuffer();
buf
..writeln(
'---------------------- FRAME #$frameNumber -------------------------')
..writeln('Surfaces retained: $surfaceRetainCount')
..writeln('Elements reused: $elementReuseCount')
..writeln('Elements allocated: $totalAllocatedDomNodeCount')
..writeln('Pictures: $pictureCount')
..writeln(' Painted: $paintCount')
..writeln(' Skipped painting: ${pictureCount - paintCount}')
..writeln('DOM canvases:')
..writeln(' Painted: $domPaintCount')
..writeln(' Skipped painting: ${domCanvasCount - domPaintCount}')
..writeln('Bitmap canvases: $bitmapCanvasCount')
..writeln(' Painted: $bitmapPaintCount')
..writeln(' Skipped painting: ${bitmapCanvasCount - bitmapPaintCount}')
..writeln(' Reused: $bitmapReuseCount')
..writeln(' Allocated: $bitmapAllocationCount')
..writeln(' Allocated pixels: $bitmapPixelsAllocated')
..writeln(' Available for reuse: ${_recycledCanvases.length}');
// A microtask will fire after the DOM is flushed, letting us probe into
// actual <canvas> tags.
scheduleMicrotask(() {
final List<html.Element> canvasElements =
html.document.querySelectorAll('canvas');
final StringBuffer canvasInfo = StringBuffer();
final int pixelCount = canvasElements
.cast<html.CanvasElement>()
.map<int>((html.CanvasElement e) {
final int pixels = e.width * e.height;
canvasInfo.writeln(' - ${e.width} x ${e.height} = $pixels pixels');
return pixels;
}).fold(0, (int total, int pixels) => total + pixels);
final double physicalScreenWidth =
html.window.innerWidth * EngineWindow.browserDevicePixelRatio;
final double physicalScreenHeight =
html.window.innerHeight * EngineWindow.browserDevicePixelRatio;
final double physicsScreenPixelCount =
physicalScreenWidth * physicalScreenHeight;
final double screenPixelRatio = pixelCount / physicsScreenPixelCount;
final String screenDescription =
'1 screen is $physicalScreenWidth x $physicalScreenHeight = $physicsScreenPixelCount pixels';
final String canvasPixelDescription =
'$pixelCount (${screenPixelRatio.toStringAsFixed(2)} x screens';
buf
..writeln(' Elements: ${canvasElements.length}')
..writeln(canvasInfo)
..writeln(' Pixels: $canvasPixelDescription; $screenDescription)')
..writeln('-----------------------------------------------------------');
final bool screenPixelRatioTooHigh =
screenPixelRatio > _kScreenPixelRatioWarningThreshold;
if (screenPixelRatioTooHigh) {
print(
'WARNING: pixel/screen ratio too high (${screenPixelRatio.toStringAsFixed(2)}x)');
}
print(buf);
});
}