Consolidate quad optimizations into single internal function.
Routes all non-textured quad draws through single internal function
Change-Id: Ief66864a0ad2d598982c5bf500c8a84ecbf84387
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/215455
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
diff --git a/src/gpu/GrRenderTargetContext.cpp b/src/gpu/GrRenderTargetContext.cpp
index f10bda9..c579c1c 100644
--- a/src/gpu/GrRenderTargetContext.cpp
+++ b/src/gpu/GrRenderTargetContext.cpp
@@ -44,6 +44,7 @@
#include "src/gpu/effects/GrTextureDomain.h"
#include "src/gpu/effects/generated/GrColorMatrixFragmentProcessor.h"
#include "src/gpu/geometry/GrQuad.h"
+#include "src/gpu/geometry/GrQuadUtils.h"
#include "src/gpu/geometry/GrShape.h"
#include "src/gpu/ops/GrAtlasTextOp.h"
#include "src/gpu/ops/GrClearOp.h"
@@ -450,67 +451,27 @@
void GrRenderTargetContext::drawPaint(const GrClip& clip,
GrPaint&& paint,
const SkMatrix& viewMatrix) {
- ASSERT_SINGLE_OWNER
- RETURN_IF_ABANDONED
- SkDEBUGCODE(this->validate();)
- GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContext", "drawPaint", fContext);
-
- // set rect to be big enough to fill the space, but not super-huge, so we
- // don't overflow fixed-point implementations
-
+ // Start with the render target, since that is the maximum content we could possibly fill.
+ // drawFilledQuad() will automatically restrict it to clip bounds for us if possible.
SkRect r = fRenderTargetProxy->getBoundsRect();
-
- // Check if we can optimize a clipped drawPaint(). We only do the transformation when there are
- // no fragment processors because they may depend on having correct local coords and this path
- // draws in device space without a local matrix. It currently handles converting clipRRect()
- // to drawRRect() and solid colors to screen-filling drawRects() (which are then converted into
- // clears if possible in drawRect).
if (!paint.numTotalFragmentProcessors()) {
- SkRRect rrect;
- GrAA aa = GrAA::kNo;
- if (clip.isRRect(r, &rrect, &aa)) {
- if (rrect.isRect()) {
- // Use drawFilledRect() with no clip and the reduced rectangle
- this->drawFilledRect(GrNoClip(), std::move(paint), aa, SkMatrix::I(), rrect.rect());
- } else {
- // Use drawRRect() with no clip
- this->drawRRect(GrNoClip(), std::move(paint), aa, SkMatrix::I(), rrect,
- GrStyle::SimpleFill());
- }
- } else {
- // Use drawFilledRect() with no view matrix to draw a fullscreen quad, but preserve
- // the clip. Since the paint has no FPs we can drop the view matrix without worrying
- // about local coordinates. If the clip is simple, drawFilledRect() will turn this into
- // a clear or a scissored clear.
- this->drawFilledRect(clip, std::move(paint), aa, SkMatrix::I(), r);
+ // The paint is trivial so we won't need to use local coordinates, so skip calculating the
+ // inverse view matrix.
+ this->fillRectToRect(clip, std::move(paint), GrAA::kNo, SkMatrix::I(), r, r);
+ } else {
+ // Use the inverse view matrix to arrive at appropriate local coordinates for the paint.
+ SkMatrix localMatrix;
+ if (!viewMatrix.invert(&localMatrix)) {
+ return;
}
- return;
+ this->fillRectWithLocalMatrix(clip, std::move(paint), GrAA::kNo, SkMatrix::I(), r,
+ localMatrix);
}
-
- // Since the paint is not trivial, there's no way at this point drawRect() could have converted
- // this drawPaint() into an optimized clear. drawRect() would then use GrFillRectOp without
- // a local matrix, so we can simplify things and use the local matrix variant to draw a screen
- // filling rect with the inverse view matrix for local coords, which works for all matrix
- // conditions.
- SkMatrix localMatrix;
- if (!viewMatrix.invert(&localMatrix)) {
- return;
- }
-
- AutoCheckFlush acf(this->drawingManager());
- std::unique_ptr<GrDrawOp> op = GrFillRectOp::Make(
- fContext, std::move(paint), GrAAType::kNone, GrQuadAAFlags::kNone,
- GrQuad(r), GrQuad::MakeFromRect(r, localMatrix));
- this->addDrawOp(clip, std::move(op));
-}
-
-static inline bool rect_contains_inclusive(const SkRect& rect, const SkPoint& point) {
- return point.fX >= rect.fLeft && point.fX <= rect.fRight &&
- point.fY >= rect.fTop && point.fY <= rect.fBottom;
}
// Attempts to crop a rect and optional local rect to the clip boundaries.
// Returns false if the draw can be skipped entirely.
+// FIXME to be removed once drawTexture et al are updated to use attemptQuadOptimization instead
static bool crop_filled_rect(int width, int height, const GrClip& clip,
const SkMatrix& viewMatrix, SkRect* rect,
SkRect* localRect = nullptr) {
@@ -560,173 +521,186 @@
return rect->intersect(clipBounds);
}
-GrQuadAAFlags set_edge_flag(GrQuadAAFlags currentFlags, GrQuadAAFlags edge, GrAA edgeState) {
- if (edgeState == GrAA::kNo) {
- // Turn off 'edge' in currentFlags
- return currentFlags & (~edge);
- } else {
- // Turn on 'edge' in currentFlags
- return currentFlags | edge;
- }
-}
+enum class GrRenderTargetContext::QuadOptimization {
+ // The rect to draw doesn't intersect clip or render target, so no draw op should be added
+ kDiscarded,
+ // The rect to draw was converted to some other op and appended to the oplist, so no additional
+ // op is necessary. Currently this can convert it to a clear op or a rrect op. Only valid if
+ // a constColor is provided.
+ kSubmitted,
+ // The clip was folded into the device quad, with updated edge flags and local coords, and
+ // caller is responsible for adding an appropriate op.
+ kClipApplied,
+ // No change to clip, but quad updated to better fit clip/render target, and caller is
+ // responsible for adding an appropriate op.
+ kCropped
+};
-bool GrRenderTargetContext::drawFilledRectAsClear(const GrClip& clip, GrPaint&& paint, GrAA aa,
- const SkMatrix& viewMatrix, const SkRect& rect) {
- // Rules for a filled rect to become a clear [+scissor]:
- // 1. The paint is a constant blend color with no other FPs
- // 2. The view matrix maps rectangles to rectangles, or the transformed quad fully covers
- // the render target (or clear region in #3).
- // 3. The clip is an intersection of rectangles, so the clear region will be the
- // intersection of the clip and the provided rect.
- // 4. The clear region aligns with pixel bounds
- // 5. There are no user stencil settings (and since the clip was IOR, the clip won't need
- // to use the stencil either).
- // If all conditions pass, the filled rect can either be a fullscreen clear (if it's big
- // enough), or the rectangle geometry will be used as the scissor clip on the clear.
- // If everything passes but rule #4, this submits a simplified fill rect op instead so that the
- // rounding differences between clip and draws don't fight each other.
- // NOTE: we route draws into clear() regardless of performColorClearsAsDraws() since the
- // clear call is allowed to reset the oplist even when it also happens to use a GrFillRectOp.
+GrRenderTargetContext::QuadOptimization GrRenderTargetContext::attemptQuadOptimization(
+ const GrClip& clip, const SkPMColor4f* constColor, bool allowAAChange,
+ GrAA* aa, GrQuadAAFlags* edgeFlags, GrQuad* deviceQuad, GrQuad* localQuad) {
+ // Optimization requirements:
+ // 1. kDiscard applies when clip bounds and quad bounds do not intersect
+ // 2. kClear applies when constColor and final geom is pixel aligned rect;
+ // pixel aligned rect requires rect clip and (rect quad or quad covers clip)
+ // 3. kRRect applies when constColor and rrect clip and quad covers clip
+ // 4. kExplicitClip applies when rect clip and (rect quad or quad covers clip)
+ // 5. kCropped applies when rect quad (currently)
+ // 6. kNone always applies
+ GrQuadAAFlags newFlags = *edgeFlags;
- SkPMColor4f clearColor;
- if (paint.numCoverageFragmentProcessors() > 0 || !paint.isConstantBlendedColor(&clearColor)) {
- return false;
+ // Must use worst case bounds so that stencil buffer updates on approximately sized
+ // render targets don't get corrupted.
+ const SkRect rtRect = SkRect::MakeWH(fRenderTargetProxy->worstCaseWidth(),
+ fRenderTargetProxy->worstCaseHeight());
+ SkRect drawBounds = deviceQuad->bounds();
+ if (constColor) {
+ // Don't bother updating local coordinates when the paint will ignore them anyways
+ localQuad = nullptr;
}
- const SkRect rtRect = fRenderTargetProxy->getBoundsRect();
- // Will be the intersection of render target, clip, and quad
- SkRect combinedRect = rtRect;
+ // If the quad is entirely off screen, it doesn't matter what the clip does
+ if (!rtRect.intersects(drawBounds)) {
+ return QuadOptimization::kDiscarded;
+ }
- SkRRect clipRRect;
- GrAA clipAA;
+ // Check if clip can be represented as a rounded rect (initialize as if clip fully contained
+ // the render target).
+ SkRRect clipRRect = SkRRect::MakeRect(rtRect);
+ GrAA clipAA = allowAAChange ? GrAA::kNo : *aa;
+ bool axisAlignedClip = true;
if (!clip.quickContains(rtRect)) {
- // If the clip is an rrect with no rounding, then it can replace the full RT bounds as the
- // limiting region, although we will have to worry about AA. If the clip is anything
- // more complicated, just punt to the regular fill rect op.
- if (!clip.isRRect(rtRect, &clipRRect, &clipAA) || !clipRRect.isRect()) {
- return false;
+ if (!clip.isRRect(rtRect, &clipRRect, &clipAA)) {
+ axisAlignedClip = false;
}
-
- combinedRect = clipRRect.rect();
- } else {
- // The clip is outside the render target, so the clip can be ignored
- clipAA = GrAA::kNo;
}
- GrQuadAAFlags edgeFlags; // To account for clip and draw mixing AA modes
- if (viewMatrix.rectStaysRect()) {
- // Skip the extra overhead of inverting the view matrix to see if rtRect is contained in the
- // drawn rectangle, and instead just intersect rtRect with the transformed rect. It will be
- // the new clear region.
- SkRect drawRect = viewMatrix.mapRect(rect);
- if (!combinedRect.intersect(drawRect)) {
- // No intersection means nothing should be drawn, so return true but don't add an op
- return true;
+ // If the clip rrect is valid (i.e. axis-aligned), we can potentially combine it with the
+ // draw geometry so that no clip is needed when drawing.
+ if (axisAlignedClip && (allowAAChange || clipAA == *aa)) {
+ // Tighten clip bounds (if clipRRect.isRect() is true, clipBounds now holds the intersection
+ // of the render target and the clip rect)
+ SkRect clipBounds = rtRect;
+ if (!clipBounds.intersect(clipRRect.rect()) || !clipBounds.intersects(drawBounds)) {
+ return QuadOptimization::kDiscarded;
}
- // In this case, edge flags start based on draw's AA and then switch per-edge to the clip's
- // AA setting if that edge was inset.
- edgeFlags = aa == GrAA::kNo ? GrQuadAAFlags::kNone : GrQuadAAFlags::kAll;
- if (combinedRect.fLeft > drawRect.fLeft) {
- edgeFlags = set_edge_flag(edgeFlags, GrQuadAAFlags::kLeft, clipAA);
+ if (clipRRect.isRect()) {
+ // No rounded corners, so the kClear and kExplicitClip optimizations are possible
+ if (GrQuadUtils::CropToRect(clipBounds, clipAA, &newFlags, deviceQuad, localQuad)) {
+ if (constColor && deviceQuad->quadType() == GrQuad::Type::kAxisAligned) {
+ // Clear optimization is possible
+ drawBounds = deviceQuad->bounds();
+ if (drawBounds.contains(rtRect)) {
+ // Fullscreen clear
+ this->clear(nullptr, *constColor, CanClearFullscreen::kYes);
+ return QuadOptimization::kSubmitted;
+ } else if (GrClip::IsPixelAligned(drawBounds) &&
+ drawBounds.width() > 256 && drawBounds.height() > 256) {
+ // Scissor + clear (round shouldn't do anything since we are pixel aligned)
+ SkIRect scissorRect;
+ drawBounds.round(&scissorRect);
+ this->clear(&scissorRect, *constColor, CanClearFullscreen::kNo);
+ return QuadOptimization::kSubmitted;
+ }
+ }
+
+ // Update overall AA setting.
+ *edgeFlags = newFlags;
+ if (*aa == GrAA::kNo && clipAA == GrAA::kYes &&
+ newFlags != GrQuadAAFlags::kNone) {
+ // The clip was anti-aliased and now the draw needs to be upgraded to AA to
+ // properly reflect the smooth edge of the clip.
+ *aa = GrAA::kYes;
+ }
+ // We intentionally do not downgrade AA here because we don't know if we need to
+ // preserve MSAA (see GrQuadAAFlags docs). But later in the pipeline, the ops can
+ // use GrResolveAATypeForQuad() to turn off coverage AA when all flags are off.
+
+ // deviceQuad is exactly the intersection of original quad and clip, so it can be
+ // drawn with no clip (submitted by caller)
+ return QuadOptimization::kClipApplied;
+ } else {
+ // The quads have been updated to better fit the clip bounds, but can't get rid of
+ // the clip entirely
+ return QuadOptimization::kCropped;
+ }
+ } else if (constColor) {
+ // Rounded corners and constant filled color (limit ourselves to solid colors because
+ // there is no way to use custom local coordinates with drawRRect).
+ if (GrQuadUtils::CropToRect(clipBounds, clipAA, &newFlags, deviceQuad, localQuad) &&
+ deviceQuad->quadType() == GrQuad::Type::kAxisAligned &&
+ deviceQuad->bounds().contains(clipBounds)) {
+ // Since the cropped quad became a rectangle which covered the bounds of the rrect,
+ // we can draw the rrect directly and ignore the edge flags
+ GrPaint paint;
+ clear_to_grpaint(*constColor, &paint);
+ this->drawRRect(GrFixedClip::Disabled(), std::move(paint), clipAA, SkMatrix::I(),
+ clipRRect, GrStyle::SimpleFill());
+ return QuadOptimization::kSubmitted;
+ } else {
+ // The quad has been updated to better fit clip bounds, but can't remove the clip
+ return QuadOptimization::kCropped;
+ }
}
- if (combinedRect.fTop > drawRect.fTop) {
- edgeFlags = set_edge_flag(edgeFlags, GrQuadAAFlags::kTop, clipAA);
- }
- if (combinedRect.fRight < drawRect.fRight) {
- edgeFlags = set_edge_flag(edgeFlags, GrQuadAAFlags::kRight, clipAA);
- }
- if (combinedRect.fBottom < drawRect.fBottom) {
- edgeFlags = set_edge_flag(edgeFlags, GrQuadAAFlags::kBottom, clipAA);
- }
- } else {
- // If the transformed rectangle does not contain the combined rt and clip, the draw is too
- // complex to be implemented as a clear
- SkMatrix invM;
- if (!viewMatrix.invert(&invM)) {
- return false;
- }
- // The clip region in the rect's local space, so the test becomes the local rect containing
- // the quad's points. If clip is non-AA, test rounded out region to avoid the scenario where
- // the draw contains the unrounded non-aa clip, but does not contain the rounded version. Be
- // conservative since we don't know how the GPU would round.
- SkRect conservative;
- if (clipAA == GrAA::kNo) {
- conservative = SkRect::Make(combinedRect.roundOut());
- } else {
- conservative = combinedRect;
- }
- GrQuad quad = GrQuad::MakeFromRect(conservative, invM);
- if (!rect_contains_inclusive(rect, quad.point(0)) ||
- !rect_contains_inclusive(rect, quad.point(1)) ||
- !rect_contains_inclusive(rect, quad.point(2)) ||
- !rect_contains_inclusive(rect, quad.point(3))) {
- // No containment, so combinedRect can't be filled by a solid color
- return false;
- }
- // combinedRect can be filled by a solid color but doesn't need to be modified since it's
- // inside the quad to be drawn, which also means the edge AA flags respect the clip AA
- edgeFlags = clipAA == GrAA::kNo ? GrQuadAAFlags::kNone : GrQuadAAFlags::kAll;
}
- // Almost every condition is met; now it requires that the combined rect align with pixel
- // boundaries in order for it to become a scissor-clear. Ignore the AA status in this case
- // since non-AA with partial-pixel coordinates can be rounded differently on the GPU,
- // leading to unexpected differences between a scissor test and a rasterized quad.
- // Also skip very small rectangles since the scissor+clear doesn't by us much then.
- if (combinedRect.contains(rtRect)) {
- // Full screen clear
- this->clear(nullptr, clearColor, CanClearFullscreen::kYes);
- return true;
- } else if (GrClip::IsPixelAligned(combinedRect) &&
- combinedRect.width() > 256 && combinedRect.height() > 256) {
- // Scissor + clear (round shouldn't do anything since we are pixel aligned)
- SkIRect scissorRect;
- combinedRect.round(&scissorRect);
- this->clear(&scissorRect, clearColor, CanClearFullscreen::kNo);
- return true;
+ // Crop the quad to the conservative bounds of the clip.
+ SkIRect clipDevBounds;
+ clip.getConservativeBounds(rtRect.width(), rtRect.height(), &clipDevBounds);
+ SkRect clipBounds = SkRect::Make(clipDevBounds);
+
+ // One final check for discarding, since we may have gone here directly due to a complex clip
+ if (!clipBounds.intersects(drawBounds)) {
+ return QuadOptimization::kDiscarded;
}
- // If we got here, we can't use a scissor + clear, but combinedRect represents the correct
- // geometry combination of quad + clip so we can perform a simplified fill rect op. We do this
- // mostly to avoid mismatches in rounding logic on the CPU vs. the GPU, which frequently appears
- // when drawing and clipping something to the same non-AA rect that never-the-less has
- // non-integer coordinates.
- aa = edgeFlags == GrQuadAAFlags::kNone ? GrAA::kNo : GrAA::kYes;
- GrAAType aaType = this->chooseAAType(aa);
- this->addDrawOp(GrFixedClip::Disabled(),
- GrFillRectOp::Make(fContext, std::move(paint), aaType, edgeFlags,
- GrQuad(combinedRect), GrQuad(combinedRect)));
- return true;
+ // Even if this were to return true, the crop rect does not exactly match the clip, so can not
+ // report explicit-clip. Since these edges aren't visible, don't update the final edge flags.
+ GrQuadUtils::CropToRect(clipBounds, clipAA, &newFlags, deviceQuad, localQuad);
+
+ return QuadOptimization::kCropped;
}
-void GrRenderTargetContext::drawFilledRect(const GrClip& clip,
+void GrRenderTargetContext::drawFilledQuad(const GrClip& clip,
GrPaint&& paint,
GrAA aa,
- const SkMatrix& viewMatrix,
- const SkRect& rect,
+ GrQuadAAFlags edgeFlags,
+ const GrQuad& deviceQuad,
+ const GrQuad& localQuad,
const GrUserStencilSettings* ss) {
+ ASSERT_SINGLE_OWNER
+ RETURN_IF_ABANDONED
+ SkDEBUGCODE(this->validate();)
+ GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContext", "drawFilledQuad", fContext);
- if (!ss) {
- if (this->drawFilledRectAsClear(clip, std::move(paint), aa, viewMatrix, rect)) {
- return;
- }
- // Fall through to fill rect op
- assert_alive(paint);
+ AutoCheckFlush acf(this->drawingManager());
+
+ SkPMColor4f* constColor = nullptr;
+ SkPMColor4f paintColor;
+ if (!ss && !paint.numCoverageFragmentProcessors() &&
+ paint.isConstantBlendedColor(&paintColor)) {
+ // Only consider clears/rrects when it's easy to guarantee 100% fill with single color
+ constColor = &paintColor;
}
- SkRect croppedRect = rect;
- if (!crop_filled_rect(this->width(), this->height(), clip, viewMatrix, &croppedRect)) {
- // The rectangle would not be drawn, so no need to add a draw op to the list
- return;
+ GrQuad croppedDeviceQuad = deviceQuad;
+ GrQuad croppedLocalQuad = localQuad;
+ // Only allow AA settings to change if there are no stencil settings. If they were allowed to
+ // change at this stage, it would bypass higher-level decisions about stencil MSAA.
+ QuadOptimization opt = this->attemptQuadOptimization(clip, constColor, /* allowAAChange */ !ss,
+ &aa, &edgeFlags, &croppedDeviceQuad,
+ &croppedLocalQuad);
+ if (opt >= QuadOptimization::kClipApplied) {
+ // These optimizations require caller to add an op themselves
+ const GrClip& finalClip = opt == QuadOptimization::kClipApplied ? GrFixedClip::Disabled()
+ : clip;
+ GrAAType aaType = ss ? (aa == GrAA::kYes ? GrAAType::kMSAA : GrAAType::kNone)
+ : this->chooseAAType(aa);
+ this->addDrawOp(finalClip, GrFillRectOp::Make(fContext, std::move(paint), aaType, edgeFlags,
+ croppedDeviceQuad, croppedLocalQuad, ss));
}
-
- GrAAType aaType = this->chooseAAType(aa);
- GrQuadAAFlags edgeFlags = aa == GrAA::kYes ? GrQuadAAFlags::kAll : GrQuadAAFlags::kNone;
- this->addDrawOp(clip,
- GrFillRectOp::Make(fContext, std::move(paint), aaType, edgeFlags,
- GrQuad::MakeFromRect(croppedRect, viewMatrix),
- GrQuad(croppedRect), ss));
+ // All other optimization levels were completely handled inside attempt(), so no extra op needed
}
void GrRenderTargetContext::drawRect(const GrClip& clip,
@@ -750,7 +724,8 @@
const SkStrokeRec& stroke = style->strokeRec();
if (stroke.getStyle() == SkStrokeRec::kFill_Style) {
- this->drawFilledRect(clip, std::move(paint), aa, viewMatrix, rect);
+ // Fills the rect, using rect as its own local coordinates
+ this->fillRectToRect(clip, std::move(paint), aa, viewMatrix, rect, rect);
return;
} else if (stroke.getStyle() == SkStrokeRec::kStroke_Style ||
stroke.getStyle() == SkStrokeRec::kHairline_Style) {
@@ -892,98 +867,6 @@
fRenderTargetContext->getRTOpList()->addOp(std::move(op), *fRenderTargetContext->caps());
}
-void GrRenderTargetContextPriv::stencilRect(const GrClip& clip,
- const GrUserStencilSettings* ss,
- GrPaint&& paint,
- GrAA doStencilMSAA,
- const SkMatrix& viewMatrix,
- const SkRect& rect,
- const SkMatrix* localMatrix) {
- ASSERT_SINGLE_OWNER_PRIV
- RETURN_IF_ABANDONED_PRIV
- SkDEBUGCODE(fRenderTargetContext->validate();)
- GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContextPriv", "stencilRect",
- fRenderTargetContext->fContext);
-
- AutoCheckFlush acf(fRenderTargetContext->drawingManager());
-
- auto aaType = (GrAA::kYes == doStencilMSAA) ? GrAAType::kMSAA : GrAAType::kNone;
-
- GrQuad localQuad = localMatrix ? GrQuad::MakeFromRect(rect, *localMatrix)
- : GrQuad(rect);
- std::unique_ptr<GrDrawOp> op = GrFillRectOp::Make(
- fRenderTargetContext->fContext, std::move(paint), aaType, GrQuadAAFlags::kNone,
- GrQuad::MakeFromRect(rect, viewMatrix), localQuad, ss);
- fRenderTargetContext->addDrawOp(clip, std::move(op));
-}
-
-void GrRenderTargetContext::fillRectWithEdgeAA(const GrClip& clip, GrPaint&& paint, GrAA aa,
- GrQuadAAFlags edgeAA, const SkMatrix& viewMatrix,
- const SkRect& rect, const SkRect* localRect) {
- ASSERT_SINGLE_OWNER
- RETURN_IF_ABANDONED
- SkDEBUGCODE(this->validate();)
- GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContext", "fillRectWithEdgeAA", fContext);
-
- GrAAType aaType = this->chooseAAType(aa);
- std::unique_ptr<GrDrawOp> op;
-
- if (localRect) {
- // If local coordinates are provided, skip the optimization check to go through
- // drawFilledRect, and also calculate clipped local coordinates
- SkRect croppedRect = rect;
- SkRect croppedLocalRect = *localRect;
- if (!crop_filled_rect(this->width(), this->height(), clip, viewMatrix, &croppedRect,
- &croppedLocalRect)) {
- return;
- }
- op = GrFillRectOp::Make(fContext, std::move(paint), aaType, edgeAA,
- GrQuad::MakeFromRect(croppedRect, viewMatrix),
- GrQuad(croppedLocalRect));
- } else {
- // If aaType turns into MSAA, make sure to keep quads with no AA edges as MSAA. Sending
- // those to drawFilledRect() would have it turn off MSAA in that case, which breaks seaming
- // with any partial AA edges that kept MSAA.
- if (aaType != GrAAType::kMSAA &&
- (edgeAA == GrQuadAAFlags::kNone || edgeAA == GrQuadAAFlags::kAll)) {
- // This is equivalent to a regular filled rect draw, so route through there to take
- // advantage of draw->clear optimizations
- this->drawFilledRect(clip, std::move(paint), GrAA(edgeAA == GrQuadAAFlags::kAll),
- viewMatrix, rect);
- return;
- }
-
- SkRect croppedRect = rect;
- if (!crop_filled_rect(this->width(), this->height(), clip, viewMatrix, &croppedRect)) {
- return;
- }
- op = GrFillRectOp::Make(fContext, std::move(paint), aaType, edgeAA,
- GrQuad::MakeFromRect(croppedRect, viewMatrix),
- GrQuad(croppedRect));
- }
-
- AutoCheckFlush acf(this->drawingManager());
- this->addDrawOp(clip, std::move(op));
-}
-
-void GrRenderTargetContext::fillQuadWithEdgeAA(const GrClip& clip, GrPaint&& paint, GrAA aa,
- GrQuadAAFlags edgeAA, const SkMatrix& viewMatrix,
- const SkPoint quad[4], const SkPoint localQuad[4]) {
- ASSERT_SINGLE_OWNER
- RETURN_IF_ABANDONED
- SkDEBUGCODE(this->validate();)
- GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContext", "fillQuadWithEdgeAA", fContext);
-
- GrAAType aaType = this->chooseAAType(aa);
-
- AutoCheckFlush acf(this->drawingManager());
- const SkPoint* localPoints = localQuad ? localQuad : quad;
- this->addDrawOp(clip,
- GrFillRectOp::Make(fContext, std::move(paint), aaType, edgeAA,
- GrQuad::MakeFromSkQuad(quad, viewMatrix),
- GrQuad::MakeFromSkQuad(localPoints, SkMatrix::I())));
-}
-
// Creates a paint for GrFillRectOp that matches behavior of GrTextureOp
static void draw_texture_to_grpaint(sk_sp<GrTextureProxy> proxy, const SkRect* domain,
GrSamplerState::Filter filter, SkBlendMode mode,
@@ -1147,32 +1030,6 @@
}
}
-void GrRenderTargetContext::fillRectWithLocalMatrix(const GrClip& clip,
- GrPaint&& paint,
- GrAA aa,
- const SkMatrix& viewMatrix,
- const SkRect& rectToDraw,
- const SkMatrix& localMatrix) {
- ASSERT_SINGLE_OWNER
- RETURN_IF_ABANDONED
- SkDEBUGCODE(this->validate();)
- GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContext", "fillRectWithLocalMatrix", fContext);
-
- SkRect croppedRect = rectToDraw;
- if (!crop_filled_rect(this->width(), this->height(), clip, viewMatrix, &croppedRect)) {
- return;
- }
-
- AutoCheckFlush acf(this->drawingManager());
-
- GrAAType aaType = this->chooseAAType(aa);
- GrQuadAAFlags edgeFlags = aa == GrAA::kYes ? GrQuadAAFlags::kAll : GrQuadAAFlags::kNone;
- this->addDrawOp(clip,
- GrFillRectOp::Make(fContext, std::move(paint), aaType, edgeFlags,
- GrQuad::MakeFromRect(croppedRect, viewMatrix),
- GrQuad::MakeFromRect(croppedRect, localMatrix)));
-}
-
void GrRenderTargetContext::drawVertices(const GrClip& clip,
GrPaint&& paint,
const SkMatrix& viewMatrix,
@@ -1918,8 +1775,8 @@
GrPaint paint;
paint.addColorFragmentProcessor(std::move(fp));
paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
- currRTC->drawFilledRect(GrNoClip(), std::move(paint), GrAA::kNo, SkMatrix::I(),
- dstRect);
+ currRTC->fillRectToRect(GrNoClip(), std::move(paint), GrAA::kNo, SkMatrix::I(),
+ dstRect, dstRect);
} else {
auto filter = rescaleQuality == kNone_SkFilterQuality ? GrSamplerState::Filter::kNearest
: GrSamplerState::Filter::kBilerp;
@@ -2274,6 +2131,9 @@
auto texMatrix = SkMatrix::MakeTrans(x, y);
+ SkRect dstRectY = SkRect::MakeWH(dstW, dstH);
+ SkRect dstRectUV = SkRect::MakeWH(dstW / 2, dstH / 2);
+
// This matrix generates (r,g,b,a) = (0, 0, 0, y)
float yM[20];
std::fill_n(yM, 15, 0.f);
@@ -2283,8 +2143,8 @@
auto yFP = GrColorMatrixFragmentProcessor::Make(yM, false, true, false);
yPaint.addColorFragmentProcessor(std::move(yFP));
yPaint.setPorterDuffXPFactory(SkBlendMode::kSrc);
- yRTC->drawFilledRect(GrNoClip(), std::move(yPaint), GrAA::kNo, SkMatrix::I(),
- SkRect::MakeWH(dstW, dstH));
+ yRTC->fillRectToRect(GrNoClip(), std::move(yPaint), GrAA::kNo, SkMatrix::I(),
+ dstRectY, dstRectY);
auto yTransfer = yRTC->transferPixels(GrColorType::kAlpha_8,
SkIRect::MakeWH(yRTC->width(), yRTC->height()));
if (!yTransfer.fTransferBuffer) {
@@ -2302,8 +2162,8 @@
auto uFP = GrColorMatrixFragmentProcessor::Make(uM, false, true, false);
uPaint.addColorFragmentProcessor(std::move(uFP));
uPaint.setPorterDuffXPFactory(SkBlendMode::kSrc);
- uRTC->drawFilledRect(GrNoClip(), std::move(uPaint), GrAA::kNo, SkMatrix::I(),
- SkRect::MakeWH(dstW / 2, dstH / 2));
+ uRTC->fillRectToRect(GrNoClip(), std::move(uPaint), GrAA::kNo, SkMatrix::I(),
+ dstRectUV, dstRectUV);
auto uTransfer = uRTC->transferPixels(GrColorType::kAlpha_8,
SkIRect::MakeWH(uRTC->width(), uRTC->height()));
if (!uTransfer.fTransferBuffer) {
@@ -2320,8 +2180,8 @@
auto vFP = GrColorMatrixFragmentProcessor::Make(vM, false, true, false);
vPaint.addColorFragmentProcessor(std::move(vFP));
vPaint.setPorterDuffXPFactory(SkBlendMode::kSrc);
- vRTC->drawFilledRect(GrNoClip(), std::move(vPaint), GrAA::kNo, SkMatrix::I(),
- SkRect::MakeWH(dstW / 2, dstH / 2));
+ vRTC->fillRectToRect(GrNoClip(), std::move(vPaint), GrAA::kNo, SkMatrix::I(),
+ dstRectUV, dstRectUV);
auto vTransfer = vRTC->transferPixels(GrColorType::kAlpha_8,
SkIRect::MakeWH(vRTC->width(), vRTC->height()));
if (!vTransfer.fTransferBuffer) {
diff --git a/src/gpu/GrRenderTargetContext.h b/src/gpu/GrRenderTargetContext.h
index fd72e52..cc46660 100644
--- a/src/gpu/GrRenderTargetContext.h
+++ b/src/gpu/GrRenderTargetContext.h
@@ -18,6 +18,7 @@
#include "src/gpu/GrRenderTargetProxy.h"
#include "src/gpu/GrSurfaceContext.h"
#include "src/gpu/GrXferProcessor.h"
+#include "src/gpu/geometry/GrQuad.h"
#include "src/gpu/text/GrTextTarget.h"
class GrBackendSemaphore;
@@ -116,9 +117,9 @@
const SkMatrix& viewMatrix,
const SkRect& rectToDraw,
const SkRect& localRect) {
- this->fillRectWithEdgeAA(clip, std::move(paint), aa,
- aa == GrAA::kYes ? GrQuadAAFlags::kAll : GrQuadAAFlags::kNone,
- viewMatrix, rectToDraw, &localRect);
+ this->drawFilledQuad(clip, std::move(paint), aa,
+ aa == GrAA::kYes ? GrQuadAAFlags::kAll : GrQuadAAFlags::kNone,
+ GrQuad::MakeFromRect(rectToDraw, viewMatrix), GrQuad(localRect));
}
/**
@@ -126,10 +127,15 @@
*/
void fillRectWithLocalMatrix(const GrClip& clip,
GrPaint&& paint,
- GrAA,
+ GrAA aa,
const SkMatrix& viewMatrix,
const SkRect& rect,
- const SkMatrix& localMatrix);
+ const SkMatrix& localMatrix) {
+ this->drawFilledQuad(clip, std::move(paint), aa,
+ aa == GrAA::kYes ? GrQuadAAFlags::kAll : GrQuadAAFlags::kNone,
+ GrQuad::MakeFromRect(rect, viewMatrix),
+ GrQuad::MakeFromRect(rect, localMatrix));
+ }
/**
* Creates an op that draws a fill rect with per-edge control over anti-aliasing.
@@ -139,7 +145,11 @@
*/
void fillRectWithEdgeAA(const GrClip& clip, GrPaint&& paint, GrAA aa, GrQuadAAFlags edgeAA,
const SkMatrix& viewMatrix, const SkRect& rect,
- const SkRect* optionalLocalRect = nullptr);
+ const SkRect* optionalLocalRect = nullptr) {
+ const SkRect& localRect = optionalLocalRect ? *optionalLocalRect : rect;
+ this->drawFilledQuad(clip, std::move(paint), aa, edgeAA,
+ GrQuad::MakeFromRect(rect, viewMatrix), GrQuad(localRect));
+ }
/**
* Similar to fillRectWithEdgeAA but draws an arbitrary 2D convex quadrilateral transformed
@@ -155,7 +165,12 @@
*/
void fillQuadWithEdgeAA(const GrClip& clip, GrPaint&& paint, GrAA aa, GrQuadAAFlags edgeAA,
const SkMatrix& viewMatrix, const SkPoint quad[4],
- const SkPoint optionalLocalQuad[4]);
+ const SkPoint optionalLocalQuad[4]) {
+ const SkPoint* localQuad = optionalLocalQuad ? optionalLocalQuad : quad;
+ this->drawFilledQuad(clip, std::move(paint), aa, edgeAA,
+ GrQuad::MakeFromSkQuad(quad, viewMatrix),
+ GrQuad::MakeFromSkQuad(localQuad, SkMatrix::I()));
+ }
/** Used with drawQuadSet */
struct QuadSetEntry {
@@ -487,6 +502,7 @@
private:
class TextTarget;
+ enum class QuadOptimization;
GrAAType chooseAAType(GrAA);
@@ -525,19 +541,34 @@
const SkRRect& origOuter,
const SkRRect& origInner);
- void drawFilledRect(const GrClip& clip,
- GrPaint&& paint,
- GrAA,
- const SkMatrix& viewMatrix,
- const SkRect& rect,
- const GrUserStencilSettings* ss = nullptr);
+ // If the drawn quad's paint is a const blended color, provide it as a non-null pointer to
+ // 'constColor', which enables the draw-as-clear optimization. Otherwise it is assumed the paint
+ // requires some form of shading that invalidates using a clear op.
+ //
+ // The non-const pointers should be the original draw request on input, and will be updated as
+ // appropriate depending on the returned optimization level.
+ //
+ // If 'allowAAChange' is true, 'aa' and 'edgeFlags' may be updated to incorporate the AA of the
+ // clip. When it is false, only optimizations that will not change the AA state will be applied.
+ QuadOptimization attemptQuadOptimization(const GrClip& clip,
+ const SkPMColor4f* constColor,
+ bool allowAAChange,
+ GrAA* aa,
+ GrQuadAAFlags* edgeFlags,
+ GrQuad* deviceQuad,
+ GrQuad* localQuad);
- // Only consumes the GrPaint if successful.
- bool drawFilledRectAsClear(const GrClip& clip,
- GrPaint&& paint,
- GrAA aa,
- const SkMatrix& viewMatrix,
- const SkRect& rect);
+ // If stencil settings, 'ss', are non-null, AA controls MSAA or no AA. If they are null, then AA
+ // can choose between coverage, MSAA as per chooseAAType(). This will always attempt to apply
+ // quad optimizations, so all quad/rect public APIs should rely on this function for consistent
+ // clipping behavior.
+ void drawFilledQuad(const GrClip& clip,
+ GrPaint&& paint,
+ GrAA aa,
+ GrQuadAAFlags edgeFlags,
+ const GrQuad& deviceQuad,
+ const GrQuad& localQuad,
+ const GrUserStencilSettings* ss = nullptr);
void drawShapeUsingPathRenderer(const GrClip&, GrPaint&&, GrAA, const SkMatrix&,
const GrShape&);
diff --git a/src/gpu/GrRenderTargetContextPriv.h b/src/gpu/GrRenderTargetContextPriv.h
index fa1d9fd..ef60655 100644
--- a/src/gpu/GrRenderTargetContextPriv.h
+++ b/src/gpu/GrRenderTargetContextPriv.h
@@ -66,8 +66,16 @@
// care to only provide hard clips or we could get stuck in a loop. The general clip is needed
// so that path renderers can use this function.
void stencilRect(
- const GrClip&, const GrUserStencilSettings* ss, GrPaint&& paint, GrAA doStencilMSAA,
- const SkMatrix& viewMatrix, const SkRect& rect, const SkMatrix* localMatrix = nullptr);
+ const GrClip& clip, const GrUserStencilSettings* ss, GrPaint&& paint,
+ GrAA doStencilMSAA, const SkMatrix& viewMatrix, const SkRect& rect,
+ const SkMatrix* localMatrix = nullptr) {
+ // Since this provides stencil settings to drawFilledQuad, it performs a different AA type
+ // resolution compared to regular rect draws, which is the main reason it remains separate.
+ GrQuad localQuad = localMatrix ? GrQuad::MakeFromRect(rect, *localMatrix) : GrQuad(rect);
+ fRenderTargetContext->drawFilledQuad(
+ clip, std::move(paint), doStencilMSAA, GrQuadAAFlags::kNone,
+ GrQuad::MakeFromRect(rect, viewMatrix), localQuad, ss);
+ }
void stencilPath(
const GrHardClip&, GrAA doStencilMSAA, const SkMatrix& viewMatrix, const GrPath*);
@@ -84,12 +92,6 @@
const SkMatrix& viewMatrix,
const SkPath&);
- void drawFilledRect(
- const GrClip& clip, GrPaint&& paint, GrAA aa, const SkMatrix& m, const SkRect& rect,
- const GrUserStencilSettings* ss = nullptr) {
- fRenderTargetContext->drawFilledRect(clip, std::move(paint), aa, m, rect, ss);
- }
-
SkBudgeted isBudgeted() const;
int maxWindowRectangles() const;
diff --git a/src/gpu/geometry/GrQuad.h b/src/gpu/geometry/GrQuad.h
index 9f4f974..9a080f3 100644
--- a/src/gpu/geometry/GrQuad.h
+++ b/src/gpu/geometry/GrQuad.h
@@ -117,6 +117,15 @@
// True if anti-aliasing affects this quad. Only valid when quadType == kAxisAligned
bool aaHasEffectOnRect() const;
+ // The non-const pointers are provided to support modifying a GrQuad in-place, but care must be
+ // taken to keep its quad type aligned with the geometric nature of the new coordinates. This is
+ // no different than using the constructors that accept a quad type.
+
+ float* xs() { return fX; }
+ float* ys() { return fY; }
+ float* ws() { return fW; }
+
+ void setQuadType(Type newType) { fType = newType; }
private:
template<typename T>
friend class GrQuadListBase; // for access to fX, fY, fW
diff --git a/src/gpu/geometry/GrQuadUtils.cpp b/src/gpu/geometry/GrQuadUtils.cpp
index ba109cf..fce38d2 100644
--- a/src/gpu/geometry/GrQuadUtils.cpp
+++ b/src/gpu/geometry/GrQuadUtils.cpp
@@ -7,9 +7,158 @@
#include "src/gpu/geometry/GrQuadUtils.h"
+#include "include/core/SkRect.h"
#include "include/private/GrTypesPriv.h"
+#include "include/private/SkVx.h"
#include "src/gpu/geometry/GrQuad.h"
+using V4f = skvx::Vec<4, float>;
+using M4f = skvx::Vec<4, int32_t>;
+
+// Since the local quad may not be type kRect, this uses the opposites for each vertex when
+// interpolating, and calculates new ws in addition to new xs, ys.
+static void interpolate_local(float alpha, int v0, int v1, int v2, int v3,
+ float lx[4], float ly[4], float lw[4]) {
+ SkASSERT(v0 >= 0 && v0 < 4);
+ SkASSERT(v1 >= 0 && v1 < 4);
+ SkASSERT(v2 >= 0 && v2 < 4);
+ SkASSERT(v3 >= 0 && v3 < 4);
+
+ float beta = 1.f - alpha;
+ lx[v0] = alpha * lx[v0] + beta * lx[v2];
+ ly[v0] = alpha * ly[v0] + beta * ly[v2];
+ lw[v0] = alpha * lw[v0] + beta * lw[v2];
+
+ lx[v1] = alpha * lx[v1] + beta * lx[v3];
+ ly[v1] = alpha * ly[v1] + beta * ly[v3];
+ lw[v1] = alpha * lw[v1] + beta * lw[v3];
+}
+
+// Crops v0 to v1 based on the clipDevRect. v2 is opposite of v0, v3 is opposite of v1.
+// It is written to not modify coordinates if there's no intersection along the edge.
+// Ideally this would have been detected earlier and the entire draw is skipped.
+static bool crop_rect_edge(const SkRect& clipDevRect, int v0, int v1, int v2, int v3,
+ float x[4], float y[4], float lx[4], float ly[4], float lw[4]) {
+ SkASSERT(v0 >= 0 && v0 < 4);
+ SkASSERT(v1 >= 0 && v1 < 4);
+ SkASSERT(v2 >= 0 && v2 < 4);
+ SkASSERT(v3 >= 0 && v3 < 4);
+
+ if (SkScalarNearlyEqual(x[v0], x[v1])) {
+ // A vertical edge
+ if (x[v0] < clipDevRect.fLeft && x[v2] >= clipDevRect.fLeft) {
+ // Overlapping with left edge of clipDevRect
+ if (lx) {
+ float alpha = (x[v2] - clipDevRect.fLeft) / (x[v2] - x[v0]);
+ interpolate_local(alpha, v0, v1, v2, v3, lx, ly, lw);
+ }
+ x[v0] = clipDevRect.fLeft;
+ x[v1] = clipDevRect.fLeft;
+ return true;
+ } else if (x[v0] > clipDevRect.fRight && x[v2] <= clipDevRect.fRight) {
+ // Overlapping with right edge of clipDevRect
+ if (lx) {
+ float alpha = (clipDevRect.fRight - x[v2]) / (x[v0] - x[v2]);
+ interpolate_local(alpha, v0, v1, v2, v3, lx, ly, lw);
+ }
+ x[v0] = clipDevRect.fRight;
+ x[v1] = clipDevRect.fRight;
+ return true;
+ }
+ } else {
+ // A horizontal edge
+ SkASSERT(SkScalarNearlyEqual(y[v0], y[v1]));
+ if (y[v0] < clipDevRect.fTop && y[v2] >= clipDevRect.fTop) {
+ // Overlapping with top edge of clipDevRect
+ if (lx) {
+ float alpha = (y[v2] - clipDevRect.fTop) / (y[v2] - y[v0]);
+ interpolate_local(alpha, v0, v1, v2, v3, lx, ly, lw);
+ }
+ y[v0] = clipDevRect.fTop;
+ y[v1] = clipDevRect.fTop;
+ return true;
+ } else if (y[v0] > clipDevRect.fBottom && y[v2] <= clipDevRect.fBottom) {
+ // Overlapping with bottom edge of clipDevRect
+ if (lx) {
+ float alpha = (clipDevRect.fBottom - y[v2]) / (y[v0] - y[v2]);
+ interpolate_local(alpha, v0, v1, v2, v3, lx, ly, lw);
+ }
+ y[v0] = clipDevRect.fBottom;
+ y[v1] = clipDevRect.fBottom;
+ return true;
+ }
+ }
+
+ // No overlap so don't crop it
+ return false;
+}
+
+// Updates x and y to intersect with clipDevRect, and applies clipAA policy to edgeFlags for each
+// intersected edge. lx, ly, and lw are updated appropriately and may be null to skip calculations.
+static void crop_rect(const SkRect& clipDevRect, GrAA clipAA, GrQuadAAFlags* edgeFlags,
+ float x[4], float y[4], float lx[4], float ly[4], float lw[4]) {
+ // Filled in as if clipAA were true, will be inverted at the end if needed.
+ GrQuadAAFlags clipEdgeFlags = GrQuadAAFlags::kNone;
+
+ // However, the quad's left edge may not align with the SkRect notion of left due to 90 degree
+ // rotations or mirrors. So, this processes the logical edges of the quad and clamps it to the 4
+ // sides of clipDevRect.
+
+ // Quad's left is v0 to v1 (op. v2 and v3)
+ if (crop_rect_edge(clipDevRect, 0, 1, 2, 3, x, y, lx, ly, lw)) {
+ clipEdgeFlags |= GrQuadAAFlags::kLeft;
+ }
+ // Quad's top edge is v0 to v2 (op. v1 and v3)
+ if (crop_rect_edge(clipDevRect, 0, 2, 1, 3, x, y, lx, ly, lw)) {
+ clipEdgeFlags |= GrQuadAAFlags::kTop;
+ }
+ // Quad's right edge is v2 to v3 (op. v0 and v1)
+ if (crop_rect_edge(clipDevRect, 2, 3, 0, 1, x, y, lx, ly, lw)) {
+ clipEdgeFlags |= GrQuadAAFlags::kRight;
+ }
+ // Quad's bottom edge is v1 to v3 (op. v0 and v2)
+ if (crop_rect_edge(clipDevRect, 1, 3, 0, 2, x, y, lx, ly, lw)) {
+ clipEdgeFlags |= GrQuadAAFlags::kBottom;
+ }
+
+ if (clipAA == GrAA::kYes) {
+ // Turn on all edges that were clipped
+ *edgeFlags |= clipEdgeFlags;
+ } else {
+ // Turn off all edges that were clipped
+ *edgeFlags &= ~clipEdgeFlags;
+ }
+}
+
+// Calculates barycentric coordinates for each point in (testX, testY) in the triangle formed by
+// (x0,y0) - (x1,y1) - (x2, y2) and stores them in u, v, w.
+static void barycentric_coords(float x0, float y0, float x1, float y1, float x2, float y2,
+ const V4f& testX, const V4f& testY,
+ V4f* u, V4f* v, V4f* w) {
+ // Modeled after SkPathOpsQuad::pointInTriangle() but uses float instead of double, is
+ // vectorized and outputs normalized barycentric coordinates instead of inside/outside test
+ float v0x = x2 - x0;
+ float v0y = y2 - y0;
+ float v1x = x1 - x0;
+ float v1y = y1 - y0;
+ V4f v2x = testX - x0;
+ V4f v2y = testY - y0;
+
+ float dot00 = v0x * v0x + v0y * v0y;
+ float dot01 = v0x * v1x + v0y * v1y;
+ V4f dot02 = v0x * v2x + v0y * v2y;
+ float dot11 = v1x * v1x + v1y * v1y;
+ V4f dot12 = v1x * v2x + v1y * v2y;
+ float invDenom = sk_ieee_float_divide(1.f, dot00 * dot11 - dot01 * dot01);
+ *u = (dot11 * dot02 - dot01 * dot12) * invDenom;
+ *v = (dot00 * dot12 - dot01 * dot02) * invDenom;
+ *w = 1.f - *u - *v;
+}
+
+static M4f inside_triangle(const V4f& u, const V4f& v, const V4f& w) {
+ return ((u >= 0.f) & (u <= 1.f)) & ((v >= 0.f) & (v <= 1.f)) & ((w >= 0.f) & (w <= 1.f));
+}
+
namespace GrQuadUtils {
void ResolveAAType(GrAAType requestedAAType, GrQuadAAFlags requestedEdgeFlags, const GrQuad& quad,
@@ -45,6 +194,79 @@
SK_ABORT("Should not use mixed sample AA with edge AA flags");
break;
}
-};
+}
+
+bool CropToRect(const SkRect& cropRect, GrAA cropAA, GrQuadAAFlags* edgeFlags, GrQuad* quad,
+ GrQuad* local) {
+ if (quad->quadType() == GrQuad::Type::kAxisAligned) {
+ // crop_rect keeps the rectangles as rectangles, so there's no need to modify types
+ if (local) {
+ crop_rect(cropRect, cropAA, edgeFlags, quad->xs(), quad->ys(),
+ local->xs(), local->ys(), local->ws());
+ } else {
+ crop_rect(cropRect, cropAA, edgeFlags, quad->xs(), quad->ys(),
+ nullptr, nullptr, nullptr);
+ }
+ return true;
+ }
+
+ if (local) {
+ // FIXME (michaelludwig) Calculate cropped local coordinates when not kAxisAligned
+ return false;
+ }
+
+ V4f devX = quad->x4f();
+ V4f devY = quad->y4f();
+ V4f devIW = quad->iw4f();
+ // Project the 3D coordinates to 2D
+ if (quad->quadType() == GrQuad::Type::kPerspective) {
+ devX *= devIW;
+ devY *= devIW;
+ }
+
+ V4f clipX = {cropRect.fLeft, cropRect.fLeft, cropRect.fRight, cropRect.fRight};
+ V4f clipY = {cropRect.fTop, cropRect.fBottom, cropRect.fTop, cropRect.fBottom};
+
+ // Calculate barycentric coordinates for the 4 rect corners in the 2 triangles that the quad
+ // is tessellated into when drawn.
+ V4f u1, v1, w1;
+ barycentric_coords(devX[0], devY[0], devX[1], devY[1], devX[2], devY[2], clipX, clipY,
+ &u1, &v1, &w1);
+ V4f u2, v2, w2;
+ barycentric_coords(devX[1], devY[1], devX[3], devY[3], devX[2], devY[2], clipX, clipY,
+ &u2, &v2, &w2);
+
+ // clipDevRect is completely inside this quad if each corner is in at least one of two triangles
+ M4f inTri1 = inside_triangle(u1, v1, w1);
+ M4f inTri2 = inside_triangle(u2, v2, w2);
+ if (all(inTri1 | inTri2)) {
+ // We can crop to exactly the clipDevRect.
+ // FIXME (michaelludwig) - there are other ways to have determined quad covering the clip
+ // rect, but the barycentric coords will be useful to derive local coordinates in the future
+
+ // Since we are cropped to exactly clipDevRect, we have discarded any perspective and the
+ // type becomes kRect. If updated locals were requested, they will incorporate perspective.
+ // FIXME (michaelludwig) - once we have local coordinates handled, it may be desirable to
+ // keep the draw as perspective so that the hardware does perspective interpolation instead
+ // of pushing it into a local coord w and having the shader do an extra divide.
+ clipX.store(quad->xs());
+ clipY.store(quad->ys());
+ quad->ws()[0] = 1.f;
+ quad->ws()[1] = 1.f;
+ quad->ws()[2] = 1.f;
+ quad->ws()[3] = 1.f;
+ quad->setQuadType(GrQuad::Type::kAxisAligned);
+
+ // Update the edge flags to match the clip setting since all 4 edges have been clipped
+ *edgeFlags = cropAA == GrAA::kYes ? GrQuadAAFlags::kAll : GrQuadAAFlags::kNone;
+
+ return true;
+ }
+
+ // FIXME (michaelludwig) - use the GrQuadPerEdgeAA tessellation inset/outset math to move
+ // edges to the closest clip corner they are outside of
+
+ return false;
+}
}; // namespace GrQuadUtils
diff --git a/src/gpu/geometry/GrQuadUtils.h b/src/gpu/geometry/GrQuadUtils.h
index 29feeeb..5f6d283 100644
--- a/src/gpu/geometry/GrQuadUtils.h
+++ b/src/gpu/geometry/GrQuadUtils.h
@@ -9,8 +9,10 @@
#define GrQuadUtils_DEFINED
enum class GrQuadAAFlags;
+enum class GrAA : bool;
enum class GrAAType : unsigned;
class GrQuad;
+struct SkRect;
namespace GrQuadUtils {
@@ -19,6 +21,23 @@
void ResolveAAType(GrAAType requestedAAType, GrQuadAAFlags requestedEdgeFlags,
const GrQuad& quad, GrAAType* outAAtype, GrQuadAAFlags* outEdgeFlags);
+ /**
+ * Crops quad to the provided device-space axis-aligned rectangle. If the intersection of this
+ * quad (projected) and cropRect results in a quadrilateral, this returns true. If not, this
+ * quad may be updated to be a smaller quad of the same type such that its intersection with
+ * cropRect is visually the same.
+ *
+ * The provided edge flags are updated to reflect edges clipped by cropRect (toggling on or off
+ * based on cropAA policy). If provided, the local coordinates will be updated to reflect the
+ * updated device coordinates of this quad.
+ *
+ * 'local' may be null, in which case the new local coordinates will not be calculated. This is
+ * useful when it's known a paint does not require local coordinates. However, neither
+ * 'edgeFlags' nore 'quad' can be null.
+ */
+ bool CropToRect(const SkRect& cropRect, GrAA cropAA, GrQuadAAFlags* edgeFlags, GrQuad* quad,
+ GrQuad* local=nullptr);
+
}; // namespace GrQuadUtils
#endif