Don't draw massively wide strokes with the tessellator

Since we outset the viewport by stroke width for pre-chopping,
astronomically wide strokes can result in an astronomical viewport
size, and therefore an exponential explosion chops and memory usage.
It is also simply inefficient to tessellate these strokes due to the
number of radial edges required. We're better off just converting them
to a path after a certain point

Bug: chromium:1266446
Change-Id: I23ea39b0bd64f22d4e293a881992e3669afbe530
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/473196
Reviewed-by: Greg Daniel <egdaniel@google.com>
Commit-Queue: Chris Dalton <csmartdalton@google.com>
This commit is contained in:
Chris Dalton 2021-11-18 00:54:11 -07:00 committed by SkCQ
parent d8a753a263
commit 32071c3f14
2 changed files with 25 additions and 8 deletions

View File

@ -98,6 +98,15 @@ PathRenderer::CanDrawPath TessellationPathRenderer::onCanDrawPath(
if (shape.inverseFilled()) {
return CanDrawPath::kNo;
}
if (shape.style().strokeRec().getWidth() * args.fViewMatrix->getMaxScale() > 10000) {
// crbug.com/1266446 -- Don't draw massively wide strokes with the tessellator. Since we
// outset the viewport by stroke width for pre-chopping, astronomically wide strokes can
// result in an astronomical viewport size, and therefore an exponential explosion chops
// and memory usage. It is also simply inefficient to tessellate these strokes due to
// the number of radial edges required. We're better off just converting them to a path
// after a certain point.
return CanDrawPath::kNo;
}
}
if (args.fHasUserStencilSettings) {
// Non-convex paths and strokes use the stencil buffer internally, so they can't support
@ -116,10 +125,10 @@ bool TessellationPathRenderer::onDrawPath(const DrawPathArgs& args) {
args.fShape->asPath(&path);
const SkRect pathDevBounds = args.fViewMatrix->mapRect(args.fShape->bounds());
float n = wangs_formula::worst_case_cubic_pow4(kTessellationPrecision,
pathDevBounds.width(),
pathDevBounds.height());
if (n > pow4(kMaxTessellationSegmentsPerCurve)) {
float n4 = wangs_formula::worst_case_cubic_pow4(kTessellationPrecision,
pathDevBounds.width(),
pathDevBounds.height());
if (n4 > pow4(kMaxTessellationSegmentsPerCurve)) {
// The path is extremely large. Pre-chop its curves to keep the number of tessellation
// segments tractable. This will also flatten curves that fall completely outside the
// viewport.
@ -206,10 +215,10 @@ void TessellationPathRenderer::onStencilPath(const StencilPathArgs& args) {
SkPath path;
args.fShape->asPath(&path);
float n = wangs_formula::worst_case_cubic_pow4(kTessellationPrecision,
pathDevBounds.width(),
pathDevBounds.height());
if (n > pow4(kMaxTessellationSegmentsPerCurve)) {
float n4 = wangs_formula::worst_case_cubic_pow4(kTessellationPrecision,
pathDevBounds.width(),
pathDevBounds.height());
if (n4 > pow4(kMaxTessellationSegmentsPerCurve)) {
SkRect viewport = SkRect::Make(*args.fClipConservativeBounds);
path = PreChopPathCurves(path, *args.fViewMatrix, viewport);
}

View File

@ -96,6 +96,14 @@ private:
} // namespace
SkPath PreChopPathCurves(const SkPath& path, const SkMatrix& matrix, const SkRect& viewport) {
// If the viewport is exceptionally large, we could end up blowing out memory with an unbounded
// number of of chops. Therefore, we require that the viewport is manageable enough that a fully
// contained curve can be tessellated in kMaxTessellationSegmentsPerCurve or fewer. (Any larger
// and that amount of pixels wouldn't fit in memory anyway.)
SkASSERT(wangs_formula::worst_case_cubic(
kTessellationPrecision,
viewport.width(),
viewport.height()) <= kMaxTessellationSegmentsPerCurve);
PathChopper chopper(matrix, viewport);
for (auto [verb, p, w] : SkPathPriv::Iterate(path)) {
switch (verb) {