mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
[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:
parent
38c531de4a
commit
fbc9a08576
@ -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
|
||||
|
||||
@ -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
1147
engine/src/flutter/lib/web_ui/lib/src/engine/surface/path.dart
Normal file
1147
engine/src/flutter/lib/web_ui/lib/src/engine/surface/path.dart
Normal file
File diff suppressed because it is too large
Load Diff
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
});
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user