skia2/tests/SubsetPath.cpp

242 lines
6.7 KiB
C++
Raw Normal View History

More conic-specific tests revealed a few conic-specific bugs. Because javascript / canvas make visualizing conics tricky, new native tools are required. The utility SubsetPath removes parts of a potentially very large path to isolate a minimal test case. SubsetPath is very useful for debugging path ops, but is not path ops specific. PathOpsBuilderConicTest compares the output of the Path Ops Builder, sequential calls to Simplify, and SkRegions for some number of rotated ovals. Some tests caused path ops to hang. It was caught adding a loop of curves because the head was not found by the tail. Even though the root cause has been fixed, SkSegment::addCurveTo callers now abort the path op if the same curve was added twice. The subdivided conic weight was been computed anew. Fortunately, it's a simpler computation that the one it replaces. Some Simplify() subroutines returned false to signal that the results needed assembling. Change these to abort the current operation instead. Coincident curve intersection triggered two small bugs; one where no perpendicular could be found for coincident curves, and one where no coincident curves remain after looping. The SixtyOvals test can be run through multiple processes instead of multiple threads. This strategy allows a 48 core machine to saturate all cores at 100%. The DEBUG_VISUALIZE_CONICS code in PathOpsConicIntersectionTest acknowleges that it is easier to visualize conics with Skia than with script and html canvas. This test also verifies that path ops subdivision matches geometry chopping. TBR=reed@google.com Review URL: https://codereview.chromium.org/1405383004
2015-10-30 19:03:06 +00:00
/*
* Copyright 2015 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "SubsetPath.h"
#include "SkMathPriv.h"
More conic-specific tests revealed a few conic-specific bugs. Because javascript / canvas make visualizing conics tricky, new native tools are required. The utility SubsetPath removes parts of a potentially very large path to isolate a minimal test case. SubsetPath is very useful for debugging path ops, but is not path ops specific. PathOpsBuilderConicTest compares the output of the Path Ops Builder, sequential calls to Simplify, and SkRegions for some number of rotated ovals. Some tests caused path ops to hang. It was caught adding a loop of curves because the head was not found by the tail. Even though the root cause has been fixed, SkSegment::addCurveTo callers now abort the path op if the same curve was added twice. The subdivided conic weight was been computed anew. Fortunately, it's a simpler computation that the one it replaces. Some Simplify() subroutines returned false to signal that the results needed assembling. Change these to abort the current operation instead. Coincident curve intersection triggered two small bugs; one where no perpendicular could be found for coincident curves, and one where no coincident curves remain after looping. The SixtyOvals test can be run through multiple processes instead of multiple threads. This strategy allows a 48 core machine to saturate all cores at 100%. The DEBUG_VISUALIZE_CONICS code in PathOpsConicIntersectionTest acknowleges that it is easier to visualize conics with Skia than with script and html canvas. This test also verifies that path ops subdivision matches geometry chopping. TBR=reed@google.com Review URL: https://codereview.chromium.org/1405383004
2015-10-30 19:03:06 +00:00
SubsetPath::SubsetPath(const SkPath& path)
: fPath(path)
, fSubset(1) {
}
int SubsetPath::range(int* end) const {
int leadingZero = SkCLZ(fSubset);
int parts = 1 << (31 - leadingZero);
int partIndex = fSubset - parts;
SkASSERT(partIndex >= 0);
int count = fSelected.count();
int start = count * partIndex / parts;
*end = count * (partIndex + 1) / parts;
return start;
}
bool SubsetPath::subset(bool testFailed, SkPath* sub) {
int start, end;
if (!testFailed) {
start = range(&end);
for (; start < end; ++start) {
fSelected[start] = true;
}
}
do {
do {
++fSubset;
start = range(&end);
// SkDebugf("%d s=%d e=%d t=%d\n", fSubset, start, end, fTries);
if (end - start > 1) {
fTries = fSelected.count();
} else if (end - start == 1) {
if (--fTries <= 0) {
return false;
}
}
} while (start == end);
} while (!fSelected[start]);
for (; start < end; ++start) {
fSelected[start] = false;
}
#if 1
SkDebugf("selected: ");
for (int index = 0; index < fSelected.count(); ++index) {
SkDebugf("%c", fSelected[index] ? 'x' : '-');
}
#endif
*sub = getSubsetPath();
return true;
}
SubsetContours::SubsetContours(const SkPath& path)
: SubsetPath(path) {
SkPath::RawIter iter(fPath);
uint8_t verb;
SkPoint pts[4];
bool foundCurve = false;
int contourCount = 0;
while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
switch (verb) {
case SkPath::kMove_Verb:
break;
case SkPath::kLine_Verb:
case SkPath::kQuad_Verb:
case SkPath::kConic_Verb:
case SkPath::kCubic_Verb:
foundCurve = true;
break;
case SkPath::kClose_Verb:
++contourCount;
foundCurve = false;
break;
default:
SkDEBUGFAIL("bad verb");
return;
}
}
contourCount += foundCurve;
for (int index = 0; index < contourCount; ++index) {
*fSelected.append() = true;
}
fTries = contourCount;
}
SkPath SubsetContours::getSubsetPath() const {
SkPath result;
result.setFillType(fPath.getFillType());
if (!fSelected.count()) {
return result;
}
SkPath::RawIter iter(fPath);
uint8_t verb;
SkPoint pts[4];
int contourCount = 0;
bool enabled = fSelected[0];
bool addMoveTo = true;
while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
if (enabled && addMoveTo) {
result.moveTo(pts[0]);
addMoveTo = false;
}
switch (verb) {
case SkPath::kMove_Verb:
break;
case SkPath::kLine_Verb:
if (enabled) {
result.lineTo(pts[1]);
}
break;
case SkPath::kQuad_Verb:
if (enabled) {
result.quadTo(pts[1], pts[2]);
}
break;
case SkPath::kConic_Verb:
if (enabled) {
result.conicTo(pts[1], pts[2], iter.conicWeight());
}
break;
case SkPath::kCubic_Verb:
if (enabled) {
result.cubicTo(pts[1], pts[2], pts[3]);
}
break;
case SkPath::kClose_Verb:
if (enabled) {
result.close();
}
if (++contourCount >= fSelected.count()) {
break;
}
enabled = fSelected[contourCount];
addMoveTo = true;
continue;
default:
SkDEBUGFAIL("bad verb");
return result;
}
}
return result;
}
SubsetVerbs::SubsetVerbs(const SkPath& path)
: SubsetPath(path) {
SkPath::RawIter iter(fPath);
uint8_t verb;
SkPoint pts[4];
int verbCount = 0;
while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
switch (verb) {
case SkPath::kMove_Verb:
break;
case SkPath::kLine_Verb:
case SkPath::kQuad_Verb:
case SkPath::kConic_Verb:
case SkPath::kCubic_Verb:
++verbCount;
break;
case SkPath::kClose_Verb:
break;
default:
SkDEBUGFAIL("bad verb");
return;
}
}
for (int index = 0; index < verbCount; ++index) {
*fSelected.append() = true;
}
fTries = verbCount;
}
SkPath SubsetVerbs::getSubsetPath() const {
SkPath result;
result.setFillType(fPath.getFillType());
if (!fSelected.count()) {
return result;
}
SkPath::RawIter iter(fPath);
uint8_t verb;
SkPoint pts[4];
int verbIndex = 0;
bool addMoveTo = true;
bool addLineTo = false;
while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
bool enabled = SkPath::kLine_Verb <= verb && verb <= SkPath::kCubic_Verb
Revert "implement SkTDArray with std::vector" This reverts commit 80e1d56e198c5fd9fe6db0c945bd558053a8dc6a. Reason for revert: SkRTree.cpp:57 asserting, probably this? Original change's description: > implement SkTDArray with std::vector > > It's always worth seeing if we can get away with replacing custom data > structures with ones from the standard library. Our array-like types > are all good candidates to replace with std::vector, and it's especially > easy to start with SkTDArray. Unlike the others, it has no preallocated > S-variant, which is tricky to make work with std::vector. > > SkTDArray also has known integer overflow bugs, leading to out of range > writes. It'd be _very_ nice to ditch it for a better standard vector. > > I removed a bunch of unused or little-used methods, and updated a couple > call sites that used methods in unusual or dangerous ways. > > I've had to tweak GrAAConvexTessellator and SkBaseShadowTessellator just > a touch to work within the constraints of an std::vector impl. It's not > intended to be legal to write to the reserved-but-not-counted elements > of an SkTDArray, but you can get away with it in our old implementation. > This version now uses setCount() to actually reserve and count them, and > should have the same performance and use the same amount of memory. > > The PathMeasure_explosion GM I added recently to reproduce this bug now > draws without triggering undefined behavior or ASAN errors, provided you > have ~40GB of RAM. > > Bug: skia:7674 > > Change-Id: I4eacae18a976cd4a6d218102f8ca5d973d4d7d0e > Reviewed-on: https://skia-review.googlesource.com/115982 > Reviewed-by: Brian Osman <brianosman@google.com> > Commit-Queue: Mike Klein <mtklein@chromium.org> TBR=mtklein@chromium.org,bungeman@google.com,brianosman@google.com Change-Id: Icffd9f22fe89746a970ff598e1a05c774960bc0e No-Presubmit: true No-Tree-Checks: true No-Try: true Bug: skia:7674 Reviewed-on: https://skia-review.googlesource.com/117901 Reviewed-by: Mike Klein <mtklein@chromium.org> Commit-Queue: Mike Klein <mtklein@chromium.org>
2018-04-02 20:37:42 +00:00
? fSelected[verbIndex++] : false;
More conic-specific tests revealed a few conic-specific bugs. Because javascript / canvas make visualizing conics tricky, new native tools are required. The utility SubsetPath removes parts of a potentially very large path to isolate a minimal test case. SubsetPath is very useful for debugging path ops, but is not path ops specific. PathOpsBuilderConicTest compares the output of the Path Ops Builder, sequential calls to Simplify, and SkRegions for some number of rotated ovals. Some tests caused path ops to hang. It was caught adding a loop of curves because the head was not found by the tail. Even though the root cause has been fixed, SkSegment::addCurveTo callers now abort the path op if the same curve was added twice. The subdivided conic weight was been computed anew. Fortunately, it's a simpler computation that the one it replaces. Some Simplify() subroutines returned false to signal that the results needed assembling. Change these to abort the current operation instead. Coincident curve intersection triggered two small bugs; one where no perpendicular could be found for coincident curves, and one where no coincident curves remain after looping. The SixtyOvals test can be run through multiple processes instead of multiple threads. This strategy allows a 48 core machine to saturate all cores at 100%. The DEBUG_VISUALIZE_CONICS code in PathOpsConicIntersectionTest acknowleges that it is easier to visualize conics with Skia than with script and html canvas. This test also verifies that path ops subdivision matches geometry chopping. TBR=reed@google.com Review URL: https://codereview.chromium.org/1405383004
2015-10-30 19:03:06 +00:00
if (enabled) {
if (addMoveTo) {
result.moveTo(pts[0]);
addMoveTo = false;
} else if (addLineTo) {
result.lineTo(pts[0]);
addLineTo = false;
}
}
switch (verb) {
case SkPath::kMove_Verb:
break;
case SkPath::kLine_Verb:
if (enabled) {
result.lineTo(pts[1]);
}
break;
case SkPath::kQuad_Verb:
if (enabled) {
result.quadTo(pts[1], pts[2]);
}
break;
case SkPath::kConic_Verb:
if (enabled) {
result.conicTo(pts[1], pts[2], iter.conicWeight());
}
break;
case SkPath::kCubic_Verb:
if (enabled) {
result.cubicTo(pts[1], pts[2], pts[3]);
}
break;
case SkPath::kClose_Verb:
result.close();
addMoveTo = true;
addLineTo = false;
continue;
default:
SkDEBUGFAIL("bad verb");
return result;
}
addLineTo = !enabled;
}
return result;
}