未验证 提交 6e15044e 编写于 作者: F Ferhat 提交者: GitHub

[web] Speed up PageView/CustomPainter rendering (#22408)

上级 aeab315d
......@@ -331,17 +331,12 @@ class BitmapCanvas extends EngineCanvas {
/// - Pictures typically have large rect/rounded rectangles as background
/// prefer DOM if canvas has not been allocated yet.
///
/// Future optimization: The check below can be used to prevent excessive
/// canvas sandwiching (switching between dom and multiple canvas(s)).
/// Once RecordingCanvas is updated to detect switch count, this can be
/// enabled.
/// (_canvasPool._canvas == null &&
/// paint.maskFilter == null &&
/// paint.shader == null &&
/// paint.style != ui.PaintingStyle.stroke)
///
bool _useDomForRendering(SurfacePaintData paint) =>
_preserveImageData == false && _contains3dTransform;
(_preserveImageData == false && _contains3dTransform) ||
(_childOverdraw && _canvasPool._canvas == null &&
paint.maskFilter == null &&
paint.shader == null &&
paint.style != ui.PaintingStyle.stroke);
@override
void drawColor(ui.Color color, ui.BlendMode blendMode) {
......@@ -495,6 +490,26 @@ class BitmapCanvas extends EngineCanvas {
if (_useDomForRendering(paint)) {
final Matrix4 transform = _canvasPool._currentTransform;
final SurfacePath surfacePath = path as SurfacePath;
final ui.Rect? pathAsLine = surfacePath.toStraightLine();
if (pathAsLine != null) {
final ui.Rect rect = (pathAsLine.top == pathAsLine.bottom) ?
ui.Rect.fromLTWH(pathAsLine.left, pathAsLine.top, pathAsLine.width, 1)
: ui.Rect.fromLTWH(pathAsLine.left, pathAsLine.top, 1, pathAsLine.height);
html.HtmlElement element = _buildDrawRectElement(
rect, paint, 'draw-rect', _canvasPool._currentTransform);
_drawElement(
element,
ui.Offset(
math.min(rect.left, rect.right), math.min(rect.top, rect.bottom)),
paint);
return;
}
final ui.Rect? pathAsRect = surfacePath.toRect();
if (pathAsRect != null) {
drawRect(pathAsRect, paint);
return;
}
final ui.Rect pathBounds = surfacePath.getBounds();
html.Element svgElm = _pathToSvgElement(
surfacePath, paint, '${pathBounds.right}', '${pathBounds.bottom}');
......
......@@ -195,11 +195,11 @@ class PersistedPhysicalShape extends PersistedContainerSurface
void recomputeTransformAndClip() {
_transform = parent!._transform;
final ui.RRect? roundRect = path.webOnlyPathAsRoundedRect;
final ui.RRect? roundRect = path.toRoundedRect();
if (roundRect != null) {
_localClipBounds = roundRect.outerRect;
} else {
final ui.Rect? rect = path.webOnlyPathAsRect;
final ui.Rect? rect = path.toRect();
if (rect != null) {
_localClipBounds = rect;
} else {
......@@ -233,7 +233,7 @@ class PersistedPhysicalShape extends PersistedContainerSurface
void _applyShape() {
// Handle special case of round rect physical shape mapping to
// rounded div.
final ui.RRect? roundRect = path.webOnlyPathAsRoundedRect;
final ui.RRect? roundRect = path.toRoundedRect();
if (roundRect != null) {
final String borderRadius =
'${roundRect.tlRadiusX}px ${roundRect.trRadiusX}px '
......@@ -253,7 +253,7 @@ class PersistedPhysicalShape extends PersistedContainerSurface
}
return;
} else {
final ui.Rect? rect = path.webOnlyPathAsRect;
final ui.Rect? rect = path.toRect();
if (rect != null) {
final html.CssStyleDeclaration style = rootElement!.style;
style
......@@ -270,7 +270,7 @@ class PersistedPhysicalShape extends PersistedContainerSurface
}
return;
} else {
final ui.Rect? ovalRect = path.webOnlyPathAsCircle;
final ui.Rect? ovalRect = path.toCircle();
if (ovalRect != null) {
final double rx = ovalRect.width / 2.0;
final double ry = ovalRect.height / 2.0;
......
......@@ -1524,24 +1524,38 @@ class SurfacePath implements ui.Path {
return SurfacePathMetrics._(this, forceClosed);
}
/// Detects if path is rounded rectangle and returns rounded rectangle or
/// null.
/// Detects if path is rounded rectangle.
///
/// Returns rounded rectangle or null.
///
/// Used for web optimization of physical shape represented as
/// a persistent div.
ui.RRect? get webOnlyPathAsRoundedRect => pathRef.getRRect();
ui.RRect? toRoundedRect() => pathRef.getRRect();
/// Detects if path is simple rectangle and returns rectangle or null.
/// Detects if path is simple rectangle.
///
/// Returns rectangle or null.
///
/// Used for web optimization of physical shape represented as
/// a persistent div. !Warning it does not detect if closed, don't use this
/// for optimizing strokes.
ui.Rect? toRect() => pathRef.getRect();
/// Detects if path is a vertical or horizontal line.
///
/// Returns LTRB or null.
///
/// Used for web optimization of physical shape represented as
/// a persistent div.
ui.Rect? get webOnlyPathAsRect => pathRef.getRect();
ui.Rect? toStraightLine() => pathRef.getStraightLine();
/// Detects if path is simple oval and returns bounding rectangle or null.
/// Detects if path is simple oval.
///
/// Returns bounding rectangle or null.
///
/// Used for web optimization of physical shape represented as
/// a persistent div.
ui.Rect? get webOnlyPathAsCircle =>
ui.Rect? toCircle() =>
pathRef.isOval == -1 ? null : pathRef.getBounds();
/// Returns if Path is empty.
......
......@@ -156,7 +156,16 @@ class PathRef {
int get isRRect => fIsRRect ? fRRectOrOvalStartIdx : -1;
int get isRect => fIsRect ? fRRectOrOvalStartIdx : -1;
ui.RRect? getRRect() => fIsRRect ? _getRRect() : null;
ui.Rect? getRect() => fIsRect ? _getRect() : null;
ui.Rect? getRect() {
/// Use _detectRect() for detection if explicity addRect was used (fIsRect) or
/// it is a potential due to moveTo + 3 lineTo verbs.
if (fIsRect) {
return ui.Rect.fromLTRB(
atPoint(0).dx, atPoint(0).dy, atPoint(1).dx, atPoint(2).dy);
} else {
return _fVerbsLength == 4 ? _detectRect() : null;
}
}
bool get isRectCCW => fRRectOrOvalIsCCW;
bool get hasComputedBounds => !fBoundsIsDirty;
......@@ -173,9 +182,49 @@ class PathRef {
}
/// Reconstructs Rect from path commands.
ui.Rect _getRect() {
return ui.Rect.fromLTRB(
atPoint(0).dx, atPoint(0).dy, atPoint(1).dx, atPoint(2).dy);
///
/// Detects clockwise starting with horizontal line.
ui.Rect? _detectRect() {
assert(_fVerbs[0] == SPath.kMoveVerb);
final double x0 = atPoint(0).dx;
final double y0 = atPoint(0).dy;
final double x1 = atPoint(1).dx;
final double y1 = atPoint(1).dy;
if (_fVerbs[1] != SPath.kLineVerb || y1 != y0) {
return null;
}
final double width = x1 - x0;
final double x2 = atPoint(2).dx;
final double y2 = atPoint(2).dy;
if (_fVerbs[2] != SPath.kLineVerb || x2 != x1) {
return null;
}
final double height = y2 - y1;
final double x3 = atPoint(3).dx;
final double y3 = atPoint(3).dy;
if (_fVerbs[3] != SPath.kLineVerb || y3 != y2) {
return null;
}
if ((x2 - x3) != width || (y3 - y0) != height) {
return null;
}
return ui.Rect.fromLTWH(x0, y0, width, height);
}
/// Returns horizontal/vertical line bounds or null if not a line.
ui.Rect? getStraightLine() {
if (_fVerbsLength != 2 || _fVerbs[0] != SPath.kMoveVerb ||
_fVerbs[1] != SPath.kLineVerb) {
return null;
}
final double x0 = _fPoints[0];
final double y0 = _fPoints[1];
final double x1 = _fPoints[2];
final double y1 = _fPoints[3];
if (y0 == y1 || x0 == x1) {
return ui.Rect.fromLTRB(x0, y0, x1, y1);
}
return null;
}
/// Reconstructs RRect from path commands.
......
......@@ -466,12 +466,12 @@ class RecordingCanvas {
// For Rect/RoundedRect paths use drawRect/drawRRect code paths for
// DomCanvas optimization.
SurfacePath sPath = path as SurfacePath;
final ui.Rect? rect = sPath.webOnlyPathAsRect;
final ui.Rect? rect = sPath.toRect();
if (rect != null) {
drawRect(rect, paint);
return;
}
final ui.RRect? rrect = sPath.webOnlyPathAsRoundedRect;
final ui.RRect? rrect = sPath.toRoundedRect();
if (rrect != null) {
drawRRect(rrect, paint);
return;
......
......@@ -83,12 +83,32 @@ void testMain() {
test('Should detect rectangular path', () {
final SurfacePath path = SurfacePath();
path.addRect(const Rect.fromLTWH(1.0, 2.0, 3.0, 4.0));
expect(path.webOnlyPathAsRect, const Rect.fromLTWH(1.0, 2.0, 3.0, 4.0));
expect(path.toRect(), const Rect.fromLTWH(1.0, 2.0, 3.0, 4.0));
});
test('Should detect horizontal line path', () {
SurfacePath path = SurfacePath();
path.moveTo(10, 20);
path.lineTo(100, 0);
expect(path.toStraightLine(), null);
path = SurfacePath();
path.moveTo(10, 20);
path.lineTo(200, 20);
Rect r = path.toStraightLine()!;
expect(r, equals(Rect.fromLTRB(10, 20, 200, 20)));
});
test('Should detect vertical line path', () {
final SurfacePath path = SurfacePath();
path.moveTo(10, 20);
path.lineTo(10, 200);
Rect r = path.toStraightLine()!;
expect(r, equals(Rect.fromLTRB(10, 20, 10, 200)));
});
test('Should detect non rectangular path if empty', () {
final SurfacePath path = SurfacePath();
expect(path.webOnlyPathAsRect, null);
expect(path.toRect(), null);
});
test('Should detect non rectangular path if there are multiple subpaths',
......@@ -96,7 +116,7 @@ void testMain() {
final SurfacePath path = SurfacePath();
path.addRect(const Rect.fromLTWH(1.0, 2.0, 3.0, 4.0));
path.addRect(const Rect.fromLTWH(5.0, 6.0, 7.0, 8.0));
expect(path.webOnlyPathAsRect, null);
expect(path.toRect(), null);
});
test('Should detect rounded rectangular path', () {
......@@ -105,20 +125,20 @@ void testMain() {
const Rect.fromLTRB(1.0, 2.0, 30.0, 40.0),
const Radius.circular(2.0)));
expect(
path.webOnlyPathAsRoundedRect,
path.toRoundedRect(),
RRect.fromRectAndRadius(const Rect.fromLTRB(1.0, 2.0, 30.0, 40.0),
const Radius.circular(2.0)));
});
test('Should detect non rounded rectangular path if empty', () {
final SurfacePath path = SurfacePath();
expect(path.webOnlyPathAsRoundedRect, null);
expect(path.toRoundedRect(), null);
});
test('Should detect rectangular path is not round', () {
final SurfacePath path = SurfacePath();
path.addRect(const Rect.fromLTWH(1.0, 2.0, 3.0, 4.0));
expect(path.webOnlyPathAsRoundedRect, null);
expect(path.toRoundedRect(), null);
});
test(
......@@ -129,7 +149,7 @@ void testMain() {
const Rect.fromLTWH(1.0, 2.0, 3.0, 4.0), const Radius.circular(2.0)));
path.addRRect(RRect.fromRectAndRadius(
const Rect.fromLTWH(1.0, 2.0, 3.0, 4.0), const Radius.circular(2.0)));
expect(path.webOnlyPathAsRoundedRect, null);
expect(path.toRoundedRect(), null);
});
test('Should compute bounds as empty for empty and moveTo only path', () {
......@@ -161,7 +181,7 @@ void testMain() {
bottomRight: Radius.elliptical(7, 8));
path.addRRect(rrect);
expect(path.getBounds(), bounds);
expect(path.webOnlyPathAsRoundedRect, rrect);
expect(path.toRoundedRect(), rrect);
path = SurfacePath();
rrect = RRect.fromRectAndCorners(bounds,
topLeft: Radius.elliptical(0, 2),
......@@ -170,7 +190,7 @@ void testMain() {
bottomRight: Radius.elliptical(7, 8));
path.addRRect(rrect);
expect(path.getBounds(), bounds);
expect(path.webOnlyPathAsRoundedRect, rrect);
expect(path.toRoundedRect(), rrect);
path = SurfacePath();
rrect = RRect.fromRectAndCorners(bounds,
topLeft: Radius.elliptical(0, 0),
......@@ -179,7 +199,7 @@ void testMain() {
bottomRight: Radius.elliptical(7, 8));
path.addRRect(rrect);
expect(path.getBounds(), bounds);
expect(path.webOnlyPathAsRoundedRect, rrect);
expect(path.toRoundedRect(), rrect);
path = SurfacePath();
rrect = RRect.fromRectAndCorners(bounds,
topLeft: Radius.elliptical(1, 2),
......@@ -188,7 +208,7 @@ void testMain() {
bottomRight: Radius.elliptical(7, 8));
path.addRRect(rrect);
expect(path.getBounds(), bounds);
expect(path.webOnlyPathAsRoundedRect, rrect);
expect(path.toRoundedRect(), rrect);
path = SurfacePath();
rrect = RRect.fromRectAndCorners(bounds,
topLeft: Radius.elliptical(1, 2),
......@@ -197,7 +217,7 @@ void testMain() {
bottomRight: Radius.elliptical(7, 8));
path.addRRect(rrect);
expect(path.getBounds(), bounds);
expect(path.webOnlyPathAsRoundedRect, rrect);
expect(path.toRoundedRect(), rrect);
path = SurfacePath();
rrect = RRect.fromRectAndCorners(bounds,
topLeft: Radius.elliptical(1, 2),
......@@ -206,7 +226,7 @@ void testMain() {
bottomRight: Radius.elliptical(0, 0));
path.addRRect(rrect);
expect(path.getBounds(), bounds);
expect(path.webOnlyPathAsRoundedRect, rrect);
expect(path.toRoundedRect(), rrect);
});
test('Should compute bounds for lines', () {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册