From a6f11c4f717961070cd6fc5e60c361db14c5c4f3 Mon Sep 17 00:00:00 2001 From: "robertphillips@google.com" Date: Mon, 23 Jul 2012 17:39:44 +0000 Subject: [PATCH] Gave GrClip an SkClipStack-style iterator http://codereview.appspot.com/6434046/ git-svn-id: http://skia.googlecode.com/svn/trunk@4723 2bbb7eff-a529-9590-31e7-b0007b416f81 --- include/gpu/GrClip.h | 63 ++++++++++++ src/core/SkClipStack.cpp | 1 + src/gpu/GrClip.cpp | 92 +++++++++++++++++ src/gpu/GrClipMaskManager.cpp | 217 ++++++++++++++++++++++++---------------- src/gpu/GrClipMaskManager.h | 3 +- src/gpu/GrInOrderDrawBuffer.cpp | 6 +- 6 files changed, 291 insertions(+), 91 deletions(-) diff --git a/include/gpu/GrClip.h b/include/gpu/GrClip.h index 4fd8ddd..c802015 100644 --- a/include/gpu/GrClip.h +++ b/include/gpu/GrClip.h @@ -40,6 +40,68 @@ public: bool requiresAA() const { return fRequiresAA; } + class Iter { + public: + enum IterStart { + kBottom_IterStart, + kTop_IterStart + }; + + /** + * Creates an uninitialized iterator. Must be reset() + */ + Iter(); + + Iter(const GrClip& stack, IterStart startLoc); + + struct Clip { + Clip() : fRect(NULL), fPath(NULL), fOp(SkRegion::kIntersect_Op), + fDoAA(false) {} + + const SkRect* fRect; // if non-null, this is a rect clip + const SkPath* fPath; // if non-null, this is a path clip + SkRegion::Op fOp; + bool fDoAA; + }; + + /** + * Return the clip for this element in the iterator. If next() returns + * NULL, then the iterator is done. The type of clip is determined by + * the pointers fRect and fPath: + * + * fRect==NULL fPath!=NULL path clip + * fRect!=NULL fPath==NULL rect clip + * fRect==NULL fPath==NULL empty clip + */ + const Clip* next(); + const Clip* prev(); + + /** + * Moves the iterator to the topmost clip with the specified RegionOp + * and returns that clip. If no clip with that op is found, + * returns NULL. + */ + const Clip* skipToTopmost(SkRegion::Op op); + + /** + * Restarts the iterator on a clip stack. + */ + void reset(const GrClip& stack, IterStart startLoc); + + private: + const GrClip* fStack; + Clip fClip; + int fCurIndex; + + /** + * updateClip updates fClip to represent the clip in the index slot of + * GrClip's list. * It unifies functionality needed by both next() and + * prev(). + */ + const Clip* updateClip(int index); + }; + +private: int getElementCount() const { return fList.count(); } GrClipType getElementType(int i) const { return fList[i].fType; } @@ -63,6 +125,7 @@ public: bool getDoAA(int i) const { return fList[i].fDoAA; } +public: bool isRect() const { if (1 == fList.count() && kRect_ClipType == fList[0].fType && (SkRegion::kIntersect_Op == fList[0].fOp || diff --git a/src/core/SkClipStack.cpp b/src/core/SkClipStack.cpp index 429d4c0..4212ba5 100644 --- a/src/core/SkClipStack.cpp +++ b/src/core/SkClipStack.cpp @@ -303,5 +303,6 @@ const SkClipStack::Iter::Clip* SkClipStack::Iter::skipToTopmost(SkRegion::Op op) } void SkClipStack::Iter::reset(const SkClipStack& stack, IterStart startLoc) { + fStack = &stack; fIter.reset(stack.fDeque, static_cast(startLoc)); } diff --git a/src/gpu/GrClip.cpp b/src/gpu/GrClip.cpp index 7803edc..84f58c6 100644 --- a/src/gpu/GrClip.cpp +++ b/src/gpu/GrClip.cpp @@ -153,3 +153,95 @@ void GrClip::setFromIterator(GrClipIterator* iter, GrScalar tx, GrScalar ty, fConservativeBoundsValid = true; } } + +/////////////////////////////////////////////////////////////////////////////// + +GrClip::Iter::Iter() + : fStack(NULL) + , fCurIndex(0) { +} + +GrClip::Iter::Iter(const GrClip& stack, IterStart startLoc) + : fStack(&stack) { + this->reset(stack, startLoc); +} + +const GrClip::Iter::Clip* GrClip::Iter::updateClip(int index) { + + if (NULL == fStack) { + return NULL; + } + + GrAssert(0 <= index && index < fStack->getElementCount()); + + + + switch (fStack->getElementType(index)) { + case kRect_ClipType: + fClip.fRect = &fStack->getRect(index); + fClip.fPath = NULL; + break; + case kPath_ClipType: + fClip.fRect = NULL; + fClip.fPath = &fStack->getPath(index); + break; + } + fClip.fOp = fStack->getOp(index); + fClip.fDoAA = fStack->getDoAA(index); + return &fClip; +} + +const GrClip::Iter::Clip* GrClip::Iter::next() { + + if (NULL == fStack) { + return NULL; + } + + if (0 > fCurIndex || fCurIndex >= fStack->getElementCount()) { + return NULL; + } + + int oldIndex = fCurIndex; + ++fCurIndex; + + return this->updateClip(oldIndex); +} + +const GrClip::Iter::Clip* GrClip::Iter::prev() { + + if (NULL == fStack) { + return NULL; + } + + if (0 > fCurIndex || fCurIndex >= fStack->getElementCount()) { + return NULL; + } + + int oldIndex = fCurIndex; + --fCurIndex; + + return this->updateClip(oldIndex); +} + +const GrClip::Iter::Clip* GrClip::Iter::skipToTopmost(SkRegion::Op op) { + + GrAssert(SkRegion::kReplace_Op == op); + + if (NULL == fStack) { + return NULL; + } + + // GrClip removes all clips below the topmost replace + this->reset(*fStack, kBottom_IterStart); + + return this->next(); +} + +void GrClip::Iter::reset(const GrClip& stack, IterStart startLoc) { + fStack = &stack; + if (kBottom_IterStart == startLoc) { + fCurIndex = 0; + } else { + fCurIndex = fStack->getElementCount()-1; + } +} diff --git a/src/gpu/GrClipMaskManager.cpp b/src/gpu/GrClipMaskManager.cpp index f313d68..ceba429 100644 --- a/src/gpu/GrClipMaskManager.cpp +++ b/src/gpu/GrClipMaskManager.cpp @@ -53,6 +53,22 @@ bool path_needs_SW_renderer(GrContext* context, return NULL == context->getPathRenderer(path, fill, gpu, doAA, false); } +GrPathFill get_path_fill(const SkPath& path) { + switch (path.getFillType()) { + case SkPath::kWinding_FillType: + return kWinding_GrPathFill; + case SkPath::kEvenOdd_FillType: + return kEvenOdd_GrPathFill; + case SkPath::kInverseWinding_FillType: + return kInverseWinding_GrPathFill; + case SkPath::kInverseEvenOdd_FillType: + return kInverseEvenOdd_GrPathFill; + default: + GrCrash("Unsupported path fill in clip."); + return kWinding_GrPathFill; // suppress warning + } +} + } /* @@ -72,9 +88,14 @@ bool GrClipMaskManager::useSWOnlyPath(const GrClip& clipIn) { // of whether it would invoke the GrSoftwarePathRenderer. bool useSW = false; - for (int i = 0; i < clipIn.getElementCount(); ++i) { + GrClip::Iter iter(clipIn, GrClip::Iter::kBottom_IterStart); + const GrClip::Iter::Clip* clip = NULL; - if (SkRegion::kReplace_Op == clipIn.getOp(i)) { + for (clip = iter.skipToTopmost(SkRegion::kReplace_Op); + NULL != clip; + clip = iter.next()) { + + if (SkRegion::kReplace_Op == clip->fOp) { // Everything before a replace op can be ignored so start // afresh w.r.t. determining if any element uses the SW path useSW = false; @@ -82,11 +103,11 @@ bool GrClipMaskManager::useSWOnlyPath(const GrClip& clipIn) { // rects can always be drawn directly w/o using the software path // so only paths need to be checked - if (kPath_ClipType == clipIn.getElementType(i) && + if (NULL != clip->fPath && path_needs_SW_renderer(this->getContext(), fGpu, - clipIn.getPath(i), - clipIn.getPathFill(i), - clipIn.getDoAA(i))) { + *clip->fPath, + get_path_fill(*clip->fPath), + clip->fDoAA)) { useSW = true; } } @@ -228,41 +249,45 @@ bool contains(const SkRect& container, const SkIRect& containee) { // determines how many elements at the head of the clip can be skipped and // whether the initial clear should be to the inside- or outside-the-clip value, // and what op should be used to draw the first element that isn't skipped. -int process_initial_clip_elements(const GrClip& clip, +const GrClip::Iter::Clip* process_initial_clip_elements( + GrClip::Iter* iter, const GrIRect& bounds, bool* clearToInside, - SkRegion::Op* startOp) { + SkRegion::Op* firstOp) { + + GrAssert(NULL != iter && NULL != clearToInside && NULL != firstOp); // logically before the first element of the clip stack is // processed the clip is entirely open. However, depending on the // first set op we may prefer to clear to 0 for performance. We may // also be able to skip the initial clip paths/rects. We loop until // we cannot skip an element. - int curr; bool done = false; *clearToInside = true; - int count = clip.getElementCount(); - for (curr = 0; curr < count && !done; ++curr) { - switch (clip.getOp(curr)) { + const GrClip::Iter::Clip* clip = NULL; + + for (clip = iter->skipToTopmost(SkRegion::kReplace_Op); + NULL != clip && !done; + clip = iter->next()) { + switch (clip->fOp) { case SkRegion::kReplace_Op: // replace ignores everything previous - *startOp = SkRegion::kReplace_Op; + *firstOp = SkRegion::kReplace_Op; *clearToInside = false; done = true; break; case SkRegion::kIntersect_Op: // if this element contains the entire bounds then we // can skip it. - if (kRect_ClipType == clip.getElementType(curr) - && contains(clip.getRect(curr), bounds)) { + if (NULL != clip->fRect && contains(*clip->fRect, bounds)) { break; } // if everything is initially clearToInside then intersect is // same as clear to 0 and treat as a replace. Otherwise, // set stays empty. if (*clearToInside) { - *startOp = SkRegion::kReplace_Op; + *firstOp = SkRegion::kReplace_Op; *clearToInside = false; done = true; } @@ -273,7 +298,7 @@ int process_initial_clip_elements(const GrClip& clip, // same as replace. Otherwise, every pixel is still // clearToInside if (!*clearToInside) { - *startOp = SkRegion::kReplace_Op; + *firstOp = SkRegion::kReplace_Op; done = true; } break; @@ -281,9 +306,9 @@ int process_initial_clip_elements(const GrClip& clip, // xor is same as difference or replace both of which // can be 1-pass instead of 2 for xor. if (*clearToInside) { - *startOp = SkRegion::kDifference_Op; + *firstOp = SkRegion::kDifference_Op; } else { - *startOp = SkRegion::kReplace_Op; + *firstOp = SkRegion::kReplace_Op; } done = true; break; @@ -292,7 +317,7 @@ int process_initial_clip_elements(const GrClip& clip, // difference, otherwise it has no effect and all pixels // remain outside. if (*clearToInside) { - *startOp = SkRegion::kDifference_Op; + *firstOp = SkRegion::kDifference_Op; done = true; } break; @@ -302,15 +327,21 @@ int process_initial_clip_elements(const GrClip& clip, if (*clearToInside) { *clearToInside = false; } else { - *startOp = SkRegion::kReplace_Op; + *firstOp = SkRegion::kReplace_Op; done = true; } break; default: GrCrash("Unknown set op."); } + + if (done) { + // we need to break out here (rather than letting the test in + // the loop do it) since backing up the iterator is very expensive + break; + } } - return done ? curr-1 : count; + return clip; } } @@ -395,27 +426,26 @@ bool draw_path(GrContext* context, //////////////////////////////////////////////////////////////////////////////// bool GrClipMaskManager::drawClipShape(GrTexture* target, - const GrClip& clipIn, - int index, + const GrClip::Iter::Clip* clip, const GrIRect& resultBounds) { GrDrawState* drawState = fGpu->drawState(); GrAssert(NULL != drawState); drawState->setRenderTarget(target->asRenderTarget()); - if (kRect_ClipType == clipIn.getElementType(index)) { - if (clipIn.getDoAA(index)) { + if (NULL != clip->fRect) { + if (clip->fDoAA) { getContext()->getAARectRenderer()->fillAARect(fGpu, fGpu, - clipIn.getRect(index), + *clip->fRect, true); } else { - fGpu->drawSimpleRect(clipIn.getRect(index), NULL); + fGpu->drawSimpleRect(*clip->fRect, NULL); } - } else { + } else if (NULL != clip->fPath) { return draw_path(this->getContext(), fGpu, - clipIn.getPath(index), - clipIn.getPathFill(index), - clipIn.getDoAA(index), + *clip->fPath, + get_path_fill(*clip->fPath), + clip->fDoAA, resultBounds); } return true; @@ -553,8 +583,6 @@ bool GrClipMaskManager::createAlphaClipMask(const GrClip& clipIn, GrDrawTarget::AutoGeometryPush agp(fGpu); - int count = clipIn.getElementCount(); - if (0 != resultBounds->fTop || 0 != resultBounds->fLeft) { // if we were able to trim down the size of the mask we need to // offset the paths & rects that will be used to compute it @@ -567,22 +595,28 @@ bool GrClipMaskManager::createAlphaClipMask(const GrClip& clipIn, } bool clearToInside; - SkRegion::Op startOp = SkRegion::kReplace_Op; // suppress warning - int start = process_initial_clip_elements(clipIn, - *resultBounds, - &clearToInside, - &startOp); + SkRegion::Op firstOp = SkRegion::kReplace_Op; // suppress warning + + GrClip::Iter iter(clipIn, GrClip::Iter::kBottom_IterStart); + const GrClip::Iter::Clip* clip = process_initial_clip_elements(&iter, + *resultBounds, + &clearToInside, + &firstOp); fGpu->clear(NULL, clearToInside ? 0xffffffff : 0x00000000, accum->asRenderTarget()); GrAutoScratchTexture temp; - + bool first = true; // walk through each clip element and perform its set op - for (int c = start; c < count; ++c) { + for ( ; NULL != clip; clip = iter.next()) { - SkRegion::Op op = (c == start) ? startOp : clipIn.getOp(c); + SkRegion::Op op = clip->fOp; + if (first) { + first = false; + op = firstOp; + } if (SkRegion::kReplace_Op == op) { // TODO: replace is actually a lot faster then intersection @@ -593,14 +627,14 @@ bool GrClipMaskManager::createAlphaClipMask(const GrClip& clipIn, fGpu->clear(NULL, 0x00000000, accum->asRenderTarget()); setup_boolean_blendcoeffs(drawState, op); - this->drawClipShape(accum, clipIn, c, *resultBounds); + this->drawClipShape(accum, clip, *resultBounds); } else if (SkRegion::kReverseDifference_Op == op || SkRegion::kIntersect_Op == op) { // there is no point in intersecting a screen filling rectangle. if (SkRegion::kIntersect_Op == op && - kRect_ClipType == clipIn.getElementType(c) && - contains(clipIn.getRect(c), *resultBounds)) { + NULL != clip->fRect && + contains(*clip->fRect, *resultBounds)) { continue; } @@ -614,7 +648,7 @@ bool GrClipMaskManager::createAlphaClipMask(const GrClip& clipIn, fGpu->clear(NULL, 0x00000000, temp.texture()->asRenderTarget()); setup_boolean_blendcoeffs(drawState, SkRegion::kReplace_Op); - this->drawClipShape(temp.texture(), clipIn, c, *resultBounds); + this->drawClipShape(temp.texture(), clip, *resultBounds); // TODO: rather than adding these two translations here // compute the bounding box needed to render the texture @@ -646,7 +680,7 @@ bool GrClipMaskManager::createAlphaClipMask(const GrClip& clipIn, // all the remaining ops can just be directly draw into // the accumulation buffer setup_boolean_blendcoeffs(drawState, op); - this->drawClipShape(accum, clipIn, c, *resultBounds); + this->drawClipShape(accum, clip, *resultBounds); } } @@ -694,7 +728,6 @@ bool GrClipMaskManager::createStencilClipMask(const GrClip& clipIn, drawState->enableState(GrDrawState::kNoColorWrites_StateBit); #endif - int count = clipCopy.getElementCount(); int clipBit = stencilBuffer->bits(); SkASSERT((clipBit <= 16) && "Ganesh only handles 16b or smaller stencil buffers"); @@ -703,24 +736,27 @@ bool GrClipMaskManager::createStencilClipMask(const GrClip& clipIn, GrIRect rtRect = GrIRect::MakeWH(rt->width(), rt->height()); bool clearToInside; - SkRegion::Op startOp = SkRegion::kReplace_Op; // suppress warning - int start = process_initial_clip_elements(clipCopy, + SkRegion::Op firstOp = SkRegion::kReplace_Op; // suppress warning + + GrClip::Iter iter(clipCopy, GrClip::Iter::kBottom_IterStart); + const GrClip::Iter::Clip* clip = process_initial_clip_elements(&iter, rtRect, &clearToInside, - &startOp); + &firstOp); fGpu->clearStencilClip(bounds, clearToInside); + bool first = true; // walk through each clip element and perform its set op // with the existing clip. - for (int c = start; c < count; ++c) { + for ( ; NULL != clip; clip = iter.next()) { GrPathFill fill; bool fillInverted; // enabled at bottom of loop drawState->disableState(GrGpu::kModifyStencilClip_StateBit); // if the target is MSAA then we want MSAA enabled when the clip is soft if (rt->isMultisampled()) { - if (clipCopy.getDoAA(c)) { + if (clip->fDoAA) { drawState->enableState(GrDrawState::kHWAntialias_StateBit); } else { drawState->disableState(GrDrawState::kHWAntialias_StateBit); @@ -733,25 +769,29 @@ bool GrClipMaskManager::createStencilClipMask(const GrClip& clipIn, // without extra passes to // resolve in/out status. - SkRegion::Op op = (c == start) ? startOp : clipCopy.getOp(c); + SkRegion::Op op = clip->fOp; + if (first) { + first = false; + op = firstOp; + } GrPathRenderer* pr = NULL; const SkPath* clipPath = NULL; - if (kRect_ClipType == clipCopy.getElementType(c)) { + if (NULL != clip->fRect) { canRenderDirectToStencil = true; fill = kEvenOdd_GrPathFill; fillInverted = false; // there is no point in intersecting a screen filling // rectangle. if (SkRegion::kIntersect_Op == op && - contains(clipCopy.getRect(c), rtRect)) { + contains(*clip->fRect, rtRect)) { continue; } - } else { - fill = clipCopy.getPathFill(c); + } else if (NULL != clip->fPath) { + fill = get_path_fill(*clip->fPath); fillInverted = GrIsFillInverted(fill); fill = GrNonInvertedFill(fill); - clipPath = &clipCopy.getPath(c); + clipPath = clip->fPath; pr = this->getContext()->getPathRenderer(*clipPath, fill, fGpu, false, true); @@ -787,9 +827,9 @@ bool GrClipMaskManager::createStencilClipMask(const GrClip& clipIn, 0x0000, 0xffff); SET_RANDOM_COLOR - if (kRect_ClipType == clipCopy.getElementType(c)) { + if (NULL != clip->fRect) { *drawState->stencil() = gDrawToStencil; - fGpu->drawSimpleRect(clipCopy.getRect(c), NULL); + fGpu->drawSimpleRect(*clip->fRect, NULL); } else { if (canRenderDirectToStencil) { *drawState->stencil() = gDrawToStencil; @@ -806,9 +846,9 @@ bool GrClipMaskManager::createStencilClipMask(const GrClip& clipIn, for (int p = 0; p < passes; ++p) { *drawState->stencil() = stencilSettings[p]; if (canDrawDirectToClip) { - if (kRect_ClipType == clipCopy.getElementType(c)) { + if (NULL != clip->fRect) { SET_RANDOM_COLOR - fGpu->drawSimpleRect(clipCopy.getRect(c), NULL); + fGpu->drawSimpleRect(*clip->fRect, NULL); } else { SET_RANDOM_COLOR pr->drawPath(*clipPath, fill, NULL, fGpu, false); @@ -1060,20 +1100,25 @@ bool GrClipMaskManager::createSoftwareClipMask(const GrClip& clipIn, helper.init(*resultBounds, NULL); - int count = clipIn.getElementCount(); - bool clearToInside; - SkRegion::Op startOp = SkRegion::kReplace_Op; // suppress warning - int start = process_initial_clip_elements(clipIn, + SkRegion::Op firstOp = SkRegion::kReplace_Op; // suppress warning + + GrClip::Iter iter(clipIn, GrClip::Iter::kBottom_IterStart); + const GrClip::Iter::Clip* clip = process_initial_clip_elements(&iter, *resultBounds, &clearToInside, - &startOp); + &firstOp); helper.clear(clearToInside ? 0xFF : 0x00); - for (int i = start; i < count; ++i) { + bool first = true; + for ( ; NULL != clip; clip = iter.next()) { - SkRegion::Op op = (i == start) ? startOp : clipIn.getOp(i); + SkRegion::Op op = clip->fOp; + if (first) { + first = false; + op = firstOp; + } if (SkRegion::kIntersect_Op == op || SkRegion::kReverseDifference_Op == op) { @@ -1094,22 +1139,20 @@ bool GrClipMaskManager::createSoftwareClipMask(const GrClip& clipIn, helper.draw(temp, SkRegion::kXOR_Op, false, 0xFF); } - if (kRect_ClipType == clipIn.getElementType(i)) { + if (NULL != clip->fRect) { // convert the rect to a path so we can invert the fill SkPath temp; - temp.addRect(clipIn.getRect(i)); + temp.addRect(*clip->fRect); helper.draw(temp, SkRegion::kReplace_Op, - kInverseEvenOdd_GrPathFill, clipIn.getDoAA(i), + kInverseEvenOdd_GrPathFill, clip->fDoAA, 0x00); - } else { - GrAssert(kPath_ClipType == clipIn.getElementType(i)); - - helper.draw(clipIn.getPath(i), + } else if (NULL != clip->fPath) { + helper.draw(*clip->fPath, SkRegion::kReplace_Op, - invert_fill(clipIn.getPathFill(i)), - clipIn.getDoAA(i), + invert_fill(get_path_fill(*clip->fPath)), + clip->fDoAA, 0x00); } @@ -1118,19 +1161,17 @@ bool GrClipMaskManager::createSoftwareClipMask(const GrClip& clipIn, // The other ops (union, xor, diff) only affect pixels inside // the geometry so they can just be drawn normally - if (kRect_ClipType == clipIn.getElementType(i)) { + if (NULL != clip->fRect) { - helper.draw(clipIn.getRect(i), + helper.draw(*clip->fRect, op, - clipIn.getDoAA(i), 0xFF); - - } else { - GrAssert(kPath_ClipType == clipIn.getElementType(i)); + clip->fDoAA, 0xFF); - helper.draw(clipIn.getPath(i), + } else if (NULL != clip->fPath) { + helper.draw(*clip->fPath, op, - clipIn.getPathFill(i), - clipIn.getDoAA(i), 0xFF); + get_path_fill(*clip->fPath), + clip->fDoAA, 0xFF); } } diff --git a/src/gpu/GrClipMaskManager.h b/src/gpu/GrClipMaskManager.h index 8106185..4346c21 100644 --- a/src/gpu/GrClipMaskManager.h +++ b/src/gpu/GrClipMaskManager.h @@ -350,8 +350,7 @@ private: bool useSWOnlyPath(const GrClip& clipIn); bool drawClipShape(GrTexture* target, - const GrClip& clipIn, - int index, + const GrClip::Iter::Clip* clip, const GrIRect& resultBounds); void drawTexture(GrTexture* target, diff --git a/src/gpu/GrInOrderDrawBuffer.cpp b/src/gpu/GrInOrderDrawBuffer.cpp index 24efb37..d0bb225 100644 --- a/src/gpu/GrInOrderDrawBuffer.cpp +++ b/src/gpu/GrInOrderDrawBuffer.cpp @@ -126,7 +126,11 @@ void GrInOrderDrawBuffer::drawRect(const GrRect& rect, bool disabledClip = false; if (drawState->isClipState() && fClip.isRect()) { - GrRect clipRect = fClip.getRect(0); + GrClip::Iter iter(fClip, GrClip::Iter::kBottom_IterStart); + const GrClip::Iter::Clip* clip = iter.next(); + GrAssert(NULL != clip && NULL != clip->fRect); + + GrRect clipRect = *clip->fRect; // If the clip rect touches the edge of the viewport, extended it // out (close) to infinity to avoid bogus intersections. // We might consider a more exact clip to viewport if this -- 2.7.4