aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--gn/gpu.gni2
-rw-r--r--src/gpu/GrOnFlushResourceProvider.cpp2
-rw-r--r--src/gpu/ccpr/GrCCAtlas.cpp34
-rw-r--r--src/gpu/ccpr/GrCCAtlas.h25
-rw-r--r--src/gpu/ccpr/GrCCClipPath.cpp8
-rw-r--r--src/gpu/ccpr/GrCCDrawPathsOp.cpp212
-rw-r--r--src/gpu/ccpr/GrCCDrawPathsOp.h78
-rw-r--r--src/gpu/ccpr/GrCCPathCache.cpp176
-rw-r--r--src/gpu/ccpr/GrCCPathCache.h176
-rw-r--r--src/gpu/ccpr/GrCCPathProcessor.cpp13
-rw-r--r--src/gpu/ccpr/GrCCPathProcessor.h36
-rw-r--r--src/gpu/ccpr/GrCCPerFlushResources.cpp189
-rw-r--r--src/gpu/ccpr/GrCCPerFlushResources.h60
-rw-r--r--src/gpu/ccpr/GrCCSTLList.h12
-rw-r--r--src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp96
-rw-r--r--src/gpu/ccpr/GrCoverageCountingPathRenderer.h7
-rw-r--r--tests/GrCCPRTest.cpp130
-rw-r--r--tools/gpu/GrTest.cpp7
18 files changed, 1084 insertions, 179 deletions
diff --git a/gn/gpu.gni b/gn/gpu.gni
index 8cb6447bad..c06522b466 100644
--- a/gn/gpu.gni
+++ b/gn/gpu.gni
@@ -320,6 +320,8 @@ skia_gpu_sources = [
"$_src/gpu/ccpr/GrCCDrawPathsOp.h",
"$_src/gpu/ccpr/GrCCGeometry.cpp",
"$_src/gpu/ccpr/GrCCGeometry.h",
+ "$_src/gpu/ccpr/GrCCPathCache.cpp",
+ "$_src/gpu/ccpr/GrCCPathCache.h",
"$_src/gpu/ccpr/GrCCPathParser.cpp",
"$_src/gpu/ccpr/GrCCPathParser.h",
"$_src/gpu/ccpr/GrCCPathProcessor.cpp",
diff --git a/src/gpu/GrOnFlushResourceProvider.cpp b/src/gpu/GrOnFlushResourceProvider.cpp
index 535f825e57..d507f16307 100644
--- a/src/gpu/GrOnFlushResourceProvider.cpp
+++ b/src/gpu/GrOnFlushResourceProvider.cpp
@@ -29,7 +29,7 @@ sk_sp<GrRenderTargetContext> GrOnFlushResourceProvider::makeRenderTargetContext(
sk_sp<GrSurfaceProxy> proxy =
proxyProvider->createProxy(tmpDesc, origin, SkBackingFit::kExact, SkBudgeted::kYes,
GrInternalSurfaceFlags::kNoPendingIO);
- if (!proxy->asRenderTargetProxy()) {
+ if (!proxy || !proxy->asRenderTargetProxy()) {
return nullptr;
}
diff --git a/src/gpu/ccpr/GrCCAtlas.cpp b/src/gpu/ccpr/GrCCAtlas.cpp
index 94a37e810f..4eeddea9cb 100644
--- a/src/gpu/ccpr/GrCCAtlas.cpp
+++ b/src/gpu/ccpr/GrCCAtlas.cpp
@@ -137,9 +137,39 @@ void GrCCAtlas::setUserBatchID(int id) {
fUserBatchID = id;
}
+static uint32_t next_atlas_unique_id() {
+ static int32_t nextID;
+ return sk_atomic_inc(&nextID);
+}
+
+const GrUniqueKey& GrCCAtlas::getOrAssignUniqueKey(GrOnFlushResourceProvider* onFlushRP) {
+ static const GrUniqueKey::Domain kAtlasDomain = GrUniqueKey::GenerateDomain();
+
+ if (!fUniqueKey.isValid()) {
+ GrUniqueKey::Builder builder(&fUniqueKey, kAtlasDomain, 1, "CCPR Atlas");
+ builder[0] = next_atlas_unique_id();
+ builder.finish();
+
+ if (fTextureProxy->priv().isInstantiated()) {
+ onFlushRP->assignUniqueKeyToProxy(fUniqueKey, fTextureProxy.get());
+ }
+ }
+ return fUniqueKey;
+}
+
+sk_sp<GrCCAtlas::CachedAtlasInfo> GrCCAtlas::refOrMakeCachedAtlasInfo() {
+ if (!fCachedAtlasInfo) {
+ fCachedAtlasInfo = sk_make_sp<CachedAtlasInfo>();
+ }
+ return fCachedAtlasInfo;
+}
+
sk_sp<GrRenderTargetContext> GrCCAtlas::makeRenderTargetContext(
GrOnFlushResourceProvider* onFlushRP) {
SkASSERT(!fTextureProxy->priv().isInstantiated()); // This method should only be called once.
+ // Caller should have cropped any paths to the destination render target instead of asking for
+ // an atlas larger than maxRenderTargetSize.
+ SkASSERT(SkTMax(fHeight, fWidth) <= fMaxTextureSize);
SkASSERT(fMaxTextureSize <= onFlushRP->caps()->maxRenderTargetSize());
sk_sp<GrRenderTargetContext> rtc =
@@ -150,6 +180,10 @@ sk_sp<GrRenderTargetContext> GrCCAtlas::makeRenderTargetContext(
return nullptr;
}
+ if (fUniqueKey.isValid()) {
+ onFlushRP->assignUniqueKeyToProxy(fUniqueKey, fTextureProxy.get());
+ }
+
SkIRect clearRect = SkIRect::MakeSize(fDrawBounds);
rtc->clear(&clearRect, 0, GrRenderTargetContext::CanClearFullscreen::kYes);
return rtc;
diff --git a/src/gpu/ccpr/GrCCAtlas.h b/src/gpu/ccpr/GrCCAtlas.h
index 6d82728050..76d2a986e1 100644
--- a/src/gpu/ccpr/GrCCAtlas.h
+++ b/src/gpu/ccpr/GrCCAtlas.h
@@ -9,6 +9,8 @@
#define GrCCAtlas_DEFINED
#include "GrAllocator.h"
+#include "GrNonAtomicRef.h"
+#include "GrResourceKey.h"
#include "GrTypes.h"
#include "GrTypesPriv.h"
#include "SkRefCnt.h"
@@ -18,6 +20,7 @@ class GrOnFlushResourceProvider;
class GrRenderTargetContext;
class GrTextureProxy;
struct SkIPoint16;
+struct SkIRect;
/**
* This class implements a dynamic size GrRectanizer that grows until it reaches the implementation-
@@ -58,6 +61,22 @@ public:
void setUserBatchID(int id);
int getUserBatchID() const { return fUserBatchID; }
+ // Manages a unique resource cache key that gets assigned to the atlas texture. The unique key
+ // does not get assigned to the texture proxy until it is instantiated.
+ const GrUniqueKey& getOrAssignUniqueKey(GrOnFlushResourceProvider*);
+ const GrUniqueKey& uniqueKey() const { return fUniqueKey; }
+
+ // An object for simple bookkeeping on the atlas texture once it has a unique key. In practice,
+ // we use it to track the percentage of the original atlas pixels that could still ever
+ // potentially be reused (i.e., those which still represent an extant path). When the percentage
+ // of useful pixels drops below 50%, the entire texture is purged from the resource cache.
+ struct CachedAtlasInfo : public GrNonAtomicRef<CachedAtlasInfo> {
+ int fNumPathPixels = 0;
+ int fNumInvalidatedPathPixels = 0;
+ bool fIsPurgedFromResourceCache = false;
+ };
+ sk_sp<CachedAtlasInfo> refOrMakeCachedAtlasInfo();
+
// Instantiates our texture proxy for the atlas and returns a pre-cleared GrRenderTargetContext
// that the caller may use to render the content. After this call, it is no longer valid to call
// addRect(), setUserBatchID(), or this method again.
@@ -74,6 +93,12 @@ private:
SkISize fDrawBounds = {0, 0};
int fUserBatchID;
+
+ // Not every atlas will have a unique key -- a mainline CCPR one won't if we don't stash any
+ // paths, and only the first atlas in the stack is eligible to be stashed.
+ GrUniqueKey fUniqueKey;
+
+ sk_sp<CachedAtlasInfo> fCachedAtlasInfo;
sk_sp<GrTextureProxy> fTextureProxy;
};
diff --git a/src/gpu/ccpr/GrCCClipPath.cpp b/src/gpu/ccpr/GrCCClipPath.cpp
index 61d58d4980..8629cc2cdf 100644
--- a/src/gpu/ccpr/GrCCClipPath.cpp
+++ b/src/gpu/ccpr/GrCCClipPath.cpp
@@ -48,15 +48,15 @@ void GrCCClipPath::init(const SkPath& deviceSpacePath, const SkIRect& accessRect
fAccessRect = accessRect;
}
-void GrCCClipPath::accountForOwnPath(GrCCPerFlushResourceSpecs* resourceSpecs) const {
+void GrCCClipPath::accountForOwnPath(GrCCPerFlushResourceSpecs* specs) const {
SkASSERT(this->isInitialized());
- ++resourceSpecs->fNumClipPaths;
- resourceSpecs->fParsingPathStats.statPath(fDeviceSpacePath);
+ ++specs->fNumClipPaths;
+ specs->fRenderedPathStats.statPath(fDeviceSpacePath);
SkIRect ibounds;
if (ibounds.intersect(fAccessRect, fPathDevIBounds)) {
- resourceSpecs->fAtlasSpecs.accountForSpace(ibounds.width(), ibounds.height());
+ specs->fRenderedAtlasSpecs.accountForSpace(ibounds.width(), ibounds.height());
}
}
diff --git a/src/gpu/ccpr/GrCCDrawPathsOp.cpp b/src/gpu/ccpr/GrCCDrawPathsOp.cpp
index c38dd76209..ad078b2853 100644
--- a/src/gpu/ccpr/GrCCDrawPathsOp.cpp
+++ b/src/gpu/ccpr/GrCCDrawPathsOp.cpp
@@ -7,7 +7,6 @@
#include "GrCCDrawPathsOp.h"
-#include "GrGpuCommandBuffer.h"
#include "GrMemoryPool.h"
#include "GrOpFlushState.h"
#include "ccpr/GrCCPerFlushResources.h"
@@ -24,23 +23,30 @@ static bool has_coord_transforms(const GrPaint& paint) {
}
std::unique_ptr<GrCCDrawPathsOp> GrCCDrawPathsOp::Make(GrContext*, const SkIRect& clipIBounds,
- const SkMatrix& m, const SkPath& path,
+ const SkMatrix& m, const GrShape& shape,
const SkRect& devBounds, GrPaint&& paint) {
+ bool canStashPathMask = true;
SkIRect looseClippedIBounds;
devBounds.roundOut(&looseClippedIBounds); // GrCCPathParser might find slightly tighter bounds.
- if (!looseClippedIBounds.intersect(clipIBounds)) {
- return nullptr;
+ if (!clipIBounds.contains(looseClippedIBounds)) {
+ canStashPathMask = false;
+ if (!looseClippedIBounds.intersect(clipIBounds)) {
+ return nullptr;
+ }
}
- return std::unique_ptr<GrCCDrawPathsOp>(
- new GrCCDrawPathsOp(looseClippedIBounds, m, path, devBounds, std::move(paint)));
+ return std::unique_ptr<GrCCDrawPathsOp>(new GrCCDrawPathsOp(looseClippedIBounds, m, shape,
+ canStashPathMask, devBounds,
+ std::move(paint)));
}
GrCCDrawPathsOp::GrCCDrawPathsOp(const SkIRect& looseClippedIBounds, const SkMatrix& m,
- const SkPath& path, const SkRect& devBounds, GrPaint&& paint)
+ const GrShape& shape, bool canStashPathMask,
+ const SkRect& devBounds, GrPaint&& paint)
: GrDrawOp(ClassID())
, fViewMatrixIfUsingLocalCoords(has_coord_transforms(paint) ? m : SkMatrix::I())
, fSRGBFlags(GrPipeline::SRGBFlagsFromPaint(paint))
- , fDraws({looseClippedIBounds, m, path, paint.getColor(), nullptr})
+ , fDraws({looseClippedIBounds, m, shape, paint.getColor(), nullptr, nullptr, {0, 0},
+ canStashPathMask, nullptr})
, fProcessors(std::move(paint)) { // Paint must be moved after fetching its color above.
SkDEBUGCODE(fBaseInstance = -1);
// FIXME: intersect with clip bounds to (hopefully) improve batching.
@@ -55,18 +61,24 @@ GrCCDrawPathsOp::~GrCCDrawPathsOp() {
}
}
+GrCCDrawPathsOp::SingleDraw::~SingleDraw() {
+ if (fCacheEntry) {
+ // All currFlushAtlas references must be reset back to null before the flush is finished.
+ fCacheEntry->setCurrFlushAtlas(nullptr);
+ }
+}
+
GrDrawOp::RequiresDstTexture GrCCDrawPathsOp::finalize(const GrCaps& caps,
const GrAppliedClip* clip,
GrPixelConfigIsClamped dstIsClamped) {
- // There should only be one single path draw in this Op right now.
- SkASSERT(1 == fNumDraws);
+ SkASSERT(1 == fNumDraws); // There should only be one single path draw in this Op right now.
GrProcessorSet::Analysis analysis =
fProcessors.finalize(fDraws.head().fColor, GrProcessorAnalysisCoverage::kSingleChannel,
clip, false, caps, dstIsClamped, &fDraws.head().fColor);
- return analysis.requiresDstTexture() ? RequiresDstTexture::kYes : RequiresDstTexture::kNo;
+ return RequiresDstTexture(analysis.requiresDstTexture());
}
-bool GrCCDrawPathsOp::onCombineIfPossible(GrOp* op, const GrCaps& caps) {
+bool GrCCDrawPathsOp::onCombineIfPossible(GrOp* op, const GrCaps&) {
GrCCDrawPathsOp* that = op->cast<GrCCDrawPathsOp>();
SkASSERT(fOwningPerOpListPaths);
SkASSERT(fNumDraws);
@@ -93,50 +105,158 @@ void GrCCDrawPathsOp::wasRecorded(GrCCPerOpListPaths* owningPerOpListPaths) {
fOwningPerOpListPaths = owningPerOpListPaths;
}
-void GrCCDrawPathsOp::accountForOwnPaths(GrCCPerFlushResourceSpecs* resourceSpecs) const {
- for (const GrCCDrawPathsOp::SingleDraw& draw : fDraws) {
- ++resourceSpecs->fNumRenderedPaths;
- resourceSpecs->fParsingPathStats.statPath(draw.fPath);
- resourceSpecs->fAtlasSpecs.accountForSpace(draw.fLooseClippedIBounds.width(),
+void GrCCDrawPathsOp::accountForOwnPaths(GrCCPathCache* pathCache,
+ GrOnFlushResourceProvider* onFlushRP,
+ const GrUniqueKey& stashedAtlasKey,
+ GrCCPerFlushResourceSpecs* specs) {
+ using CreateIfAbsent = GrCCPathCache::CreateIfAbsent;
+ using MaskTransform = GrCCPathCache::MaskTransform;
+
+ for (SingleDraw& draw : fDraws) {
+ SkASSERT(!draw.fCacheEntry);
+
+ SkPath path;
+ draw.fShape.asPath(&path);
+
+ MaskTransform m(draw.fMatrix, &draw.fCachedMaskShift);
+ draw.fCacheEntry = pathCache->find(draw.fShape, m, CreateIfAbsent(draw.fCanStashPathMask));
+ if (auto cacheEntry = draw.fCacheEntry.get()) {
+ SkASSERT(!cacheEntry->currFlushAtlas()); // Shouldn't be set until setupResources().
+ if (cacheEntry->atlasKey().isValid()) {
+ // Does the path already exist in a cached atlas?
+ if (cacheEntry->hasCachedAtlas() &&
+ (draw.fCachedAtlasProxy = onFlushRP->findOrCreateProxyByUniqueKey(
+ cacheEntry->atlasKey(),
+ GrCCAtlas::kTextureOrigin))) {
+ ++specs->fNumCachedPaths;
+ continue;
+ }
+
+ // Does the path exist in the atlas that we stashed away from last flush? If so we
+ // can copy it into a new 8-bit atlas and keep it in the resource cache.
+ if (stashedAtlasKey.isValid() && stashedAtlasKey == cacheEntry->atlasKey()) {
+ SkASSERT(!cacheEntry->hasCachedAtlas());
+ ++specs->fNumCopiedPaths;
+ specs->fCopyPathStats.statPath(path);
+ specs->fCopyAtlasSpecs.accountForSpace(cacheEntry->width(),
+ cacheEntry->height());
+ continue;
+ }
+
+ // Whatever atlas the path used to reside in, it no longer exists.
+ cacheEntry->resetAtlasKeyAndInfo();
+ }
+
+ if (!draw.fCanStashPathMask) {
+ // No point in keeping this cache entry around anymore if we aren't going to try and
+ // stash the the rendered path mask after flush.
+ draw.fCacheEntry = nullptr;
+ pathCache->evict(cacheEntry);
+ }
+ }
+
+ ++specs->fNumRenderedPaths;
+ specs->fRenderedPathStats.statPath(path);
+ specs->fRenderedAtlasSpecs.accountForSpace(draw.fLooseClippedIBounds.width(),
draw.fLooseClippedIBounds.height());
}
}
-void GrCCDrawPathsOp::setupResources(GrCCPerFlushResources* resources,
- GrOnFlushResourceProvider* onFlushRP) {
- const GrCCAtlas* currentAtlas = nullptr;
+void GrCCDrawPathsOp::setupResources(GrOnFlushResourceProvider* onFlushRP,
+ GrCCPerFlushResources* resources, DoCopiesToCache doCopies) {
+ using DoEvenOddFill = GrCCPathProcessor::DoEvenOddFill;
SkASSERT(fNumDraws > 0);
SkASSERT(-1 == fBaseInstance);
fBaseInstance = resources->nextPathInstanceIdx();
- for (const SingleDraw& draw : fDraws) {
- // renderPathInAtlas gives us two tight bounding boxes: one in device space, as well as a
- // second one rotated an additional 45 degrees. The path vertex shader uses these two
- // bounding boxes to generate an octagon that circumscribes the path.
+ for (SingleDraw& draw : fDraws) {
+ SkPath path;
+ draw.fShape.asPath(&path);
+
+ auto doEvenOddFill = DoEvenOddFill(SkPath::kEvenOdd_FillType == path.getFillType());
+ SkASSERT(SkPath::kEvenOdd_FillType == path.getFillType() ||
+ SkPath::kWinding_FillType == path.getFillType());
+
+ if (auto cacheEntry = draw.fCacheEntry.get()) {
+ // Does the path already exist in a cached atlas texture?
+ if (auto proxy = draw.fCachedAtlasProxy.get()) {
+ SkASSERT(!cacheEntry->currFlushAtlas());
+ this->recordInstance(proxy, resources->nextPathInstanceIdx());
+ resources->appendDrawPathInstance().set(*cacheEntry, draw.fCachedMaskShift,
+ draw.fColor);
+ continue;
+ }
+
+ // Have we already encountered this path during the flush? (i.e. was the same SkPath
+ // drawn more than once during the same flush, with a compatible matrix?)
+ if (auto atlas = cacheEntry->currFlushAtlas()) {
+ this->recordInstance(atlas->textureProxy(), resources->nextPathInstanceIdx());
+ resources->appendDrawPathInstance().set(
+ *cacheEntry, draw.fCachedMaskShift, draw.fColor,
+ cacheEntry->hasCachedAtlas() ? DoEvenOddFill::kNo : doEvenOddFill);
+ continue;
+ }
+
+ // If the cache entry still has a valid atlas key at this point, it means the path
+ // exists in the atlas that we stashed away from last flush. Copy it into a permanent
+ // 8-bit atlas in the resource cache.
+ if (DoCopiesToCache::kYes == doCopies && cacheEntry->atlasKey().isValid()) {
+ SkIVector newOffset;
+ GrCCAtlas* atlas =
+ resources->copyPathToCachedAtlas(*cacheEntry, doEvenOddFill, &newOffset);
+ cacheEntry->updateToCachedAtlas(atlas->getOrAssignUniqueKey(onFlushRP),
+ newOffset, atlas->refOrMakeCachedAtlasInfo());
+ this->recordInstance(atlas->textureProxy(), resources->nextPathInstanceIdx());
+ resources->appendDrawPathInstance().set(*cacheEntry, draw.fCachedMaskShift,
+ draw.fColor);
+ // Remember this atlas in case we encounter the path again during the same flush.
+ cacheEntry->setCurrFlushAtlas(atlas);
+ continue;
+ }
+ }
+
+ // Render the raw path into a coverage count atlas. renderPathInAtlas() gives us two tight
+ // bounding boxes: One in device space, as well as a second one rotated an additional 45
+ // degrees. The path vertex shader uses these two bounding boxes to generate an octagon that
+ // circumscribes the path.
+ SkASSERT(!draw.fCachedAtlasProxy);
SkRect devBounds, devBounds45;
+ SkIRect devIBounds;
SkIVector devToAtlasOffset;
- const GrCCAtlas* atlas = resources->renderPathInAtlas(draw.fLooseClippedIBounds,
- draw.fMatrix, draw.fPath, &devBounds,
- &devBounds45, &devToAtlasOffset);
- if (!atlas) {
- SkDEBUGCODE(++fNumSkippedInstances);
- continue;
- }
- if (currentAtlas != atlas) {
- if (currentAtlas) {
- this->recordInstanceRange(currentAtlas->textureProxy(),
- resources->nextPathInstanceIdx());
+ if (auto atlas = resources->renderPathInAtlas(draw.fLooseClippedIBounds, draw.fMatrix, path,
+ &devBounds, &devBounds45, &devIBounds,
+ &devToAtlasOffset)) {
+ this->recordInstance(atlas->textureProxy(), resources->nextPathInstanceIdx());
+ resources->appendDrawPathInstance().set(devBounds, devBounds45, devToAtlasOffset,
+ draw.fColor, doEvenOddFill);
+ if (draw.fCacheEntry && draw.fCanStashPathMask &&
+ resources->nextAtlasToStash() == atlas) {
+ const GrUniqueKey& atlasKey =
+ resources->nextAtlasToStash()->getOrAssignUniqueKey(onFlushRP);
+ draw.fCacheEntry->initAsStashedAtlas(atlasKey, devToAtlasOffset, devBounds,
+ devBounds45, devIBounds,
+ draw.fCachedMaskShift);
+ // Remember this atlas in case we encounter the path again during the same flush.
+ draw.fCacheEntry->setCurrFlushAtlas(atlas);
}
- currentAtlas = atlas;
+ continue;
}
+ }
- resources->appendDrawPathInstance().set(draw.fPath.getFillType(), devBounds, devBounds45,
- devToAtlasOffset, draw.fColor);
+ if (!fInstanceRanges.empty()) {
+ fInstanceRanges.back().fEndInstanceIdx = resources->nextPathInstanceIdx();
}
+}
- SkASSERT(resources->nextPathInstanceIdx() == fBaseInstance + fNumDraws - fNumSkippedInstances);
- if (currentAtlas) {
- this->recordInstanceRange(currentAtlas->textureProxy(), resources->nextPathInstanceIdx());
+inline void GrCCDrawPathsOp::recordInstance(const GrTextureProxy* atlasProxy, int instanceIdx) {
+ if (fInstanceRanges.empty()) {
+ fInstanceRanges.push_back({atlasProxy, instanceIdx});
+ return;
+ }
+ if (fInstanceRanges.back().fAtlasProxy != atlasProxy) {
+ fInstanceRanges.back().fEndInstanceIdx = instanceIdx;
+ fInstanceRanges.push_back({atlasProxy, instanceIdx});
+ return;
}
}
@@ -148,8 +268,6 @@ void GrCCDrawPathsOp::onExecute(GrOpFlushState* flushState) {
return; // Setup failed.
}
- SkASSERT(fBaseInstance >= 0); // Make sure setupResources has been called.
-
GrPipeline::InitArgs initArgs;
initArgs.fFlags = fSRGBFlags;
initArgs.fProxy = flushState->drawOpArgs().fProxy;
@@ -159,18 +277,16 @@ void GrCCDrawPathsOp::onExecute(GrOpFlushState* flushState) {
GrPipeline pipeline(initArgs, std::move(fProcessors), flushState->detachAppliedClip());
int baseInstance = fBaseInstance;
+ SkASSERT(baseInstance >= 0); // Make sure setupResources() has been called.
for (const InstanceRange& range : fInstanceRanges) {
SkASSERT(range.fEndInstanceIdx > baseInstance);
GrCCPathProcessor pathProc(flushState->resourceProvider(), sk_ref_sp(range.fAtlasProxy),
fViewMatrixIfUsingLocalCoords);
- pathProc.drawPaths(flushState, pipeline, resources->indexBuffer(),
- resources->vertexBuffer(), resources->instanceBuffer(),
- baseInstance, range.fEndInstanceIdx, this->bounds());
+ pathProc.drawPaths(flushState, pipeline, *resources, baseInstance, range.fEndInstanceIdx,
+ this->bounds());
baseInstance = range.fEndInstanceIdx;
}
-
- SkASSERT(baseInstance == fBaseInstance + fNumDraws - fNumSkippedInstances);
}
diff --git a/src/gpu/ccpr/GrCCDrawPathsOp.h b/src/gpu/ccpr/GrCCDrawPathsOp.h
index 071a21a614..a779475e59 100644
--- a/src/gpu/ccpr/GrCCDrawPathsOp.h
+++ b/src/gpu/ccpr/GrCCDrawPathsOp.h
@@ -8,13 +8,16 @@
#ifndef GrCCDrawPathsOp_DEFINED
#define GrCCDrawPathsOp_DEFINED
+#include "GrShape.h"
#include "SkTInternalLList.h"
-#include "ccpr/GrCCPathProcessor.h"
#include "ccpr/GrCCSTLList.h"
#include "ops/GrDrawOp.h"
struct GrCCPerFlushResourceSpecs;
class GrCCAtlas;
+class GrOnFlushResourceProvider;
+class GrCCPathCache;
+class GrCCPathCacheEntry;
class GrCCPerFlushResources;
class GrCCPerOpListPaths;
@@ -27,53 +30,70 @@ public:
SK_DECLARE_INTERNAL_LLIST_INTERFACE(GrCCDrawPathsOp);
static std::unique_ptr<GrCCDrawPathsOp> Make(GrContext*, const SkIRect& clipIBounds,
- const SkMatrix&, const SkPath&,
+ const SkMatrix&, const GrShape&,
const SkRect& devBounds, GrPaint&&);
~GrCCDrawPathsOp() override;
- const char* name() const override { return "GrCCDrawOp"; }
+ const char* name() const override { return "GrCCDrawPathsOp"; }
FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; }
RequiresDstTexture finalize(const GrCaps&, const GrAppliedClip*,
GrPixelConfigIsClamped) override;
- bool onCombineIfPossible(GrOp* other, const GrCaps& caps) override;
- void visitProxies(const VisitProxyFunc& func) const override {
- fProcessors.visitProxies(func);
- }
+ bool onCombineIfPossible(GrOp*, const GrCaps&) override;
+ void visitProxies(const VisitProxyFunc& fn) const override { fProcessors.visitProxies(fn); }
void onPrepare(GrOpFlushState*) override {}
void wasRecorded(GrCCPerOpListPaths* owningPerOpListPaths);
- void accountForOwnPaths(GrCCPerFlushResourceSpecs*) const;
- void setupResources(GrCCPerFlushResources*, GrOnFlushResourceProvider*);
- SkDEBUGCODE(int numSkippedInstances_debugOnly() const { return fNumSkippedInstances; })
+
+ // Makes decisions about how to draw each path (cached, copied, rendered, etc.), and
+ // increments/fills out the corresponding GrCCPerFlushResourceSpecs. 'stashedAtlasKey', if
+ // valid, references the mainline coverage count atlas from the previous flush. Paths found in
+ // this atlas will be copied to more permanent atlases in the resource cache.
+ void accountForOwnPaths(GrCCPathCache*, GrOnFlushResourceProvider*,
+ const GrUniqueKey& stashedAtlasKey, GrCCPerFlushResourceSpecs*);
+
+ // Allows the caller to decide whether to copy paths out of the stashed atlas and into the
+ // resource cache, or to just re-render the paths from scratch. If there aren't many copies or
+ // the copies would only fill a small atlas, it's probably best to just re-render.
+ enum class DoCopiesToCache : bool {
+ kNo = false,
+ kYes = true
+ };
+
+ // Allocates the GPU resources indicated by accountForOwnPaths(), in preparation for drawing. If
+ // DoCopiesToCache is kNo, the paths slated for copy will instead be re-rendered from scratch.
+ //
+ // NOTE: If using DoCopiesToCache::kNo, it is the caller's responsibility to call
+ // convertCopiesToRenders() on the GrCCPerFlushResourceSpecs.
+ void setupResources(GrOnFlushResourceProvider*, GrCCPerFlushResources*, DoCopiesToCache);
void onExecute(GrOpFlushState*) override;
private:
friend class GrOpMemoryPool;
- GrCCDrawPathsOp(const SkIRect& looseClippedIBounds, const SkMatrix&, const SkPath&,
- const SkRect& devBounds, GrPaint&&);
+ GrCCDrawPathsOp(const SkIRect& clippedDevIBounds, const SkMatrix&, const GrShape&,
+ bool canStashPathMask, const SkRect& devBounds, GrPaint&&);
- struct InstanceRange {
- const GrTextureProxy* fAtlasProxy;
- int fEndInstanceIdx;
- };
-
- void recordInstanceRange(const GrTextureProxy* atlasProxy, int endInstanceIdx) {
- SkASSERT(endInstanceIdx > fBaseInstance);
- SkASSERT(fInstanceRanges.empty() ||
- endInstanceIdx > fInstanceRanges.back().fEndInstanceIdx);
- fInstanceRanges.push_back() = {atlasProxy, endInstanceIdx};
- }
+ void recordInstance(const GrTextureProxy* atlasProxy, int instanceIdx);
const SkMatrix fViewMatrixIfUsingLocalCoords;
const uint32_t fSRGBFlags;
struct SingleDraw {
+ ~SingleDraw();
+
SkIRect fLooseClippedIBounds;
SkMatrix fMatrix;
- SkPath fPath;
+ GrShape fShape;
GrColor fColor;
+
+ sk_sp<GrCCPathCacheEntry> fCacheEntry;
+ sk_sp<GrTextureProxy> fCachedAtlasProxy;
+ SkIVector fCachedMaskShift;
+
+ // If we render the path, can we stash its atlas and copy to the resource cache next flush?
+ bool fCanStashPathMask;
+
SingleDraw* fNext;
};
@@ -83,9 +103,13 @@ private:
GrCCPerOpListPaths* fOwningPerOpListPaths = nullptr;
GrProcessorSet fProcessors;
- int fBaseInstance;
- SkSTArray<1, InstanceRange, true> fInstanceRanges;
- SkDEBUGCODE(int fNumSkippedInstances = 0);
+ struct InstanceRange {
+ const GrTextureProxy* fAtlasProxy;
+ int fEndInstanceIdx;
+ };
+
+ SkSTArray<2, InstanceRange, true> fInstanceRanges;
+ int fBaseInstance SkDEBUGCODE(= -1);
};
#endif
diff --git a/src/gpu/ccpr/GrCCPathCache.cpp b/src/gpu/ccpr/GrCCPathCache.cpp
new file mode 100644
index 0000000000..a2b6416c1e
--- /dev/null
+++ b/src/gpu/ccpr/GrCCPathCache.cpp
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrCCPathCache.h"
+
+#include "GrShape.h"
+#include "SkNx.h"
+#include "ccpr/GrCCPathParser.h"
+
+// The maximum number of cache entries we allow in our own cache.
+static constexpr int kMaxCacheCount = 1 << 16;
+
+GrCCPathCache::MaskTransform::MaskTransform(const SkMatrix& m, SkIVector* shift)
+ : fMatrix2x2{m.getScaleX(), m.getSkewX(), m.getSkewY(), m.getScaleY()} {
+ SkASSERT(!m.hasPerspective());
+ Sk2f translate = Sk2f(m.getTranslateX(), m.getTranslateY());
+ Sk2f floor = translate.floor();
+ (translate - floor).store(fSubpixelTranslate);
+ shift->set((int)floor[0], (int)floor[1]);
+ SkASSERT((float)shift->fX == floor[0]);
+ SkASSERT((float)shift->fY == floor[1]);
+}
+
+inline static bool fuzzy_equals(const GrCCPathCache::MaskTransform& a,
+ const GrCCPathCache::MaskTransform& b) {
+ return (Sk4f::Load(a.fMatrix2x2) == Sk4f::Load(b.fMatrix2x2)).allTrue() &&
+ ((Sk2f::Load(a.fSubpixelTranslate) -
+ Sk2f::Load(b.fSubpixelTranslate)).abs() < 1.f/256).allTrue();
+}
+
+inline GrCCPathCache::HashNode::HashNode(GrCCPathCache* cache, const MaskTransform& m,
+ const GrShape& shape) {
+ SkASSERT(shape.hasUnstyledKey());
+
+ int keyLength = 1 + shape.unstyledKeySize();
+ void* mem = ::operator new (sizeof(GrCCPathCacheEntry) + keyLength * sizeof(uint32_t));
+ fEntry = new (mem) GrCCPathCacheEntry(cache, m);
+
+ // The shape key is a variable-length footer to the entry allocation.
+ uint32_t* keyData = (uint32_t*)((char*)mem + sizeof(GrCCPathCacheEntry));
+ keyData[0] = keyLength - 1;
+ shape.writeUnstyledKey(&keyData[1]);
+}
+
+inline bool operator==(const GrCCPathCache::HashKey& key1, const GrCCPathCache::HashKey& key2) {
+ return key1.fData[0] == key2.fData[0] &&
+ !memcmp(&key1.fData[1], &key2.fData[1], key1.fData[0] * sizeof(uint32_t));
+}
+
+inline GrCCPathCache::HashKey GrCCPathCache::HashNode::GetKey(const GrCCPathCacheEntry* entry) {
+ // The shape key is a variable-length footer to the entry allocation.
+ return HashKey{(const uint32_t*)((const char*)entry + sizeof(GrCCPathCacheEntry))};
+}
+
+inline uint32_t GrCCPathCache::HashNode::Hash(HashKey key) {
+ return GrResourceKeyHash(&key.fData[1], key.fData[0]);
+}
+
+GrCCPathCache::HashNode::~HashNode() {
+ if (!fEntry) {
+ return;
+ }
+
+ // Finalize our eviction from the path cache.
+ SkASSERT(fEntry->fCacheWeakPtr);
+ fEntry->fCacheWeakPtr->fLRU.remove(fEntry);
+ fEntry->fCacheWeakPtr = nullptr;
+
+ if (GrCCAtlas::CachedAtlasInfo* info = fEntry->fCachedAtlasInfo.get()) {
+ // Mark our own pixels invalid in the cached atlas texture now that we have been evicted.
+ info->fNumInvalidatedPathPixels += fEntry->height() * fEntry->width();
+ if (!info->fIsPurgedFromResourceCache &&
+ info->fNumInvalidatedPathPixels >= info->fNumPathPixels / 2) {
+ // Too many invalidated pixels: purge the atlas texture from the resource cache.
+ SkMessageBus<GrUniqueKeyInvalidatedMessage>::Post(
+ GrUniqueKeyInvalidatedMessage(fEntry->fAtlasKey));
+ info->fIsPurgedFromResourceCache = true;
+ }
+ }
+
+ fEntry->unref();
+}
+
+GrCCPathCache::HashNode& GrCCPathCache::HashNode::operator=(HashNode&& node) {
+ this->~HashNode();
+ return *new (this) HashNode(std::move(node));
+}
+
+sk_sp<GrCCPathCacheEntry> GrCCPathCache::find(const GrShape& shape, const MaskTransform& m,
+ CreateIfAbsent createIfAbsent) {
+ if (!shape.hasUnstyledKey()) {
+ return nullptr;
+ }
+
+ int keyLength = 1 + shape.unstyledKeySize();
+ SkAutoSTMalloc<GrShape::kMaxKeyFromDataVerbCnt * 4, uint32_t> keyData(keyLength);
+ keyData[0] = keyLength - 1;
+ shape.writeUnstyledKey(&keyData[1]);
+
+ GrCCPathCacheEntry* entry = nullptr;
+ if (HashNode* node = fHashTable.find({keyData.get()})) {
+ entry = node->entry();
+ SkASSERT(this == entry->fCacheWeakPtr);
+ if (!fuzzy_equals(m, entry->fMaskTransform)) {
+ this->evict(entry); // The path was reused with an incompatible matrix.
+ entry = nullptr;
+ }
+ }
+
+ if (!entry) {
+ if (CreateIfAbsent::kNo == createIfAbsent) {
+ return nullptr;
+ }
+ if (fHashTable.count() >= kMaxCacheCount) {
+ this->evict(fLRU.tail()); // We've exceeded our limit.
+ }
+ entry = fHashTable.set(HashNode(this, m, shape))->entry();
+ SkASSERT(fHashTable.count() <= kMaxCacheCount);
+ } else {
+ fLRU.remove(entry); // Will be re-added at head.
+ }
+
+ fLRU.addToHead(entry);
+ return sk_ref_sp(entry);
+}
+
+void GrCCPathCache::evict(const GrCCPathCacheEntry* entry) {
+ SkASSERT(entry);
+ SkASSERT(this == entry->fCacheWeakPtr);
+ SkASSERT(fLRU.isInList(entry));
+ SkASSERT(fHashTable.find(HashNode::GetKey(entry))->entry() == entry);
+
+ fHashTable.remove(HashNode::GetKey(entry)); // ~HashNode() handles the rest.
+}
+
+void GrCCPathCacheEntry::initAsStashedAtlas(const GrUniqueKey& atlasKey,
+ const SkIVector& atlasOffset, const SkRect& devBounds,
+ const SkRect& devBounds45, const SkIRect& devIBounds,
+ const SkIVector& maskShift) {
+ SkASSERT(atlasKey.isValid());
+ SkASSERT(!fCurrFlushAtlas); // Otherwise we should reuse the atlas from last time.
+
+ fAtlasKey = atlasKey;
+ fAtlasOffset = atlasOffset + maskShift;
+ SkASSERT(!fCachedAtlasInfo); // Otherwise they should have reused the cached atlas instead.
+
+ float dx = (float)maskShift.fX, dy = (float)maskShift.fY;
+ fDevBounds = devBounds.makeOffset(-dx, -dy);
+ fDevBounds45 = GrCCPathProcessor::MakeOffset45(devBounds45, -dx, -dy);
+ fDevIBounds = devIBounds.makeOffset(-maskShift.fX, -maskShift.fY);
+}
+
+void GrCCPathCacheEntry::updateToCachedAtlas(const GrUniqueKey& atlasKey,
+ const SkIVector& newAtlasOffset,
+ sk_sp<GrCCAtlas::CachedAtlasInfo> info) {
+ SkASSERT(atlasKey.isValid());
+ SkASSERT(!fCurrFlushAtlas); // Otherwise we should reuse the atlas from last time.
+
+ fAtlasKey = atlasKey;
+ fAtlasOffset = newAtlasOffset;
+
+ SkASSERT(!fCachedAtlasInfo); // Otherwise we need to invalidate our pixels in the old info.
+ fCachedAtlasInfo = std::move(info);
+ fCachedAtlasInfo->fNumPathPixels += this->height() * this->width();
+}
+
+void GrCCPathCacheEntry::onChange() {
+ // Our corresponding path was modified or deleted. Evict ourselves.
+ if (fCacheWeakPtr) {
+ fCacheWeakPtr->evict(this);
+ }
+}
diff --git a/src/gpu/ccpr/GrCCPathCache.h b/src/gpu/ccpr/GrCCPathCache.h
new file mode 100644
index 0000000000..e6e25d2562
--- /dev/null
+++ b/src/gpu/ccpr/GrCCPathCache.h
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrCCPathCache_DEFINED
+#define GrCCPathCache_DEFINED
+
+#include "SkExchange.h"
+#include "SkTHash.h"
+#include "SkTInternalLList.h"
+#include "ccpr/GrCCAtlas.h"
+#include "ccpr/GrCCPathProcessor.h"
+
+class GrCCPathCacheEntry;
+class GrShape;
+
+/**
+ * This class implements an LRU cache that maps from GrShape to GrCCPathCacheEntry objects. Shapes
+ * are only given one entry in the cache, so any time they are accessed with a different matrix, the
+ * old entry gets evicted.
+ */
+class GrCCPathCache {
+public:
+#ifdef SK_DEBUG
+ ~GrCCPathCache() {
+ // Ensure the hash table and LRU list are still coherent.
+ fHashTable.reset();
+ SkASSERT(fLRU.isEmpty());
+ }
+#endif
+
+ // Stores the components of a transformation that affect a path mask (i.e. everything but
+ // integer translation). During construction, any integer portions of the matrix's translate are
+ // shaved off and returned to the caller. The caller is responsible for those integer shifts.
+ struct MaskTransform {
+ MaskTransform(const SkMatrix& m, SkIVector* shift);
+ float fMatrix2x2[4];
+ float fSubpixelTranslate[2];
+ };
+
+ enum class CreateIfAbsent : bool {
+ kNo = false,
+ kYes = true
+ };
+
+ // Finds an entry in the cache. Shapes are only given one entry, so any time they are accessed
+ // with a different MaskTransform, the old entry gets evicted.
+ sk_sp<GrCCPathCacheEntry> find(const GrShape&, const MaskTransform&,
+ CreateIfAbsent = CreateIfAbsent::kNo);
+
+ void evict(const GrCCPathCacheEntry*);
+
+private:
+ // Wrapper around a raw GrShape key that has a specialized operator==. Used by the hash table.
+ struct HashKey {
+ const uint32_t* fData;
+ };
+ friend bool operator==(const HashKey&, const HashKey&);
+
+ // This is a special ref ptr for GrCCPathCacheEntry, used by the hash table. It can only be
+ // moved, which guarantees the hash table holds exactly one reference for each entry. When a
+ // HashNode goes out of scope, it therefore means the entry has been evicted from the cache.
+ class HashNode : SkNoncopyable {
+ public:
+ static HashKey GetKey(const HashNode& node) { return GetKey(node.fEntry); }
+ static HashKey GetKey(const GrCCPathCacheEntry*);
+ static uint32_t Hash(HashKey);
+
+ HashNode() = default;
+ HashNode(GrCCPathCache*, const MaskTransform&, const GrShape&);
+ HashNode(HashNode&& node) { fEntry = skstd::exchange(node.fEntry, nullptr); }
+ ~HashNode(); // Called when fEntry (if not null) has been evicted from the cache.
+
+ HashNode& operator=(HashNode&&);
+
+ GrCCPathCacheEntry* entry() const { return fEntry; }
+
+ private:
+ GrCCPathCacheEntry* fEntry = nullptr;
+ // The GrShape's unstyled key is stored as a variable-length footer to the 'fEntry'
+ // allocation. GetKey provides access to it.
+ };
+
+ SkTHashTable<HashNode, HashKey> fHashTable;
+ SkTInternalLList<GrCCPathCacheEntry> fLRU;
+};
+
+/**
+ * This class stores all the data necessary to draw a specific path from its corresponding cached
+ * atlas.
+ */
+class GrCCPathCacheEntry : public SkPathRef::GenIDChangeListener {
+public:
+ SK_DECLARE_INTERNAL_LLIST_INTERFACE(GrCCPathCacheEntry);
+
+ // Does this entry reference a permanent, 8-bit atlas that resides in the resource cache?
+ // (i.e. not a temporarily-stashed, fp16 coverage count atlas.)
+ bool hasCachedAtlas() const { return SkToBool(fCachedAtlasInfo); }
+
+ const SkIRect& devIBounds() const { return fDevIBounds; }
+ int width() const { return fDevIBounds.width(); }
+ int height() const { return fDevIBounds.height(); }
+
+ // Called once our path has been rendered into the mainline CCPR (fp16, coverage count) atlas.
+ // The caller will stash this atlas texture away after drawing, and during the next flush,
+ // recover it and attempt to copy any paths that got reused into permanent 8-bit atlases.
+ void initAsStashedAtlas(const GrUniqueKey& atlasKey, const SkIVector& atlasOffset,
+ const SkRect& devBounds, const SkRect& devBounds45,
+ const SkIRect& devIBounds, const SkIVector& maskShift);
+
+ // Called once our path mask has been copied into a permanent, 8-bit atlas. This method points
+ // the entry at the new atlas and updates the CachedAtlasInfo data.
+ void updateToCachedAtlas(const GrUniqueKey& atlasKey, const SkIVector& newAtlasOffset,
+ sk_sp<GrCCAtlas::CachedAtlasInfo>);
+
+ const GrUniqueKey& atlasKey() const { return fAtlasKey; }
+
+ void resetAtlasKeyAndInfo() {
+ fAtlasKey.reset();
+ fCachedAtlasInfo.reset();
+ }
+
+ // This is a utility for the caller to detect when a path gets drawn more than once during the
+ // same flush, with compatible matrices. Before adding a path to an atlas, the caller may check
+ // here to see if they have already placed the path previously during the same flush. The caller
+ // is required to reset all currFlushAtlas references back to null before any subsequent flush.
+ void setCurrFlushAtlas(const GrCCAtlas* currFlushAtlas) {
+ // This should not get called more than once in a single flush. Once fCurrFlushAtlas is
+ // non-null, it can only be set back to null (once the flush is over).
+ SkASSERT(!fCurrFlushAtlas || !currFlushAtlas);
+ fCurrFlushAtlas = currFlushAtlas;
+ }
+ const GrCCAtlas* currFlushAtlas() const { return fCurrFlushAtlas; }
+
+private:
+ using MaskTransform = GrCCPathCache::MaskTransform;
+
+ GrCCPathCacheEntry(GrCCPathCache* cache, const MaskTransform& m)
+ : fCacheWeakPtr(cache), fMaskTransform(m) {}
+
+ // Called when our corresponding path is modified or deleted.
+ void onChange() override;
+
+ GrCCPathCache* fCacheWeakPtr; // Gets manually reset to null by the path cache upon eviction.
+ const MaskTransform fMaskTransform;
+
+ GrUniqueKey fAtlasKey;
+ SkIVector fAtlasOffset;
+
+ // If null, then we are referencing a "stashed" atlas (see initAsStashedAtlas()).
+ sk_sp<GrCCAtlas::CachedAtlasInfo> fCachedAtlasInfo;
+
+ SkRect fDevBounds;
+ SkRect fDevBounds45;
+ SkIRect fDevIBounds;
+
+ // This field is for when a path gets drawn more than once during the same flush.
+ const GrCCAtlas* fCurrFlushAtlas = nullptr;
+
+ friend class GrCCPathCache;
+ friend void GrCCPathProcessor::Instance::set(const GrCCPathCacheEntry&, const SkIVector&,
+ uint32_t, DoEvenOddFill); // To access data.
+};
+
+inline void GrCCPathProcessor::Instance::set(const GrCCPathCacheEntry& entry,
+ const SkIVector& shift, GrColor color,
+ DoEvenOddFill doEvenOddFill) {
+ float dx = (float)shift.fX, dy = (float)shift.fY;
+ this->set(entry.fDevBounds.makeOffset(dx, dy), MakeOffset45(entry.fDevBounds45, dx, dy),
+ entry.fAtlasOffset - shift, color, doEvenOddFill);
+}
+
+#endif
diff --git a/src/gpu/ccpr/GrCCPathProcessor.cpp b/src/gpu/ccpr/GrCCPathProcessor.cpp
index 39d5ef2819..b8ebb44059 100644
--- a/src/gpu/ccpr/GrCCPathProcessor.cpp
+++ b/src/gpu/ccpr/GrCCPathProcessor.cpp
@@ -10,6 +10,7 @@
#include "GrGpuCommandBuffer.h"
#include "GrOnFlushResourceProvider.h"
#include "GrTexture.h"
+#include "ccpr/GrCCPerFlushResources.h"
#include "glsl/GrGLSLFragmentShaderBuilder.h"
#include "glsl/GrGLSLGeometryProcessor.h"
#include "glsl/GrGLSLProgramBuilder.h"
@@ -131,9 +132,8 @@ GrGLSLPrimitiveProcessor* GrCCPathProcessor::createGLSLInstance(const GrShaderCa
}
void GrCCPathProcessor::drawPaths(GrOpFlushState* flushState, const GrPipeline& pipeline,
- const GrBuffer* indexBuffer, const GrBuffer* vertexBuffer,
- GrBuffer* instanceBuffer, int baseInstance, int endInstance,
- const SkRect& bounds) const {
+ const GrCCPerFlushResources& resources, int baseInstance,
+ int endInstance, const SkRect& bounds) const {
const GrCaps& caps = flushState->caps();
GrPrimitiveType primitiveType = caps.usePrimitiveRestart()
? GrPrimitiveType::kTriangleStrip
@@ -144,9 +144,10 @@ void GrCCPathProcessor::drawPaths(GrOpFlushState* flushState, const GrPipeline&
GrMesh mesh(primitiveType);
auto enablePrimitiveRestart = GrPrimitiveRestart(flushState->caps().usePrimitiveRestart());
- mesh.setIndexedInstanced(indexBuffer, numIndicesPerInstance, instanceBuffer,
- endInstance - baseInstance, baseInstance, enablePrimitiveRestart);
- mesh.setVertexData(vertexBuffer);
+ mesh.setIndexedInstanced(resources.indexBuffer(), numIndicesPerInstance,
+ resources.instanceBuffer(), endInstance - baseInstance, baseInstance,
+ enablePrimitiveRestart);
+ mesh.setVertexData(resources.vertexBuffer());
flushState->rtCommandBuffer()->draw(pipeline, *this, &mesh, nullptr, 1, bounds);
}
diff --git a/src/gpu/ccpr/GrCCPathProcessor.h b/src/gpu/ccpr/GrCCPathProcessor.h
index 9ea0a164a0..785dd2c919 100644
--- a/src/gpu/ccpr/GrCCPathProcessor.h
+++ b/src/gpu/ccpr/GrCCPathProcessor.h
@@ -13,6 +13,8 @@
#include "SkPath.h"
#include <array>
+class GrCCPathCacheEntry;
+class GrCCPerFlushResources;
class GrOnFlushResourceProvider;
class GrOpFlushState;
class GrPipeline;
@@ -37,15 +39,29 @@ public:
};
static constexpr int kNumInstanceAttribs = 1 + (int)InstanceAttribs::kColor;
+ // Helper to offset the 45-degree bounding box returned by GrCCPathParser::parsePath().
+ static SkRect MakeOffset45(const SkRect& devBounds45, float dx, float dy) {
+ // devBounds45 is in "| 1 -1 | * devCoords" space.
+ // | 1 1 |
+ return devBounds45.makeOffset(dx - dy, dx + dy);
+ }
+
+ enum class DoEvenOddFill : bool {
+ kNo = false,
+ kYes = true
+ };
+
struct Instance {
SkRect fDevBounds; // "right < left" indicates even-odd fill type.
SkRect fDevBounds45; // Bounding box in "| 1 -1 | * devCoords" space.
// | 1 1 |
SkIVector fDevToAtlasOffset; // Translation from device space to location in atlas.
- uint32_t fColor;
+ GrColor fColor;
- void set(SkPath::FillType, const SkRect& devBounds, const SkRect& devBounds45,
- const SkIVector& devToAtlasOffset, uint32_t color);
+ void set(const SkRect& devBounds, const SkRect& devBounds45,
+ const SkIVector& devToAtlasOffset, GrColor, DoEvenOddFill = DoEvenOddFill::kNo);
+ void set(const GrCCPathCacheEntry&, const SkIVector& shift, GrColor,
+ DoEvenOddFill = DoEvenOddFill::kNo);
};
GR_STATIC_ASSERT(4 * 11 == sizeof(Instance));
@@ -75,9 +91,8 @@ public:
void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override {}
GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override;
- void drawPaths(GrOpFlushState*, const GrPipeline&, const GrBuffer* indexBuffer,
- const GrBuffer* vertexBuffer, GrBuffer* instanceBuffer, int baseInstance,
- int endInstance, const SkRect& bounds) const;
+ void drawPaths(GrOpFlushState*, const GrPipeline&, const GrCCPerFlushResources&,
+ int baseInstance, int endInstance, const SkRect& bounds) const;
private:
const TextureSampler fAtlasAccess;
@@ -86,14 +101,13 @@ private:
typedef GrGeometryProcessor INHERITED;
};
-inline void GrCCPathProcessor::Instance::set(SkPath::FillType fillType, const SkRect& devBounds,
- const SkRect& devBounds45,
- const SkIVector& devToAtlasOffset, uint32_t color) {
- if (SkPath::kEvenOdd_FillType == fillType) {
+inline void GrCCPathProcessor::Instance::set(const SkRect& devBounds, const SkRect& devBounds45,
+ const SkIVector& devToAtlasOffset, GrColor color,
+ DoEvenOddFill doEvenOddFill) {
+ if (DoEvenOddFill::kYes == doEvenOddFill) {
// "right < left" indicates even-odd fill type.
fDevBounds.setLTRB(devBounds.fRight, devBounds.fTop, devBounds.fLeft, devBounds.fBottom);
} else {
- SkASSERT(SkPath::kWinding_FillType == fillType);
fDevBounds = devBounds;
}
fDevBounds45 = devBounds45;
diff --git a/src/gpu/ccpr/GrCCPerFlushResources.cpp b/src/gpu/ccpr/GrCCPerFlushResources.cpp
index 4738a257fe..84f905cbc6 100644
--- a/src/gpu/ccpr/GrCCPerFlushResources.cpp
+++ b/src/gpu/ccpr/GrCCPerFlushResources.cpp
@@ -13,14 +13,83 @@
#include "GrSurfaceContextPriv.h"
#include "GrRenderTargetContext.h"
#include "SkMakeUnique.h"
+#include "ccpr/GrCCPathCache.h"
using CoverageCountBatchID = GrCCPathParser::CoverageCountBatchID;
using PathInstance = GrCCPathProcessor::Instance;
namespace {
+// Base class for an Op that renders a CCPR atlas.
+class AtlasOp : public GrDrawOp {
+public:
+ FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; }
+ RequiresDstTexture finalize(const GrCaps&, const GrAppliedClip*,
+ GrPixelConfigIsClamped) override { return RequiresDstTexture::kNo; }
+ bool onCombineIfPossible(GrOp* other, const GrCaps&) override {
+ SK_ABORT("Only expected one Op per CCPR atlas.");
+ return true;
+ }
+ void onPrepare(GrOpFlushState*) override {}
+
+protected:
+ AtlasOp(uint32_t classID, sk_sp<const GrCCPerFlushResources> resources,
+ const SkISize& drawBounds)
+ : GrDrawOp(classID)
+ , fResources(std::move(resources)) {
+ this->setBounds(SkRect::MakeIWH(drawBounds.width(), drawBounds.height()),
+ GrOp::HasAABloat::kNo, GrOp::IsZeroArea::kNo);
+ }
+
+ const sk_sp<const GrCCPerFlushResources> fResources;
+};
+
+// Copies paths from a stashed coverage count atlas into an 8-bit literal-coverage atlas.
+class CopyAtlasOp : public AtlasOp {
+public:
+ DEFINE_OP_CLASS_ID
+
+ static std::unique_ptr<GrDrawOp> Make(GrContext* context,
+ sk_sp<const GrCCPerFlushResources> resources,
+ sk_sp<GrTextureProxy> copyProxy, int baseInstance,
+ int endInstance, const SkISize& drawBounds) {
+ return std::unique_ptr<GrDrawOp>(new CopyAtlasOp(std::move(resources), std::move(copyProxy),
+ baseInstance, endInstance, drawBounds));
+ }
+
+ const char* name() const override { return "CopyAtlasOp (CCPR)"; }
+ void visitProxies(const VisitProxyFunc& fn) const override { fn(fStashedAtlasProxy.get()); }
+
+ void onExecute(GrOpFlushState* flushState) override {
+ SkASSERT(fStashedAtlasProxy);
+ GrPipeline pipeline(flushState->proxy(), GrPipeline::ScissorState::kDisabled,
+ SkBlendMode::kSrc);
+ GrCCPathProcessor pathProc(flushState->resourceProvider(), std::move(fStashedAtlasProxy));
+ pathProc.drawPaths(flushState, pipeline, *fResources, fBaseInstance, fEndInstance,
+ this->bounds());
+ // Ensure we released the stashed atlas proxy. This allows its underlying texture to be
+ // reused as the current flush's mainline CCPR atlas if needed.
+ SkASSERT(!fStashedAtlasProxy);
+ }
+
+private:
+ friend class ::GrOpMemoryPool; // for ctor
+
+ CopyAtlasOp(sk_sp<const GrCCPerFlushResources> resources, sk_sp<GrTextureProxy> copyProxy,
+ int baseInstance, int endInstance, const SkISize& drawBounds)
+ : AtlasOp(ClassID(), std::move(resources), drawBounds)
+ , fStashedAtlasProxy(copyProxy)
+ , fBaseInstance(baseInstance)
+ , fEndInstance(endInstance) {
+ }
+
+ sk_sp<GrTextureProxy> fStashedAtlasProxy;
+ const int fBaseInstance;
+ const int fEndInstance;
+};
+
// Renders coverage counts to a CCPR atlas using the resources' pre-filled GrCCPathParser.
-class RenderAtlasOp : public GrDrawOp {
+class RenderAtlasOp : public AtlasOp {
public:
DEFINE_OP_CLASS_ID
@@ -33,14 +102,6 @@ public:
// GrDrawOp interface.
const char* name() const override { return "RenderAtlasOp (CCPR)"; }
- FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; }
- RequiresDstTexture finalize(const GrCaps&, const GrAppliedClip*,
- GrPixelConfigIsClamped) override { return RequiresDstTexture::kNo; }
- bool onCombineIfPossible(GrOp* other, const GrCaps&) override {
- SK_ABORT("Only expected one Op per CCPR atlas.");
- return true;
- }
- void onPrepare(GrOpFlushState*) override {}
void onExecute(GrOpFlushState* flushState) override {
fResources->pathParser().drawCoverageCount(flushState, fBatchID, fDrawBounds);
@@ -51,29 +112,35 @@ private:
RenderAtlasOp(sk_sp<const GrCCPerFlushResources> resources, CoverageCountBatchID batchID,
const SkISize& drawBounds)
- : GrDrawOp(ClassID())
- , fResources(std::move(resources))
+ : AtlasOp(ClassID(), std::move(resources), drawBounds)
, fBatchID(batchID)
, fDrawBounds(SkIRect::MakeWH(drawBounds.width(), drawBounds.height())) {
- this->setBounds(SkRect::MakeIWH(fDrawBounds.width(), fDrawBounds.height()),
- GrOp::HasAABloat::kNo, GrOp::IsZeroArea::kNo);
}
- const sk_sp<const GrCCPerFlushResources> fResources;
const CoverageCountBatchID fBatchID;
const SkIRect fDrawBounds;
};
}
+static int inst_buffer_count(const GrCCPerFlushResourceSpecs& specs) {
+ return specs.fNumCachedPaths +
+ specs.fNumCopiedPaths*2 + // 1 copy + 1 draw.
+ specs.fNumRenderedPaths;
+}
+
GrCCPerFlushResources::GrCCPerFlushResources(GrOnFlushResourceProvider* onFlushRP,
const GrCCPerFlushResourceSpecs& specs)
- : fPathParser(specs.fNumRenderedPaths + specs.fNumClipPaths, specs.fParsingPathStats)
- , fAtlasStack(kAlpha_half_GrPixelConfig, specs.fAtlasSpecs, onFlushRP->caps())
+ : fPathParser(specs.fNumRenderedPaths + specs.fNumClipPaths, specs.fRenderedPathStats)
+ , fCopyAtlasStack(kAlpha_8_GrPixelConfig, specs.fCopyAtlasSpecs, onFlushRP->caps())
+ , fRenderedAtlasStack(kAlpha_half_GrPixelConfig, specs.fRenderedAtlasSpecs,
+ onFlushRP->caps())
, fIndexBuffer(GrCCPathProcessor::FindIndexBuffer(onFlushRP))
, fVertexBuffer(GrCCPathProcessor::FindVertexBuffer(onFlushRP))
, fInstanceBuffer(onFlushRP->makeBuffer(kVertex_GrBufferType,
- specs.fNumRenderedPaths * sizeof(PathInstance))) {
+ inst_buffer_count(specs) * sizeof(PathInstance)))
+ , fNextCopyInstanceIdx(0)
+ , fNextPathInstanceIdx(specs.fNumCopiedPaths) {
if (!fIndexBuffer) {
SkDebugf("WARNING: failed to allocate CCPR index buffer. No paths will be drawn.\n");
return;
@@ -88,26 +155,43 @@ GrCCPerFlushResources::GrCCPerFlushResources(GrOnFlushResourceProvider* onFlushR
}
fPathInstanceData = static_cast<PathInstance*>(fInstanceBuffer->map());
SkASSERT(fPathInstanceData);
- SkDEBUGCODE(fEndPathInstance = specs.fNumRenderedPaths);
+ SkDEBUGCODE(fEndCopyInstance = specs.fNumCopiedPaths);
+ SkDEBUGCODE(fEndPathInstance = inst_buffer_count(specs));
+}
+
+GrCCAtlas* GrCCPerFlushResources::copyPathToCachedAtlas(const GrCCPathCacheEntry& entry,
+ GrCCPathProcessor::DoEvenOddFill evenOdd,
+ SkIVector* newAtlasOffset) {
+ SkASSERT(this->isMapped());
+ SkASSERT(fNextCopyInstanceIdx < fEndCopyInstance);
+ SkASSERT(!entry.hasCachedAtlas()); // Unexpected, but not necessarily a problem.
+
+ if (GrCCAtlas* retiredAtlas = fCopyAtlasStack.addRect(entry.devIBounds(), newAtlasOffset)) {
+ // We did not fit in the previous copy atlas and it was retired. We will render the copies
+ // up until fNextCopyInstanceIdx into the retired atlas during finalize().
+ retiredAtlas->setUserBatchID(fNextCopyInstanceIdx);
+ }
+
+ fPathInstanceData[fNextCopyInstanceIdx++].set(entry, *newAtlasOffset, GrColor_WHITE, evenOdd);
+ return &fCopyAtlasStack.current();
}
const GrCCAtlas* GrCCPerFlushResources::renderPathInAtlas(const SkIRect& clipIBounds,
const SkMatrix& m, const SkPath& path,
SkRect* devBounds, SkRect* devBounds45,
+ SkIRect* devIBounds,
SkIVector* devToAtlasOffset) {
SkASSERT(this->isMapped());
SkASSERT(fNextPathInstanceIdx < fEndPathInstance);
fPathParser.parsePath(m, path, devBounds, devBounds45);
+ devBounds->roundOut(devIBounds);
- SkIRect devIBounds;
- devBounds->roundOut(&devIBounds);
-
- if (!this->placeParsedPathInAtlas(clipIBounds, devIBounds, devToAtlasOffset)) {
+ if (!this->placeParsedPathInAtlas(clipIBounds, *devIBounds, devToAtlasOffset)) {
SkDEBUGCODE(--fEndPathInstance);
return nullptr; // Path was degenerate or clipped away.
}
- return &fAtlasStack.current();
+ return &fRenderedAtlasStack.current();
}
const GrCCAtlas* GrCCPerFlushResources::renderDeviceSpacePathInAtlas(
@@ -118,7 +202,7 @@ const GrCCAtlas* GrCCPerFlushResources::renderDeviceSpacePathInAtlas(
if (!this->placeParsedPathInAtlas(clipIBounds, devPathIBounds, devToAtlasOffset)) {
return nullptr;
}
- return &fAtlasStack.current();
+ return &fRenderedAtlasStack.current();
}
bool GrCCPerFlushResources::placeParsedPathInAtlas(const SkIRect& clipIBounds,
@@ -137,7 +221,8 @@ bool GrCCPerFlushResources::placeParsedPathInAtlas(const SkIRect& clipIBounds,
return false;
}
- if (GrCCAtlas* retiredAtlas = fAtlasStack.addRect(clippedPathIBounds, devToAtlasOffset)) {
+ if (GrCCAtlas* retiredAtlas =
+ fRenderedAtlasStack.addRect(clippedPathIBounds, devToAtlasOffset)) {
// We did not fit in the previous coverage count atlas and it was retired. Close the path
// parser's current batch (which does not yet include the path we just parsed). We will
// render this batch into the retired atlas during finalize().
@@ -149,16 +234,21 @@ bool GrCCPerFlushResources::placeParsedPathInAtlas(const SkIRect& clipIBounds,
}
bool GrCCPerFlushResources::finalize(GrOnFlushResourceProvider* onFlushRP,
+ sk_sp<GrTextureProxy> stashedAtlasProxy,
SkTArray<sk_sp<GrRenderTargetContext>>* out) {
SkASSERT(this->isMapped());
SkASSERT(fNextPathInstanceIdx == fEndPathInstance);
+ // No assert for fEndCopyInstance because the caller may have detected and skipped duplicates.
fInstanceBuffer->unmap();
fPathInstanceData = nullptr;
- if (!fAtlasStack.empty()) {
+ if (!fCopyAtlasStack.empty()) {
+ fCopyAtlasStack.current().setUserBatchID(fNextCopyInstanceIdx);
+ }
+ if (!fRenderedAtlasStack.empty()) {
CoverageCountBatchID batchID = fPathParser.closeCurrentBatch();
- fAtlasStack.current().setUserBatchID(batchID);
+ fRenderedAtlasStack.current().setUserBatchID(batchID);
}
// Build the GPU buffers to render path coverage counts. (This must not happen until after the
@@ -168,8 +258,30 @@ bool GrCCPerFlushResources::finalize(GrOnFlushResourceProvider* onFlushRP,
return false;
}
- // Render the atlas(es).
- for (GrCCAtlasStack::Iter atlas(fAtlasStack); atlas.next();) {
+ // Draw the copies from the stashed atlas into 8-bit cached atlas(es).
+ int baseCopyInstance = 0;
+ for (GrCCAtlasStack::Iter atlas(fCopyAtlasStack); atlas.next();) {
+ int endCopyInstance = atlas->getUserBatchID();
+ if (endCopyInstance <= baseCopyInstance) {
+ SkASSERT(endCopyInstance == baseCopyInstance);
+ continue;
+ }
+ if (auto rtc = atlas->makeRenderTargetContext(onFlushRP)) {
+ GrContext* ctx = rtc->surfPriv().getContext();
+ auto op = CopyAtlasOp::Make(ctx, sk_ref_sp(this), stashedAtlasProxy, baseCopyInstance,
+ endCopyInstance, atlas->drawBounds());
+ rtc->addDrawOp(GrNoClip(), std::move(op));
+ out->push_back(std::move(rtc));
+ }
+ baseCopyInstance = endCopyInstance;
+ }
+
+ // Release the stashed atlas before creating new one(s). This allows us to recycle the same
+ // underlying texture with the upcoming rendered atlases.
+ stashedAtlasProxy = nullptr;
+
+ // Render the coverage count atlas(es).
+ for (GrCCAtlasStack::Iter atlas(fRenderedAtlasStack); atlas.next();) {
if (auto rtc = atlas->makeRenderTargetContext(onFlushRP)) {
auto op = RenderAtlasOp::Make(rtc->surfPriv().getContext(), sk_ref_sp(this),
atlas->getUserBatchID(), atlas->drawBounds());
@@ -180,3 +292,22 @@ bool GrCCPerFlushResources::finalize(GrOnFlushResourceProvider* onFlushRP,
return true;
}
+
+void GrCCPerFlushResourceSpecs::convertCopiesToRenders() {
+ fNumRenderedPaths += fNumCopiedPaths;
+ fNumCopiedPaths = 0;
+
+ fRenderedAtlasSpecs.fApproxNumPixels += fCopyAtlasSpecs.fApproxNumPixels;
+ fRenderedAtlasSpecs.fMinWidth =
+ SkTMax(fRenderedAtlasSpecs.fMinWidth, fCopyAtlasSpecs.fMinWidth);
+ fRenderedAtlasSpecs.fMinHeight =
+ SkTMax(fRenderedAtlasSpecs.fMinHeight, fCopyAtlasSpecs.fMinHeight);
+ fCopyAtlasSpecs = GrCCAtlas::Specs();
+
+ fRenderedPathStats.fMaxPointsPerPath =
+ SkTMax(fRenderedPathStats.fMaxPointsPerPath, fCopyPathStats.fMaxPointsPerPath);
+ fRenderedPathStats.fNumTotalSkPoints += fCopyPathStats.fNumTotalSkPoints;
+ fRenderedPathStats.fNumTotalSkVerbs += fCopyPathStats.fNumTotalSkVerbs;
+ fRenderedPathStats.fNumTotalConicWeights += fCopyPathStats.fNumTotalConicWeights;
+ fCopyPathStats = GrCCPathParser::PathStats();
+}
diff --git a/src/gpu/ccpr/GrCCPerFlushResources.h b/src/gpu/ccpr/GrCCPerFlushResources.h
index e920c06acc..cdc89f8312 100644
--- a/src/gpu/ccpr/GrCCPerFlushResources.h
+++ b/src/gpu/ccpr/GrCCPerFlushResources.h
@@ -13,17 +13,29 @@
#include "ccpr/GrCCPathParser.h"
#include "ccpr/GrCCPathProcessor.h"
+class GrCCPathCacheEntry;
+class GrOnFlushResourceProvider;
+
/**
* This struct encapsulates the minimum and desired requirements for the GPU resources required by
* CCPR in a given flush.
*/
struct GrCCPerFlushResourceSpecs {
+ int fNumCachedPaths = 0;
+
+ int fNumCopiedPaths = 0;
+ GrCCPathParser::PathStats fCopyPathStats;
+ GrCCAtlas::Specs fCopyAtlasSpecs;
+
int fNumRenderedPaths = 0;
int fNumClipPaths = 0;
- GrCCPathParser::PathStats fParsingPathStats;
- GrCCAtlas::Specs fAtlasSpecs;
+ GrCCPathParser::PathStats fRenderedPathStats;
+ GrCCAtlas::Specs fRenderedAtlasSpecs;
- bool isEmpty() const { return 0 == fNumRenderedPaths + fNumClipPaths; }
+ bool isEmpty() const {
+ return 0 == fNumCachedPaths + fNumCopiedPaths + fNumRenderedPaths + fNumClipPaths;
+ }
+ void convertCopiesToRenders();
};
/**
@@ -37,9 +49,17 @@ public:
bool isMapped() const { return SkToBool(fPathInstanceData); }
- // Renders a path into a temporary atlas. See GrCCPathParser for a description of the arguments.
+ // Copies a path out of the the previous flush's stashed mainline coverage count atlas, and into
+ // a cached, 8-bit, literal-coverage atlas. The actual source texture to copy from will be
+ // provided at the time finalize() is called.
+ GrCCAtlas* copyPathToCachedAtlas(const GrCCPathCacheEntry&, GrCCPathProcessor::DoEvenOddFill,
+ SkIVector* newAtlasOffset);
+
+ // These two methods render a path into a temporary coverage count atlas. See GrCCPathParser for
+ // a description of the arguments. The returned atlases are "const" to prevent the caller from
+ // assigning a unique key.
const GrCCAtlas* renderPathInAtlas(const SkIRect& clipIBounds, const SkMatrix&, const SkPath&,
- SkRect* devBounds, SkRect* devBounds45,
+ SkRect* devBounds, SkRect* devBounds45, SkIRect* devIBounds,
SkIVector* devToAtlasOffset);
const GrCCAtlas* renderDeviceSpacePathInAtlas(const SkIRect& clipIBounds, const SkPath& devPath,
const SkIRect& devPathIBounds,
@@ -58,8 +78,11 @@ public:
return fPathInstanceData[fNextPathInstanceIdx++];
}
- // Finishes off the GPU buffers and renders the atlas(es).
- bool finalize(GrOnFlushResourceProvider*, SkTArray<sk_sp<GrRenderTargetContext>>* out);
+ // Finishes off the GPU buffers and renders the atlas(es). 'stashedAtlasProxy', if provided, is
+ // the mainline coverage count atlas from the previous flush. It will be used as the source
+ // texture for any copies setup by copyStashedPathToAtlas().
+ bool finalize(GrOnFlushResourceProvider*, sk_sp<GrTextureProxy> stashedAtlasProxy,
+ SkTArray<sk_sp<GrRenderTargetContext>>* out);
// Accessors used by draw calls, once the resources have been finalized.
const GrCCPathParser& pathParser() const { SkASSERT(!this->isMapped()); return fPathParser; }
@@ -67,19 +90,38 @@ public:
const GrBuffer* vertexBuffer() const { SkASSERT(!this->isMapped()); return fVertexBuffer.get();}
GrBuffer* instanceBuffer() const { SkASSERT(!this->isMapped()); return fInstanceBuffer.get(); }
+ // Returns the mainline coverage count atlas that the client may stash for next flush, if any.
+ // The caller is responsible to call getOrAssignUniqueKey() on this atlas if they wish to
+ // actually stash it in order to copy paths into cached atlases.
+ GrCCAtlas* nextAtlasToStash() {
+ return fRenderedAtlasStack.empty() ? nullptr : &fRenderedAtlasStack.front();
+ }
+
+ // Returs true if the client has called getOrAssignUniqueKey() on our nextAtlasToStash().
+ bool hasStashedAtlas() const {
+ return !fRenderedAtlasStack.empty() && fRenderedAtlasStack.front().uniqueKey().isValid();
+ }
+ const GrUniqueKey& stashedAtlasKey() const {
+ SkASSERT(this->hasStashedAtlas());
+ return fRenderedAtlasStack.front().uniqueKey();
+ }
+
private:
bool placeParsedPathInAtlas(const SkIRect& clipIBounds, const SkIRect& pathIBounds,
SkIVector* devToAtlasOffset);
GrCCPathParser fPathParser;
- GrCCAtlasStack fAtlasStack;
+ GrCCAtlasStack fCopyAtlasStack;
+ GrCCAtlasStack fRenderedAtlasStack;
const sk_sp<const GrBuffer> fIndexBuffer;
const sk_sp<const GrBuffer> fVertexBuffer;
const sk_sp<GrBuffer> fInstanceBuffer;
GrCCPathProcessor::Instance* fPathInstanceData = nullptr;
- int fNextPathInstanceIdx = 0;
+ int fNextCopyInstanceIdx;
+ SkDEBUGCODE(int fEndCopyInstance);
+ int fNextPathInstanceIdx;
SkDEBUGCODE(int fEndPathInstance);
};
diff --git a/src/gpu/ccpr/GrCCSTLList.h b/src/gpu/ccpr/GrCCSTLList.h
index aed61ea24e..dec257d077 100644
--- a/src/gpu/ccpr/GrCCSTLList.h
+++ b/src/gpu/ccpr/GrCCSTLList.h
@@ -45,14 +45,16 @@ public:
fTail = !nextTail ? newRightHead : nextTail;
}
- struct Iter {
+ template<typename U> struct Iter {
bool operator!=(const Iter& that) { return fCurr != that.fCurr; }
- const T& operator*() { return *fCurr; }
+ U& operator*() { return *fCurr; }
void operator++() { fCurr = fCurr->fNext; }
- const T* fCurr;
+ U* fCurr;
};
- Iter begin() const { return Iter{&fHead}; }
- Iter end() const { return Iter{nullptr}; }
+ Iter<const T> begin() const { return Iter<const T>{&fHead}; }
+ Iter<const T> end() const { return Iter<const T>{nullptr}; }
+ Iter<T> begin() { return Iter<T>{&fHead}; }
+ Iter<T> end() { return Iter<T>{nullptr}; }
private:
T fHead;
diff --git a/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp b/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp
index eaab184ffd..122569c072 100644
--- a/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp
+++ b/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp
@@ -50,6 +50,8 @@ bool GrCoverageCountingPathRenderer::IsSupported(const GrCaps& caps) {
caps.instanceAttribSupport() && GrCaps::kNone_MapFlags != caps.mapBufferFlags() &&
caps.isConfigTexturable(kAlpha_half_GrPixelConfig) &&
caps.isConfigRenderable(kAlpha_half_GrPixelConfig) &&
+ caps.isConfigTexturable(kAlpha_8_GrPixelConfig) &&
+ caps.isConfigRenderable(kAlpha_8_GrPixelConfig) &&
!caps.blacklistCoverageCounting();
}
@@ -112,24 +114,22 @@ bool GrCoverageCountingPathRenderer::onDrawPath(const DrawPathArgs& args) {
GrRenderTargetContext* rtc = args.fRenderTargetContext;
args.fClip->getConservativeBounds(rtc->width(), rtc->height(), &clipIBounds, nullptr);
- SkPath path;
- args.fShape->asPath(&path);
-
SkRect devBounds;
- args.fViewMatrix->mapRect(&devBounds, path.getBounds());
+ args.fViewMatrix->mapRect(&devBounds, args.fShape->bounds());
std::unique_ptr<GrCCDrawPathsOp> op;
if (SkTMax(devBounds.height(), devBounds.width()) > kPathCropThreshold) {
// The path is too large. Crop it or analytic AA can run out of fp32 precision.
SkPath croppedPath;
- path.transform(*args.fViewMatrix, &croppedPath);
+ args.fShape->asPath(&croppedPath);
+ croppedPath.transform(*args.fViewMatrix, &croppedPath);
crop_path(croppedPath, clipIBounds, &croppedPath);
// FIXME: This breaks local coords: http://skbug.com/8003
- op = GrCCDrawPathsOp::Make(args.fContext, clipIBounds, SkMatrix::I(), croppedPath,
+ op = GrCCDrawPathsOp::Make(args.fContext, clipIBounds, SkMatrix::I(), GrShape(croppedPath),
croppedPath.getBounds(), std::move(args.fPaint));
} else {
- op = GrCCDrawPathsOp::Make(args.fContext, clipIBounds, *args.fViewMatrix, path, devBounds,
- std::move(args.fPaint));
+ op = GrCCDrawPathsOp::Make(args.fContext, clipIBounds, *args.fViewMatrix, *args.fShape,
+ devBounds, std::move(args.fPaint));
}
this->recordOp(std::move(op), args);
@@ -179,18 +179,37 @@ std::unique_ptr<GrFragmentProcessor> GrCoverageCountingPathRenderer::makeClipPro
void GrCoverageCountingPathRenderer::preFlush(GrOnFlushResourceProvider* onFlushRP,
const uint32_t* opListIDs, int numOpListIDs,
SkTArray<sk_sp<GrRenderTargetContext>>* out) {
+ using DoCopiesToCache = GrCCDrawPathsOp::DoCopiesToCache;
SkASSERT(!fFlushing);
SkASSERT(fFlushingPaths.empty());
SkDEBUGCODE(fFlushing = true);
+ // Dig up the stashed atlas from the previous flush (if any) so we can attempt to copy any
+ // reusable paths out of it and into the resource cache. We also need to clear its unique key.
+ sk_sp<GrTextureProxy> stashedAtlasProxy;
+ if (fStashedAtlasKey.isValid()) {
+ stashedAtlasProxy = onFlushRP->findOrCreateProxyByUniqueKey(fStashedAtlasKey,
+ GrCCAtlas::kTextureOrigin);
+ if (stashedAtlasProxy) {
+ // Instantiate the proxy so we can clear the underlying texture's unique key.
+ onFlushRP->instatiateProxy(stashedAtlasProxy.get());
+ onFlushRP->removeUniqueKeyFromProxy(fStashedAtlasKey, stashedAtlasProxy.get());
+ } else {
+ fStashedAtlasKey.reset(); // Indicate there is no stashed atlas to copy from.
+ }
+ }
+
if (fPendingPaths.empty()) {
+ fStashedAtlasKey.reset();
return; // Nothing to draw.
}
- GrCCPerFlushResourceSpecs resourceSpecs;
+ GrCCPerFlushResourceSpecs specs;
int maxPreferredRTSize = onFlushRP->caps()->maxPreferredRenderTargetSize();
- resourceSpecs.fAtlasSpecs.fMaxPreferredTextureSize = maxPreferredRTSize;
- resourceSpecs.fAtlasSpecs.fMinTextureSize = SkTMin(1024, maxPreferredRTSize);
+ specs.fCopyAtlasSpecs.fMaxPreferredTextureSize = SkTMin(2048, maxPreferredRTSize);
+ SkASSERT(0 == specs.fCopyAtlasSpecs.fMinTextureSize);
+ specs.fRenderedAtlasSpecs.fMaxPreferredTextureSize = maxPreferredRTSize;
+ specs.fRenderedAtlasSpecs.fMinTextureSize = SkTMin(1024, maxPreferredRTSize);
// Move the per-opList paths that are about to be flushed from fPendingPaths to fFlushingPaths,
// and count them up so we can preallocate buffers.
@@ -204,40 +223,49 @@ void GrCoverageCountingPathRenderer::preFlush(GrOnFlushResourceProvider* onFlush
fFlushingPaths.push_back(std::move(iter->second));
fPendingPaths.erase(iter);
- for (const GrCCDrawPathsOp* op : fFlushingPaths.back()->fDrawOps) {
- op->accountForOwnPaths(&resourceSpecs);
+ for (GrCCDrawPathsOp* op : fFlushingPaths.back()->fDrawOps) {
+ op->accountForOwnPaths(&fPathCache, onFlushRP, fStashedAtlasKey, &specs);
}
for (const auto& clipsIter : fFlushingPaths.back()->fClipPaths) {
- clipsIter.second.accountForOwnPath(&resourceSpecs);
+ clipsIter.second.accountForOwnPath(&specs);
}
}
+ fStashedAtlasKey.reset();
- if (resourceSpecs.isEmpty()) {
+ if (specs.isEmpty()) {
return; // Nothing to draw.
}
- auto resources = sk_make_sp<GrCCPerFlushResources>(onFlushRP, resourceSpecs);
+ // Determine if there are enough reusable paths from last flush for it to be worth our time to
+ // copy them to cached atlas(es).
+ DoCopiesToCache doCopies = DoCopiesToCache(specs.fNumCopiedPaths > 100 ||
+ specs.fCopyAtlasSpecs.fApproxNumPixels > 512 * 256);
+ if (specs.fNumCopiedPaths && DoCopiesToCache::kNo == doCopies) {
+ specs.convertCopiesToRenders();
+ SkASSERT(!specs.fNumCopiedPaths);
+ }
+
+ auto resources = sk_make_sp<GrCCPerFlushResources>(onFlushRP, specs);
if (!resources->isMapped()) {
return; // Some allocation failed.
}
- // Layout atlas(es) and parse paths.
- SkDEBUGCODE(int numSkippedPaths = 0);
+ // Layout the atlas(es) and parse paths.
for (const auto& flushingPaths : fFlushingPaths) {
for (GrCCDrawPathsOp* op : flushingPaths->fDrawOps) {
- op->setupResources(resources.get(), onFlushRP);
- SkDEBUGCODE(numSkippedPaths += op->numSkippedInstances_debugOnly());
+ op->setupResources(onFlushRP, resources.get(), doCopies);
}
for (auto& clipsIter : flushingPaths->fClipPaths) {
clipsIter.second.renderPathInAtlas(resources.get(), onFlushRP);
}
}
- SkASSERT(resources->nextPathInstanceIdx() == resourceSpecs.fNumRenderedPaths - numSkippedPaths);
- // Allocate the atlases and create instance buffers to draw them.
- if (!resources->finalize(onFlushRP, out)) {
+ // Allocate resources and then render the atlas(es).
+ if (!resources->finalize(onFlushRP, std::move(stashedAtlasProxy), out)) {
return;
}
+ // Verify the stashed atlas got released so its texture could be recycled.
+ SkASSERT(!stashedAtlasProxy);
// Commit flushing paths to the resources once they are successfully completed.
for (auto& flushingPaths : fFlushingPaths) {
@@ -249,14 +277,24 @@ void GrCoverageCountingPathRenderer::preFlush(GrOnFlushResourceProvider* onFlush
void GrCoverageCountingPathRenderer::postFlush(GrDeferredUploadToken, const uint32_t* opListIDs,
int numOpListIDs) {
SkASSERT(fFlushing);
+ SkASSERT(!fStashedAtlasKey.isValid()); // Should have been cleared in preFlush().
- // In DDL mode these aren't guaranteed to be deleted so we must clear out the perFlush
- // resources manually.
- for (auto& flushingPaths : fFlushingPaths) {
- flushingPaths->fFlushResources = nullptr;
+ if (!fFlushingPaths.empty()) {
+ // Note the stashed atlas's key for next flush, if any.
+ auto resources = fFlushingPaths.front()->fFlushResources.get();
+ if (resources && resources->hasStashedAtlas()) {
+ fStashedAtlasKey = resources->stashedAtlasKey();
+ }
+
+ // In DDL mode these aren't guaranteed to be deleted so we must clear out the perFlush
+ // resources manually.
+ for (auto& flushingPaths : fFlushingPaths) {
+ flushingPaths->fFlushResources = nullptr;
+ }
+
+ // We wait to erase these until after flush, once Ops and FPs are done accessing their data.
+ fFlushingPaths.reset();
}
- // We wait to erase these until after flush, once Ops and FPs are done accessing their data.
- fFlushingPaths.reset();
SkDEBUGCODE(fFlushing = false);
}
diff --git a/src/gpu/ccpr/GrCoverageCountingPathRenderer.h b/src/gpu/ccpr/GrCoverageCountingPathRenderer.h
index 8cb9713bcb..0007319b55 100644
--- a/src/gpu/ccpr/GrCoverageCountingPathRenderer.h
+++ b/src/gpu/ccpr/GrCoverageCountingPathRenderer.h
@@ -11,6 +11,7 @@
#include "GrCCPerOpListPaths.h"
#include "GrPathRenderer.h"
#include "GrRenderTargetOpList.h"
+#include "ccpr/GrCCPathCache.h"
#include "ccpr/GrCCPerFlushResources.h"
#include <map>
@@ -70,6 +71,8 @@ public:
SkTArray<sk_sp<GrRenderTargetContext>>* out) override;
void postFlush(GrDeferredUploadToken, const uint32_t* opListIDs, int numOpListIDs) override;
+ const GrUniqueKey& testingOnly_getStashedAtlasKey() const;
+
private:
GrCoverageCountingPathRenderer(bool drawCachablePaths)
: fDrawCachablePaths(drawCachablePaths) {}
@@ -85,6 +88,10 @@ private:
// fFlushingPaths holds the GrCCPerOpListPaths objects that are currently being flushed.
// (It will only contain elements when fFlushing is true.)
SkSTArray<4, sk_sp<GrCCPerOpListPaths>> fFlushingPaths;
+
+ GrCCPathCache fPathCache;
+ GrUniqueKey fStashedAtlasKey;
+
SkDEBUGCODE(bool fFlushing = false);
const bool fDrawCachablePaths;
diff --git a/tests/GrCCPRTest.cpp b/tests/GrCCPRTest.cpp
index bbcf6706a7..3b1ab3368d 100644
--- a/tests/GrCCPRTest.cpp
+++ b/tests/GrCCPRTest.cpp
@@ -17,9 +17,11 @@
#include "GrRenderTargetContext.h"
#include "GrRenderTargetContextPriv.h"
#include "GrShape.h"
+#include "GrTexture.h"
#include "SkMatrix.h"
#include "SkPathPriv.h"
#include "SkRect.h"
+#include "sk_tool_utils.h"
#include "ccpr/GrCoverageCountingPathRenderer.h"
#include "mock/GrMockTypes.h"
#include <cmath>
@@ -51,6 +53,11 @@ private:
const SkPath fPath;
};
+enum class MarkVolatile : bool {
+ kNo = false,
+ kYes = true
+};
+
class CCPRPathDrawer {
public:
CCPRPathDrawer(GrContext* ctx, skiatest::Reporter* reporter)
@@ -68,22 +75,26 @@ public:
}
}
+ GrContext* ctx() const { return fCtx; }
+ GrCoverageCountingPathRenderer* ccpr() const { return fCCPR; }
+
bool valid() const { return fCCPR && fRTC; }
void clear() const { fRTC->clear(nullptr, 0, GrRenderTargetContext::CanClearFullscreen::kYes); }
void abandonGrContext() { fCtx = nullptr; fCCPR = nullptr; fRTC = nullptr; }
- void drawPath(SkPath path, GrColor4f color = GrColor4f(0, 1, 0, 1)) const {
+ void drawPath(SkPath path, const SkMatrix& matrix = SkMatrix::I(),
+ MarkVolatile markVolatile = MarkVolatile::kYes) const {
SkASSERT(this->valid());
GrPaint paint;
- paint.setColor4f(color);
+ paint.setColor4f(GrColor4f(0, 1, 0, 1));
GrNoClip noClip;
SkIRect clipBounds = SkIRect::MakeWH(kCanvasSize, kCanvasSize);
- SkMatrix matrix = SkMatrix::I();
-
- path.setIsVolatile(true);
+ if (MarkVolatile::kYes == markVolatile) {
+ path.setIsVolatile(true);
+ }
GrShape shape(path);
fCCPR->drawPath({fCtx, std::move(paint), &GrUserStencilSettings::kUnused, fRTC.get(),
@@ -106,9 +117,9 @@ public:
}
private:
- GrContext* fCtx;
- GrCoverageCountingPathRenderer* fCCPR;
- sk_sp<GrRenderTargetContext> fRTC;
+ GrContext* fCtx;
+ GrCoverageCountingPathRenderer* fCCPR;
+ sk_sp<GrRenderTargetContext> fRTC;
};
class CCPRTest {
@@ -120,6 +131,9 @@ public:
mockOptions.fConfigOptions[kAlpha_half_GrPixelConfig].fRenderability =
GrMockOptions::ConfigOptions::Renderability::kNonMSAA;
mockOptions.fConfigOptions[kAlpha_half_GrPixelConfig].fTexturable = true;
+ mockOptions.fConfigOptions[kAlpha_8_GrPixelConfig].fRenderability =
+ GrMockOptions::ConfigOptions::Renderability::kNonMSAA;
+ mockOptions.fConfigOptions[kAlpha_8_GrPixelConfig].fTexturable = true;
mockOptions.fGeometryShaderSupport = true;
mockOptions.fIntegerSupport = true;
mockOptions.fFlatInterpolationSupport = true;
@@ -155,8 +169,8 @@ protected:
virtual void customizeMockOptions(GrMockOptions*) {}
virtual void onRun(skiatest::Reporter* reporter, CCPRPathDrawer& ccpr) = 0;
- sk_sp<GrContext> fMockContext;
- SkPath fPath;
+ sk_sp<GrContext> fMockContext;
+ SkPath fPath;
};
#define DEF_CCPR_TEST(name) \
@@ -263,6 +277,102 @@ class GrCCPRTest_parseEmptyPath : public CCPRTest {
};
DEF_CCPR_TEST(GrCCPRTest_parseEmptyPath)
+// This test exercises CCPR's cache capabilities by drawing many paths with two different
+// transformation matrices. We then vary the matrices independently by whole and partial pixels,
+// and verify the caching behaved as expected.
+class GrCCPRTest_cache : public CCPRTest {
+ void onRun(skiatest::Reporter* reporter, CCPRPathDrawer& ccpr) override {
+ static constexpr int kPathSize = 20;
+ SkRandom rand;
+
+ SkPath paths[200];
+ int primes[11] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31};
+ for (size_t i = 0; i < SK_ARRAY_COUNT(paths); ++i) {
+ int numPts = rand.nextRangeU(GrShape::kMaxKeyFromDataVerbCnt + 1,
+ GrShape::kMaxKeyFromDataVerbCnt * 2);
+ paths[i] = sk_tool_utils::make_star(SkRect::MakeIWH(kPathSize, kPathSize), numPts,
+ primes[rand.nextU() % SK_ARRAY_COUNT(primes)]);
+ }
+
+ SkMatrix matrices[2] = {
+ SkMatrix::MakeTrans(5, 5),
+ SkMatrix::MakeTrans(kCanvasSize - kPathSize - 5, kCanvasSize - kPathSize - 5)
+ };
+
+ int firstAtlasID = -1;
+
+ for (int flushIdx = 0; flushIdx < 10; ++flushIdx) {
+ // Draw all the paths and flush.
+ for (size_t i = 0; i < SK_ARRAY_COUNT(paths); ++i) {
+ ccpr.drawPath(paths[i], matrices[i % 2], MarkVolatile::kNo);
+ }
+ ccpr.flush();
+
+ // Figure out the mock backend ID of the atlas texture stashed away by CCPR.
+ GrMockTextureInfo stashedAtlasInfo;
+ stashedAtlasInfo.fID = -1;
+ const GrUniqueKey& stashedAtlasKey = ccpr.ccpr()->testingOnly_getStashedAtlasKey();
+ if (stashedAtlasKey.isValid()) {
+ GrResourceProvider* rp = ccpr.ctx()->contextPriv().resourceProvider();
+ sk_sp<GrSurface> stashedAtlas = rp->findByUniqueKey<GrSurface>(stashedAtlasKey);
+ REPORTER_ASSERT(reporter, stashedAtlas);
+ if (stashedAtlas) {
+ const auto& backendTexture = stashedAtlas->asTexture()->getBackendTexture();
+ backendTexture.getMockTextureInfo(&stashedAtlasInfo);
+ }
+ }
+
+ if (0 == flushIdx) {
+ // First flush: just note the ID of the stashed atlas and continue.
+ REPORTER_ASSERT(reporter, stashedAtlasKey.isValid());
+ firstAtlasID = stashedAtlasInfo.fID;
+ continue;
+ }
+
+ switch (flushIdx % 3) {
+ case 1:
+ // This draw should have gotten 100% cache hits; we only did integer translates
+ // last time (or none if it was the first flush). Therefore, no atlas should
+ // have been stashed away.
+ REPORTER_ASSERT(reporter, !stashedAtlasKey.isValid());
+
+ // Invalidate even path masks.
+ matrices[0].preTranslate(1.6f, 1.4f);
+ break;
+
+ case 2:
+ // Even path masks were invalidated last iteration by a subpixel translate. They
+ // should have been re-rendered this time and stashed away in the CCPR atlas.
+ REPORTER_ASSERT(reporter, stashedAtlasKey.isValid());
+
+ // 'firstAtlasID' should be kept as a scratch texture in the resource cache.
+ REPORTER_ASSERT(reporter, stashedAtlasInfo.fID == firstAtlasID);
+
+ // Invalidate odd path masks.
+ matrices[1].preTranslate(-1.4f, -1.6f);
+ break;
+
+ case 0:
+ // Odd path masks were invalidated last iteration by a subpixel translate. They
+ // should have been re-rendered this time and stashed away in the CCPR atlas.
+ REPORTER_ASSERT(reporter, stashedAtlasKey.isValid());
+
+ // 'firstAtlasID' is the same texture that got stashed away last time (assuming
+ // no assertion failures). So if it also got stashed this time, it means we
+ // first copied the even paths out of it, then recycled the exact same texture
+ // to render the odd paths. This is the expected behavior.
+ REPORTER_ASSERT(reporter, stashedAtlasInfo.fID == firstAtlasID);
+
+ // Integer translates: all path masks stay valid.
+ matrices[0].preTranslate(-1, -1);
+ matrices[1].preTranslate(1, 1);
+ break;
+ }
+ }
+ }
+};
+DEF_CCPR_TEST(GrCCPRTest_cache)
+
class CCPRRenderingTest {
public:
void run(skiatest::Reporter* reporter, GrContext* ctx) const {
diff --git a/tools/gpu/GrTest.cpp b/tools/gpu/GrTest.cpp
index 00abd957e0..e8598868b5 100644
--- a/tools/gpu/GrTest.cpp
+++ b/tools/gpu/GrTest.cpp
@@ -25,6 +25,7 @@
#include "SkMathPriv.h"
#include "SkString.h"
#include "SkTo.h"
+#include "ccpr/GrCoverageCountingPathRenderer.h"
#include "ops/GrMeshDrawOp.h"
#include "text/GrGlyphCache.h"
#include "text/GrTextBlobCache.h"
@@ -301,6 +302,12 @@ GrPixelConfig GrBackendRenderTarget::testingOnly_getPixelConfig() const {
//////////////////////////////////////////////////////////////////////////////
+const GrUniqueKey& GrCoverageCountingPathRenderer::testingOnly_getStashedAtlasKey() const {
+ return fStashedAtlasKey;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
#define DRAW_OP_TEST_EXTERN(Op) \
extern std::unique_ptr<GrDrawOp> Op##__Test(GrPaint&&, SkRandom*, GrContext*, GrFSAAType)
#define DRAW_OP_TEST_ENTRY(Op) Op##__Test