Index: src/pathops/SkPathOpsDebug.cpp |
diff --git a/src/pathops/SkPathOpsDebug.cpp b/src/pathops/SkPathOpsDebug.cpp |
index 141041ad365f4a22ff66943e3a374a0471ea3f75..a037d005713796d9e51d02720d56bf2d02777761 100644 |
--- a/src/pathops/SkPathOpsDebug.cpp |
+++ b/src/pathops/SkPathOpsDebug.cpp |
@@ -6,10 +6,14 @@ |
*/ |
#include "SkMutex.h" |
+#include "SkOpCoincidence.h" |
+#include "SkOpContour.h" |
#include "SkPath.h" |
#include "SkPathOpsDebug.h" |
#include "SkString.h" |
+struct SkCoincidentSpans; |
+ |
#if DEBUG_VALIDATE |
extern bool FLAGS_runFail; |
#endif |
@@ -27,10 +31,8 @@ const char* SkPathOpsDebug::kPathOpStr[] = {"diff", "sect", "union", "xor"}; |
const char* SkPathOpsDebug::kLVerbStr[] = {"", "line", "quad", "cubic"}; |
-#if defined(SK_DEBUG) || !FORCE_RELEASE |
int SkPathOpsDebug::gContourID = 0; |
int SkPathOpsDebug::gSegmentID = 0; |
-#endif |
bool SkPathOpsDebug::ChaseContains(const SkTDArray<SkOpSpanBase* >& chaseArray, |
const SkOpSpanBase* span) { |
@@ -42,7 +44,152 @@ bool SkPathOpsDebug::ChaseContains(const SkTDArray<SkOpSpanBase* >& chaseArray, |
} |
return false; |
} |
+#endif |
+ |
+#if DEBUG_COINCIDENCE |
+enum GlitchType { |
+ kAddCorruptCoin_Glitch, |
+ kAddExpandedCoin_Glitch, |
+ kAddMissingCoin_Glitch, |
+ kCollapsedCoin_Glitch, |
+ kCollapsedDone_Glitch, |
+ kCollapsedOppValue_Glitch, |
+ kCollapsedSpan_Glitch, |
+ kCollapsedWindValue_Glitch, |
+ kDeletedCoin_Glitch, |
+ kExpandCoin_Glitch, |
+ kMarkCoinEnd_Glitch, |
+ kMarkCoinInsert_Glitch, |
+ kMissingCoin_Glitch, |
+ kMissingDone_Glitch, |
+ kMissingIntersection_Glitch, |
+ kMoveMultiple_Glitch, |
+ kUnaligned_Glitch, |
+ kUnalignedHead_Glitch, |
+ kUnalignedTail_Glitch, |
+ kUndetachedSpan_Glitch, |
+ kUnmergedSpan_Glitch, |
+}; |
+ |
+static const int kGlitchType_Count = kUnmergedSpan_Glitch + 1; |
+ |
+struct SpanGlitch { |
+ const char* fStage; |
+ const SkOpSpanBase* fBase; |
+ const SkOpSpanBase* fSuspect; |
+ const SkCoincidentSpans* fCoin; |
+ const SkOpSegment* fSegment; |
+ const SkOpPtT* fCoinSpan; |
+ const SkOpPtT* fEndSpan; |
+ const SkOpPtT* fOppSpan; |
+ const SkOpPtT* fOppEndSpan; |
+ double fT; |
+ SkPoint fPt; |
+ GlitchType fType; |
+}; |
+ |
+struct SkPathOpsDebug::GlitchLog { |
+ SpanGlitch* recordCommon(GlitchType type, const char* stage) { |
+ SpanGlitch* glitch = fGlitches.push(); |
+ glitch->fStage = stage; |
+ glitch->fBase = nullptr; |
+ glitch->fSuspect = nullptr; |
+ glitch->fCoin = nullptr; |
+ glitch->fSegment = nullptr; |
+ glitch->fCoinSpan = nullptr; |
+ glitch->fEndSpan = nullptr; |
+ glitch->fOppSpan = nullptr; |
+ glitch->fOppEndSpan = nullptr; |
+ glitch->fT = SK_ScalarNaN; |
+ glitch->fPt = { SK_ScalarNaN, SK_ScalarNaN }; |
+ glitch->fType = type; |
+ return glitch; |
+ } |
+ |
+ void record(GlitchType type, const char* stage, const SkOpSpanBase* base, |
+ const SkOpSpanBase* suspect = NULL) { |
+ SpanGlitch* glitch = recordCommon(type, stage); |
+ glitch->fBase = base; |
+ glitch->fSuspect = suspect; |
+ } |
+ |
+ void record(GlitchType type, const char* stage, const SkCoincidentSpans* coin, |
+ const SkOpPtT* coinSpan) { |
+ SpanGlitch* glitch = recordCommon(type, stage); |
+ glitch->fCoin = coin; |
+ glitch->fCoinSpan = coinSpan; |
+ } |
+ |
+ void record(GlitchType type, const char* stage, const SkOpSpanBase* base, |
+ const SkOpSegment* seg, double t, SkPoint pt) { |
+ SpanGlitch* glitch = recordCommon(type, stage); |
+ glitch->fBase = base; |
+ glitch->fSegment = seg; |
+ glitch->fT = t; |
+ glitch->fPt = pt; |
+ } |
+ |
+ void record(GlitchType type, const char* stage, const SkOpSpanBase* base, double t, |
+ SkPoint pt) { |
+ SpanGlitch* glitch = recordCommon(type, stage); |
+ glitch->fBase = base; |
+ glitch->fT = t; |
+ glitch->fPt = pt; |
+ } |
+ |
+ void record(GlitchType type, const char* stage, const SkCoincidentSpans* coin, |
+ const SkOpPtT* coinSpan, const SkOpPtT* endSpan) { |
+ SpanGlitch* glitch = recordCommon(type, stage); |
+ glitch->fCoin = coin; |
+ glitch->fCoinSpan = coinSpan; |
+ glitch->fEndSpan = endSpan; |
+ } |
+ |
+ void record(GlitchType type, const char* stage, const SkCoincidentSpans* coin, |
+ const SkOpSpanBase* suspect) { |
+ SpanGlitch* glitch = recordCommon(type, stage); |
+ glitch->fSuspect = suspect; |
+ glitch->fCoin = coin; |
+ } |
+ |
+ void record(GlitchType type, const char* stage, const SkOpPtT* ptTS, const SkOpPtT* ptTE, |
+ const SkOpPtT* oPtTS, const SkOpPtT* oPtTE) { |
+ SpanGlitch* glitch = recordCommon(type, stage); |
+ glitch->fCoinSpan = ptTS; |
+ glitch->fEndSpan = ptTE; |
+ glitch->fOppSpan = oPtTS; |
+ glitch->fOppEndSpan = oPtTE; |
+ } |
+ |
+ SkTDArray<SpanGlitch> fGlitches; |
+}; |
+ |
+void SkPathOpsDebug::CheckHealth(SkOpContourHead* contourList, const char* id) { |
+ GlitchLog glitches; |
+ const SkOpContour* contour = contourList; |
+ const SkOpCoincidence* coincidence = contour->globalState()->coincidence(); |
+ do { |
+ contour->debugCheckHealth(id, &glitches); |
+ contour->debugMissingCoincidence(id, &glitches, coincidence); |
+ } while ((contour = contour->next())); |
+ coincidence->debugFixAligned(id, &glitches); |
+ coincidence->debugAddMissing(id, &glitches); |
+ coincidence->debugExpand(id, &glitches); |
+ coincidence->debugAddExpanded(id, &glitches); |
+ coincidence->debugMark(id, &glitches); |
+ unsigned mask = 0; |
+ for (int index = 0; index < glitches.fGlitches.count(); ++index) { |
+ const SpanGlitch& glitch = glitches.fGlitches[index]; |
+ mask |= 1 << glitch.fType; |
+ } |
+ for (int index = 0; index < kGlitchType_Count; ++index) { |
+ SkDebugf(mask & (1 << index) ? "x" : "-"); |
+ } |
+ SkDebugf(" %s\n", id); |
+} |
+#endif |
+#if defined SK_DEBUG || !FORCE_RELEASE |
void SkPathOpsDebug::MathematicaIze(char* str, size_t bufferLen) { |
size_t len = strlen(str); |
bool num = false; |
@@ -132,6 +279,91 @@ void SkPathOpsDebug::ShowPath(const SkPath& a, const SkPath& b, SkPathOp shapeOp |
} |
#include "SkPathOpsTypes.h" |
+#include "SkIntersectionHelper.h" |
+#include "SkIntersections.h" |
+ |
+#if DEBUG_T_SECT_LOOP_COUNT |
+void SkOpGlobalState::debugAddLoopCount(SkIntersections* i, const SkIntersectionHelper& wt, |
+ const SkIntersectionHelper& wn) { |
+ for (int index = 0; index < (int) SK_ARRAY_COUNT(fDebugLoopCount); ++index) { |
+ SkIntersections::DebugLoop looper = (SkIntersections::DebugLoop) index; |
+ if (fDebugLoopCount[index] >= i->debugLoopCount(looper)) { |
+ continue; |
+ } |
+ fDebugLoopCount[index] = i->debugLoopCount(looper); |
+ fDebugWorstVerb[index * 2] = wt.segment()->verb(); |
+ fDebugWorstVerb[index * 2 + 1] = wn.segment()->verb(); |
+ sk_bzero(&fDebugWorstPts[index * 8], sizeof(SkPoint) * 8); |
+ memcpy(&fDebugWorstPts[index * 2 * 4], wt.pts(), |
+ (SkPathOpsVerbToPoints(wt.segment()->verb()) + 1) * sizeof(SkPoint)); |
+ memcpy(&fDebugWorstPts[(index * 2 + 1) * 4], wn.pts(), |
+ (SkPathOpsVerbToPoints(wn.segment()->verb()) + 1) * sizeof(SkPoint)); |
+ fDebugWorstWeight[index * 2] = wt.weight(); |
+ fDebugWorstWeight[index * 2 + 1] = wn.weight(); |
+ } |
+ i->debugResetLoopCount(); |
+} |
+ |
+void SkOpGlobalState::debugDoYourWorst(SkOpGlobalState* local) { |
+ for (int index = 0; index < (int) SK_ARRAY_COUNT(fDebugLoopCount); ++index) { |
+ if (fDebugLoopCount[index] >= local->fDebugLoopCount[index]) { |
+ continue; |
+ } |
+ fDebugLoopCount[index] = local->fDebugLoopCount[index]; |
+ fDebugWorstVerb[index * 2] = local->fDebugWorstVerb[index * 2]; |
+ fDebugWorstVerb[index * 2 + 1] = local->fDebugWorstVerb[index * 2 + 1]; |
+ memcpy(&fDebugWorstPts[index * 2 * 4], &local->fDebugWorstPts[index * 2 * 4], |
+ sizeof(SkPoint) * 8); |
+ fDebugWorstWeight[index * 2] = local->fDebugWorstWeight[index * 2]; |
+ fDebugWorstWeight[index * 2 + 1] = local->fDebugWorstWeight[index * 2 + 1]; |
+ } |
+ local->debugResetLoopCounts(); |
+} |
+ |
+static void dump_curve(SkPath::Verb verb, const SkPoint& pts, float weight) { |
+ if (!verb) { |
+ return; |
+ } |
+ const char* verbs[] = { "", "line", "quad", "conic", "cubic" }; |
+ SkDebugf("%s: {{", verbs[verb]); |
+ int ptCount = SkPathOpsVerbToPoints(verb); |
+ for (int index = 0; index <= ptCount; ++index) { |
+ SkDPoint::Dump((&pts)[index]); |
+ if (index < ptCount - 1) { |
+ SkDebugf(", "); |
+ } |
+ } |
+ SkDebugf("}"); |
+ if (weight != 1) { |
+ SkDebugf(", "); |
+ if (weight == floorf(weight)) { |
+ SkDebugf("%.0f", weight); |
+ } else { |
+ SkDebugf("%1.9gf", weight); |
+ } |
+ } |
+ SkDebugf("}\n"); |
+} |
+ |
+void SkOpGlobalState::debugLoopReport() { |
+ const char* loops[] = { "iterations", "coinChecks", "perpCalcs" }; |
+ SkDebugf("\n"); |
+ for (int index = 0; index < (int) SK_ARRAY_COUNT(fDebugLoopCount); ++index) { |
+ SkDebugf("%s: %d\n", loops[index], fDebugLoopCount[index]); |
+ dump_curve(fDebugWorstVerb[index * 2], fDebugWorstPts[index * 2 * 4], |
+ fDebugWorstWeight[index * 2]); |
+ dump_curve(fDebugWorstVerb[index * 2 + 1], fDebugWorstPts[(index * 2 + 1) * 4], |
+ fDebugWorstWeight[index * 2 + 1]); |
+ } |
+} |
+ |
+void SkOpGlobalState::debugResetLoopCounts() { |
+ sk_bzero(fDebugLoopCount, sizeof(fDebugLoopCount)); |
+ sk_bzero(fDebugWorstVerb, sizeof(fDebugWorstVerb)); |
+ sk_bzero(fDebugWorstPts, sizeof(fDebugWorstPts)); |
+ sk_bzero(fDebugWorstWeight, sizeof(fDebugWorstWeight)); |
+} |
+#endif |
#ifdef SK_DEBUG |
bool SkOpGlobalState::debugRunFail() const { |
@@ -143,6 +375,20 @@ bool SkOpGlobalState::debugRunFail() const { |
} |
#endif |
+#if DEBUG_T_SECT_LOOP_COUNT |
+void SkIntersections::debugBumpLoopCount(DebugLoop index) { |
+ fDebugLoopCount[index]++; |
+} |
+ |
+int SkIntersections::debugLoopCount(DebugLoop index) const { |
+ return fDebugLoopCount[index]; |
+} |
+ |
+void SkIntersections::debugResetLoopCount() { |
+ sk_bzero(fDebugLoopCount, sizeof(fDebugLoopCount)); |
+} |
+#endif |
+ |
#include "SkPathOpsCubic.h" |
#include "SkPathOpsQuad.h" |
@@ -159,9 +405,190 @@ SkDCubic SkDQuad::debugToCubic() const { |
} |
#include "SkOpAngle.h" |
-#include "SkOpCoincidence.h" |
#include "SkOpSegment.h" |
+#if DEBUG_COINCIDENCE |
+void SkOpSegment::debugAddAlignIntersection(const char* id, SkPathOpsDebug::GlitchLog* log, |
+ const SkOpPtT& endPtT, const SkPoint& oldPt, const SkOpContourHead* contourList) const { |
+ const SkPoint& newPt = endPtT.fPt; |
+ if (newPt == oldPt) { |
+ return; |
+ } |
+ SkPoint line[2] = { newPt, oldPt }; |
+ SkPathOpsBounds lineBounds; |
+ lineBounds.setBounds(line, 2); |
+ SkDLine aLine; |
+ aLine.set(line); |
+ const SkOpContour* current = contourList; |
+ do { |
+ if (!SkPathOpsBounds::Intersects(current->bounds(), lineBounds)) { |
+ continue; |
+ } |
+ const SkOpSegment* segment = current->first(); |
+ do { |
+ if (!SkPathOpsBounds::Intersects(segment->bounds(), lineBounds)) { |
+ continue; |
+ } |
+ if (newPt == segment->fPts[0]) { |
+ continue; |
+ } |
+ if (newPt == segment->fPts[SkPathOpsVerbToPoints(segment->fVerb)]) { |
+ continue; |
+ } |
+ if (oldPt == segment->fPts[0]) { |
+ continue; |
+ } |
+ if (oldPt == segment->fPts[SkPathOpsVerbToPoints(segment->fVerb)]) { |
+ continue; |
+ } |
+ if (endPtT.debugContains(segment)) { |
+ continue; |
+ } |
+ SkIntersections i; |
+ switch (segment->fVerb) { |
+ case SkPath::kLine_Verb: { |
+ SkDLine bLine; |
+ bLine.set(segment->fPts); |
+ i.intersect(bLine, aLine); |
+ } break; |
+ case SkPath::kQuad_Verb: { |
+ SkDQuad bQuad; |
+ bQuad.set(segment->fPts); |
+ i.intersect(bQuad, aLine); |
+ } break; |
+ case SkPath::kConic_Verb: { |
+ SkDConic bConic; |
+ bConic.set(segment->fPts, segment->fWeight); |
+ i.intersect(bConic, aLine); |
+ } break; |
+ case SkPath::kCubic_Verb: { |
+ SkDCubic bCubic; |
+ bCubic.set(segment->fPts); |
+ i.intersect(bCubic, aLine); |
+ } break; |
+ default: |
+ SkASSERT(0); |
+ } |
+ if (i.used()) { |
+ SkASSERT(i.used() == 1); |
+ SkASSERT(!zero_or_one(i[0][0])); |
+ SkOpSpanBase* checkSpan = fHead.next(); |
+ while (!checkSpan->final()) { |
+ if (checkSpan->contains(segment)) { |
+ goto nextSegment; |
+ } |
+ checkSpan = checkSpan->upCast()->next(); |
+ } |
+ log->record(kMissingIntersection_Glitch, id, checkSpan, segment, i[0][0], newPt); |
+ } |
+ nextSegment: |
+ ; |
+ } while ((segment = segment->next())); |
+ } while ((current = current->next())); |
+} |
+ |
+bool SkOpSegment::debugAddMissing(double t, const SkOpSegment* opp) const { |
+ const SkOpSpanBase* existing = nullptr; |
+ const SkOpSpanBase* test = &fHead; |
+ double testT; |
+ do { |
+ if ((testT = test->ptT()->fT) >= t) { |
+ if (testT == t) { |
+ existing = test; |
+ } |
+ break; |
+ } |
+ } while ((test = test->upCast()->next())); |
+ return !existing || !existing->debugContains(opp); |
+} |
+ |
+void SkOpSegment::debugAlign(const char* id, SkPathOpsDebug::GlitchLog* glitches) const { |
+ const SkOpSpanBase* span = &fHead; |
+ if (!span->aligned()) { |
+ if (!span->debugAlignedEnd(0, fPts[0])) { |
+ glitches->record(kUnalignedHead_Glitch, id, span); |
+ } |
+ } |
+ while ((span = span->upCast()->next())) { |
+ if (span == &fTail) { |
+ break; |
+ } |
+ if (!span->aligned()) { |
+ glitches->record(kUnaligned_Glitch, id, span); |
+ } |
+ } |
+ if (!span->aligned()) { |
+ span->debugAlignedEnd(1, fPts[SkPathOpsVerbToPoints(fVerb)]); |
+ } |
+ if (this->collapsed()) { |
+ const SkOpSpan* span = &fHead; |
+ do { |
+ if (span->windValue()) { |
+ glitches->record(kCollapsedWindValue_Glitch, id, span); |
+ } |
+ if (span->oppValue()) { |
+ glitches->record(kCollapsedOppValue_Glitch, id, span); |
+ } |
+ if (!span->done()) { |
+ glitches->record(kCollapsedDone_Glitch, id, span); |
+ } |
+ } while ((span = span->next()->upCastable())); |
+ } |
+} |
+#endif |
+ |
+#if DEBUG_ANGLE |
+void SkOpSegment::debugCheckAngleCoin() const { |
+ const SkOpSpanBase* base = &fHead; |
+ const SkOpSpan* span; |
+ do { |
+ const SkOpAngle* angle = base->fromAngle(); |
+ if (angle && angle->fCheckCoincidence) { |
+ angle->debugCheckNearCoincidence(); |
+ } |
+ if (base->final()) { |
+ break; |
+ } |
+ span = base->upCast(); |
+ angle = span->toAngle(); |
+ if (angle && angle->fCheckCoincidence) { |
+ angle->debugCheckNearCoincidence(); |
+ } |
+ } while ((base = span->next())); |
+} |
+#endif |
+ |
+#if DEBUG_COINCIDENCE |
+// this mimics the order of the checks in handle coincidence |
+void SkOpSegment::debugCheckHealth(const char* id, SkPathOpsDebug::GlitchLog* glitches) const { |
+ debugMoveMultiples(id, glitches); |
+ debugFindCollapsed(id, glitches); |
+ debugMoveNearby(id, glitches); |
+ debugAlign(id, glitches); |
+ debugAddAlignIntersections(id, glitches, this->globalState()->contourHead()); |
+ |
+} |
+ |
+void SkOpSegment::debugFindCollapsed(const char* id, SkPathOpsDebug::GlitchLog* glitches) const { |
+ if (fHead.contains(&fTail)) { |
+ const SkOpSpan* span = this->head(); |
+ bool missingDone = false; |
+ do { |
+ missingDone |= !span->done(); |
+ } while ((span = span->next()->upCastable())); |
+ if (missingDone) { |
+ glitches->record(kMissingDone_Glitch, id, &fHead); |
+ } |
+ if (!fHead.debugAlignedEnd(0, fHead.pt())) { |
+ glitches->record(kUnalignedHead_Glitch, id, &fHead); |
+ } |
+ if (!fTail.aligned()) { |
+ glitches->record(kUnalignedTail_Glitch, id, &fTail); |
+ } |
+ } |
+} |
+#endif |
+ |
SkOpAngle* SkOpSegment::debugLastAngle() { |
SkOpAngle* result = nullptr; |
SkOpSpan* span = this->head(); |
@@ -175,6 +602,241 @@ SkOpAngle* SkOpSegment::debugLastAngle() { |
return result; |
} |
+#if DEBUG_COINCIDENCE |
+void SkOpSegment::debugMissingCoincidence(const char* id, SkPathOpsDebug::GlitchLog* log, |
+ const SkOpCoincidence* coincidences) const { |
+ if (this->verb() != SkPath::kLine_Verb) { |
+ return; |
+ } |
+ if (this->done()) { |
+ return; |
+ } |
+ const SkOpSpan* prior = nullptr; |
+ const SkOpSpanBase* spanBase = &fHead; |
+ do { |
+ const SkOpPtT* ptT = spanBase->ptT(), * spanStopPtT = ptT; |
+ SkASSERT(ptT->span() == spanBase); |
+ while ((ptT = ptT->next()) != spanStopPtT) { |
+ if (ptT->deleted()) { |
+ continue; |
+ } |
+ SkOpSegment* opp = ptT->span()->segment(); |
+// if (opp->verb() == SkPath::kLine_Verb) { |
+// continue; |
+// } |
+ if (opp->done()) { |
+ continue; |
+ } |
+ // when opp is encounted the 1st time, continue; on 2nd encounter, look for coincidence |
+ if (!opp->visited()) { |
+ continue; |
+ } |
+ if (spanBase == &fHead) { |
+ continue; |
+ } |
+ const SkOpSpan* span = spanBase->upCastable(); |
+ // FIXME?: this assumes that if the opposite segment is coincident then no more |
+ // coincidence needs to be detected. This may not be true. |
+ if (span && span->segment() != opp && span->containsCoincidence(opp)) { |
+ continue; |
+ } |
+ if (spanBase->segment() != opp && spanBase->containsCoinEnd(opp)) { |
+ continue; |
+ } |
+ const SkOpPtT* priorPtT = nullptr, * priorStopPtT; |
+ // find prior span containing opp segment |
+ const SkOpSegment* priorOpp = nullptr; |
+ const SkOpSpan* priorTest = spanBase->prev(); |
+ while (!priorOpp && priorTest) { |
+ priorStopPtT = priorPtT = priorTest->ptT(); |
+ while ((priorPtT = priorPtT->next()) != priorStopPtT) { |
+ if (priorPtT->deleted()) { |
+ continue; |
+ } |
+ SkOpSegment* segment = priorPtT->span()->segment(); |
+ if (segment == opp) { |
+ prior = priorTest; |
+ priorOpp = opp; |
+ break; |
+ } |
+ } |
+ priorTest = priorTest->prev(); |
+ } |
+ if (!priorOpp) { |
+ continue; |
+ } |
+ const SkOpPtT* oppStart = prior->ptT(); |
+ const SkOpPtT* oppEnd = spanBase->ptT(); |
+ bool swapped = priorPtT->fT > ptT->fT; |
+ if (swapped) { |
+ SkTSwap(priorPtT, ptT); |
+ SkTSwap(oppStart, oppEnd); |
+ } |
+ bool flipped = oppStart->fT > oppEnd->fT; |
+ bool coincident = false; |
+ if (coincidences->contains(priorPtT, ptT, oppStart, oppEnd, flipped)) { |
+ goto swapBack; |
+ } |
+ if (opp->verb() == SkPath::kLine_Verb) { |
+ coincident = (SkDPoint::ApproximatelyEqual(priorPtT->fPt, oppStart->fPt) || |
+ SkDPoint::ApproximatelyEqual(priorPtT->fPt, oppEnd->fPt)) && |
+ (SkDPoint::ApproximatelyEqual(ptT->fPt, oppStart->fPt) || |
+ SkDPoint::ApproximatelyEqual(ptT->fPt, oppEnd->fPt)); |
+ } |
+ if (!coincident) { |
+ coincident = testForCoincidence(priorPtT, ptT, prior, spanBase, opp, 5000); |
+ } |
+ if (coincident) { |
+ log->record(kMissingCoin_Glitch, id, priorPtT, ptT, oppStart, oppEnd); |
+ } |
+ swapBack: |
+ if (swapped) { |
+ SkTSwap(priorPtT, ptT); |
+ } |
+ } |
+ } while ((spanBase = spanBase->final() ? nullptr : spanBase->upCast()->next())); |
+} |
+ |
+void SkOpSegment::debugMoveMultiples(const char* id, SkPathOpsDebug::GlitchLog* glitches) const { |
+ const SkOpSpanBase* test = &fHead; |
+ do { |
+ int addCount = test->spanAddsCount(); |
+ SkASSERT(addCount >= 1); |
+ if (addCount == 1) { |
+ continue; |
+ } |
+ const SkOpPtT* startPtT = test->ptT(); |
+ const SkOpPtT* testPtT = startPtT; |
+ do { // iterate through all spans associated with start |
+ const SkOpSpanBase* oppSpan = testPtT->span(); |
+ if (oppSpan->spanAddsCount() == addCount) { |
+ continue; |
+ } |
+ if (oppSpan->deleted()) { |
+ continue; |
+ } |
+ const SkOpSegment* oppSegment = oppSpan->segment(); |
+ if (oppSegment == this) { |
+ continue; |
+ } |
+ // find range of spans to consider merging |
+ const SkOpSpanBase* oppPrev = oppSpan; |
+ const SkOpSpanBase* oppFirst = oppSpan; |
+ while ((oppPrev = oppPrev->prev())) { |
+ if (!roughly_equal(oppPrev->t(), oppSpan->t())) { |
+ break; |
+ } |
+ if (oppPrev->spanAddsCount() == addCount) { |
+ continue; |
+ } |
+ if (oppPrev->deleted()) { |
+ continue; |
+ } |
+ oppFirst = oppPrev; |
+ } |
+ const SkOpSpanBase* oppNext = oppSpan; |
+ const SkOpSpanBase* oppLast = oppSpan; |
+ while ((oppNext = oppNext->final() ? nullptr : oppNext->upCast()->next())) { |
+ if (!roughly_equal(oppNext->t(), oppSpan->t())) { |
+ break; |
+ } |
+ if (oppNext->spanAddsCount() == addCount) { |
+ continue; |
+ } |
+ if (oppNext->deleted()) { |
+ continue; |
+ } |
+ oppLast = oppNext; |
+ } |
+ if (oppFirst == oppLast) { |
+ continue; |
+ } |
+ const SkOpSpanBase* oppTest = oppFirst; |
+ do { |
+ if (oppTest == oppSpan) { |
+ continue; |
+ } |
+ // check to see if the candidate meets specific criteria: |
+ // it contains spans of segments in test's loop but not including 'this' |
+ const SkOpPtT* oppStartPtT = oppTest->ptT(); |
+ const SkOpPtT* oppPtT = oppStartPtT; |
+ while ((oppPtT = oppPtT->next()) != oppStartPtT) { |
+ const SkOpSegment* oppPtTSegment = oppPtT->segment(); |
+ if (oppPtTSegment == this) { |
+ goto tryNextSpan; |
+ } |
+ const SkOpPtT* matchPtT = startPtT; |
+ do { |
+ if (matchPtT->segment() == oppPtTSegment) { |
+ goto foundMatch; |
+ } |
+ } while ((matchPtT = matchPtT->next()) != startPtT); |
+ goto tryNextSpan; |
+ foundMatch: // merge oppTest and oppSpan |
+ if (oppTest == &oppSegment->fTail || oppTest == &oppSegment->fHead) { |
+ SkASSERT(oppSpan != &oppSegment->fHead); // don't expect collapse |
+ SkASSERT(oppSpan != &oppSegment->fTail); |
+ glitches->record(kMoveMultiple_Glitch, id, oppTest, oppSpan); |
+ } else { |
+ glitches->record(kMoveMultiple_Glitch, id, oppSpan, oppTest); |
+ } |
+ goto checkNextSpan; |
+ } |
+ tryNextSpan: |
+ ; |
+ } while (oppTest != oppLast && (oppTest = oppTest->upCast()->next())); |
+ } while ((testPtT = testPtT->next()) != startPtT); |
+checkNextSpan: |
+ ; |
+ } while ((test = test->final() ? nullptr : test->upCast()->next())); |
+} |
+ |
+void SkOpSegment::debugMoveNearby(const char* id, SkPathOpsDebug::GlitchLog* glitches) const { |
+ const SkOpSpanBase* spanS = &fHead; |
+ do { |
+ const SkOpSpanBase* test = spanS->upCast()->next(); |
+ const SkOpSpanBase* next; |
+ if (spanS->contains(test)) { |
+ if (!test->final()) { |
+ glitches->record(kUndetachedSpan_Glitch, id, test, spanS); |
+ } else if (spanS != &fHead) { |
+ glitches->record(kUndetachedSpan_Glitch, id, spanS, test); |
+ } |
+ } |
+ do { // iterate through all spans associated with start |
+ const SkOpPtT* startBase = spanS->ptT(); |
+ next = test->final() ? nullptr : test->upCast()->next(); |
+ do { |
+ const SkOpPtT* testBase = test->ptT(); |
+ do { |
+ if (startBase == testBase) { |
+ goto checkNextSpan; |
+ } |
+ if (testBase->duplicate()) { |
+ continue; |
+ } |
+ if (this->match(startBase, testBase->segment(), testBase->fT, testBase->fPt)) { |
+ if (test == &this->fTail) { |
+ if (spanS == &fHead) { |
+ glitches->record(kCollapsedSpan_Glitch, id, spanS); |
+ } else { |
+ glitches->record(kUnmergedSpan_Glitch, id, &this->fTail, spanS); |
+ } |
+ } else { |
+ glitches->record(kUnmergedSpan_Glitch, id, spanS, test); |
+ goto checkNextSpan; |
+ } |
+ } |
+ } while ((testBase = testBase->next()) != test->ptT()); |
+ } while ((startBase = startBase->next()) != spanS->ptT()); |
+ checkNextSpan: |
+ ; |
+ } while ((test = next)); |
+ spanS = spanS->upCast()->next(); |
+ } while (!spanS->final()); |
+} |
+#endif |
+ |
void SkOpSegment::debugReset() { |
this->init(this->fPts, this->fWeight, this->contour(), this->verb()); |
} |
@@ -289,6 +951,51 @@ void SkOpSegment::debugShowNewWinding(const char* fun, const SkOpSpan* span, int |
#endif |
+// loop looking for a pair of angle parts that are too close to be sorted |
+/* This is called after other more simple intersection and angle sorting tests have been exhausted. |
+ This should be rarely called -- the test below is thorough and time consuming. |
+ This checks the distance between start points; the distance between |
+*/ |
+#if DEBUG_ANGLE |
+void SkOpAngle::debugCheckNearCoincidence() const { |
+ const SkOpAngle* test = this; |
+ do { |
+ const SkOpSegment* testSegment = test->segment(); |
+ double testStartT = test->start()->t(); |
+ SkDPoint testStartPt = testSegment->dPtAtT(testStartT); |
+ double testEndT = test->end()->t(); |
+ SkDPoint testEndPt = testSegment->dPtAtT(testEndT); |
+ double testLenSq = testStartPt.distanceSquared(testEndPt); |
+ SkDebugf("%s testLenSq=%1.9g id=%d\n", __FUNCTION__, testLenSq, testSegment->debugID()); |
+ double testMidT = (testStartT + testEndT) / 2; |
+ const SkOpAngle* next = test; |
+ while ((next = next->fNext) != this) { |
+ SkOpSegment* nextSegment = next->segment(); |
+ double testMidDistSq = testSegment->distSq(testMidT, next); |
+ double testEndDistSq = testSegment->distSq(testEndT, next); |
+ double nextStartT = next->start()->t(); |
+ SkDPoint nextStartPt = nextSegment->dPtAtT(nextStartT); |
+ double distSq = testStartPt.distanceSquared(nextStartPt); |
+ double nextEndT = next->end()->t(); |
+ double nextMidT = (nextStartT + nextEndT) / 2; |
+ double nextMidDistSq = nextSegment->distSq(nextMidT, test); |
+ double nextEndDistSq = nextSegment->distSq(nextEndT, test); |
+ SkDebugf("%s distSq=%1.9g testId=%d nextId=%d\n", __FUNCTION__, distSq, |
+ testSegment->debugID(), nextSegment->debugID()); |
+ SkDebugf("%s testMidDistSq=%1.9g\n", __FUNCTION__, testMidDistSq); |
+ SkDebugf("%s testEndDistSq=%1.9g\n", __FUNCTION__, testEndDistSq); |
+ SkDebugf("%s nextMidDistSq=%1.9g\n", __FUNCTION__, nextMidDistSq); |
+ SkDebugf("%s nextEndDistSq=%1.9g\n", __FUNCTION__, nextEndDistSq); |
+ SkDPoint nextEndPt = nextSegment->dPtAtT(nextEndT); |
+ double nextLenSq = nextStartPt.distanceSquared(nextEndPt); |
+ SkDebugf("%s nextLenSq=%1.9g\n", __FUNCTION__, nextLenSq); |
+ SkDebugf("\n"); |
+ } |
+ test = test->fNext; |
+ } while (test->fNext != this); |
+} |
+#endif |
+ |
#if DEBUG_ANGLE |
SkString SkOpAngle::debugPart() const { |
SkString result; |
@@ -397,6 +1104,337 @@ void SkOpAngle::debugValidateNext() const { |
#endif |
} |
+ |
+#if DEBUG_COINCIDENCE |
+void SkOpCoincidence::debugAddExpanded(const char* id, SkPathOpsDebug::GlitchLog* log) const { |
+ // for each coincident pair, match the spans |
+ // if the spans don't match, add the mssing pt to the segment and loop it in the opposite span |
+ const SkCoincidentSpans* coin = this->fHead; |
+ if (!coin) { |
+ coin = this->fTop; |
+ } |
+ SkASSERT(coin); |
+ do { |
+ const SkOpPtT* startPtT = coin->fCoinPtTStart; |
+ const SkOpPtT* oStartPtT = coin->fOppPtTStart; |
+ SkASSERT(startPtT->contains(oStartPtT)); |
+ SkASSERT(coin->fCoinPtTEnd->contains(coin->fOppPtTEnd)); |
+ const SkOpSpanBase* start = startPtT->span(); |
+ const SkOpSpanBase* oStart = oStartPtT->span(); |
+ const SkOpSpanBase* end = coin->fCoinPtTEnd->span(); |
+ const SkOpSpanBase* oEnd = coin->fOppPtTEnd->span(); |
+ const SkOpSpanBase* test = start->upCast()->next(); |
+ const SkOpSpanBase* oTest = coin->fFlipped ? oStart->prev() : oStart->upCast()->next(); |
+ while (test != end || oTest != oEnd) { |
+ bool bumpTest = true; |
+ bool bumpOTest = true; |
+ if (!test->ptT()->contains(oTest->ptT())) { |
+ // use t ranges to guess which one is missing |
+ double startRange = coin->fCoinPtTEnd->fT - startPtT->fT; |
+ double startPart = (test->t() - startPtT->fT) / startRange; |
+ double oStartRange = coin->fOppPtTEnd->fT - oStartPtT->fT; |
+ double oStartPart = (oTest->t() - oStartPtT->fT) / oStartRange; |
+ if (startPart == oStartPart) { |
+ // data is corrupt |
+ log->record(kAddCorruptCoin_Glitch, id, start, oStart); |
+ break; |
+ } |
+ if (startPart < oStartPart) { |
+ double newT = oStartPtT->fT + oStartRange * startPart; |
+ log->record(kAddExpandedCoin_Glitch, id, oStart, newT, test->pt()); |
+ bumpOTest = false; |
+ } else { |
+ double newT = startPtT->fT + startRange * oStartPart; |
+ log->record(kAddExpandedCoin_Glitch, id, start, newT, oTest->pt()); |
+ bumpTest = false; |
+ } |
+ } |
+ if (bumpTest && test != end) { |
+ test = test->upCast()->next(); |
+ } |
+ if (bumpOTest && oTest != oEnd) { |
+ oTest = coin->fFlipped ? oTest->prev() : oTest->upCast()->next(); |
+ } |
+ } |
+ } while ((coin = coin->fNext)); |
+} |
+ |
+static void t_range(const SkOpPtT* overS, const SkOpPtT* overE, double tStart, double tEnd, |
+ const SkOpPtT* coinPtTStart, const SkOpPtT* coinPtTEnd, double* coinTs, double* coinTe) { |
+ double denom = overE->fT - overS->fT; |
+ double start = 0 < denom ? tStart : tEnd; |
+ double end = 0 < denom ? tEnd : tStart; |
+ double sRatio = (start - overS->fT) / denom; |
+ double eRatio = (end - overS->fT) / denom; |
+ *coinTs = coinPtTStart->fT + (coinPtTEnd->fT - coinPtTStart->fT) * sRatio; |
+ *coinTe = coinPtTStart->fT + (coinPtTEnd->fT - coinPtTStart->fT) * eRatio; |
+} |
+ |
+bool SkOpCoincidence::debugAddIfMissing(const SkCoincidentSpans* outer, const SkOpPtT* over1s, |
+ const SkOpPtT* over1e) const { |
+ const SkCoincidentSpans* check = this->fTop; |
+ while (check) { |
+ if (check->fCoinPtTStart->span() == over1s->span() |
+ && check->fOppPtTStart->span() == outer->fOppPtTStart->span()) { |
+ SkASSERT(check->fCoinPtTEnd->span() == over1e->span() |
+ || !fDebugState->debugRunFail()); |
+ SkASSERT(check->fOppPtTEnd->span() == outer->fOppPtTEnd->span() |
+ || !fDebugState->debugRunFail()); |
+ return false; |
+ } |
+ if (check->fCoinPtTStart->span() == outer->fCoinPtTStart->span() |
+ && check->fOppPtTStart->span() == over1s->span()) { |
+ SkASSERT(check->fCoinPtTEnd->span() == outer->fCoinPtTEnd->span() |
+ || !fDebugState->debugRunFail()); |
+ SkASSERT(check->fOppPtTEnd->span() == over1e->span() |
+ || !fDebugState->debugRunFail()); |
+ return false; |
+ } |
+ check = check->fNext; |
+ } |
+ return true; |
+} |
+ |
+bool SkOpCoincidence::debugAddIfMissing(const SkOpPtT* over1s, const SkOpPtT* over1e, |
+ const SkOpPtT* over2s, const SkOpPtT* over2e, double tStart, double tEnd, |
+ SkOpPtT* coinPtTStart, const SkOpPtT* coinPtTEnd, |
+ SkOpPtT* oppPtTStart, const SkOpPtT* oppPtTEnd) const { |
+ double coinTs, coinTe, oppTs, oppTe; |
+ t_range(over1s, over1e, tStart, tEnd, coinPtTStart, coinPtTEnd, &coinTs, &coinTe); |
+ t_range(over2s, over2e, tStart, tEnd, oppPtTStart, oppPtTEnd, &oppTs, &oppTe); |
+ const SkOpSegment* coinSeg = coinPtTStart->segment(); |
+ const SkOpSegment* oppSeg = oppPtTStart->segment(); |
+ SkASSERT(coinSeg != oppSeg); |
+ const SkCoincidentSpans* check = this->fTop; |
+ ; |
+ while (check) { |
+ const SkOpSegment* checkCoinSeg = check->fCoinPtTStart->segment(); |
+ const SkOpSegment* checkOppSeg; |
+ if (checkCoinSeg != coinSeg && checkCoinSeg != oppSeg) { |
+ goto next; |
+ } |
+ checkOppSeg = check->fOppPtTStart->segment(); |
+ if (checkOppSeg != coinSeg && checkOppSeg != oppSeg) { |
+ goto next; |
+ } |
+ { |
+ int cTs = coinTs; |
+ int cTe = coinTe; |
+ int oTs = oppTs; |
+ int oTe = oppTe; |
+ if (checkCoinSeg != coinSeg) { |
+ SkASSERT(checkOppSeg != oppSeg); |
+ SkTSwap(cTs, oTs); |
+ SkTSwap(cTe, oTe); |
+ } |
+ int tweenCount = (int) between(check->fCoinPtTStart->fT, cTs, check->fCoinPtTEnd->fT) |
+ + (int) between(check->fCoinPtTStart->fT, cTe, check->fCoinPtTEnd->fT) |
+ + (int) between(check->fOppPtTStart->fT, oTs, check->fOppPtTEnd->fT) |
+ + (int) between(check->fOppPtTStart->fT, oTe, check->fOppPtTEnd->fT); |
+ // SkASSERT(tweenCount == 0 || tweenCount == 4); |
+ if (tweenCount) { |
+ return true; |
+ } |
+ } |
+next: |
+ check = check->fNext; |
+ } |
+ if ((over1s->fT < over1e->fT) != (over2s->fT < over2e->fT)) { |
+ SkTSwap(oppTs, oppTe); |
+ } |
+ if (coinTs > coinTe) { |
+ SkTSwap(coinTs, coinTe); |
+ SkTSwap(oppTs, oppTe); |
+ } |
+ bool cs = coinSeg->debugAddMissing(coinTs, oppSeg); |
+ bool ce = coinSeg->debugAddMissing(coinTe, oppSeg); |
+ if (cs == ce) { |
+ return false; |
+ } |
+ return true; |
+} |
+ |
+void SkOpCoincidence::debugAddMissing(const char* id, SkPathOpsDebug::GlitchLog* log) const { |
+ const SkCoincidentSpans* outer = fHead; |
+ if (!outer) { |
+ return; |
+ } |
+ do { |
+ // addifmissing can modify the list that this is walking |
+ // save head so that walker can iterate over old data unperturbed |
+ // addifmissing adds to head freely then add saved head in the end |
+ const SkOpSegment* outerCoin = outer->fCoinPtTStart->segment(); |
+ SkASSERT(outerCoin == outer->fCoinPtTEnd->segment()); |
+ const SkOpSegment* outerOpp = outer->fOppPtTStart->segment(); |
+ SkASSERT(outerOpp == outer->fOppPtTEnd->segment()); |
+ const SkCoincidentSpans* inner = outer; |
+ while ((inner = inner->fNext)) { |
+ double overS, overE; |
+ const SkOpSegment* innerCoin = inner->fCoinPtTStart->segment(); |
+ SkASSERT(innerCoin == inner->fCoinPtTEnd->segment()); |
+ const SkOpSegment* innerOpp = inner->fOppPtTStart->segment(); |
+ SkASSERT(innerOpp == inner->fOppPtTEnd->segment()); |
+ if (outerCoin == innerCoin |
+ && this->overlap(outer->fCoinPtTStart, outer->fCoinPtTEnd, |
+ inner->fCoinPtTStart, inner->fCoinPtTEnd, &overS, &overE)) { |
+ if (this->debugAddIfMissing(outer->fCoinPtTStart, outer->fCoinPtTEnd, |
+ inner->fCoinPtTStart, inner->fCoinPtTEnd, overS, overE, |
+ outer->fOppPtTStart, outer->fOppPtTEnd, |
+ inner->fOppPtTStart, inner->fOppPtTEnd)) { |
+ log->record(kAddMissingCoin_Glitch, id, outer, inner->fCoinPtTStart); |
+ } |
+ } else if (outerCoin == innerOpp |
+ && this->overlap(outer->fCoinPtTStart, outer->fCoinPtTEnd, |
+ inner->fOppPtTStart, inner->fOppPtTEnd, &overS, &overE)) { |
+ if (this->debugAddIfMissing(outer->fCoinPtTStart, outer->fCoinPtTEnd, |
+ inner->fOppPtTStart, inner->fOppPtTEnd, overS, overE, |
+ outer->fOppPtTStart, outer->fOppPtTEnd, |
+ inner->fCoinPtTStart, inner->fCoinPtTEnd)) { |
+ log->record(kAddMissingCoin_Glitch, id, outer, inner->fOppPtTStart); |
+ } |
+ } else if (outerOpp == innerCoin |
+ && this->overlap(outer->fOppPtTStart, outer->fOppPtTEnd, |
+ inner->fCoinPtTStart, inner->fCoinPtTEnd, &overS, &overE)) { |
+ if (this->debugAddIfMissing(outer->fOppPtTStart, outer->fOppPtTEnd, |
+ inner->fCoinPtTStart, inner->fCoinPtTEnd, overS, overE, |
+ outer->fCoinPtTStart, outer->fCoinPtTEnd, |
+ inner->fOppPtTStart, inner->fOppPtTEnd)) { |
+ log->record(kAddMissingCoin_Glitch, id, outer, inner->fCoinPtTStart); |
+ } |
+ } else if (outerOpp == innerOpp |
+ && this->overlap(outer->fOppPtTStart, outer->fOppPtTEnd, |
+ inner->fOppPtTStart, inner->fOppPtTEnd, &overS, &overE)) { |
+ if (this->debugAddIfMissing(outer->fOppPtTStart, outer->fOppPtTEnd, |
+ inner->fOppPtTStart, inner->fOppPtTEnd, overS, overE, |
+ outer->fCoinPtTStart, outer->fCoinPtTEnd, |
+ inner->fCoinPtTStart, inner->fCoinPtTEnd)) { |
+ log->record(kAddMissingCoin_Glitch, id, outer, inner->fOppPtTStart); |
+ } |
+ } else if (outerCoin != innerCoin) { |
+ // check to see if outer span overlaps the inner span |
+ // look for inner segment in pt-t list |
+ // if present, and if t values are in coincident range |
+ // add two pairs of new coincidence |
+ const SkOpPtT* testS = outer->fCoinPtTStart->debugContains(innerCoin); |
+ const SkOpPtT* testE = outer->fCoinPtTEnd->debugContains(innerCoin); |
+ if (testS && testS->fT >= inner->fCoinPtTStart->fT |
+ && testE && testE->fT <= inner->fCoinPtTEnd->fT |
+ && this->testForCoincidence(outer, testS, testE)) { |
+ if (this->debugAddIfMissing(outer, testS, testE)) { |
+ log->record(kAddMissingCoin_Glitch, id, outer, testS, testE); |
+ } |
+ } else { |
+ testS = inner->fCoinPtTStart->debugContains(outerCoin); |
+ testE = inner->fCoinPtTEnd->debugContains(outerCoin); |
+ if (testS && testS->fT >= outer->fCoinPtTStart->fT |
+ && testE && testE->fT <= outer->fCoinPtTEnd->fT |
+ && this->testForCoincidence(inner, testS, testE)) { |
+ if (this->debugAddIfMissing(inner, testS, testE)) { |
+ log->record(kAddMissingCoin_Glitch, id, inner, testS, testE); |
+ } |
+ } |
+ } |
+ } |
+ } |
+ } while ((outer = outer->fNext)); |
+} |
+ |
+bool SkOpCoincidence::debugExpand(const char* id, SkPathOpsDebug::GlitchLog* log) const { |
+ const SkCoincidentSpans* coin = fHead; |
+ if (!coin) { |
+ return false; |
+ } |
+ bool expanded = false; |
+ do { |
+ const SkOpSpan* start = coin->fCoinPtTStart->span()->upCast(); |
+ const SkOpSpanBase* end = coin->fCoinPtTEnd->span(); |
+ const SkOpSegment* segment = coin->fCoinPtTStart->segment(); |
+ const SkOpSegment* oppSegment = coin->fOppPtTStart->segment(); |
+ const SkOpSpan* prev = start->prev(); |
+ if (prev && prev->debugContains(oppSegment)) { |
+ double midT = (prev->t() + start->t()) / 2; |
+ if (segment->isClose(midT, oppSegment)) { |
+ log->record(kExpandCoin_Glitch, id, coin, prev); |
+ } |
+ } |
+ SkOpSpanBase* next = end->final() ? nullptr : end->upCast()->next(); |
+ if (next && next->debugContains(oppSegment)) { |
+ double midT = (end->t() + next->t()) / 2; |
+ if (segment->isClose(midT, oppSegment)) { |
+ log->record(kExpandCoin_Glitch, id, coin, next); |
+ } |
+ } |
+ } while ((coin = coin->fNext)); |
+ return expanded; |
+} |
+ |
+void SkOpCoincidence::debugFixAligned(const char* id, SkPathOpsDebug::GlitchLog* log) const { |
+ const SkCoincidentSpans* coin = fHead; |
+ if (!coin) { |
+ return; |
+ } |
+ do { |
+ if (coin->fCoinPtTStart->deleted()) { |
+ log->record(kDeletedCoin_Glitch, id, coin, coin->fCoinPtTStart); |
+ } |
+ if (coin->fCoinPtTEnd->deleted()) { |
+ log->record(kDeletedCoin_Glitch, id, coin, coin->fCoinPtTEnd); |
+ } |
+ if (coin->fOppPtTStart->deleted()) { |
+ log->record(kDeletedCoin_Glitch, id, coin, coin->fOppPtTStart); |
+ } |
+ if (coin->fOppPtTEnd->deleted()) { |
+ log->record(kDeletedCoin_Glitch, id, coin, coin->fOppPtTEnd); |
+ } |
+ } while ((coin = coin->fNext)); |
+ coin = fHead; |
+ do { |
+ if (coin->fCoinPtTStart->collapsed(coin->fCoinPtTEnd)) { |
+ log->record(kCollapsedCoin_Glitch, id, coin, coin->fCoinPtTStart); |
+ } |
+ if (coin->fOppPtTStart->collapsed(coin->fOppPtTEnd)) { |
+ log->record(kCollapsedCoin_Glitch, id, coin, coin->fOppPtTStart); |
+ } |
+ } while ((coin = coin->fNext)); |
+} |
+ |
+void SkOpCoincidence::debugMark(const char* id, SkPathOpsDebug::GlitchLog* log) const { |
+ const SkCoincidentSpans* coin = fHead; |
+ if (!coin) { |
+ return; |
+ } |
+ do { |
+ const SkOpSpanBase* end = coin->fCoinPtTEnd->span(); |
+ const SkOpSpanBase* oldEnd = end; |
+ const SkOpSpan* start = coin->fCoinPtTStart->span()->debugStarter(&end); |
+ const SkOpSpanBase* oEnd = coin->fOppPtTEnd->span(); |
+ const SkOpSpanBase* oOldEnd = oEnd; |
+ const SkOpSpanBase* oStart = coin->fOppPtTStart->span()->debugStarter(&oEnd); |
+ bool flipped = (end == oldEnd) != (oEnd == oOldEnd); |
+ if (flipped) { |
+ SkTSwap(oStart, oEnd); |
+ } |
+ const SkOpSpanBase* next = start; |
+ const SkOpSpanBase* oNext = oStart; |
+ do { |
+ next = next->upCast()->next(); |
+ oNext = flipped ? oNext->prev() : oNext->upCast()->next(); |
+ if (next == end || oNext == oEnd) { |
+ break; |
+ } |
+ if (!next->containsCoinEnd(oNext)) { |
+ log->record(kMarkCoinEnd_Glitch, id, next, oNext); |
+ } |
+ const SkOpSpan* nextSpan = next->upCast(); |
+ const SkOpSpan* oNextSpan = oNext->upCast(); |
+ if (!nextSpan->containsCoincidence(oNextSpan)) { |
+ log->record(kMarkCoinInsert_Glitch, id, nextSpan, oNextSpan); |
+ } |
+ } while (true); |
+ } while ((coin = coin->fNext)); |
+} |
+#endif |
+ |
void SkOpCoincidence::debugShowCoincidence() const { |
SkCoincidentSpans* span = fHead; |
while (span) { |
@@ -410,6 +1448,23 @@ void SkOpCoincidence::debugShowCoincidence() const { |
} |
} |
+#if DEBUG_COINCIDENCE |
+void SkOpContour::debugCheckHealth(const char* id, SkPathOpsDebug::GlitchLog* log) const { |
+ const SkOpSegment* segment = &fHead; |
+ do { |
+ segment->debugCheckHealth(id, log); |
+ } while ((segment = segment->next())); |
+} |
+ |
+void SkOpContour::debugMissingCoincidence(const char* id, SkPathOpsDebug::GlitchLog* log, |
+ const SkOpCoincidence* coincidence) const { |
+ const SkOpSegment* segment = &fHead; |
+ do { |
+ segment->debugMissingCoincidence(id, log, coincidence); |
+ } while ((segment = segment->next())); |
+} |
+#endif |
+ |
void SkOpSegment::debugValidate() const { |
#if DEBUG_VALIDATE |
const SkOpSpanBase* span = &fHead; |
@@ -439,6 +1494,57 @@ void SkOpSegment::debugValidate() const { |
#endif |
} |
+bool SkOpSpanBase::debugAlignedEnd(double t, const SkPoint& pt) const { |
+ SkASSERT(zero_or_one(t)); |
+ const SkOpSegment* segment = this->segment(); |
+ SkASSERT(t ? segment->lastPt() == pt : segment->pts()[0] == pt); |
+ if (!debugAlignedInner()) { |
+ return false; |
+ } |
+ if ((t ? segment->lastPt() : segment->pts()[0]) != pt) { |
+ return false; |
+ } |
+ const SkOpPtT* ptT = &this->fPtT; |
+ SkASSERT(t == ptT->fT); |
+ SkASSERT(pt == ptT->fPt); |
+ const SkOpPtT* test = ptT, * stopPtT = ptT; |
+ while ((test = test->next()) != stopPtT) { |
+ const SkOpSegment* other = test->segment(); |
+ if (other == this->segment()) { |
+ continue; |
+ } |
+ if (!zero_or_one(test->fT)) { |
+ continue; |
+ } |
+ if ((test->fT ? other->lastPt() : other->pts()[0]) != pt) { |
+ return false; |
+ } |
+ } |
+ return this->fAligned; |
+} |
+ |
+bool SkOpSpanBase::debugAlignedInner() const { |
+ // force the spans to share points and t values |
+ const SkOpPtT* ptT = &this->fPtT, * stopPtT = ptT; |
+ const SkPoint& pt = ptT->fPt; |
+ do { |
+ if (ptT->fPt != pt) { |
+ return false; |
+ } |
+ const SkOpSpanBase* span = ptT->span(); |
+ const SkOpPtT* test = ptT; |
+ do { |
+ if ((test = test->next()) == stopPtT) { |
+ break; |
+ } |
+ if (span == test->span() && !span->segment()->ptsDisjoint(*ptT, *test)) { |
+ return false; |
+ } |
+ } while (true); |
+ } while ((ptT = ptT->next()) != stopPtT); |
+ return true; |
+} |
+ |
bool SkOpSpanBase::debugCoinEndLoopCheck() const { |
int loop = 0; |
const SkOpSpanBase* next = this; |
@@ -462,6 +1568,30 @@ bool SkOpSpanBase::debugCoinEndLoopCheck() const { |
return true; |
} |
+bool SkOpSpanBase::debugContains(const SkOpSegment* segment) const { |
+ const SkOpPtT* start = &fPtT; |
+ const SkOpPtT* walk = start; |
+ while ((walk = walk->next()) != start) { |
+ if (walk->segment() == segment) { |
+ return true; |
+ } |
+ } |
+ return false; |
+} |
+ |
+const SkOpSpan* SkOpSpanBase::debugStarter(SkOpSpanBase const** endPtr) const { |
+ const SkOpSpanBase* end = *endPtr; |
+ SkASSERT(this->segment() == end->segment()); |
+ const SkOpSpanBase* result; |
+ if (t() < end->t()) { |
+ result = this; |
+ } else { |
+ result = end; |
+ *endPtr = this; |
+ } |
+ return result->upCast(); |
+} |
+ |
void SkOpSpanBase::debugValidate() const { |
#if DEBUG_VALIDATE |
const SkOpPtT* ptT = &fPtT; |
@@ -531,6 +1661,46 @@ int SkIntersections::debugCoincidentUsed() const { |
#include "SkOpContour.h" |
+bool SkOpPtT::debugContains(const SkOpPtT* check) const { |
+ SkASSERT(this != check); |
+ const SkOpPtT* ptT = this; |
+ int links = 0; |
+ do { |
+ ptT = ptT->next(); |
+ if (ptT == check) { |
+ return true; |
+ } |
+ ++links; |
+ const SkOpPtT* test = this; |
+ for (int index = 0; index < links; ++index) { |
+ if (ptT == test) { |
+ return false; |
+ } |
+ test = test->next(); |
+ } |
+ } while (true); |
+} |
+ |
+const SkOpPtT* SkOpPtT::debugContains(const SkOpSegment* check) const { |
+ SkASSERT(this->segment() != check); |
+ const SkOpPtT* ptT = this; |
+ int links = 0; |
+ do { |
+ ptT = ptT->next(); |
+ if (ptT->segment() == check) { |
+ return ptT; |
+ } |
+ ++links; |
+ const SkOpPtT* test = this; |
+ for (int index = 0; index < links; ++index) { |
+ if (ptT == test) { |
+ return nullptr; |
+ } |
+ test = test->next(); |
+ } |
+ } while (true); |
+} |
+ |
int SkOpPtT::debugLoopLimit(bool report) const { |
int loop = 0; |
const SkOpPtT* next = this; |
@@ -548,7 +1718,13 @@ int SkOpPtT::debugLoopLimit(bool report) const { |
} |
} |
} |
- ++loop; |
+ // there's nothing wrong with extremely large loop counts -- but this may appear to hang |
+ // by taking a very long time to figure out that no loop entry is a duplicate |
+ // -- and it's likely that a large loop count is indicative of a bug somewhere |
+ if (++loop > 1000) { |
+ SkDebugf("*** loop count exceeds 1000 ***\n"); |
+ return 1000; |
+ } |
} while ((next = next->fNext) && next != this); |
return 0; |
} |