* Copyright 2020 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "src/gpu/ganesh/ClipStack.h"
#include "include/core/SkAlphaType.h"
#include "include/core/SkBlendMode.h"
#include "include/core/SkClipOp.h"
#include "include/core/SkColorSpace.h"
#include "include/core/SkMatrix.h"
#include "include/core/SkPath.h"
#include "include/core/SkRRect.h"
#include "include/core/SkRegion.h"
#include "include/core/SkSamplingOptions.h"
#include "include/core/SkScalar.h"
#include "include/gpu/GpuTypes.h"
#include "include/gpu/ganesh/GrBackendSurface.h"
#include "include/gpu/ganesh/GrDirectContext.h"
#include "include/gpu/ganesh/GrRecordingContext.h"
#include "include/gpu/ganesh/GrTypes.h"
#include "include/private/base/SkPoint_impl.h"
#include "include/private/base/SkTArray.h"
#include "include/private/base/SkTo.h"
#include "include/private/gpu/ganesh/GrTypesPriv.h"
#include "src/base/SkVx.h"
#include "src/core/SkPathPriv.h"
#include "src/core/SkRRectPriv.h"
#include "src/core/SkRectPriv.h"
#include "src/core/SkTaskGroup.h"
#include "src/core/SkTraceEvent.h"
#include "src/gpu/SkBackingFit.h"
#include "src/gpu/Swizzle.h"
#include "src/gpu/ganesh/GrAppliedClip.h"
#include "src/gpu/ganesh/GrCaps.h"
#include "src/gpu/ganesh/GrClip.h"
#include "src/gpu/ganesh/GrColorInfo.h"
#include "src/gpu/ganesh/GrDeferredProxyUploader.h"
#include "src/gpu/ganesh/GrDirectContextPriv.h"
#include "src/gpu/ganesh/GrDrawingManager.h"
#include "src/gpu/ganesh/GrFPArgs.h"
#include "src/gpu/ganesh/GrFragmentProcessor.h"
#include "src/gpu/ganesh/GrFragmentProcessors.h"
#include "src/gpu/ganesh/GrProxyProvider.h"
#include "src/gpu/ganesh/GrRecordingContextPriv.h"
#include "src/gpu/ganesh/GrRenderTargetProxy.h"
#include "src/gpu/ganesh/GrSWMaskHelper.h"
#include "src/gpu/ganesh/GrSamplerState.h"
#include "src/gpu/ganesh/GrSurfaceProxy.h"
#include "src/gpu/ganesh/GrSurfaceProxyView.h"
#include "src/gpu/ganesh/GrTextureProxy.h"
#include "src/gpu/ganesh/GrTextureProxyPriv.h"
#include "src/gpu/ganesh/GrWindowRectangles.h"
#include "src/gpu/ganesh/GrWindowRectsState.h"
#include "src/gpu/ganesh/StencilMaskHelper.h"
#include "src/gpu/ganesh/SurfaceDrawContext.h"
#include "src/gpu/ganesh/effects/GrBlendFragmentProcessor.h"
#include "src/gpu/ganesh/effects/GrConvexPolyEffect.h"
#include "src/gpu/ganesh/effects/GrRRectEffect.h"
#include "src/gpu/ganesh/effects/GrTextureEffect.h"
#include "src/gpu/ganesh/geometry/GrQuad.h"
#include "src/gpu/ganesh/geometry/GrQuadUtils.h"
#include "src/gpu/ganesh/ops/AtlasPathRenderer.h"
#include "src/gpu/ganesh/ops/GrDrawOp.h"
#include <algorithm>
#include <atomic>
#include <functional>
#include <tuple>
#include <utility>
class GrOp;
struct GrShaderCaps;
using namespace skia_private;
namespace {
enum class ClipGeometry {
kEmpty,
kAOnly,
kBOnly,
kBoth
};
template<typename A, typename B>
ClipGeometry get_clip_geometry(const A& a, const B& b) {
if (a.op() == SkClipOp::kIntersect) {
if (b.op() == SkClipOp::kIntersect) {
if (!SkIRect::Intersects(a.outerBounds(), b.outerBounds())) {
return ClipGeometry::kEmpty;
} else if (b.contains(a)) {
return ClipGeometry::kAOnly;
} else if (a.contains(b)) {
return ClipGeometry::kBOnly;
} else {
return ClipGeometry::kBoth;
}
} else {
SkASSERT(b.op() == SkClipOp::kDifference);
if (!SkIRect::Intersects(a.outerBounds(), b.outerBounds())) {
return ClipGeometry::kAOnly;
} else if (b.contains(a)) {
return ClipGeometry::kEmpty;
} else {
return ClipGeometry::kBoth;
}
}
} else {
SkASSERT(a.op() == SkClipOp::kDifference);
if (b.op() == SkClipOp::kIntersect) {
if (!SkIRect::Intersects(b.outerBounds(), a.outerBounds())) {
return ClipGeometry::kBOnly;
} else if (a.contains(b)) {
return ClipGeometry::kEmpty;
} else {
return ClipGeometry::kBoth;
}
} else {
SkASSERT(b.op() == SkClipOp::kDifference);
if (a.contains(b)) {
return ClipGeometry::kAOnly;
} else if (b.contains(a)) {
return ClipGeometry::kBOnly;
} else {
return ClipGeometry::kBoth;
}
}
}
}
bool shape_contains_rect(const GrShape& a, const SkMatrix& aToDevice, const SkMatrix& deviceToA,
const SkRect& b, const SkMatrix& bToDevice, bool mixedAAMode) {
if (!a.convex()) {
return false;
}
if (!mixedAAMode && aToDevice == bToDevice) {
return a.conservativeContains(b);
} else if (bToDevice.isIdentity() && aToDevice.preservesAxisAlignment()) {
SkRect bInA = b;
if (mixedAAMode) {
bInA.outset(0.5f, 0.5f);
}
SkAssertResult(deviceToA.mapRect(&bInA));
return a.conservativeContains(bInA);
}
GrQuad deviceQuad = GrQuad::MakeFromRect(b, bToDevice);
if (mixedAAMode) {
GrQuadUtils::Outset({0.5f, 0.5f, 0.5f, 0.5f}, &deviceQuad);
}
if (any(deviceQuad.w4f() < SkPathPriv::kW0PlaneDistance)) {
return false;
}
for (int i = 0; i < 4; ++i) {
SkPoint cornerInA = deviceQuad.point(i);
deviceToA.mapPoints(&cornerInA, 1);
if (!a.conservativeContains(cornerInA)) {
return false;
}
}
return true;
}
SkIRect subtract(const SkIRect& a, const SkIRect& b, bool exact) {
SkIRect diff;
if (SkRectPriv::Subtract(a, b, &diff) || !exact) {
return diff;
} else {
return a;
}
}
GrClipEdgeType get_clip_edge_type(SkClipOp op, GrAA aa) {
if (op == SkClipOp::kIntersect) {
return aa == GrAA::kYes ? GrClipEdgeType::kFillAA : GrClipEdgeType::kFillBW;
} else {
return aa == GrAA::kYes ? GrClipEdgeType::kInverseFillAA : GrClipEdgeType::kInverseFillBW;
}
}
static uint32_t kInvalidGenID = 0;
static uint32_t kEmptyGenID = 1;
static uint32_t kWideOpenGenID = 2;
uint32_t next_gen_id() {
static const uint32_t kFirstUnreservedGenID = 3;
static std::atomic<uint32_t> nextID{kFirstUnreservedGenID};
uint32_t id;
do {
id = nextID.fetch_add(1, std::memory_order_relaxed);
} while (id < kFirstUnreservedGenID);
return id;
}
static constexpr GrSurfaceOrigin kMaskOrigin = kTopLeft_GrSurfaceOrigin;
GrFPResult analytic_clip_fp(const skgpu::ganesh::ClipStack::Element& e,
const GrShaderCaps& caps,
std::unique_ptr<GrFragmentProcessor> fp) {
GrClipEdgeType edgeType = get_clip_edge_type(e.fOp, e.fAA);
if (e.fLocalToDevice.isIdentity()) {
if (e.fShape.isRect()) {
return GrFPSuccess(GrFragmentProcessor::Rect(std::move(fp), edgeType, e.fShape.rect()));
} else if (e.fShape.isRRect()) {
return GrRRectEffect::Make(std::move(fp), edgeType, e.fShape.rrect(), caps);
}
}
if (e.fShape.segmentMask() == SkPath::kLine_SegmentMask && e.fShape.convex()) {
SkPath devicePath;
e.fShape.asPath(&devicePath);
devicePath.transform(e.fLocalToDevice);
return GrConvexPolyEffect::Make(std::move(fp), edgeType, devicePath);
}
return GrFPFailure(std::move(fp));
}
GrFPResult clip_atlas_fp(const skgpu::ganesh::SurfaceDrawContext* sdc,
const GrOp* opBeingClipped,
skgpu::ganesh::AtlasPathRenderer* atlasPathRenderer,
const SkIRect& scissorBounds,
const skgpu::ganesh::ClipStack::Element& e,
std::unique_ptr<GrFragmentProcessor> inputFP) {
if (e.fAA != GrAA::kYes) {
return GrFPFailure(std::move(inputFP));
}
SkPath path;
e.fShape.asPath(&path);
SkASSERT(!path.isInverseFillType());
if (e.fOp == SkClipOp::kDifference) {
path.toggleInverseFillType();
}
return atlasPathRenderer->makeAtlasClipEffect(sdc, opBeingClipped, std::move(inputFP),
scissorBounds, e.fLocalToDevice, path);
}
void draw_to_sw_mask(GrSWMaskHelper* helper,
const skgpu::ganesh::ClipStack::Element& e,
bool clearMask) {
if (clearMask) {
helper->clear(e.fOp == SkClipOp::kIntersect ? 0x00 : 0xFF);
}
uint8_t alpha;
bool invert;
if (e.fOp == SkClipOp::kIntersect) {
if (clearMask) {
alpha = 0xFF;
invert = false;
} else {
alpha = 0x00;
invert = true;
}
} else {
SkASSERT(e.fOp == SkClipOp::kDifference);
alpha = 0x00;
invert = false;
}
if (invert) {
SkASSERT(!e.fShape.inverted());
GrShape inverted(e.fShape);
inverted.setInverted(true);
helper->drawShape(inverted, e.fLocalToDevice, e.fAA, alpha);
} else {
helper->drawShape(e.fShape, e.fLocalToDevice, e.fAA, alpha);
}
}
GrSurfaceProxyView render_sw_mask(GrRecordingContext* context,
const SkIRect& bounds,
const skgpu::ganesh::ClipStack::Element** elements,
int count) {
SkASSERT(count > 0);
SkTaskGroup* taskGroup = nullptr;
if (auto direct = context->asDirectContext()) {
taskGroup = direct->priv().getTaskGroup();
}
if (taskGroup) {
const GrCaps* caps = context->priv().caps();
GrProxyProvider* proxyProvider = context->priv().proxyProvider();
GrBackendFormat format = caps->getDefaultBackendFormat(GrColorType::kAlpha_8,
GrRenderable::kNo);
skgpu::Swizzle swizzle = context->priv().caps()->getReadSwizzle(format,
GrColorType::kAlpha_8);
auto proxy = proxyProvider->createProxy(format,
bounds.size(),
GrRenderable::kNo,
1,
skgpu::Mipmapped::kNo,
SkBackingFit::kApprox,
skgpu::Budgeted::kYes,
GrProtected::kNo,
"ClipStack_RenderSwMask");
using Uploader = GrTDeferredProxyUploader<TArray<skgpu::ganesh::ClipStack::Element>>;
std::unique_ptr<Uploader> uploader = std::make_unique<Uploader>(count);
for (int i = 0; i < count; ++i) {
uploader->data().push_back(*(elements[i]));
}
Uploader* uploaderRaw = uploader.get();
auto drawAndUploadMask = [uploaderRaw, bounds] {
TRACE_EVENT0("skia.gpu", "Threaded SW Clip Mask Render");
GrSWMaskHelper helper(uploaderRaw->getPixels());
if (helper.init(bounds)) {
for (int i = 0; i < uploaderRaw->data().size(); ++i) {
draw_to_sw_mask(&helper, uploaderRaw->data()[i], i == 0);
}
} else {
SkDEBUGFAIL("Unable to allocate SW clip mask.");
}
uploaderRaw->signalAndFreeData();
};
taskGroup->add(std::move(drawAndUploadMask));
proxy->texPriv().setDeferredUploader(std::move(uploader));
return {std::move(proxy), kMaskOrigin, swizzle};
} else {
GrSWMaskHelper helper;
if (!helper.init(bounds)) {
return {};
}
for (int i = 0; i < count; ++i) {
draw_to_sw_mask(&helper,*(elements[i]), i == 0);
}
return helper.toTextureView(context, SkBackingFit::kApprox);
}
}
void render_stencil_mask(GrRecordingContext* rContext,
skgpu::ganesh::SurfaceDrawContext* sdc,
uint32_t genID,
const SkIRect& bounds,
const skgpu::ganesh::ClipStack::Element** elements,
int count,
GrAppliedClip* out) {
skgpu::ganesh::StencilMaskHelper helper(rContext, sdc);
if (helper.init(bounds, genID, out->windowRectsState().windows(), 0)) {
bool startInside = elements[0]->fOp == SkClipOp::kDifference;
helper.clear(startInside);
for (int i = 0; i < count; ++i) {
const skgpu::ganesh::ClipStack::Element& e = *(elements[i]);
SkRegion::Op op;
if (e.fOp == SkClipOp::kIntersect) {
op = (i == 0) ? SkRegion::kReplace_Op : SkRegion::kIntersect_Op;
} else {
op = SkRegion::kDifference_Op;
}
helper.drawShape(e.fShape, e.fLocalToDevice, op, e.fAA);
}
helper.finish();
}
out->hardClip().addStencilClip(genID);
}
}
namespace skgpu::ganesh {
class ClipStack::Draw {
public:
Draw(const SkRect& drawBounds, GrAA aa)
: fBounds(GrClip::GetPixelIBounds(drawBounds, aa, BoundsType::kExterior))
, fAA(aa) {
fOriginalBounds = drawBounds.makeInset(GrClip::kBoundsTolerance, GrClip::kBoundsTolerance);
if (fOriginalBounds.isEmpty()) {
fOriginalBounds = drawBounds;
}
}
SkClipOp op() const { return SkClipOp::kIntersect; }
const SkIRect& outerBounds() const { return fBounds; }
bool contains(const RawElement& e) const { return false; }
bool contains(const SaveRecord& s) const { return false; }
bool applyDeviceBounds(const SkIRect& deviceBounds) {
return fBounds.intersect(deviceBounds);
}
const SkRect& bounds() const { return fOriginalBounds; }
GrAA aa() const { return fAA; }
private:
SkRect fOriginalBounds;
SkIRect fBounds;
GrAA fAA;
};
ClipStack::RawElement::RawElement(const SkMatrix& localToDevice, const GrShape& shape,
GrAA aa, SkClipOp op)
: Element{shape, localToDevice, op, aa}
, fInnerBounds(SkIRect::MakeEmpty())
, fOuterBounds(SkIRect::MakeEmpty())
, fInvalidatedByIndex(-1) {
if (!localToDevice.invert(&fDeviceToLocal)) {
fShape.reset();
}
}
void ClipStack::RawElement::markInvalid(const SaveRecord& current) {
SkASSERT(!this->isInvalid());
fInvalidatedByIndex = current.firstActiveElementIndex();
}
void ClipStack::RawElement::restoreValid(const SaveRecord& current) {
if (current.firstActiveElementIndex() < fInvalidatedByIndex) {
fInvalidatedByIndex = -1;
}
}
bool ClipStack::RawElement::contains(const Draw& d) const {
if (fInnerBounds.contains(d.outerBounds())) {
return true;
} else {
SkRect queryBounds = d.aa() == GrAA::kYes ? d.bounds() : SkRect::Make(d.outerBounds());
return shape_contains_rect(fShape, fLocalToDevice, fDeviceToLocal,
queryBounds, SkMatrix::I(), false);
}
}
bool ClipStack::RawElement::contains(const SaveRecord& s) const {
if (fInnerBounds.contains(s.outerBounds())) {
return true;
} else {
SkRect queryBounds = SkRect::Make(s.outerBounds());
return shape_contains_rect(fShape, fLocalToDevice, fDeviceToLocal,
queryBounds, SkMatrix::I(), false);
}
}
bool ClipStack::RawElement::contains(const RawElement& e) const {
if (fInnerBounds.contains(e.fOuterBounds)) {
return true;
}
bool mixedAA = fAA != e.fAA;
if (!mixedAA && fLocalToDevice == e.fLocalToDevice) {
static constexpr int kMaxPathComparePoints = 16;
if (fShape.isRRect() && e.fShape.isRRect()) {
return SkRRectPriv::ConservativeIntersect(fShape.rrect(), e.fShape.rrect())
== e.fShape.rrect();
} else if (fShape.isPath() && e.fShape.isPath()) {
return fShape.path().getGenerationID() == e.fShape.path().getGenerationID() ||
(fShape.path().getPoints(nullptr, 0) <= kMaxPathComparePoints &&
fShape.path() == e.fShape.path());
}
}
return shape_contains_rect(fShape, fLocalToDevice, fDeviceToLocal,
e.fShape.bounds(), e.fLocalToDevice, mixedAA);
}
void ClipStack::RawElement::simplify(const SkIRect& deviceBounds, bool forceAA) {
if (fShape.inverted()) {
fOp = fOp == SkClipOp::kIntersect ? SkClipOp::kDifference : SkClipOp::kIntersect;
fShape.setInverted(false);
}
fShape.simplify();
SkASSERT(!fShape.inverted());
if (fShape.isEmpty()) {
return;
}
SkASSERT(!fShape.isPoint() && !fShape.isLine());
SkASSERT(!fShape.isArc());
SkRect outer = fLocalToDevice.mapRect(fShape.bounds());
if (!outer.intersect(SkRect::Make(deviceBounds))) {
fShape.reset();
return;
}
if (forceAA && !(fShape.isRect() && fLocalToDevice.preservesAxisAlignment())) {
fAA = GrAA::kYes;
}
fOuterBounds = GrClip::GetPixelIBounds(outer, fAA, BoundsType::kExterior);
if (fLocalToDevice.preservesAxisAlignment()) {
if (fShape.isRect()) {
fShape.rect() = outer;
fLocalToDevice.setIdentity();
fDeviceToLocal.setIdentity();
if (fAA == GrAA::kNo && outer.width() >= 1.f && outer.height() >= 1.f) {
fOuterBounds = outer.round();
fInnerBounds = fOuterBounds;
} else {
fInnerBounds = GrClip::GetPixelIBounds(outer, fAA, BoundsType::kInterior);
SkASSERT(fOuterBounds.contains(fInnerBounds) || fInnerBounds.isEmpty());
}
} else if (fShape.isRRect()) {
SkRRect src;
if (fShape.rrect().transform(fLocalToDevice, &src)) {
fShape.rrect() = src;
fLocalToDevice.setIdentity();
fDeviceToLocal.setIdentity();
SkRect inner = SkRRectPriv::InnerBounds(fShape.rrect());
fInnerBounds = GrClip::GetPixelIBounds(inner, fAA, BoundsType::kInterior);
if (!fInnerBounds.intersect(deviceBounds)) {
fInnerBounds = SkIRect::MakeEmpty();
}
}
}
}
if (fOuterBounds.isEmpty()) {
fShape.reset();
}
SkASSERT(fShape.isEmpty() || (!fOuterBounds.isEmpty() && deviceBounds.contains(fOuterBounds)));
SkASSERT(fShape.isEmpty() || fInnerBounds.isEmpty() || fOuterBounds.contains(fInnerBounds));
}
bool ClipStack::RawElement::combine(const RawElement& other, const SaveRecord& current) {
if (other.fOp != SkClipOp::kIntersect || fOp != SkClipOp::kIntersect) {
return false;
}
bool shapeUpdated = false;
if (fShape.isRect() && other.fShape.isRect()) {
bool aaMatch = fAA == other.fAA;
if (fLocalToDevice.isIdentity() && other.fLocalToDevice.isIdentity() && !aaMatch) {
if (GrClip::IsPixelAligned(fShape.rect())) {
fAA = other.fAA;
} else if (!GrClip::IsPixelAligned(other.fShape.rect())) {
return false;
}
aaMatch = true;
}
if (aaMatch && fLocalToDevice == other.fLocalToDevice) {
if (!fShape.rect().intersect(other.fShape.rect())) {
this->fShape.reset();
this->markInvalid(current);
return true;
}
shapeUpdated = true;
}
} else if ((fShape.isRect() || fShape.isRRect()) &&
(other.fShape.isRect() || other.fShape.isRRect())) {
if (fAA == other.fAA && fLocalToDevice == other.fLocalToDevice) {
SkRRect a = fShape.isRect() ? SkRRect::MakeRect(fShape.rect()) : fShape.rrect();
SkRRect b = other.fShape.isRect() ? SkRRect::MakeRect(other.fShape.rect())
: other.fShape.rrect();
SkRRect joined = SkRRectPriv::ConservativeIntersect(a, b);
if (!joined.isEmpty()) {
if (joined.isRect()) {
fShape.setRect(joined.rect());
} else {
fShape.setRRect(joined);
}
shapeUpdated = true;
} else if (!a.getBounds().intersects(b.getBounds())) {
fShape.reset();
this->markInvalid(current);
return true;
}
}
}
if (shapeUpdated) {
SkASSERT(fOp == SkClipOp::kIntersect && other.fOp == SkClipOp::kIntersect);
SkAssertResult(fOuterBounds.intersect(other.fOuterBounds));
if (!fInnerBounds.intersect(other.fInnerBounds)) {
fInnerBounds = SkIRect::MakeEmpty();
}
return true;
} else {
return false;
}
}
void ClipStack::RawElement::updateForElement(RawElement* added, const SaveRecord& current) {
if (this->isInvalid()) {
return;
}
switch (get_clip_geometry(*this, *added)) {
case ClipGeometry::kEmpty:
this->markInvalid(current);
added->markInvalid(current);
break;
case ClipGeometry::kAOnly:
added->markInvalid(current);
break;
case ClipGeometry::kBOnly:
this->markInvalid(current);
break;
case ClipGeometry::kBoth:
if (added->combine(*this, current)) {
this->markInvalid(current);
}
break;
}
}
ClipStack::ClipState ClipStack::RawElement::clipType() const {
switch (fShape.type()) {
case GrShape::Type::kEmpty:
return ClipState::kEmpty;
case GrShape::Type::kRect:
return fOp == SkClipOp::kIntersect && fLocalToDevice.isIdentity()
? ClipState::kDeviceRect : ClipState::kComplex;
case GrShape::Type::kRRect:
return fOp == SkClipOp::kIntersect && fLocalToDevice.isIdentity()
? ClipState::kDeviceRRect : ClipState::kComplex;
case GrShape::Type::kArc:
case GrShape::Type::kLine:
case GrShape::Type::kPoint:
SkASSERT(false);
[[fallthrough]];
case GrShape::Type::kPath:
return ClipState::kComplex;
}
SkUNREACHABLE;
}
ClipStack::Mask::Mask(const SaveRecord& current, const SkIRect& drawBounds)
: fBounds(drawBounds)
, fGenID(current.genID()) {
static const UniqueKey::Domain kDomain = UniqueKey::GenerateDomain();
SkASSERT(fGenID != kInvalidGenID && fGenID != kEmptyGenID && fGenID != kWideOpenGenID);
UniqueKey::Builder builder(&fKey, kDomain, 5, "clip_mask");
builder[0] = fGenID;
builder[1] = drawBounds.fLeft;
builder[2] = drawBounds.fRight;
builder[3] = drawBounds.fTop;
builder[4] = drawBounds.fBottom;
SkASSERT(fKey.isValid());
SkDEBUGCODE(fOwner = ¤t;)
}
bool ClipStack::Mask::appliesToDraw(const SaveRecord& current, const SkIRect& drawBounds) const {
SkASSERT(fGenID != current.genID() || ¤t == fOwner);
return fGenID == current.genID() && fBounds.contains(drawBounds);
}
void ClipStack::Mask::invalidate(GrProxyProvider* proxyProvider) {
SkASSERT(proxyProvider);
SkASSERT(fKey.isValid());
proxyProvider->processInvalidUniqueKey(
fKey, nullptr, GrProxyProvider::InvalidateGPUResource::kYes);
fKey.reset();
}
ClipStack::SaveRecord::SaveRecord(const SkIRect& deviceBounds)
: fInnerBounds(deviceBounds)
, fOuterBounds(deviceBounds)
, fShader(nullptr)
, fStartingMaskIndex(0)
, fStartingElementIndex(0)
, fOldestValidIndex(0)
, fDeferredSaveCount(0)
, fStackOp(SkClipOp::kIntersect)
, fState(ClipState::kWideOpen)
, fGenID(kInvalidGenID) {}
ClipStack::SaveRecord::SaveRecord(const SaveRecord& prior,
int startingMaskIndex,
int startingElementIndex)
: fInnerBounds(prior.fInnerBounds)
, fOuterBounds(prior.fOuterBounds)
, fShader(prior.fShader)
, fStartingMaskIndex(startingMaskIndex)
, fStartingElementIndex(startingElementIndex)
, fOldestValidIndex(prior.fOldestValidIndex)
, fDeferredSaveCount(0)
, fStackOp(prior.fStackOp)
, fState(prior.fState)
, fGenID(kInvalidGenID) {
SkASSERT(startingMaskIndex >= prior.fStartingMaskIndex);
SkASSERT(startingElementIndex >= prior.fStartingElementIndex);
}
uint32_t ClipStack::SaveRecord::genID() const {
if (fState == ClipState::kEmpty) {
return kEmptyGenID;
} else if (fState == ClipState::kWideOpen) {
return kWideOpenGenID;
} else {
SkASSERT(fGenID != kEmptyGenID && fGenID != kWideOpenGenID);
return fGenID;
}
}
ClipStack::ClipState ClipStack::SaveRecord::state() const {
if (fShader && fState != ClipState::kEmpty) {
return ClipState::kComplex;
} else {
return fState;
}
}
bool ClipStack::SaveRecord::contains(const ClipStack::Draw& draw) const {
return fInnerBounds.contains(draw.outerBounds());
}
bool ClipStack::SaveRecord::contains(const ClipStack::RawElement& element) const {
return fInnerBounds.contains(element.outerBounds());
}
void ClipStack::SaveRecord::removeElements(RawElement::Stack* elements) {
while (elements->count() > fStartingElementIndex) {
elements->pop_back();
}
}
void ClipStack::SaveRecord::restoreElements(RawElement::Stack* elements) {
int i = elements->count() - 1;
for (RawElement& e : elements->ritems()) {
if (i < fOldestValidIndex) {
break;
}
e.restoreValid(*this);
--i;
}
}
void ClipStack::SaveRecord::invalidateMasks(GrProxyProvider* proxyProvider,
Mask::Stack* masks) {
while (masks->count() > fStartingMaskIndex) {
SkASSERT(masks->back().owner() == this && proxyProvider);
masks->back().invalidate(proxyProvider);
masks->pop_back();
}
SkASSERT(masks->empty() || masks->back().genID() != fGenID);
}
void ClipStack::SaveRecord::reset(const SkIRect& bounds) {
SkASSERT(this->canBeUpdated());
fOldestValidIndex = fStartingElementIndex;
fOuterBounds = bounds;
fInnerBounds = bounds;
fStackOp = SkClipOp::kIntersect;
fState = ClipState::kWideOpen;
fShader = nullptr;
}
void ClipStack::SaveRecord::addShader(sk_sp<SkShader> shader) {
SkASSERT(shader);
SkASSERT(this->canBeUpdated());
if (!fShader) {
fShader = std::move(shader);
} else {
fShader = SkShaders::Blend(SkBlendMode::kSrcIn, std::move(shader), fShader);
}
}
bool ClipStack::SaveRecord::addElement(RawElement&& toAdd, RawElement::Stack* elements) {
SkASSERT((toAdd.shape().isEmpty() || !toAdd.outerBounds().isEmpty()) &&
(toAdd.innerBounds().isEmpty() || toAdd.outerBounds().contains(toAdd.innerBounds())));
SkASSERT(this->canBeUpdated());
if (fState == ClipState::kEmpty) {
return false;
} else if (toAdd.shape().isEmpty()) {
SkASSERT(toAdd.op() == SkClipOp::kIntersect);
fState = ClipState::kEmpty;
return true;
}
switch (get_clip_geometry(*this, toAdd)) {
case ClipGeometry::kEmpty:
fState = ClipState::kEmpty;
return true;
case ClipGeometry::kAOnly:
return false;
case ClipGeometry::kBOnly:
this->replaceWithElement(std::move(toAdd), elements);
return true;
case ClipGeometry::kBoth:
break;
}
if (fState == ClipState::kWideOpen) {
this->replaceWithElement(std::move(toAdd), elements);
return true;
}
if (fStackOp == SkClipOp::kIntersect) {
if (toAdd.op() == SkClipOp::kIntersect) {
SkAssertResult(fOuterBounds.intersect(toAdd.outerBounds()));
if (!fInnerBounds.intersect(toAdd.innerBounds())) {
fInnerBounds = SkIRect::MakeEmpty();
}
} else {
fOuterBounds = subtract(fOuterBounds, toAdd.innerBounds(), true);
fInnerBounds = subtract(fInnerBounds, toAdd.outerBounds(), false);
}
} else {
if (toAdd.op() == SkClipOp::kIntersect) {
SkIRect oldOuter = fOuterBounds;
fOuterBounds = subtract(toAdd.outerBounds(), fInnerBounds, true);
fInnerBounds = subtract(toAdd.innerBounds(), oldOuter, false);
} else {
fOuterBounds.join(toAdd.outerBounds());
if (toAdd.innerBounds().width() * toAdd.innerBounds().height() >
fInnerBounds.width() * fInnerBounds.height()) {
fInnerBounds = toAdd.innerBounds();
}
}
}
SkASSERT(!fOuterBounds.isEmpty() &&
(fInnerBounds.isEmpty() || fOuterBounds.contains(fInnerBounds)));
return this->appendElement(std::move(toAdd), elements);
}
bool ClipStack::SaveRecord::appendElement(RawElement&& toAdd, RawElement::Stack* elements) {
int i = elements->count() - 1;
int youngestValid = fStartingElementIndex - 1;
int oldestValid = elements->count();
RawElement* oldestActiveInvalid = nullptr;
int oldestActiveInvalidIndex = elements->count();
for (RawElement& existing : elements->ritems()) {
if (i < fOldestValidIndex) {
break;
}
existing.updateForElement(&toAdd, *this);
if (toAdd.isInvalid()) {
if (existing.isInvalid()) {
fState = ClipState::kEmpty;
return true;
} else {
return false;
}
} else if (existing.isInvalid()) {
if (i >= fStartingElementIndex) {
oldestActiveInvalid = &existing;
oldestActiveInvalidIndex = i;
}
} else {
oldestValid = i;
if (i > youngestValid) {
youngestValid = i;
}
}
--i;
}
SkASSERT(oldestValid == elements->count() ||
(oldestValid >= fOldestValidIndex && oldestValid < elements->count()));
SkASSERT(youngestValid == fStartingElementIndex - 1 ||
(youngestValid >= fStartingElementIndex && youngestValid < elements->count()));
SkASSERT((oldestActiveInvalid && oldestActiveInvalidIndex >= fStartingElementIndex &&
oldestActiveInvalidIndex < elements->count()) || !oldestActiveInvalid);
SkASSERT(oldestValid >= fOldestValidIndex);
fOldestValidIndex = std::min(oldestValid, oldestActiveInvalidIndex);
fState = oldestValid == elements->count() ? toAdd.clipType() : ClipState::kComplex;
if (fStackOp == SkClipOp::kDifference && toAdd.op() == SkClipOp::kIntersect) {
fStackOp = SkClipOp::kIntersect;
}
int targetCount = youngestValid + 1;
if (!oldestActiveInvalid || oldestActiveInvalidIndex >= targetCount) {
targetCount++;
oldestActiveInvalid = nullptr;
}
while (elements->count() > targetCount) {
SkASSERT(oldestActiveInvalid != &elements->back());
elements->pop_back();
}
if (oldestActiveInvalid) {
*oldestActiveInvalid = std::move(toAdd);
} else if (elements->count() < targetCount) {
elements->push_back(std::move(toAdd));
} else {
elements->back() = std::move(toAdd);
}
fGenID = next_gen_id();
return true;
}
void ClipStack::SaveRecord::replaceWithElement(RawElement&& toAdd, RawElement::Stack* elements) {
fInnerBounds = toAdd.innerBounds();
fOuterBounds = toAdd.outerBounds();
fStackOp = toAdd.op();
fState = toAdd.clipType();
int targetCount = fStartingElementIndex + 1;
while (elements->count() > targetCount) {
elements->pop_back();
}
if (elements->count() < targetCount) {
elements->push_back(std::move(toAdd));
} else {
elements->back() = std::move(toAdd);
}
SkASSERT(elements->count() == fStartingElementIndex + 1);
fOldestValidIndex = fStartingElementIndex;
fGenID = next_gen_id();
}
static constexpr int kElementStackIncrement = 8;
static constexpr int kSaveStackIncrement = 8;
static constexpr int kMaskStackIncrement = 4;
static constexpr int kMaxAnalyticFPs = 4;
static constexpr int kNumStackMasks = 4;
ClipStack::ClipStack(const SkIRect& deviceBounds, const SkMatrix* ctm, bool forceAA)
: fElements(kElementStackIncrement)
, fSaves(kSaveStackIncrement)
, fMasks(kMaskStackIncrement)
, fProxyProvider(nullptr)
, fDeviceBounds(deviceBounds)
, fCTM(ctm)
, fForceAA(forceAA) {
fSaves.emplace_back(deviceBounds);
}
ClipStack::~ClipStack() {
SkASSERT(fProxyProvider || fMasks.empty());
if (fProxyProvider) {
for (Mask& m : fMasks.ritems()) {
m.invalidate(fProxyProvider);
}
}
}
void ClipStack::save() {
SkASSERT(!fSaves.empty());
fSaves.back().pushSave();
}
void ClipStack::restore() {
SkASSERT(!fSaves.empty());
SaveRecord& current = fSaves.back();
if (current.popSave()) {
return;
}
current.removeElements(&fElements);
SkASSERT(fProxyProvider || fMasks.empty());
if (fProxyProvider) {
current.invalidateMasks(fProxyProvider, &fMasks);
}
fSaves.pop_back();
fSaves.back().restoreElements(&fElements);
}
SkIRect ClipStack::getConservativeBounds() const {
const SaveRecord& current = this->currentSaveRecord();
if (current.state() == ClipState::kEmpty) {
return SkIRect::MakeEmpty();
} else if (current.state() == ClipState::kWideOpen) {
return fDeviceBounds;
} else {
if (current.op() == SkClipOp::kDifference) {
return subtract(fDeviceBounds, current.innerBounds(), true);
} else {
SkASSERT(fDeviceBounds.contains(current.outerBounds()));
return current.outerBounds();
}
}
}
GrClip::PreClipResult ClipStack::preApply(const SkRect& bounds, GrAA aa) const {
Draw draw(bounds, fForceAA ? GrAA::kYes : aa);
if (!draw.applyDeviceBounds(fDeviceBounds)) {
return GrClip::Effect::kClippedOut;
}
const SaveRecord& cs = this->currentSaveRecord();
if (cs.state() == ClipState::kEmpty) {
return GrClip::Effect::kClippedOut;
} else if (cs.state() == ClipState::kWideOpen) {
SkASSERT(!cs.shader());
return GrClip::Effect::kUnclipped;
}
switch (get_clip_geometry(cs, draw)) {
case ClipGeometry::kEmpty:
return GrClip::Effect::kClippedOut;
case ClipGeometry::kBOnly:
return cs.shader() ? GrClip::Effect::kClipped : GrClip::Effect::kUnclipped;
case ClipGeometry::kAOnly:
SkASSERT(false);
[[fallthrough]];
case ClipGeometry::kBoth: {
SkASSERT(fElements.count() > 0);
const RawElement& back = fElements.back();
if (cs.state() == ClipState::kDeviceRect) {
SkASSERT(back.clipType() == ClipState::kDeviceRect);
return {back.shape().rect(), back.aa()};
} else if (cs.state() == ClipState::kDeviceRRect) {
SkASSERT(back.clipType() == ClipState::kDeviceRRect);
return {back.shape().rrect(), back.aa()};
} else {
SkASSERT(cs.state() == ClipState::kComplex);
return GrClip::Effect::kClipped;
}
}
}
SkUNREACHABLE;
}
GrClip::Effect ClipStack::apply(GrRecordingContext* rContext,
SurfaceDrawContext* sdc,
GrDrawOp* op,
GrAAType aa,
GrAppliedClip* out,
SkRect* bounds) const {
if (!fProxyProvider) {
fProxyProvider = rContext->priv().proxyProvider();
}
SkASSERT(fProxyProvider == rContext->priv().proxyProvider());
const GrCaps* caps = rContext->priv().caps();
Draw draw(*bounds, GrAA(fForceAA || aa != GrAAType::kNone));
if (!draw.applyDeviceBounds(fDeviceBounds)) {
return Effect::kClippedOut;
}
SkAssertResult(bounds->intersect(SkRect::Make(fDeviceBounds)));
const SaveRecord& cs = this->currentSaveRecord();
if (cs.state() == ClipState::kEmpty) {
return Effect::kClippedOut;
} else if (cs.state() == ClipState::kWideOpen) {
SkASSERT(!cs.shader());
return Effect::kUnclipped;
}
std::unique_ptr<GrFragmentProcessor> clipFP = nullptr;
if (cs.shader()) {
static const GrColorInfo kCoverageColorInfo{GrColorType::kUnknown, kPremul_SkAlphaType,
nullptr};
GrFPArgs args(
rContext, &kCoverageColorInfo, sdc->surfaceProps(), GrFPArgs::Scope::kDefault);
clipFP = GrFragmentProcessors::Make(cs.shader(), args, *fCTM);
if (clipFP) {
clipFP = GrFragmentProcessor::MulInputByChildAlpha(std::move(clipFP));
}
}
switch (get_clip_geometry(cs, draw)) {
case ClipGeometry::kEmpty:
return Effect::kClippedOut;
case ClipGeometry::kBOnly:
if (clipFP) {
out->addCoverageFP(std::move(clipFP));
return Effect::kClipped;
} else {
return Effect::kUnclipped;
}
case ClipGeometry::kAOnly:
SkASSERT(false);
[[fallthrough]];
case ClipGeometry::kBoth:
SkASSERT(cs.state() == ClipState::kDeviceRect ||
cs.state() == ClipState::kDeviceRRect ||
cs.state() == ClipState::kComplex);
break;
}
SkIRect scissorBounds;
if (cs.op() == SkClipOp::kIntersect) {
scissorBounds = cs.outerBounds();
} else {
scissorBounds = subtract(draw.outerBounds(), cs.innerBounds(), true);
}
bool scissorIsNeeded = SkToBool(cs.shader());
SkDEBUGCODE(bool opClippedInternally = false;)
int remainingAnalyticFPs = kMaxAnalyticFPs;
int maxWindowRectangles = sdc->maxWindowRectangles();
GrWindowRectangles windowRects;
STArray<kNumStackMasks, const Element*> elementsForMask;
bool maskRequiresAA = false;
auto atlasPathRenderer = rContext->priv().drawingManager()->getAtlasPathRenderer();
int i = fElements.count();
for (const RawElement& e : fElements.ritems()) {
--i;
if (i < cs.oldestElementIndex()) {
break;
} else if (e.isInvalid()) {
continue;
}
switch (get_clip_geometry(e, draw)) {
case ClipGeometry::kEmpty:
return Effect::kClippedOut;
case ClipGeometry::kBOnly:
break;
case ClipGeometry::kAOnly:
SkASSERT(false);
[[fallthrough]];
case ClipGeometry::kBoth: {
scissorIsNeeded = true;
bool fullyApplied = false;
SkASSERT(!e.shape().inverted());
auto result = op->clipToShape(sdc, e.op(), e.localToDevice(), e.shape(),
GrAA(e.aa() == GrAA::kYes || fForceAA));
if (result != GrDrawOp::ClipResult::kFail) {
if (result == GrDrawOp::ClipResult::kClippedOut) {
return Effect::kClippedOut;
}
if (result == GrDrawOp::ClipResult::kClippedGeometrically) {
bounds->intersect(SkRect::Make(e.outerBounds()));
}
fullyApplied = true;
SkDEBUGCODE(opClippedInternally = true;)
}
if (!fullyApplied) {
if (e.op() == SkClipOp::kIntersect) {
fullyApplied = e.innerBounds() == e.outerBounds() ||
e.innerBounds().contains(scissorBounds);
} else {
if (!e.innerBounds().isEmpty() &&
windowRects.count() < maxWindowRectangles) {
windowRects.addWindow(e.innerBounds());
fullyApplied = e.innerBounds() == e.outerBounds();
}
}
}
if (!fullyApplied && remainingAnalyticFPs > 0) {
std::tie(fullyApplied, clipFP) = analytic_clip_fp(e.asElement(),
*caps->shaderCaps(),
std::move(clipFP));
if (!fullyApplied && atlasPathRenderer) {
std::tie(fullyApplied, clipFP) = clip_atlas_fp(sdc, op,
atlasPathRenderer,
scissorBounds, e.asElement(),
std::move(clipFP));
}
if (fullyApplied) {
remainingAnalyticFPs--;
}
}
if (!fullyApplied) {
elementsForMask.push_back(&e.asElement());
maskRequiresAA |= (e.aa() == GrAA::kYes);
}
break;
}
}
}
if (!scissorIsNeeded) {
SkASSERT(elementsForMask.empty() && !clipFP);
return Effect::kUnclipped;
}
if (cs.op() == SkClipOp::kIntersect && !elementsForMask.empty()) {
SkAssertResult(scissorBounds.intersect(draw.outerBounds()));
}
if (!GrClip::IsInsideClip(scissorBounds, *bounds, draw.aa())) {
out->hardClip().addScissor(scissorBounds, bounds);
}
if (!windowRects.empty()) {
out->hardClip().addWindowRectangles(windowRects, GrWindowRectsState::Mode::kExclusive);
}
if (!elementsForMask.empty()) {
bool stencilUnavailable =
!sdc->asRenderTargetProxy()->canUseStencil(*rContext->priv().caps());
bool hasSWMask = false;
if ((sdc->numSamples() <= 1 && !sdc->canUseDynamicMSAA() && maskRequiresAA) ||
stencilUnavailable) {
std::tie(hasSWMask, clipFP) = GetSWMaskFP(
rContext, &fMasks, cs, scissorBounds, elementsForMask.begin(),
elementsForMask.size(), std::move(clipFP));
}
if (!hasSWMask) {
if (stencilUnavailable) {
SkDebugf("WARNING: Clip mask requires stencil, but stencil unavailable. "
"Draw will be ignored.\n");
return Effect::kClippedOut;
} else {
render_stencil_mask(rContext, sdc, cs.genID(), scissorBounds,
elementsForMask.begin(), elementsForMask.size(), out);
}
}
}
if (clipFP) {
out->addCoverageFP(std::move(clipFP));
}
SkASSERT(out->doesClip() || opClippedInternally);
return Effect::kClipped;
}
ClipStack::SaveRecord& ClipStack::writableSaveRecord(bool* wasDeferred) {
SaveRecord& current = fSaves.back();
if (current.canBeUpdated()) {
*wasDeferred = false;
return current;
} else {
SkAssertResult(current.popSave());
*wasDeferred = true;
return fSaves.emplace_back(current, fMasks.count(), fElements.count());
}
}
void ClipStack::clipShader(sk_sp<SkShader> shader) {
if (this->currentSaveRecord().state() == ClipState::kEmpty) {
return;
}
bool wasDeferred;
this->writableSaveRecord(&wasDeferred).addShader(std::move(shader));
}
void ClipStack::replaceClip(const SkIRect& rect) {
bool wasDeferred;
SaveRecord& save = this->writableSaveRecord(&wasDeferred);
if (!wasDeferred) {
save.removeElements(&fElements);
save.invalidateMasks(fProxyProvider, &fMasks);
}
save.reset(fDeviceBounds);
if (rect != fDeviceBounds) {
this->clipRect(SkMatrix::I(), SkRect::Make(rect), GrAA::kNo, SkClipOp::kIntersect);
}
}
void ClipStack::clip(RawElement&& element) {
if (this->currentSaveRecord().state() == ClipState::kEmpty) {
return;
}
element.simplify(fDeviceBounds, fForceAA);
SkASSERT(!element.shape().inverted());
if (element.shape().isEmpty()) {
if (element.op() == SkClipOp::kDifference) {
return;
}
}
bool wasDeferred;
SaveRecord& save = this->writableSaveRecord(&wasDeferred);
SkDEBUGCODE(uint32_t oldGenID = save.genID();)
SkDEBUGCODE(int elementCount = fElements.count();)
if (!save.addElement(std::move(element), &fElements)) {
if (wasDeferred) {
SkASSERT(elementCount == fElements.count());
fSaves.pop_back();
fSaves.back().pushSave();
} else {
SkASSERT(oldGenID == save.genID());
}
} else {
SkASSERT(oldGenID != save.genID() && save.genID() != kInvalidGenID);
if (fProxyProvider && !wasDeferred) {
save.invalidateMasks(fProxyProvider, &fMasks);
}
}
}
GrFPResult ClipStack::GetSWMaskFP(GrRecordingContext* context, Mask::Stack* masks,
const SaveRecord& current, const SkIRect& bounds,
const Element** elements, int count,
std::unique_ptr<GrFragmentProcessor> clipFP) {
GrProxyProvider* proxyProvider = context->priv().proxyProvider();
GrSurfaceProxyView maskProxy;
SkIRect maskBounds;
for (const Mask& m : masks->ritems()) {
if (m.genID() != current.genID()) {
break;
}
if (m.appliesToDraw(current, bounds)) {
maskProxy = proxyProvider->findCachedProxyWithColorTypeFallback(
m.key(), kMaskOrigin, GrColorType::kAlpha_8, 1);
if (maskProxy) {
maskBounds = m.bounds();
break;
}
}
}
if (!maskProxy) {
maskProxy = render_sw_mask(context, bounds, elements, count);
if (!maskProxy) {
return GrFPFailure(std::move(clipFP));
}
Mask& mask = masks->emplace_back(current, bounds);
proxyProvider->assignUniqueKeyToProxy(mask.key(), maskProxy.asTextureProxy());
maskBounds = bounds;
}
SkASSERT(maskProxy && maskProxy.origin() == kMaskOrigin);
GrSamplerState samplerState(GrSamplerState::WrapMode::kClampToBorder,
GrSamplerState::Filter::kNearest);
auto m = SkMatrix::Translate(-maskBounds.fLeft, -maskBounds.fTop);
auto subset = SkRect::Make(bounds);
subset.offset(-maskBounds.fLeft, -maskBounds.fTop);
auto domain = subset.makeInset(0.5, 0.5);
auto fp = GrTextureEffect::MakeSubset(std::move(maskProxy), kPremul_SkAlphaType, m,
samplerState, subset, domain, *context->priv().caps());
fp = GrFragmentProcessor::DeviceSpace(std::move(fp));
fp = GrBlendFragmentProcessor::Make<SkBlendMode::kDstIn>(std::move(fp), std::move(clipFP));
return GrFPSuccess(std::move(fp));
}
}