Revert "ccpr: Rework the path cache to support sporadic flushing"
This reverts commit d6fa45472c
.
Reason for revert: Assertion failures
Original change's description:
> ccpr: Rework the path cache to support sporadic flushing
>
> Removes the notion of a stashed atlas that we store from the previous
> flush. Now we just cache every atlas we ever render. Cached atlases
> can either be 16-bit or 8-bit.
>
> The "reuse" and "animation" cases should both behave exactly the same
> as before: Where before we would copy from the stashed atlas to 8-bit
> atlases, we now copy from a cached 16-bit atlas and then invalidate
> it. Where before we would recycle the stashed atlas's backing texture
> object, we now recycle this same texture object from an invalidated
> 16-bit cached atlas.
>
> The main difference is that cases like tiled rendering now work. If
> you draw your whole scene in one flush, you still get one big 16-bit
> cached atlas, just like the "stashed atlas" implementation. But if you
> draw your scene in tiles, you now get lots of little cached 16-bit
> atlases, which can be reused and eventually copied to 8-bit atlases.
>
> Bug: skia:8462
> Change-Id: Ibae65febb948230aaaf1f1361eef9c8f06ebef18
> Reviewed-on: https://skia-review.googlesource.com/c/179991
> Commit-Queue: Chris Dalton <csmartdalton@google.com>
> Reviewed-by: Robert Phillips <robertphillips@google.com>
TBR=bsalomon@google.com,robertphillips@google.com,csmartdalton@google.com
Change-Id: Iad74a14fcb09da12f32b9b78f803b8472a5d60ae
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Bug: skia:8462
Reviewed-on: https://skia-review.googlesource.com/c/181444
Reviewed-by: Chris Dalton <csmartdalton@google.com>
Commit-Queue: Chris Dalton <csmartdalton@google.com>
This commit is contained in:
parent
d5cdc2cd4d
commit
2e825a3c5d
@ -317,7 +317,7 @@ void GrContext::performDeferredCleanup(std::chrono::milliseconds msNotUsed) {
|
||||
fResourceCache->purgeResourcesNotUsedSince(purgeTime);
|
||||
|
||||
if (auto ccpr = fDrawingManager->getCoverageCountingPathRenderer()) {
|
||||
ccpr->purgeCacheEntriesOlderThan(fProxyProvider, purgeTime);
|
||||
ccpr->purgeCacheEntriesOlderThan(purgeTime);
|
||||
}
|
||||
|
||||
fTextBlobCache->purgeStaleBlobs();
|
||||
|
@ -40,8 +40,7 @@ GrPathRendererChain::GrPathRendererChain(GrContext* context, const Options& opti
|
||||
if (options.fGpuPathRenderers & GpuPathRenderers::kCoverageCounting) {
|
||||
using AllowCaching = GrCoverageCountingPathRenderer::AllowCaching;
|
||||
if (auto ccpr = GrCoverageCountingPathRenderer::CreateIfSupported(
|
||||
caps, AllowCaching(options.fAllowPathMaskCaching),
|
||||
context->uniqueID())) {
|
||||
caps, AllowCaching(options.fAllowPathMaskCaching))) {
|
||||
fCoverageCountingPathRenderer = ccpr.get();
|
||||
context->contextPriv().addOnFlushCallbackObject(fCoverageCountingPathRenderer);
|
||||
fChain.push_back(std::move(ccpr));
|
||||
|
@ -16,7 +16,6 @@
|
||||
#include "GrTextureProxy.h"
|
||||
#include "SkMakeUnique.h"
|
||||
#include "SkMathPriv.h"
|
||||
#include "ccpr/GrCCPathCache.h"
|
||||
#include <atomic>
|
||||
|
||||
class GrCCAtlas::Node {
|
||||
@ -48,9 +47,8 @@ private:
|
||||
GrRectanizerSkyline fRectanizer;
|
||||
};
|
||||
|
||||
GrCCAtlas::GrCCAtlas(CoverageType coverageType, const Specs& specs, const GrCaps& caps)
|
||||
: fCoverageType(coverageType)
|
||||
, fMaxTextureSize(SkTMax(SkTMax(specs.fMinHeight, specs.fMinWidth),
|
||||
GrCCAtlas::GrCCAtlas(GrPixelConfig pixelConfig, const Specs& specs, const GrCaps& caps)
|
||||
: fMaxTextureSize(SkTMax(SkTMax(specs.fMinHeight, specs.fMinWidth),
|
||||
specs.fMaxPreferredTextureSize)) {
|
||||
// Caller should have cropped any paths to the destination render target instead of asking for
|
||||
// an atlas larger than maxRenderTargetSize.
|
||||
@ -75,12 +73,12 @@ GrCCAtlas::GrCCAtlas(CoverageType coverageType, const Specs& specs, const GrCaps
|
||||
|
||||
fTopNode = skstd::make_unique<Node>(nullptr, 0, 0, fWidth, fHeight);
|
||||
|
||||
GrColorType colorType = (CoverageType::kFP16_CoverageCount == fCoverageType)
|
||||
? GrColorType::kAlpha_F16 : GrColorType::kAlpha_8;
|
||||
// TODO: don't have this rely on the GrPixelConfig
|
||||
GrSRGBEncoded srgbEncoded = GrSRGBEncoded::kNo;
|
||||
GrColorType colorType = GrPixelConfigToColorTypeAndEncoding(pixelConfig, &srgbEncoded);
|
||||
|
||||
const GrBackendFormat format =
|
||||
caps.getBackendFormatFromGrColorType(colorType, GrSRGBEncoded::kNo);
|
||||
GrPixelConfig pixelConfig = (CoverageType::kFP16_CoverageCount == fCoverageType)
|
||||
? kAlpha_half_GrPixelConfig : kAlpha_8_GrPixelConfig;
|
||||
caps.getBackendFormatFromGrColorType(colorType, srgbEncoded);
|
||||
|
||||
fTextureProxy = GrProxyProvider::MakeFullyLazyProxy(
|
||||
[this, pixelConfig](GrResourceProvider* resourceProvider) {
|
||||
@ -161,23 +159,27 @@ static uint32_t next_atlas_unique_id() {
|
||||
return nextID++;
|
||||
}
|
||||
|
||||
sk_sp<GrCCCachedAtlas> GrCCAtlas::refOrMakeCachedAtlas(GrOnFlushResourceProvider* onFlushRP) {
|
||||
if (!fCachedAtlas) {
|
||||
static const GrUniqueKey::Domain kAtlasDomain = GrUniqueKey::GenerateDomain();
|
||||
const GrUniqueKey& GrCCAtlas::getOrAssignUniqueKey(GrOnFlushResourceProvider* onFlushRP) {
|
||||
static const GrUniqueKey::Domain kAtlasDomain = GrUniqueKey::GenerateDomain();
|
||||
|
||||
GrUniqueKey atlasUniqueKey;
|
||||
GrUniqueKey::Builder builder(&atlasUniqueKey, kAtlasDomain, 1, "CCPR Atlas");
|
||||
if (!fUniqueKey.isValid()) {
|
||||
GrUniqueKey::Builder builder(&fUniqueKey, kAtlasDomain, 1, "CCPR Atlas");
|
||||
builder[0] = next_atlas_unique_id();
|
||||
builder.finish();
|
||||
|
||||
onFlushRP->assignUniqueKeyToProxy(atlasUniqueKey, fTextureProxy.get());
|
||||
|
||||
fCachedAtlas = sk_make_sp<GrCCCachedAtlas>(fCoverageType, atlasUniqueKey, fTextureProxy);
|
||||
if (fTextureProxy->isInstantiated()) {
|
||||
onFlushRP->assignUniqueKeyToProxy(fUniqueKey, fTextureProxy.get());
|
||||
}
|
||||
}
|
||||
return fUniqueKey;
|
||||
}
|
||||
|
||||
SkASSERT(fCachedAtlas->coverageType() == fCoverageType);
|
||||
SkASSERT(fCachedAtlas->getOnFlushProxy() == fTextureProxy.get());
|
||||
return fCachedAtlas;
|
||||
sk_sp<GrCCAtlas::CachedAtlasInfo> GrCCAtlas::refOrMakeCachedAtlasInfo(uint32_t contextUniqueID) {
|
||||
if (!fCachedAtlasInfo) {
|
||||
fCachedAtlasInfo = sk_make_sp<CachedAtlasInfo>(contextUniqueID);
|
||||
}
|
||||
SkASSERT(fCachedAtlasInfo->fContextUniqueID == contextUniqueID);
|
||||
return fCachedAtlasInfo;
|
||||
}
|
||||
|
||||
sk_sp<GrRenderTargetContext> GrCCAtlas::makeRenderTargetContext(
|
||||
@ -203,6 +205,10 @@ sk_sp<GrRenderTargetContext> GrCCAtlas::makeRenderTargetContext(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (fUniqueKey.isValid()) {
|
||||
onFlushRP->assignUniqueKeyToProxy(fUniqueKey, fTextureProxy.get());
|
||||
}
|
||||
|
||||
SkIRect clearRect = SkIRect::MakeSize(fDrawBounds);
|
||||
rtc->clear(&clearRect, SK_PMColor4fTRANSPARENT,
|
||||
GrRenderTargetContext::CanClearFullscreen::kYes);
|
||||
@ -214,7 +220,7 @@ GrCCAtlas* GrCCAtlasStack::addRect(const SkIRect& devIBounds, SkIVector* devToAt
|
||||
if (fAtlases.empty() || !fAtlases.back().addRect(devIBounds, devToAtlasOffset)) {
|
||||
// The retired atlas is out of room and can't grow any bigger.
|
||||
retiredAtlas = !fAtlases.empty() ? &fAtlases.back() : nullptr;
|
||||
fAtlases.emplace_back(fCoverageType, fSpecs, *fCaps);
|
||||
fAtlases.emplace_back(fPixelConfig, fSpecs, *fCaps);
|
||||
SkASSERT(devIBounds.width() <= fSpecs.fMinWidth);
|
||||
SkASSERT(devIBounds.height() <= fSpecs.fMinHeight);
|
||||
SkAssertResult(fAtlases.back().addRect(devIBounds, devToAtlasOffset));
|
||||
|
@ -15,7 +15,6 @@
|
||||
#include "SkRefCnt.h"
|
||||
#include "SkSize.h"
|
||||
|
||||
class GrCCCachedAtlas;
|
||||
class GrOnFlushResourceProvider;
|
||||
class GrRenderTargetContext;
|
||||
class GrTextureProxy;
|
||||
@ -46,12 +45,7 @@ public:
|
||||
void accountForSpace(int width, int height);
|
||||
};
|
||||
|
||||
enum class CoverageType : bool {
|
||||
kFP16_CoverageCount,
|
||||
kA8_LiteralCoverage
|
||||
};
|
||||
|
||||
GrCCAtlas(CoverageType, const Specs&, const GrCaps&);
|
||||
GrCCAtlas(GrPixelConfig, const Specs&, const GrCaps&);
|
||||
~GrCCAtlas();
|
||||
|
||||
GrTextureProxy* textureProxy() const { return fTextureProxy.get(); }
|
||||
@ -70,7 +64,23 @@ public:
|
||||
void setStrokeBatchID(int id);
|
||||
int getStrokeBatchID() const { return fStrokeBatchID; }
|
||||
|
||||
sk_sp<GrCCCachedAtlas> refOrMakeCachedAtlas(GrOnFlushResourceProvider*);
|
||||
// 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> {
|
||||
CachedAtlasInfo(uint32_t contextUniqueID) : fContextUniqueID(contextUniqueID) {}
|
||||
const uint32_t fContextUniqueID;
|
||||
int fNumPathPixels = 0;
|
||||
int fNumInvalidatedPathPixels = 0;
|
||||
bool fIsPurgedFromResourceCache = false;
|
||||
};
|
||||
sk_sp<CachedAtlasInfo> refOrMakeCachedAtlasInfo(uint32_t contextUniqueID);
|
||||
|
||||
// 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
|
||||
@ -87,7 +97,6 @@ private:
|
||||
|
||||
bool internalPlaceRect(int w, int h, SkIPoint16* loc);
|
||||
|
||||
const CoverageType fCoverageType;
|
||||
const int fMaxTextureSize;
|
||||
int fWidth, fHeight;
|
||||
std::unique_ptr<Node> fTopNode;
|
||||
@ -96,7 +105,11 @@ private:
|
||||
int fFillBatchID;
|
||||
int fStrokeBatchID;
|
||||
|
||||
sk_sp<GrCCCachedAtlas> fCachedAtlas;
|
||||
// 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;
|
||||
sk_sp<GrTexture> fBackingTexture;
|
||||
};
|
||||
@ -107,10 +120,8 @@ private:
|
||||
*/
|
||||
class GrCCAtlasStack {
|
||||
public:
|
||||
using CoverageType = GrCCAtlas::CoverageType;
|
||||
|
||||
GrCCAtlasStack(CoverageType coverageType, const GrCCAtlas::Specs& specs, const GrCaps* caps)
|
||||
: fCoverageType(coverageType), fSpecs(specs), fCaps(caps) {}
|
||||
GrCCAtlasStack(GrPixelConfig pixelConfig, const GrCCAtlas::Specs& specs, const GrCaps* caps)
|
||||
: fPixelConfig(pixelConfig), fSpecs(specs), fCaps(caps) {}
|
||||
|
||||
bool empty() const { return fAtlases.empty(); }
|
||||
const GrCCAtlas& front() const { SkASSERT(!this->empty()); return fAtlases.front(); }
|
||||
@ -136,7 +147,7 @@ public:
|
||||
GrCCAtlas* addRect(const SkIRect& devIBounds, SkIVector* devToAtlasOffset);
|
||||
|
||||
private:
|
||||
const CoverageType fCoverageType;
|
||||
const GrPixelConfig fPixelConfig;
|
||||
const GrCCAtlas::Specs fSpecs;
|
||||
const GrCaps* const fCaps;
|
||||
GrSTAllocator<4, GrCCAtlas> fAtlases;
|
||||
|
@ -6,7 +6,6 @@
|
||||
*/
|
||||
|
||||
#include "GrCCDrawPathsOp.h"
|
||||
|
||||
#include "GrContext.h"
|
||||
#include "GrContextPriv.h"
|
||||
#include "GrMemoryPool.h"
|
||||
@ -158,6 +157,13 @@ GrCCDrawPathsOp::SingleDraw::SingleDraw(const SkMatrix& m, const GrShape& shape,
|
||||
#endif
|
||||
}
|
||||
|
||||
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) {
|
||||
SkASSERT(1 == fNumDraws); // There should only be one single path draw in this Op right now.
|
||||
@ -227,10 +233,10 @@ void GrCCDrawPathsOp::addToOwningPerOpListPaths(sk_sp<GrCCPerOpListPaths> owning
|
||||
|
||||
void GrCCDrawPathsOp::accountForOwnPaths(GrCCPathCache* pathCache,
|
||||
GrOnFlushResourceProvider* onFlushRP,
|
||||
const GrUniqueKey& stashedAtlasKey,
|
||||
GrCCPerFlushResourceSpecs* specs) {
|
||||
using CreateIfAbsent = GrCCPathCache::CreateIfAbsent;
|
||||
using MaskTransform = GrCCPathCache::MaskTransform;
|
||||
using CoverageType = GrCCAtlas::CoverageType;
|
||||
|
||||
for (SingleDraw& draw : fDraws) {
|
||||
SkPath path;
|
||||
@ -241,32 +247,41 @@ void GrCCDrawPathsOp::accountForOwnPaths(GrCCPathCache* pathCache,
|
||||
if (pathCache) {
|
||||
MaskTransform m(draw.fMatrix, &draw.fCachedMaskShift);
|
||||
bool canStashPathMask = draw.fMaskVisibility >= Visibility::kMostlyComplete;
|
||||
draw.fCacheEntry =
|
||||
pathCache->find(onFlushRP, draw.fShape, m, CreateIfAbsent(canStashPathMask));
|
||||
draw.fCacheEntry = pathCache->find(draw.fShape, m, CreateIfAbsent(canStashPathMask));
|
||||
}
|
||||
|
||||
if (draw.fCacheEntry) {
|
||||
if (const GrCCCachedAtlas* cachedAtlas = draw.fCacheEntry->cachedAtlas()) {
|
||||
SkASSERT(cachedAtlas->getOnFlushProxy());
|
||||
if (CoverageType::kA8_LiteralCoverage == cachedAtlas->coverageType()) {
|
||||
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;
|
||||
} else {
|
||||
// Suggest that this path be copied to a literal coverage atlas, to save memory.
|
||||
// (The client may decline this copy via DoCopiesToA8Coverage::kNo.)
|
||||
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());
|
||||
int idx = (draw.fShape.style().strokeRec().isFillStyle())
|
||||
? GrCCPerFlushResourceSpecs::kFillIdx
|
||||
: GrCCPerFlushResourceSpecs::kStrokeIdx;
|
||||
++specs->fNumCopiedPaths[idx];
|
||||
specs->fCopyPathStats[idx].statPath(path);
|
||||
specs->fCopyAtlasSpecs.accountForSpace(
|
||||
draw.fCacheEntry->width(), draw.fCacheEntry->height());
|
||||
draw.fDoCopyToA8Coverage = true;
|
||||
specs->fCopyAtlasSpecs.accountForSpace(cacheEntry->width(),
|
||||
cacheEntry->height());
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
|
||||
// Whatever atlas the path used to reside in, it no longer exists.
|
||||
cacheEntry->resetAtlasKeyAndInfo();
|
||||
}
|
||||
|
||||
if (Visibility::kMostlyComplete == draw.fMaskVisibility &&
|
||||
draw.fCacheEntry->hitCount() > 1) {
|
||||
if (Visibility::kMostlyComplete == draw.fMaskVisibility && cacheEntry->hitCount() > 1) {
|
||||
int shapeSize = SkTMax(draw.fShapeConservativeIBounds.height(),
|
||||
draw.fShapeConservativeIBounds.width());
|
||||
if (shapeSize <= onFlushRP->caps()->maxRenderTargetSize()) {
|
||||
@ -288,9 +303,8 @@ void GrCCDrawPathsOp::accountForOwnPaths(GrCCPathCache* pathCache,
|
||||
}
|
||||
}
|
||||
|
||||
void GrCCDrawPathsOp::setupResources(
|
||||
GrCCPathCache* pathCache, GrOnFlushResourceProvider* onFlushRP,
|
||||
GrCCPerFlushResources* resources, DoCopiesToA8Coverage doCopies) {
|
||||
void GrCCDrawPathsOp::setupResources(GrOnFlushResourceProvider* onFlushRP,
|
||||
GrCCPerFlushResources* resources, DoCopiesToCache doCopies) {
|
||||
using DoEvenOddFill = GrCCPathProcessor::DoEvenOddFill;
|
||||
SkASSERT(fNumDraws > 0);
|
||||
SkASSERT(-1 == fBaseInstance);
|
||||
@ -307,29 +321,51 @@ void GrCCDrawPathsOp::setupResources(
|
||||
|
||||
if (auto cacheEntry = draw.fCacheEntry.get()) {
|
||||
// Does the path already exist in a cached atlas texture?
|
||||
if (cacheEntry->cachedAtlas()) {
|
||||
SkASSERT(cacheEntry->cachedAtlas()->getOnFlushProxy());
|
||||
if (DoCopiesToA8Coverage::kYes == doCopies && draw.fDoCopyToA8Coverage) {
|
||||
resources->upgradeEntryToLiteralCoverageAtlas(pathCache, onFlushRP, cacheEntry,
|
||||
doEvenOddFill);
|
||||
SkASSERT(cacheEntry->cachedAtlas());
|
||||
SkASSERT(GrCCAtlas::CoverageType::kA8_LiteralCoverage
|
||||
== cacheEntry->cachedAtlas()->coverageType());
|
||||
SkASSERT(cacheEntry->cachedAtlas()->getOnFlushProxy());
|
||||
}
|
||||
this->recordInstance(cacheEntry->cachedAtlas()->getOnFlushProxy(),
|
||||
resources->nextPathInstanceIdx());
|
||||
if (auto proxy = draw.fCachedAtlasProxy.get()) {
|
||||
SkASSERT(!cacheEntry->currFlushAtlas());
|
||||
this->recordInstance(proxy, resources->nextPathInstanceIdx());
|
||||
// TODO4F: Preserve float colors
|
||||
resources->appendDrawPathInstance().set(*cacheEntry, draw.fCachedMaskShift,
|
||||
draw.fColor.toBytes_RGBA());
|
||||
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());
|
||||
// TODO4F: Preserve float colors
|
||||
resources->appendDrawPathInstance().set(
|
||||
*cacheEntry, draw.fCachedMaskShift, draw.fColor.toBytes_RGBA(),
|
||||
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(onFlushRP->contextUniqueID()));
|
||||
this->recordInstance(atlas->textureProxy(), resources->nextPathInstanceIdx());
|
||||
// TODO4F: Preserve float colors
|
||||
resources->appendDrawPathInstance().set(*cacheEntry, draw.fCachedMaskShift,
|
||||
draw.fColor.toBytes_RGBA());
|
||||
// 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. renderShapeInAtlas() gives us two tight
|
||||
// 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;
|
||||
@ -344,7 +380,7 @@ void GrCCDrawPathsOp::setupResources(
|
||||
// If we have a spot in the path cache, try to make a note of where this mask is so we
|
||||
// can reuse it in the future.
|
||||
if (auto cacheEntry = draw.fCacheEntry.get()) {
|
||||
SkASSERT(!cacheEntry->cachedAtlas());
|
||||
SkASSERT(!cacheEntry->hasCachedAtlas());
|
||||
|
||||
if (Visibility::kComplete != draw.fMaskVisibility || cacheEntry->hitCount() <= 1) {
|
||||
// Don't cache a path mask unless it's completely visible with a hit count > 1.
|
||||
@ -354,9 +390,19 @@ void GrCCDrawPathsOp::setupResources(
|
||||
continue;
|
||||
}
|
||||
|
||||
cacheEntry->setCoverageCountAtlas(onFlushRP, atlas, devToAtlasOffset, devBounds,
|
||||
devBounds45, devIBounds, draw.fCachedMaskShift);
|
||||
if (resources->nextAtlasToStash() != atlas) {
|
||||
// This mask does not belong to the atlas that will be stashed for next flush.
|
||||
continue;
|
||||
}
|
||||
|
||||
const GrUniqueKey& atlasKey =
|
||||
resources->nextAtlasToStash()->getOrAssignUniqueKey(onFlushRP);
|
||||
cacheEntry->initAsStashedAtlas(atlasKey, devToAtlasOffset, devBounds, devBounds45,
|
||||
devIBounds, draw.fCachedMaskShift);
|
||||
// Remember this atlas in case we encounter the path again during the same flush.
|
||||
cacheEntry->setCurrFlushAtlas(atlas);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,13 +11,14 @@
|
||||
#include "GrShape.h"
|
||||
#include "SkTInternalLList.h"
|
||||
#include "ccpr/GrCCSTLList.h"
|
||||
#include "ccpr/GrCCPathCache.h"
|
||||
#include "ops/GrDrawOp.h"
|
||||
|
||||
struct GrCCPerFlushResourceSpecs;
|
||||
struct GrCCPerOpListPaths;
|
||||
class GrCCAtlas;
|
||||
class GrOnFlushResourceProvider;
|
||||
class GrCCPathCache;
|
||||
class GrCCPathCacheEntry;
|
||||
class GrCCPerFlushResources;
|
||||
|
||||
/**
|
||||
@ -44,24 +45,26 @@ public:
|
||||
void addToOwningPerOpListPaths(sk_sp<GrCCPerOpListPaths> owningPerOpListPaths);
|
||||
|
||||
// Makes decisions about how to draw each path (cached, copied, rendered, etc.), and
|
||||
// increments/fills out the corresponding GrCCPerFlushResourceSpecs.
|
||||
void accountForOwnPaths(GrCCPathCache*, GrOnFlushResourceProvider*, GrCCPerFlushResourceSpecs*);
|
||||
// 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 actually do the suggested copies from cached 16-bit
|
||||
// coverage count atlases, and into 8-bit literal coverage atlases. Purely to save space.
|
||||
enum class DoCopiesToA8Coverage : bool {
|
||||
// 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
|
||||
// DoCopiesToA8Coverage is kNo, the paths slated for copy will instead be left in their 16-bit
|
||||
// coverage count atlases.
|
||||
// DoCopiesToCache is kNo, the paths slated for copy will instead be re-rendered from scratch.
|
||||
//
|
||||
// NOTE: If using DoCopiesToA8Coverage::kNo, it is the caller's responsibility to have called
|
||||
// cancelCopies() on the GrCCPerFlushResourceSpecs, prior to making this call.
|
||||
void setupResources(GrCCPathCache*, GrOnFlushResourceProvider*, GrCCPerFlushResources*,
|
||||
DoCopiesToA8Coverage);
|
||||
// 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*, const SkRect& chainBounds) override;
|
||||
|
||||
@ -91,6 +94,7 @@ private:
|
||||
SingleDraw(const SkMatrix&, const GrShape&, float strokeDevWidth,
|
||||
const SkIRect& shapeConservativeIBounds, const SkIRect& maskDevIBounds,
|
||||
Visibility maskVisibility, const SkPMColor4f&);
|
||||
~SingleDraw();
|
||||
|
||||
SkMatrix fMatrix;
|
||||
GrShape fShape;
|
||||
@ -100,9 +104,9 @@ private:
|
||||
Visibility fMaskVisibility;
|
||||
SkPMColor4f fColor;
|
||||
|
||||
GrCCPathCache::OnFlushEntryRef fCacheEntry;
|
||||
sk_sp<GrCCPathCacheEntry> fCacheEntry;
|
||||
sk_sp<GrTextureProxy> fCachedAtlasProxy;
|
||||
SkIVector fCachedMaskShift;
|
||||
bool fDoCopyToA8Coverage = false;
|
||||
|
||||
SingleDraw* fNext = nullptr;
|
||||
};
|
||||
|
@ -7,8 +7,7 @@
|
||||
|
||||
#include "GrCCPathCache.h"
|
||||
|
||||
#include "GrOnFlushResourceProvider.h"
|
||||
#include "GrProxyProvider.h"
|
||||
#include "GrShape.h"
|
||||
#include "SkNx.h"
|
||||
|
||||
static constexpr int kMaxKeyDataCountU32 = 256; // 1kB of uint32_t's.
|
||||
@ -85,33 +84,66 @@ uint32_t* GrCCPathCache::Key::data() {
|
||||
return reinterpret_cast<uint32_t*>(reinterpret_cast<char*>(this) + sizeof(Key));
|
||||
}
|
||||
|
||||
inline bool GrCCPathCache::Key::operator==(const GrCCPathCache::Key& that) const {
|
||||
return fDataSizeInBytes == that.fDataSizeInBytes &&
|
||||
!memcmp(this->data(), that.data(), fDataSizeInBytes);
|
||||
}
|
||||
|
||||
void GrCCPathCache::Key::onChange() {
|
||||
// Our key's corresponding path was invalidated. Post a thread-safe eviction message.
|
||||
SkMessageBus<sk_sp<Key>>::Post(sk_ref_sp(this));
|
||||
}
|
||||
|
||||
GrCCPathCache::GrCCPathCache(uint32_t contextUniqueID)
|
||||
: fContextUniqueID(contextUniqueID)
|
||||
, fInvalidatedKeysInbox(next_path_cache_id())
|
||||
inline const GrCCPathCache::Key& GrCCPathCache::HashNode::GetKey(
|
||||
const GrCCPathCache::HashNode& node) {
|
||||
return *node.entry()->fCacheKey;
|
||||
}
|
||||
|
||||
inline uint32_t GrCCPathCache::HashNode::Hash(const Key& key) {
|
||||
return GrResourceKeyHash(key.data(), key.dataSizeInBytes());
|
||||
}
|
||||
|
||||
inline GrCCPathCache::HashNode::HashNode(GrCCPathCache* pathCache, sk_sp<Key> key,
|
||||
const MaskTransform& m, const GrShape& shape)
|
||||
: fPathCache(pathCache)
|
||||
, fEntry(new GrCCPathCacheEntry(key, m)) {
|
||||
SkASSERT(shape.hasUnstyledKey());
|
||||
shape.addGenIDChangeListener(std::move(key));
|
||||
}
|
||||
|
||||
inline GrCCPathCache::HashNode::~HashNode() {
|
||||
this->willExitHashTable();
|
||||
}
|
||||
|
||||
inline GrCCPathCache::HashNode& GrCCPathCache::HashNode::operator=(HashNode&& node) {
|
||||
this->willExitHashTable();
|
||||
fPathCache = node.fPathCache;
|
||||
fEntry = std::move(node.fEntry);
|
||||
SkASSERT(!node.fEntry);
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline void GrCCPathCache::HashNode::willExitHashTable() {
|
||||
if (!fEntry) {
|
||||
return; // We were moved.
|
||||
}
|
||||
|
||||
SkASSERT(fPathCache);
|
||||
SkASSERT(fPathCache->fLRU.isInList(fEntry.get()));
|
||||
|
||||
fEntry->fCacheKey->markShouldUnregisterFromPath(); // Unregister the path listener.
|
||||
fPathCache->fLRU.remove(fEntry.get());
|
||||
}
|
||||
|
||||
|
||||
GrCCPathCache::GrCCPathCache()
|
||||
: fInvalidatedKeysInbox(next_path_cache_id())
|
||||
, fScratchKey(Key::Make(fInvalidatedKeysInbox.uniqueID(), kMaxKeyDataCountU32)) {
|
||||
}
|
||||
|
||||
GrCCPathCache::~GrCCPathCache() {
|
||||
while (!fLRU.isEmpty()) {
|
||||
this->evict(*fLRU.tail()->fCacheKey, fLRU.tail());
|
||||
}
|
||||
SkASSERT(0 == fHashTable.count()); // Ensure the hash table and LRU list were coherent.
|
||||
|
||||
// Now take all the atlas textures we just invalidated and purge them from the GrResourceCache.
|
||||
// We just purge via message bus since we don't have any access to the resource cache right now.
|
||||
for (sk_sp<GrTextureProxy>& proxy : fInvalidatedProxies) {
|
||||
SkMessageBus<GrUniqueKeyInvalidatedMessage>::Post(
|
||||
GrUniqueKeyInvalidatedMessage(proxy->getUniqueKey(), fContextUniqueID));
|
||||
}
|
||||
for (const GrUniqueKey& key : fInvalidatedProxyUniqueKeys) {
|
||||
SkMessageBus<GrUniqueKeyInvalidatedMessage>::Post(
|
||||
GrUniqueKeyInvalidatedMessage(key, fContextUniqueID));
|
||||
}
|
||||
fHashTable.reset(); // Must be cleared first; ~HashNode calls fLRU.remove() on us.
|
||||
SkASSERT(fLRU.isEmpty()); // Ensure the hash table and LRU list were coherent.
|
||||
}
|
||||
|
||||
namespace {
|
||||
@ -158,16 +190,15 @@ private:
|
||||
|
||||
}
|
||||
|
||||
GrCCPathCache::OnFlushEntryRef GrCCPathCache::find(GrOnFlushResourceProvider* onFlushRP,
|
||||
const GrShape& shape, const MaskTransform& m,
|
||||
CreateIfAbsent createIfAbsent) {
|
||||
sk_sp<GrCCPathCacheEntry> GrCCPathCache::find(const GrShape& shape, const MaskTransform& m,
|
||||
CreateIfAbsent createIfAbsent) {
|
||||
if (!shape.hasUnstyledKey()) {
|
||||
return OnFlushEntryRef();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
WriteKeyHelper writeKeyHelper(shape);
|
||||
if (writeKeyHelper.allocCountU32() > kMaxKeyDataCountU32) {
|
||||
return OnFlushEntryRef();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SkASSERT(fScratchKey->unique());
|
||||
@ -178,15 +209,14 @@ GrCCPathCache::OnFlushEntryRef GrCCPathCache::find(GrOnFlushResourceProvider* on
|
||||
if (HashNode* node = fHashTable.find(*fScratchKey)) {
|
||||
entry = node->entry();
|
||||
SkASSERT(fLRU.isInList(entry));
|
||||
|
||||
if (!fuzzy_equals(m, entry->fMaskTransform)) {
|
||||
// The path was reused with an incompatible matrix.
|
||||
if (CreateIfAbsent::kYes == createIfAbsent && entry->unique()) {
|
||||
// This entry is unique: recycle it instead of deleting and malloc-ing a new one.
|
||||
SkASSERT(0 == entry->fOnFlushRefCnt); // Because we are unique.
|
||||
entry->fMaskTransform = m;
|
||||
entry->fHitCount = 0;
|
||||
entry->releaseCachedAtlas(this);
|
||||
entry->invalidateAtlas();
|
||||
SkASSERT(!entry->fCurrFlushAtlas); // Should be null because 'entry' is unique.
|
||||
} else {
|
||||
this->evict(*fScratchKey);
|
||||
entry = nullptr;
|
||||
@ -196,7 +226,7 @@ GrCCPathCache::OnFlushEntryRef GrCCPathCache::find(GrOnFlushResourceProvider* on
|
||||
|
||||
if (!entry) {
|
||||
if (CreateIfAbsent::kNo == createIfAbsent) {
|
||||
return OnFlushEntryRef();
|
||||
return nullptr;
|
||||
}
|
||||
if (fHashTable.count() >= kMaxCacheCount) {
|
||||
SkDEBUGCODE(HashNode* node = fHashTable.find(*fLRU.tail()->fCacheKey));
|
||||
@ -220,54 +250,20 @@ GrCCPathCache::OnFlushEntryRef GrCCPathCache::find(GrOnFlushResourceProvider* on
|
||||
SkASSERT(node && node->entry() == entry);
|
||||
fLRU.addToHead(entry);
|
||||
|
||||
if (0 == entry->fOnFlushRefCnt) {
|
||||
// Only update the time stamp and hit count if we haven't seen this entry yet during the
|
||||
// current flush.
|
||||
entry->fTimestamp = this->quickPerFlushTimestamp();
|
||||
++entry->fHitCount;
|
||||
|
||||
if (entry->fCachedAtlas) {
|
||||
SkASSERT(SkToBool(entry->fCachedAtlas->peekOnFlushRefCnt())
|
||||
== SkToBool(entry->fCachedAtlas->getOnFlushProxy()));
|
||||
if (!entry->fCachedAtlas->getOnFlushProxy()) {
|
||||
entry->fCachedAtlas->setOnFlushProxy(
|
||||
onFlushRP->findOrCreateProxyByUniqueKey(entry->fCachedAtlas->textureKey(),
|
||||
GrCCAtlas::kTextureOrigin));
|
||||
}
|
||||
if (!entry->fCachedAtlas->getOnFlushProxy()) {
|
||||
// Our atlas's backing texture got purged from the GrResourceCache. Release the
|
||||
// cached atlas.
|
||||
entry->releaseCachedAtlas(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
SkASSERT(!entry->fCachedAtlas || entry->fCachedAtlas->getOnFlushProxy());
|
||||
return OnFlushEntryRef::OnFlushRef(entry);
|
||||
entry->fTimestamp = this->quickPerFlushTimestamp();
|
||||
++entry->fHitCount;
|
||||
return sk_ref_sp(entry);
|
||||
}
|
||||
|
||||
void GrCCPathCache::evict(const GrCCPathCache::Key& key, GrCCPathCacheEntry* entry) {
|
||||
if (!entry) {
|
||||
HashNode* node = fHashTable.find(key);
|
||||
SkASSERT(node);
|
||||
entry = node->entry();
|
||||
}
|
||||
SkASSERT(*entry->fCacheKey == key);
|
||||
entry->fCacheKey->markShouldUnregisterFromPath(); // Unregister the path listener.
|
||||
entry->releaseCachedAtlas(this);
|
||||
fLRU.remove(entry);
|
||||
fHashTable.remove(key);
|
||||
}
|
||||
|
||||
void GrCCPathCache::doPreFlushProcessing() {
|
||||
this->evictInvalidatedCacheKeys();
|
||||
void GrCCPathCache::doPostFlushProcessing() {
|
||||
this->purgeInvalidatedKeys();
|
||||
|
||||
// Mark the per-flush timestamp as needing to be updated with a newer clock reading.
|
||||
fPerFlushTimestamp = GrStdSteadyClock::time_point::min();
|
||||
}
|
||||
|
||||
void GrCCPathCache::purgeEntriesOlderThan(GrProxyProvider* proxyProvider,
|
||||
const GrStdSteadyClock::time_point& purgeTime) {
|
||||
this->evictInvalidatedCacheKeys();
|
||||
void GrCCPathCache::purgeEntriesOlderThan(const GrStdSteadyClock::time_point& purgeTime) {
|
||||
this->purgeInvalidatedKeys();
|
||||
|
||||
#ifdef SK_DEBUG
|
||||
auto lastTimestamp = (fLRU.isEmpty())
|
||||
@ -275,7 +271,7 @@ void GrCCPathCache::purgeEntriesOlderThan(GrProxyProvider* proxyProvider,
|
||||
: fLRU.tail()->fTimestamp;
|
||||
#endif
|
||||
|
||||
// Evict every entry from our local path cache whose timestamp is older than purgeTime.
|
||||
// Drop every cache entry whose timestamp is older than purgeTime.
|
||||
while (!fLRU.isEmpty() && fLRU.tail()->fTimestamp < purgeTime) {
|
||||
#ifdef SK_DEBUG
|
||||
// Verify that fLRU is sorted by timestamp.
|
||||
@ -285,37 +281,9 @@ void GrCCPathCache::purgeEntriesOlderThan(GrProxyProvider* proxyProvider,
|
||||
#endif
|
||||
this->evict(*fLRU.tail()->fCacheKey);
|
||||
}
|
||||
|
||||
// Now take all the atlas textures we just invalidated and purge them from the GrResourceCache.
|
||||
this->purgeInvalidatedAtlasTextures(proxyProvider);
|
||||
}
|
||||
|
||||
void GrCCPathCache::purgeInvalidatedAtlasTextures(GrOnFlushResourceProvider* onFlushRP) {
|
||||
for (sk_sp<GrTextureProxy>& proxy : fInvalidatedProxies) {
|
||||
onFlushRP->removeUniqueKeyFromProxy(proxy.get());
|
||||
}
|
||||
fInvalidatedProxies.reset();
|
||||
|
||||
for (const GrUniqueKey& key : fInvalidatedProxyUniqueKeys) {
|
||||
onFlushRP->processInvalidUniqueKey(key);
|
||||
}
|
||||
fInvalidatedProxyUniqueKeys.reset();
|
||||
}
|
||||
|
||||
void GrCCPathCache::purgeInvalidatedAtlasTextures(GrProxyProvider* proxyProvider) {
|
||||
for (sk_sp<GrTextureProxy>& proxy : fInvalidatedProxies) {
|
||||
proxyProvider->removeUniqueKeyFromProxy(proxy.get());
|
||||
}
|
||||
fInvalidatedProxies.reset();
|
||||
|
||||
for (const GrUniqueKey& key : fInvalidatedProxyUniqueKeys) {
|
||||
proxyProvider->processInvalidUniqueKey(key, nullptr,
|
||||
GrProxyProvider::InvalidateGPUResource::kYes);
|
||||
}
|
||||
fInvalidatedProxyUniqueKeys.reset();
|
||||
}
|
||||
|
||||
void GrCCPathCache::evictInvalidatedCacheKeys() {
|
||||
void GrCCPathCache::purgeInvalidatedKeys() {
|
||||
SkTArray<sk_sp<Key>> invalidatedKeys;
|
||||
fInvalidatedKeysInbox.poll(&invalidatedKeys);
|
||||
for (const sk_sp<Key>& key : invalidatedKeys) {
|
||||
@ -326,41 +294,17 @@ void GrCCPathCache::evictInvalidatedCacheKeys() {
|
||||
}
|
||||
}
|
||||
|
||||
GrCCPathCache::OnFlushEntryRef
|
||||
GrCCPathCache::OnFlushEntryRef::OnFlushRef(GrCCPathCacheEntry* entry) {
|
||||
entry->ref();
|
||||
++entry->fOnFlushRefCnt;
|
||||
if (entry->fCachedAtlas) {
|
||||
entry->fCachedAtlas->incrOnFlushRefCnt();
|
||||
}
|
||||
return OnFlushEntryRef(entry);
|
||||
}
|
||||
|
||||
GrCCPathCache::OnFlushEntryRef::~OnFlushEntryRef() {
|
||||
if (!fEntry) {
|
||||
return;
|
||||
}
|
||||
--fEntry->fOnFlushRefCnt;
|
||||
SkASSERT(fEntry->fOnFlushRefCnt >= 0);
|
||||
if (fEntry->fCachedAtlas) {
|
||||
fEntry->fCachedAtlas->decrOnFlushRefCnt();
|
||||
}
|
||||
fEntry->unref();
|
||||
}
|
||||
|
||||
|
||||
void GrCCPathCacheEntry::setCoverageCountAtlas(
|
||||
GrOnFlushResourceProvider* onFlushRP, GrCCAtlas* atlas, const SkIVector& atlasOffset,
|
||||
const SkRect& devBounds, const SkRect& devBounds45, const SkIRect& devIBounds,
|
||||
const SkIVector& maskShift) {
|
||||
SkASSERT(fOnFlushRefCnt > 0);
|
||||
SkASSERT(!fCachedAtlas); // Otherwise we would need to call releaseCachedAtlas().
|
||||
|
||||
fCachedAtlas = atlas->refOrMakeCachedAtlas(onFlushRP);
|
||||
fCachedAtlas->incrOnFlushRefCnt(fOnFlushRefCnt);
|
||||
fCachedAtlas->addPathPixels(devIBounds.height() * devIBounds.width());
|
||||
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);
|
||||
@ -368,65 +312,34 @@ void GrCCPathCacheEntry::setCoverageCountAtlas(
|
||||
fDevIBounds = devIBounds.makeOffset(-maskShift.fX, -maskShift.fY);
|
||||
}
|
||||
|
||||
GrCCPathCacheEntry::ReleaseAtlasResult GrCCPathCacheEntry::upgradeToLiteralCoverageAtlas(
|
||||
GrCCPathCache* pathCache, GrOnFlushResourceProvider* onFlushRP, GrCCAtlas* atlas,
|
||||
const SkIVector& newAtlasOffset) {
|
||||
SkASSERT(fOnFlushRefCnt > 0);
|
||||
SkASSERT(fCachedAtlas);
|
||||
SkASSERT(GrCCAtlas::CoverageType::kFP16_CoverageCount == fCachedAtlas->coverageType());
|
||||
|
||||
ReleaseAtlasResult releaseAtlasResult = this->releaseCachedAtlas(pathCache);
|
||||
|
||||
fCachedAtlas = atlas->refOrMakeCachedAtlas(onFlushRP);
|
||||
fCachedAtlas->incrOnFlushRefCnt(fOnFlushRefCnt);
|
||||
fCachedAtlas->addPathPixels(this->height() * this->width());
|
||||
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;
|
||||
return releaseAtlasResult;
|
||||
|
||||
SkASSERT(!fCachedAtlasInfo); // Otherwise we need to invalidate our pixels in the old info.
|
||||
fCachedAtlasInfo = std::move(info);
|
||||
fCachedAtlasInfo->fNumPathPixels += this->height() * this->width();
|
||||
}
|
||||
|
||||
GrCCPathCacheEntry::ReleaseAtlasResult GrCCPathCacheEntry::releaseCachedAtlas(
|
||||
GrCCPathCache* pathCache) {
|
||||
ReleaseAtlasResult result = ReleaseAtlasResult::kNone;
|
||||
if (fCachedAtlas) {
|
||||
result = fCachedAtlas->invalidatePathPixels(pathCache, this->height() * this->width());
|
||||
if (fOnFlushRefCnt) {
|
||||
SkASSERT(fOnFlushRefCnt > 0);
|
||||
fCachedAtlas->decrOnFlushRefCnt(fOnFlushRefCnt);
|
||||
void GrCCPathCacheEntry::invalidateAtlas() {
|
||||
if (fCachedAtlasInfo) {
|
||||
// Mark our own pixels invalid in the cached atlas texture.
|
||||
fCachedAtlasInfo->fNumInvalidatedPathPixels += this->height() * this->width();
|
||||
if (!fCachedAtlasInfo->fIsPurgedFromResourceCache &&
|
||||
fCachedAtlasInfo->fNumInvalidatedPathPixels >= fCachedAtlasInfo->fNumPathPixels / 2) {
|
||||
// Too many invalidated pixels: purge the atlas texture from the resource cache.
|
||||
// The GrContext and CCPR path cache both share the same unique ID.
|
||||
SkMessageBus<GrUniqueKeyInvalidatedMessage>::Post(
|
||||
GrUniqueKeyInvalidatedMessage(fAtlasKey, fCachedAtlasInfo->fContextUniqueID));
|
||||
fCachedAtlasInfo->fIsPurgedFromResourceCache = true;
|
||||
}
|
||||
fCachedAtlas = nullptr;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
GrCCPathCacheEntry::ReleaseAtlasResult GrCCCachedAtlas::invalidatePathPixels(
|
||||
GrCCPathCache* pathCache, int numPixels) {
|
||||
// Mark the pixels invalid in the cached atlas texture.
|
||||
fNumInvalidatedPathPixels += numPixels;
|
||||
SkASSERT(fNumInvalidatedPathPixels <= fNumPathPixels);
|
||||
if (!fIsInvalidatedFromResourceCache && fNumInvalidatedPathPixels >= fNumPathPixels / 2) {
|
||||
// Too many invalidated pixels: purge the atlas texture from the resource cache.
|
||||
if (fOnFlushProxy) {
|
||||
// Don't clear (or std::move) fOnFlushProxy. Other path cache entries might still have a
|
||||
// reference on this atlas and expect to use our proxy during the current flush.
|
||||
// fOnFlushProxy will be cleared once fOnFlushRefCnt decrements to zero.
|
||||
pathCache->fInvalidatedProxies.push_back(fOnFlushProxy);
|
||||
} else {
|
||||
pathCache->fInvalidatedProxyUniqueKeys.push_back(fTextureKey);
|
||||
}
|
||||
fIsInvalidatedFromResourceCache = true;
|
||||
return ReleaseAtlasResult::kDidInvalidateFromCache;
|
||||
}
|
||||
return ReleaseAtlasResult::kNone;
|
||||
}
|
||||
|
||||
void GrCCCachedAtlas::decrOnFlushRefCnt(int count) const {
|
||||
SkASSERT(count > 0);
|
||||
fOnFlushRefCnt -= count;
|
||||
SkASSERT(fOnFlushRefCnt >= 0);
|
||||
if (0 == fOnFlushRefCnt) {
|
||||
// Don't hold the actual proxy past the end of the current flush.
|
||||
SkASSERT(fOnFlushProxy);
|
||||
fOnFlushProxy = nullptr;
|
||||
}
|
||||
fAtlasKey.reset();
|
||||
fCachedAtlasInfo = nullptr;
|
||||
}
|
||||
|
@ -8,7 +8,6 @@
|
||||
#ifndef GrCCPathCache_DEFINED
|
||||
#define GrCCPathCache_DEFINED
|
||||
|
||||
#include "GrShape.h"
|
||||
#include "SkExchange.h"
|
||||
#include "SkTHash.h"
|
||||
#include "SkTInternalLList.h"
|
||||
@ -25,7 +24,7 @@ class GrShape;
|
||||
*/
|
||||
class GrCCPathCache {
|
||||
public:
|
||||
GrCCPathCache(uint32_t contextUniqueID);
|
||||
GrCCPathCache();
|
||||
~GrCCPathCache();
|
||||
|
||||
class Key : public SkPathRef::GenIDChangeListener {
|
||||
@ -44,10 +43,7 @@ public:
|
||||
}
|
||||
uint32_t* data();
|
||||
|
||||
bool operator==(const Key& that) const {
|
||||
return fDataSizeInBytes == that.fDataSizeInBytes &&
|
||||
!memcmp(this->data(), that.data(), fDataSizeInBytes);
|
||||
}
|
||||
bool operator==(const Key&) const;
|
||||
|
||||
// Called when our corresponding path is modified or deleted. Not threadsafe.
|
||||
void onChange() override;
|
||||
@ -80,25 +76,6 @@ public:
|
||||
#endif
|
||||
};
|
||||
|
||||
// Represents a ref on a GrCCPathCacheEntry that should only be used during the current flush.
|
||||
class OnFlushEntryRef : SkNoncopyable {
|
||||
public:
|
||||
static OnFlushEntryRef OnFlushRef(GrCCPathCacheEntry*);
|
||||
OnFlushEntryRef() = default;
|
||||
OnFlushEntryRef(OnFlushEntryRef&& ref) : fEntry(skstd::exchange(ref.fEntry, nullptr)) {}
|
||||
~OnFlushEntryRef();
|
||||
|
||||
GrCCPathCacheEntry* get() const { return fEntry; }
|
||||
GrCCPathCacheEntry* operator->() const { return fEntry; }
|
||||
GrCCPathCacheEntry& operator*() const { return *fEntry; }
|
||||
explicit operator bool() const { return fEntry; }
|
||||
void operator=(OnFlushEntryRef&& ref) { fEntry = skstd::exchange(ref.fEntry, nullptr); }
|
||||
|
||||
private:
|
||||
OnFlushEntryRef(GrCCPathCacheEntry* entry) : fEntry(entry) {}
|
||||
GrCCPathCacheEntry* fEntry = nullptr;
|
||||
};
|
||||
|
||||
enum class CreateIfAbsent : bool {
|
||||
kNo = false,
|
||||
kYes = true
|
||||
@ -106,19 +83,11 @@ public:
|
||||
|
||||
// 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.
|
||||
OnFlushEntryRef find(GrOnFlushResourceProvider*, const GrShape&, const MaskTransform&,
|
||||
CreateIfAbsent = CreateIfAbsent::kNo);
|
||||
sk_sp<GrCCPathCacheEntry> find(const GrShape&, const MaskTransform&,
|
||||
CreateIfAbsent = CreateIfAbsent::kNo);
|
||||
|
||||
void doPreFlushProcessing();
|
||||
|
||||
void purgeEntriesOlderThan(GrProxyProvider*, const GrStdSteadyClock::time_point& purgeTime);
|
||||
|
||||
// As we evict entries from our local path cache, we accumulate a list of invalidated atlas
|
||||
// textures. This call purges the invalidated atlas textures from the mainline GrResourceCache.
|
||||
// This call is available with two different "provider" objects, to accomodate whatever might
|
||||
// be available at the callsite.
|
||||
void purgeInvalidatedAtlasTextures(GrOnFlushResourceProvider*);
|
||||
void purgeInvalidatedAtlasTextures(GrProxyProvider*);
|
||||
void doPostFlushProcessing();
|
||||
void purgeEntriesOlderThan(const GrStdSteadyClock::time_point& purgeTime);
|
||||
|
||||
private:
|
||||
// This is a special ref ptr for GrCCPathCacheEntry, used by the hash table. It provides static
|
||||
@ -128,9 +97,7 @@ private:
|
||||
class HashNode : SkNoncopyable {
|
||||
public:
|
||||
static const Key& GetKey(const HashNode&);
|
||||
inline static uint32_t Hash(const Key& key) {
|
||||
return GrResourceKeyHash(key.data(), key.dataSizeInBytes());
|
||||
}
|
||||
static uint32_t Hash(const Key&);
|
||||
|
||||
HashNode() = default;
|
||||
HashNode(GrCCPathCache*, sk_sp<Key>, const MaskTransform&, const GrShape&);
|
||||
@ -141,11 +108,13 @@ private:
|
||||
|
||||
~HashNode();
|
||||
|
||||
void operator=(HashNode&& node);
|
||||
HashNode& operator=(HashNode&& node);
|
||||
|
||||
GrCCPathCacheEntry* entry() const { return fEntry.get(); }
|
||||
|
||||
private:
|
||||
void willExitHashTable();
|
||||
|
||||
GrCCPathCache* fPathCache = nullptr;
|
||||
sk_sp<GrCCPathCacheEntry> fEntry;
|
||||
};
|
||||
@ -158,15 +127,13 @@ private:
|
||||
return fPerFlushTimestamp;
|
||||
}
|
||||
|
||||
void evict(const GrCCPathCache::Key&, GrCCPathCacheEntry* = nullptr);
|
||||
void evict(const GrCCPathCache::Key& key) {
|
||||
fHashTable.remove(key); // HashNode::willExitHashTable() takes care of the rest.
|
||||
}
|
||||
|
||||
// Evicts all the cache entries whose keys have been queued up in fInvalidatedKeysInbox via
|
||||
// SkPath listeners.
|
||||
void evictInvalidatedCacheKeys();
|
||||
void purgeInvalidatedKeys();
|
||||
|
||||
const uint32_t fContextUniqueID;
|
||||
|
||||
SkTHashTable<HashNode, const Key&> fHashTable;
|
||||
SkTHashTable<HashNode, const GrCCPathCache::Key&> fHashTable;
|
||||
SkTInternalLList<GrCCPathCacheEntry> fLRU;
|
||||
SkMessageBus<sk_sp<Key>>::Inbox fInvalidatedKeysInbox;
|
||||
sk_sp<Key> fScratchKey; // Reused for creating a temporary key in the find() method.
|
||||
@ -174,18 +141,6 @@ private:
|
||||
// We only read the clock once per flush, and cache it in this variable. This prevents us from
|
||||
// excessive clock reads for cache timestamps that might degrade performance.
|
||||
GrStdSteadyClock::time_point fPerFlushTimestamp = GrStdSteadyClock::time_point::min();
|
||||
|
||||
// As we evict entries from our local path cache, we accumulate lists of invalidated atlas
|
||||
// textures in these two members. We hold these until we purge them from the GrResourceCache
|
||||
// (e.g. via purgeInvalidatedAtlasTextures().)
|
||||
SkSTArray<4, sk_sp<GrTextureProxy>> fInvalidatedProxies;
|
||||
SkSTArray<4, GrUniqueKey> fInvalidatedProxyUniqueKeys;
|
||||
|
||||
friend class GrCCCachedAtlas; // To append to fInvalidatedProxies, fInvalidatedProxyUniqueKeys.
|
||||
|
||||
public:
|
||||
const SkTHashTable<HashNode, const Key&>& testingOnly_getHashTable() const;
|
||||
const SkTInternalLList<GrCCPathCacheEntry>& testingOnly_getLRU() const;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -197,12 +152,10 @@ public:
|
||||
SK_DECLARE_INTERNAL_LLIST_INTERFACE(GrCCPathCacheEntry);
|
||||
|
||||
~GrCCPathCacheEntry() {
|
||||
SkASSERT(0 == fOnFlushRefCnt);
|
||||
SkASSERT(!fCachedAtlas); // Should have called GrCCPathCache::evict().
|
||||
SkASSERT(!fCurrFlushAtlas); // Client is required to reset fCurrFlushAtlas back to null.
|
||||
this->invalidateAtlas();
|
||||
}
|
||||
|
||||
const GrCCPathCache::Key& cacheKey() const { SkASSERT(fCacheKey); return *fCacheKey; }
|
||||
|
||||
// The number of times this specific entry (path + matrix combination) has been pulled from
|
||||
// the path cache. As long as the caller does exactly one lookup per draw, this translates to
|
||||
// the number of times the path has been drawn with a compatible matrix.
|
||||
@ -211,28 +164,44 @@ public:
|
||||
// GrCCPathCache::find(.., CreateIfAbsent::kYes), its hit count will be 1.
|
||||
int hitCount() const { return fHitCount; }
|
||||
|
||||
const GrCCCachedAtlas* cachedAtlas() const { return fCachedAtlas.get(); }
|
||||
// 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(); }
|
||||
|
||||
enum class ReleaseAtlasResult : bool {
|
||||
kNone,
|
||||
kDidInvalidateFromCache
|
||||
};
|
||||
|
||||
// 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 setCoverageCountAtlas(GrOnFlushResourceProvider*, GrCCAtlas*, const SkIVector& atlasOffset,
|
||||
const SkRect& devBounds, const SkRect& devBounds45,
|
||||
const SkIRect& devIBounds, const SkIVector& maskShift);
|
||||
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 GrCCCCachedAtlas data.
|
||||
ReleaseAtlasResult upgradeToLiteralCoverageAtlas(GrCCPathCache*, GrOnFlushResourceProvider*,
|
||||
GrCCAtlas*, const SkIVector& newAtlasOffset);
|
||||
// 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;
|
||||
@ -243,115 +212,32 @@ private:
|
||||
|
||||
// Resets this entry back to not having an atlas, and purges its previous atlas texture from the
|
||||
// resource cache if needed.
|
||||
ReleaseAtlasResult releaseCachedAtlas(GrCCPathCache*);
|
||||
void invalidateAtlas();
|
||||
|
||||
sk_sp<GrCCPathCache::Key> fCacheKey;
|
||||
|
||||
GrStdSteadyClock::time_point fTimestamp;
|
||||
int fHitCount = 0;
|
||||
MaskTransform fMaskTransform;
|
||||
|
||||
sk_sp<GrCCCachedAtlas> fCachedAtlas;
|
||||
GrUniqueKey fAtlasKey;
|
||||
SkIVector fAtlasOffset;
|
||||
|
||||
MaskTransform fMaskTransform;
|
||||
SkRect fDevBounds;
|
||||
SkRect fDevBounds45;
|
||||
SkIRect fDevIBounds;
|
||||
|
||||
int fOnFlushRefCnt = 0;
|
||||
// If null, then we are referencing a "stashed" atlas (see initAsStashedAtlas()).
|
||||
sk_sp<GrCCAtlas::CachedAtlasInfo> fCachedAtlasInfo;
|
||||
|
||||
// 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&,
|
||||
GrColor, DoEvenOddFill); // To access data.
|
||||
|
||||
public:
|
||||
int testingOnly_peekOnFlushRefCnt() const;
|
||||
};
|
||||
|
||||
/**
|
||||
* Encapsulates the data for an atlas whose texture is stored in the mainline GrResourceCache. Many
|
||||
* instances of GrCCPathCacheEntry will reference the same GrCCCachedAtlas.
|
||||
*
|
||||
* We use this object 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%, we purge the entire texture from the resource cache.
|
||||
*
|
||||
* This object also holds a ref on the atlas's actual texture proxy during flush. When
|
||||
* fOnFlushRefCnt decrements back down to zero, we release fOnFlushProxy and reset it back to null.
|
||||
*/
|
||||
class GrCCCachedAtlas : public GrNonAtomicRef<GrCCCachedAtlas> {
|
||||
public:
|
||||
using ReleaseAtlasResult = GrCCPathCacheEntry::ReleaseAtlasResult;
|
||||
|
||||
GrCCCachedAtlas(GrCCAtlas::CoverageType type, const GrUniqueKey& textureKey,
|
||||
sk_sp<GrTextureProxy> onFlushProxy)
|
||||
: fCoverageType(type)
|
||||
, fTextureKey(textureKey)
|
||||
, fOnFlushProxy(std::move(onFlushProxy)) {}
|
||||
|
||||
~GrCCCachedAtlas() {
|
||||
SkASSERT(!fOnFlushProxy);
|
||||
SkASSERT(!fOnFlushRefCnt);
|
||||
}
|
||||
|
||||
GrCCAtlas::CoverageType coverageType() const { return fCoverageType; }
|
||||
const GrUniqueKey& textureKey() const { return fTextureKey; }
|
||||
|
||||
GrTextureProxy* getOnFlushProxy() const { return fOnFlushProxy.get(); }
|
||||
|
||||
void setOnFlushProxy(sk_sp<GrTextureProxy> proxy) {
|
||||
SkASSERT(!fOnFlushProxy);
|
||||
fOnFlushProxy = std::move(proxy);
|
||||
}
|
||||
|
||||
void addPathPixels(int numPixels) { fNumPathPixels += numPixels; }
|
||||
ReleaseAtlasResult invalidatePathPixels(GrCCPathCache*, int numPixels);
|
||||
|
||||
int peekOnFlushRefCnt() const { return fOnFlushRefCnt; }
|
||||
void incrOnFlushRefCnt(int count = 1) const {
|
||||
SkASSERT(count > 0);
|
||||
SkASSERT(fOnFlushProxy);
|
||||
fOnFlushRefCnt += count;
|
||||
}
|
||||
void decrOnFlushRefCnt(int count = 1) const;
|
||||
|
||||
private:
|
||||
const GrCCAtlas::CoverageType fCoverageType;
|
||||
const GrUniqueKey fTextureKey;
|
||||
|
||||
int fNumPathPixels = 0;
|
||||
int fNumInvalidatedPathPixels = 0;
|
||||
bool fIsInvalidatedFromResourceCache = false;
|
||||
|
||||
mutable sk_sp<GrTextureProxy> fOnFlushProxy;
|
||||
mutable int fOnFlushRefCnt = 0;
|
||||
|
||||
public:
|
||||
int testingOnly_peekOnFlushRefCnt() const;
|
||||
};
|
||||
|
||||
|
||||
inline GrCCPathCache::HashNode::HashNode(GrCCPathCache* pathCache, sk_sp<Key> key,
|
||||
const MaskTransform& m, const GrShape& shape)
|
||||
: fPathCache(pathCache)
|
||||
, fEntry(new GrCCPathCacheEntry(key, m)) {
|
||||
SkASSERT(shape.hasUnstyledKey());
|
||||
shape.addGenIDChangeListener(std::move(key));
|
||||
}
|
||||
|
||||
inline const GrCCPathCache::Key& GrCCPathCache::HashNode::GetKey(
|
||||
const GrCCPathCache::HashNode& node) {
|
||||
return *node.entry()->fCacheKey;
|
||||
}
|
||||
|
||||
inline GrCCPathCache::HashNode::~HashNode() {
|
||||
SkASSERT(!fEntry || !fEntry->fCachedAtlas); // Should have called GrCCPathCache::evict().
|
||||
}
|
||||
|
||||
inline void GrCCPathCache::HashNode::operator=(HashNode&& node) {
|
||||
SkASSERT(!fEntry || !fEntry->fCachedAtlas); // Should have called GrCCPathCache::evict().
|
||||
fEntry = skstd::exchange(node.fEntry, nullptr);
|
||||
}
|
||||
|
||||
inline void GrCCPathProcessor::Instance::set(const GrCCPathCacheEntry& entry,
|
||||
const SkIVector& shift, GrColor color,
|
||||
DoEvenOddFill doEvenOddFill) {
|
||||
|
@ -33,9 +33,8 @@ public:
|
||||
return RequiresDstTexture::kNo;
|
||||
}
|
||||
CombineResult onCombineIfPossible(GrOp* other, const GrCaps&) override {
|
||||
// We will only make multiple copy ops if they have different source proxies.
|
||||
// TODO: make use of texture chaining.
|
||||
return CombineResult::kCannotCombine;
|
||||
SK_ABORT("Only expected one Op per CCPR atlas.");
|
||||
return CombineResult::kMerged;
|
||||
}
|
||||
void onPrepare(GrOpFlushState*) override {}
|
||||
|
||||
@ -51,7 +50,7 @@ protected:
|
||||
const sk_sp<const GrCCPerFlushResources> fResources;
|
||||
};
|
||||
|
||||
// Copies paths from a cached coverage count atlas into an 8-bit literal-coverage atlas.
|
||||
// Copies paths from a stashed coverage count atlas into an 8-bit literal-coverage atlas.
|
||||
class CopyAtlasOp : public AtlasOp {
|
||||
public:
|
||||
DEFINE_OP_CLASS_ID
|
||||
@ -67,16 +66,18 @@ public:
|
||||
}
|
||||
|
||||
const char* name() const override { return "CopyAtlasOp (CCPR)"; }
|
||||
void visitProxies(const VisitProxyFunc& fn, VisitorType) const override { fn(fSrcProxy.get()); }
|
||||
void visitProxies(const VisitProxyFunc& fn, VisitorType) const override {
|
||||
fn(fStashedAtlasProxy.get());
|
||||
}
|
||||
|
||||
void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
|
||||
SkASSERT(fSrcProxy);
|
||||
SkASSERT(fStashedAtlasProxy);
|
||||
GrPipeline::FixedDynamicState dynamicState;
|
||||
auto srcProxy = fSrcProxy.get();
|
||||
dynamicState.fPrimitiveProcessorTextures = &srcProxy;
|
||||
auto atlasProxy = fStashedAtlasProxy.get();
|
||||
dynamicState.fPrimitiveProcessorTextures = &atlasProxy;
|
||||
|
||||
GrPipeline pipeline(flushState->proxy(), GrScissorTest::kDisabled, SkBlendMode::kSrc);
|
||||
GrCCPathProcessor pathProc(srcProxy);
|
||||
GrCCPathProcessor pathProc(atlasProxy);
|
||||
pathProc.drawPaths(flushState, pipeline, &dynamicState, *fResources, fBaseInstance,
|
||||
fEndInstance, this->bounds());
|
||||
}
|
||||
@ -84,14 +85,15 @@ public:
|
||||
private:
|
||||
friend class ::GrOpMemoryPool; // for ctor
|
||||
|
||||
CopyAtlasOp(sk_sp<const GrCCPerFlushResources> resources, sk_sp<GrTextureProxy> srcProxy,
|
||||
CopyAtlasOp(sk_sp<const GrCCPerFlushResources> resources, sk_sp<GrTextureProxy> copyProxy,
|
||||
int baseInstance, int endInstance, const SkISize& drawBounds)
|
||||
: AtlasOp(ClassID(), std::move(resources), drawBounds)
|
||||
, fSrcProxy(srcProxy)
|
||||
, fStashedAtlasProxy(copyProxy)
|
||||
, fBaseInstance(baseInstance)
|
||||
, fEndInstance(endInstance) {
|
||||
}
|
||||
sk_sp<GrTextureProxy> fSrcProxy;
|
||||
|
||||
sk_sp<GrTextureProxy> fStashedAtlasProxy;
|
||||
const int fBaseInstance;
|
||||
const int fEndInstance;
|
||||
};
|
||||
@ -159,10 +161,9 @@ GrCCPerFlushResources::GrCCPerFlushResources(GrOnFlushResourceProvider* onFlushR
|
||||
, fStroker(specs.fNumRenderedPaths[kStrokeIdx],
|
||||
specs.fRenderedPathStats[kStrokeIdx].fNumTotalSkPoints,
|
||||
specs.fRenderedPathStats[kStrokeIdx].fNumTotalSkVerbs)
|
||||
, fCopyAtlasStack(GrCCAtlas::CoverageType::kA8_LiteralCoverage, specs.fCopyAtlasSpecs,
|
||||
onFlushRP->caps())
|
||||
, fRenderedAtlasStack(GrCCAtlas::CoverageType::kFP16_CoverageCount,
|
||||
specs.fRenderedAtlasSpecs, onFlushRP->caps())
|
||||
, 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,
|
||||
@ -189,84 +190,21 @@ GrCCPerFlushResources::GrCCPerFlushResources(GrOnFlushResourceProvider* onFlushR
|
||||
SkDEBUGCODE(fEndPathInstance = inst_buffer_count(specs));
|
||||
}
|
||||
|
||||
void GrCCPerFlushResources::upgradeEntryToLiteralCoverageAtlas(
|
||||
GrCCPathCache* pathCache, GrOnFlushResourceProvider* onFlushRP, GrCCPathCacheEntry* entry,
|
||||
GrCCPathProcessor::DoEvenOddFill evenOdd) {
|
||||
using ReleaseAtlasResult = GrCCPathCacheEntry::ReleaseAtlasResult;
|
||||
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.
|
||||
|
||||
const GrCCCachedAtlas* cachedAtlas = entry->cachedAtlas();
|
||||
SkASSERT(cachedAtlas);
|
||||
SkASSERT(cachedAtlas->getOnFlushProxy());
|
||||
|
||||
if (GrCCAtlas::CoverageType::kA8_LiteralCoverage == cachedAtlas->coverageType()) {
|
||||
// This entry has already been upgraded to literal coverage. The path must have been drawn
|
||||
// multiple times during the flush.
|
||||
SkDEBUGCODE(--fEndCopyInstance);
|
||||
return;
|
||||
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->setFillBatchID(fNextCopyInstanceIdx);
|
||||
}
|
||||
|
||||
SkIVector newAtlasOffset;
|
||||
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 ranges
|
||||
// up until fCopyPathRanges.count() into the retired atlas during finalize().
|
||||
retiredAtlas->setFillBatchID(fCopyPathRanges.count());
|
||||
fCurrCopyAtlasRangesIdx = fCopyPathRanges.count();
|
||||
}
|
||||
|
||||
this->recordCopyPathInstance(*entry, newAtlasOffset, evenOdd,
|
||||
sk_ref_sp(cachedAtlas->getOnFlushProxy()));
|
||||
|
||||
sk_sp<GrTexture> previousAtlasTexture =
|
||||
sk_ref_sp(cachedAtlas->getOnFlushProxy()->peekTexture());
|
||||
GrCCAtlas* newAtlas = &fCopyAtlasStack.current();
|
||||
if (ReleaseAtlasResult::kDidInvalidateFromCache ==
|
||||
entry->upgradeToLiteralCoverageAtlas(pathCache, onFlushRP, newAtlas, newAtlasOffset)) {
|
||||
// This texture just got booted out of the cache. Keep it around, in case we might be able
|
||||
// to recycle it for a new atlas. We can recycle it because copying happens before rendering
|
||||
// new paths, and every path from the atlas that we're planning to use this flush will be
|
||||
// copied to a new atlas. We'll never copy some and leave others.
|
||||
fRecyclableAtlasTextures.push_back(std::move(previousAtlasTexture));
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T, typename... Args>
|
||||
static void emplace_at_memcpy(SkTArray<T>* array, int idx, Args&&... args) {
|
||||
if (int moveCount = array->count() - idx) {
|
||||
array->push_back();
|
||||
T* location = array->begin() + idx;
|
||||
memcpy(location+1, location, moveCount * sizeof(T));
|
||||
new (location) T(std::forward<Args>(args)...);
|
||||
} else {
|
||||
array->emplace_back(std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
|
||||
void GrCCPerFlushResources::recordCopyPathInstance(const GrCCPathCacheEntry& entry,
|
||||
const SkIVector& newAtlasOffset,
|
||||
GrCCPathProcessor::DoEvenOddFill evenOdd,
|
||||
sk_sp<GrTextureProxy> srcProxy) {
|
||||
SkASSERT(fNextCopyInstanceIdx < fEndCopyInstance);
|
||||
|
||||
// Write the instance at the back of the array.
|
||||
int currentInstanceIdx = fNextCopyInstanceIdx++;
|
||||
fPathInstanceData[currentInstanceIdx].set(entry, newAtlasOffset, GrColor_WHITE, evenOdd);
|
||||
|
||||
// Percolate the instance forward until it's contiguous with other instances that share the same
|
||||
// proxy.
|
||||
for (int i = fCopyPathRanges.count() - 1; i >= fCurrCopyAtlasRangesIdx; --i) {
|
||||
if (fCopyPathRanges[i].fSrcProxy == srcProxy) {
|
||||
++fCopyPathRanges[i].fCount;
|
||||
return;
|
||||
}
|
||||
int rangeFirstInstanceIdx = currentInstanceIdx - fCopyPathRanges[i].fCount;
|
||||
std::swap(fPathInstanceData[rangeFirstInstanceIdx], fPathInstanceData[currentInstanceIdx]);
|
||||
currentInstanceIdx = rangeFirstInstanceIdx;
|
||||
}
|
||||
|
||||
// An instance with this particular proxy did not yet exist in the array. Add a range for it.
|
||||
emplace_at_memcpy(&fCopyPathRanges, fCurrCopyAtlasRangesIdx, std::move(srcProxy), 1);
|
||||
fPathInstanceData[fNextCopyInstanceIdx++].set(entry, *newAtlasOffset, GrColor_WHITE, evenOdd);
|
||||
return &fCopyAtlasStack.current();
|
||||
}
|
||||
|
||||
static bool transform_path_pts(const SkMatrix& m, const SkPath& path,
|
||||
@ -325,7 +263,7 @@ static bool transform_path_pts(const SkMatrix& m, const SkPath& path,
|
||||
return true;
|
||||
}
|
||||
|
||||
GrCCAtlas* GrCCPerFlushResources::renderShapeInAtlas(
|
||||
const GrCCAtlas* GrCCPerFlushResources::renderShapeInAtlas(
|
||||
const SkIRect& clipIBounds, const SkMatrix& m, const GrShape& shape, float strokeDevWidth,
|
||||
SkRect* devBounds, SkRect* devBounds45, SkIRect* devIBounds, SkIVector* devToAtlasOffset) {
|
||||
SkASSERT(this->isMapped());
|
||||
@ -423,17 +361,17 @@ bool GrCCPerFlushResources::placeRenderedPathInAtlas(const SkIRect& clipIBounds,
|
||||
}
|
||||
|
||||
bool GrCCPerFlushResources::finalize(GrOnFlushResourceProvider* onFlushRP,
|
||||
sk_sp<GrTextureProxy> stashedAtlasProxy,
|
||||
SkTArray<sk_sp<GrRenderTargetContext>>* out) {
|
||||
SkASSERT(this->isMapped());
|
||||
SkASSERT(fNextPathInstanceIdx == fEndPathInstance);
|
||||
SkASSERT(fNextCopyInstanceIdx == fEndCopyInstance);
|
||||
// No assert for fEndCopyInstance because the caller may have detected and skipped duplicates.
|
||||
|
||||
fInstanceBuffer->unmap();
|
||||
fPathInstanceData = nullptr;
|
||||
|
||||
if (!fCopyAtlasStack.empty()) {
|
||||
fCopyAtlasStack.current().setFillBatchID(fCopyPathRanges.count());
|
||||
fCurrCopyAtlasRangesIdx = fCopyPathRanges.count();
|
||||
fCopyAtlasStack.current().setFillBatchID(fNextCopyInstanceIdx);
|
||||
}
|
||||
if (!fRenderedAtlasStack.empty()) {
|
||||
fRenderedAtlasStack.current().setFillBatchID(fFiller.closeCurrentBatch());
|
||||
@ -449,44 +387,38 @@ bool GrCCPerFlushResources::finalize(GrOnFlushResourceProvider* onFlushRP,
|
||||
return false;
|
||||
}
|
||||
|
||||
// Draw the copies from 16-bit literal coverage atlas(es) into 8-bit cached atlas(es).
|
||||
int copyRangeIdx = 0;
|
||||
// Draw the copies from the stashed atlas into 8-bit cached atlas(es).
|
||||
int baseCopyInstance = 0;
|
||||
for (GrCCAtlasStack::Iter atlas(fCopyAtlasStack); atlas.next();) {
|
||||
int endCopyRange = atlas->getFillBatchID();
|
||||
SkASSERT(endCopyRange > copyRangeIdx);
|
||||
|
||||
sk_sp<GrRenderTargetContext> rtc = atlas->makeRenderTargetContext(onFlushRP);
|
||||
for (; copyRangeIdx < endCopyRange; ++copyRangeIdx) {
|
||||
const CopyPathRange& copyRange = fCopyPathRanges[copyRangeIdx];
|
||||
int endCopyInstance = baseCopyInstance + copyRange.fCount;
|
||||
if (rtc) {
|
||||
auto op = CopyAtlasOp::Make(rtc->surfPriv().getContext(), sk_ref_sp(this),
|
||||
copyRange.fSrcProxy, baseCopyInstance, endCopyInstance,
|
||||
atlas->drawBounds());
|
||||
rtc->addDrawOp(GrNoClip(), std::move(op));
|
||||
}
|
||||
baseCopyInstance = endCopyInstance;
|
||||
int endCopyInstance = atlas->getFillBatchID();
|
||||
if (endCopyInstance <= baseCopyInstance) {
|
||||
SkASSERT(endCopyInstance == baseCopyInstance);
|
||||
continue;
|
||||
}
|
||||
out->push_back(std::move(rtc));
|
||||
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;
|
||||
}
|
||||
SkASSERT(fCopyPathRanges.count() == copyRangeIdx);
|
||||
SkASSERT(fNextCopyInstanceIdx == baseCopyInstance);
|
||||
SkASSERT(baseCopyInstance == fEndCopyInstance);
|
||||
|
||||
// Render the coverage count atlas(es).
|
||||
for (GrCCAtlasStack::Iter atlas(fRenderedAtlasStack); atlas.next();) {
|
||||
// Copies will be finished by the time we get to rendering new atlases. See if we can
|
||||
// recycle any previous invalidated atlas textures instead of creating new ones.
|
||||
// Copies will be finished by the time we get to this atlas. See if we can recycle the
|
||||
// stashed atlas texture instead of creating a new one.
|
||||
sk_sp<GrTexture> backingTexture;
|
||||
for (sk_sp<GrTexture>& texture : fRecyclableAtlasTextures) {
|
||||
if (texture && atlas->currentHeight() == texture->height() &&
|
||||
atlas->currentWidth() == texture->width()) {
|
||||
backingTexture = skstd::exchange(texture, nullptr);
|
||||
break;
|
||||
}
|
||||
if (stashedAtlasProxy && atlas->currentWidth() == stashedAtlasProxy->width() &&
|
||||
atlas->currentHeight() == stashedAtlasProxy->height()) {
|
||||
backingTexture = sk_ref_sp(stashedAtlasProxy->peekTexture());
|
||||
}
|
||||
|
||||
// Delete the stashed proxy here. That way, if we can't recycle the stashed atlas texture,
|
||||
// we free this memory prior to allocating a new backing texture.
|
||||
stashedAtlasProxy = nullptr;
|
||||
|
||||
if (auto rtc = atlas->makeRenderTargetContext(onFlushRP, std::move(backingTexture))) {
|
||||
auto op = RenderAtlasOp::Make(rtc->surfPriv().getContext(), sk_ref_sp(this),
|
||||
atlas->getFillBatchID(), atlas->getStrokeBatchID(),
|
||||
@ -499,10 +431,23 @@ bool GrCCPerFlushResources::finalize(GrOnFlushResourceProvider* onFlushRP,
|
||||
return true;
|
||||
}
|
||||
|
||||
void GrCCPerFlushResourceSpecs::cancelCopies() {
|
||||
// Convert copies to cached draws.
|
||||
fNumCachedPaths += fNumCopiedPaths[kFillIdx] + fNumCopiedPaths[kStrokeIdx];
|
||||
fNumCopiedPaths[kFillIdx] = fNumCopiedPaths[kStrokeIdx] = 0;
|
||||
fCopyPathStats[kFillIdx] = fCopyPathStats[kStrokeIdx] = GrCCRenderedPathStats();
|
||||
void GrCCPerFlushResourceSpecs::convertCopiesToRenders() {
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
fNumRenderedPaths[i] += fNumCopiedPaths[i];
|
||||
fNumCopiedPaths[i] = 0;
|
||||
|
||||
fRenderedPathStats[i].fMaxPointsPerPath =
|
||||
SkTMax(fRenderedPathStats[i].fMaxPointsPerPath, fCopyPathStats[i].fMaxPointsPerPath);
|
||||
fRenderedPathStats[i].fNumTotalSkPoints += fCopyPathStats[i].fNumTotalSkPoints;
|
||||
fRenderedPathStats[i].fNumTotalSkVerbs += fCopyPathStats[i].fNumTotalSkVerbs;
|
||||
fRenderedPathStats[i].fNumTotalConicWeights += fCopyPathStats[i].fNumTotalConicWeights;
|
||||
fCopyPathStats[i] = GrCCRenderedPathStats();
|
||||
}
|
||||
|
||||
fRenderedAtlasSpecs.fApproxNumPixels += fCopyAtlasSpecs.fApproxNumPixels;
|
||||
fRenderedAtlasSpecs.fMinWidth =
|
||||
SkTMax(fRenderedAtlasSpecs.fMinWidth, fCopyAtlasSpecs.fMinWidth);
|
||||
fRenderedAtlasSpecs.fMinHeight =
|
||||
SkTMax(fRenderedAtlasSpecs.fMinHeight, fCopyAtlasSpecs.fMinHeight);
|
||||
fCopyAtlasSpecs = GrCCAtlas::Specs();
|
||||
}
|
||||
|
@ -14,7 +14,6 @@
|
||||
#include "ccpr/GrCCStroker.h"
|
||||
#include "ccpr/GrCCPathProcessor.h"
|
||||
|
||||
class GrCCPathCache;
|
||||
class GrCCPathCacheEntry;
|
||||
class GrOnFlushResourceProvider;
|
||||
class GrShape;
|
||||
@ -54,8 +53,7 @@ struct GrCCPerFlushResourceSpecs {
|
||||
return 0 == fNumCachedPaths + fNumCopiedPaths[kFillIdx] + fNumCopiedPaths[kStrokeIdx] +
|
||||
fNumRenderedPaths[kFillIdx] + fNumRenderedPaths[kStrokeIdx] + fNumClipPaths;
|
||||
}
|
||||
// Converts the copies to normal cached draws.
|
||||
void cancelCopies();
|
||||
void convertCopiesToRenders();
|
||||
};
|
||||
|
||||
/**
|
||||
@ -69,19 +67,22 @@ public:
|
||||
|
||||
bool isMapped() const { return SkToBool(fPathInstanceData); }
|
||||
|
||||
// Copies a coverage-counted path out of the given texture proxy, and into a cached, 8-bit,
|
||||
// literal coverage atlas. Updates the cache entry to reference the new atlas.
|
||||
void upgradeEntryToLiteralCoverageAtlas(GrCCPathCache*, GrOnFlushResourceProvider*,
|
||||
GrCCPathCacheEntry*, GrCCPathProcessor::DoEvenOddFill);
|
||||
// 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
|
||||
// GrCCPathProcessor::Instance for a description of the outputs.
|
||||
// GrCCPathProcessor::Instance for a description of the outputs. The returned atlases are
|
||||
// "const" to prevent the caller from assigning a unique key.
|
||||
//
|
||||
// strokeDevWidth must be 0 for fills, 1 for hairlines, or the stroke width in device-space
|
||||
// pixels for non-hairline strokes (implicitly requiring a rigid-body transform).
|
||||
GrCCAtlas* renderShapeInAtlas(const SkIRect& clipIBounds, const SkMatrix&, const GrShape&,
|
||||
float strokeDevWidth, SkRect* devBounds, SkRect* devBounds45,
|
||||
SkIRect* devIBounds, SkIVector* devToAtlasOffset);
|
||||
const GrCCAtlas* renderShapeInAtlas(const SkIRect& clipIBounds, const SkMatrix&, const GrShape&,
|
||||
float strokeDevWidth, SkRect* devBounds,
|
||||
SkRect* devBounds45, SkIRect* devIBounds,
|
||||
SkIVector* devToAtlasOffset);
|
||||
const GrCCAtlas* renderDeviceSpacePathInAtlas(const SkIRect& clipIBounds, const SkPath& devPath,
|
||||
const SkIRect& devPathIBounds,
|
||||
SkIVector* devToAtlasOffset);
|
||||
@ -99,8 +100,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 GrCCFiller& filler() const { SkASSERT(!this->isMapped()); return fFiller; }
|
||||
@ -109,9 +113,23 @@ 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:
|
||||
void recordCopyPathInstance(const GrCCPathCacheEntry&, const SkIVector& newAtlasOffset,
|
||||
GrCCPathProcessor::DoEvenOddFill, sk_sp<GrTextureProxy> srcProxy);
|
||||
bool placeRenderedPathInAtlas(const SkIRect& clipIBounds, const SkIRect& pathIBounds,
|
||||
GrScissorTest*, SkIRect* clippedPathIBounds,
|
||||
SkIVector* devToAtlasOffset);
|
||||
@ -131,30 +149,6 @@ private:
|
||||
SkDEBUGCODE(int fEndCopyInstance);
|
||||
int fNextPathInstanceIdx;
|
||||
SkDEBUGCODE(int fEndPathInstance);
|
||||
|
||||
// Represents a range of copy-path instances that all share the same source proxy. (i.e. Draw
|
||||
// instances that copy a path mask from a 16-bit coverage count atlas into an 8-bit literal
|
||||
// coverage atlas.)
|
||||
struct CopyPathRange {
|
||||
CopyPathRange() = default;
|
||||
CopyPathRange(sk_sp<GrTextureProxy> srcProxy, int count)
|
||||
: fSrcProxy(std::move(srcProxy)), fCount(count) {}
|
||||
sk_sp<GrTextureProxy> fSrcProxy;
|
||||
int fCount;
|
||||
};
|
||||
|
||||
SkSTArray<4, CopyPathRange> fCopyPathRanges;
|
||||
int fCurrCopyAtlasRangesIdx = 0;
|
||||
|
||||
// This is a list of coverage count atlas textures that have been invalidated due to us copying
|
||||
// their paths into new 8-bit literal coverage atlases. Since copying is finished by the time
|
||||
// we begin rendering new atlases, we can recycle these textures for the rendered atlases rather
|
||||
// than allocating new texture objects upon instantiation.
|
||||
SkSTArray<2, sk_sp<GrTexture>> fRecyclableAtlasTextures;
|
||||
|
||||
public:
|
||||
const GrTexture* testingOnly_frontCopyAtlasTexture() const;
|
||||
const GrTexture* testingOnly_frontRenderedAtlasTexture() const;
|
||||
};
|
||||
|
||||
inline void GrCCRenderedPathStats::statPath(const SkPath& path) {
|
||||
|
@ -30,16 +30,15 @@ bool GrCoverageCountingPathRenderer::IsSupported(const GrCaps& caps) {
|
||||
}
|
||||
|
||||
sk_sp<GrCoverageCountingPathRenderer> GrCoverageCountingPathRenderer::CreateIfSupported(
|
||||
const GrCaps& caps, AllowCaching allowCaching, uint32_t contextUniqueID) {
|
||||
const GrCaps& caps, AllowCaching allowCaching) {
|
||||
return sk_sp<GrCoverageCountingPathRenderer>((IsSupported(caps))
|
||||
? new GrCoverageCountingPathRenderer(allowCaching, contextUniqueID)
|
||||
? new GrCoverageCountingPathRenderer(allowCaching)
|
||||
: nullptr);
|
||||
}
|
||||
|
||||
GrCoverageCountingPathRenderer::GrCoverageCountingPathRenderer(AllowCaching allowCaching,
|
||||
uint32_t contextUniqueID) {
|
||||
GrCoverageCountingPathRenderer::GrCoverageCountingPathRenderer(AllowCaching allowCaching) {
|
||||
if (AllowCaching::kYes == allowCaching) {
|
||||
fPathCache = skstd::make_unique<GrCCPathCache>(contextUniqueID);
|
||||
fPathCache = skstd::make_unique<GrCCPathCache>();
|
||||
}
|
||||
}
|
||||
|
||||
@ -189,16 +188,29 @@ std::unique_ptr<GrFragmentProcessor> GrCoverageCountingPathRenderer::makeClipPro
|
||||
void GrCoverageCountingPathRenderer::preFlush(GrOnFlushResourceProvider* onFlushRP,
|
||||
const uint32_t* opListIDs, int numOpListIDs,
|
||||
SkTArray<sk_sp<GrRenderTargetContext>>* out) {
|
||||
using DoCopiesToA8Coverage = GrCCDrawPathsOp::DoCopiesToA8Coverage;
|
||||
using DoCopiesToCache = GrCCDrawPathsOp::DoCopiesToCache;
|
||||
SkASSERT(!fFlushing);
|
||||
SkASSERT(fFlushingPaths.empty());
|
||||
SkDEBUGCODE(fFlushing = true);
|
||||
|
||||
if (fPathCache) {
|
||||
fPathCache->doPreFlushProcessing();
|
||||
// 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());
|
||||
SkASSERT(fStashedAtlasKey == stashedAtlasProxy->getUniqueKey());
|
||||
onFlushRP->removeUniqueKeyFromProxy(stashedAtlasProxy.get());
|
||||
} else {
|
||||
fStashedAtlasKey.reset(); // Indicate there is no stashed atlas to copy from.
|
||||
}
|
||||
}
|
||||
|
||||
if (fPendingPaths.empty()) {
|
||||
fStashedAtlasKey.reset();
|
||||
return; // Nothing to draw.
|
||||
}
|
||||
|
||||
@ -222,12 +234,13 @@ void GrCoverageCountingPathRenderer::preFlush(GrOnFlushResourceProvider* onFlush
|
||||
fPendingPaths.erase(iter);
|
||||
|
||||
for (GrCCDrawPathsOp* op : fFlushingPaths.back()->fDrawOps) {
|
||||
op->accountForOwnPaths(fPathCache.get(), onFlushRP, &specs);
|
||||
op->accountForOwnPaths(fPathCache.get(), onFlushRP, fStashedAtlasKey, &specs);
|
||||
}
|
||||
for (const auto& clipsIter : fFlushingPaths.back()->fClipPaths) {
|
||||
clipsIter.second.accountForOwnPath(&specs);
|
||||
}
|
||||
}
|
||||
fStashedAtlasKey.reset();
|
||||
|
||||
if (specs.isEmpty()) {
|
||||
return; // Nothing to draw.
|
||||
@ -237,10 +250,12 @@ void GrCoverageCountingPathRenderer::preFlush(GrOnFlushResourceProvider* onFlush
|
||||
// copy them to cached atlas(es).
|
||||
int numCopies = specs.fNumCopiedPaths[GrCCPerFlushResourceSpecs::kFillIdx] +
|
||||
specs.fNumCopiedPaths[GrCCPerFlushResourceSpecs::kStrokeIdx];
|
||||
auto doCopies = DoCopiesToA8Coverage(numCopies > 100 ||
|
||||
specs.fCopyAtlasSpecs.fApproxNumPixels > 256 * 256);
|
||||
if (numCopies && DoCopiesToA8Coverage::kNo == doCopies) {
|
||||
specs.cancelCopies();
|
||||
DoCopiesToCache doCopies = DoCopiesToCache(numCopies > 100 ||
|
||||
specs.fCopyAtlasSpecs.fApproxNumPixels > 256 * 256);
|
||||
if (numCopies && DoCopiesToCache::kNo == doCopies) {
|
||||
specs.convertCopiesToRenders();
|
||||
SkASSERT(!specs.fNumCopiedPaths[GrCCPerFlushResourceSpecs::kFillIdx]);
|
||||
SkASSERT(!specs.fNumCopiedPaths[GrCCPerFlushResourceSpecs::kStrokeIdx]);
|
||||
}
|
||||
|
||||
auto resources = sk_make_sp<GrCCPerFlushResources>(onFlushRP, specs);
|
||||
@ -251,24 +266,21 @@ void GrCoverageCountingPathRenderer::preFlush(GrOnFlushResourceProvider* onFlush
|
||||
// Layout the atlas(es) and parse paths.
|
||||
for (const auto& flushingPaths : fFlushingPaths) {
|
||||
for (GrCCDrawPathsOp* op : flushingPaths->fDrawOps) {
|
||||
op->setupResources(fPathCache.get(), onFlushRP, resources.get(), doCopies);
|
||||
op->setupResources(onFlushRP, resources.get(), doCopies);
|
||||
}
|
||||
for (auto& clipsIter : flushingPaths->fClipPaths) {
|
||||
clipsIter.second.renderPathInAtlas(resources.get(), onFlushRP);
|
||||
}
|
||||
}
|
||||
|
||||
if (fPathCache) {
|
||||
// Purge invalidated textures from previous atlases *before* calling finalize(). That way,
|
||||
// the underlying textures objects can be freed up and reused for the next atlases.
|
||||
fPathCache->purgeInvalidatedAtlasTextures(onFlushRP);
|
||||
}
|
||||
|
||||
// Allocate resources and then render the atlas(es).
|
||||
if (!resources->finalize(onFlushRP, out)) {
|
||||
if (!resources->finalize(onFlushRP, std::move(stashedAtlasProxy), out)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify the stashed atlas got released so its texture could be recycled.
|
||||
SkASSERT(!stashedAtlasProxy); // NOLINT(bugprone-use-after-move)
|
||||
|
||||
// Commit flushing paths to the resources once they are successfully completed.
|
||||
for (auto& flushingPaths : fFlushingPaths) {
|
||||
SkASSERT(!flushingPaths->fFlushResources);
|
||||
@ -279,8 +291,15 @@ 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().
|
||||
|
||||
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) {
|
||||
@ -291,13 +310,17 @@ void GrCoverageCountingPathRenderer::postFlush(GrDeferredUploadToken, const uint
|
||||
fFlushingPaths.reset();
|
||||
}
|
||||
|
||||
if (fPathCache) {
|
||||
fPathCache->doPostFlushProcessing();
|
||||
}
|
||||
|
||||
SkDEBUGCODE(fFlushing = false);
|
||||
}
|
||||
|
||||
void GrCoverageCountingPathRenderer::purgeCacheEntriesOlderThan(
|
||||
GrProxyProvider* proxyProvider, const GrStdSteadyClock::time_point& purgeTime) {
|
||||
const GrStdSteadyClock::time_point& purgeTime) {
|
||||
if (fPathCache) {
|
||||
fPathCache->purgeEntriesOlderThan(proxyProvider, purgeTime);
|
||||
fPathCache->purgeEntriesOlderThan(purgeTime);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,8 +34,7 @@ public:
|
||||
kYes = true
|
||||
};
|
||||
|
||||
static sk_sp<GrCoverageCountingPathRenderer> CreateIfSupported(const GrCaps&, AllowCaching,
|
||||
uint32_t contextUniqueID);
|
||||
static sk_sp<GrCoverageCountingPathRenderer> CreateIfSupported(const GrCaps&, AllowCaching);
|
||||
|
||||
using PendingPathsMap = std::map<uint32_t, sk_sp<GrCCPerOpListPaths>>;
|
||||
|
||||
@ -66,7 +65,10 @@ public:
|
||||
SkTArray<sk_sp<GrRenderTargetContext>>* out) override;
|
||||
void postFlush(GrDeferredUploadToken, const uint32_t* opListIDs, int numOpListIDs) override;
|
||||
|
||||
void purgeCacheEntriesOlderThan(GrProxyProvider*, const GrStdSteadyClock::time_point&);
|
||||
void purgeCacheEntriesOlderThan(const GrStdSteadyClock::time_point& purgeTime);
|
||||
|
||||
void testingOnly_drawPathDirectly(const DrawPathArgs&);
|
||||
const GrUniqueKey& testingOnly_getStashedAtlasKey() const;
|
||||
|
||||
// If a path spans more pixels than this, we need to crop it or else analytic AA can run out of
|
||||
// fp32 precision.
|
||||
@ -82,7 +84,7 @@ public:
|
||||
float* inflationRadius = nullptr);
|
||||
|
||||
private:
|
||||
GrCoverageCountingPathRenderer(AllowCaching, uint32_t contextUniqueID);
|
||||
GrCoverageCountingPathRenderer(AllowCaching);
|
||||
|
||||
// GrPathRenderer overrides.
|
||||
StencilSupport onGetStencilSupport(const GrShape&) const override {
|
||||
@ -104,13 +106,9 @@ private:
|
||||
SkSTArray<4, sk_sp<GrCCPerOpListPaths>> fFlushingPaths;
|
||||
|
||||
std::unique_ptr<GrCCPathCache> fPathCache;
|
||||
GrUniqueKey fStashedAtlasKey;
|
||||
|
||||
SkDEBUGCODE(bool fFlushing = false);
|
||||
|
||||
public:
|
||||
void testingOnly_drawPathDirectly(const DrawPathArgs&);
|
||||
const GrCCPerFlushResources* testingOnly_getCurrentFlushResources();
|
||||
const GrCCPathCache* testingOnly_getPathCache() const;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -15,11 +15,7 @@
|
||||
|
||||
int GrMockGpu::NextInternalTextureID() {
|
||||
static std::atomic<int> nextID{1};
|
||||
int id;
|
||||
do {
|
||||
id = nextID.fetch_add(1);
|
||||
} while (0 == id); // Reserve 0 for an invalid ID.
|
||||
return id;
|
||||
return nextID++;
|
||||
}
|
||||
|
||||
int GrMockGpu::NextExternalTextureID() {
|
||||
|
@ -18,13 +18,11 @@
|
||||
#include "GrRenderTargetContextPriv.h"
|
||||
#include "GrShape.h"
|
||||
#include "GrTexture.h"
|
||||
#include "SkExchange.h"
|
||||
#include "SkMatrix.h"
|
||||
#include "SkPathPriv.h"
|
||||
#include "SkRect.h"
|
||||
#include "sk_tool_utils.h"
|
||||
#include "ccpr/GrCoverageCountingPathRenderer.h"
|
||||
#include "ccpr/GrCCPathCache.h"
|
||||
#include "mock/GrMockTypes.h"
|
||||
|
||||
#include <cmath>
|
||||
@ -58,7 +56,7 @@ private:
|
||||
|
||||
class CCPRPathDrawer {
|
||||
public:
|
||||
CCPRPathDrawer(sk_sp<GrContext> ctx, skiatest::Reporter* reporter, bool doStroke)
|
||||
CCPRPathDrawer(GrContext* ctx, skiatest::Reporter* reporter, bool doStroke)
|
||||
: fCtx(ctx)
|
||||
, fCCPR(fCtx->contextPriv().drawingManager()->getCoverageCountingPathRenderer())
|
||||
, fRTC(fCtx->contextPriv().makeDeferredRenderTargetContext(
|
||||
@ -74,19 +72,13 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
GrContext* ctx() const { return fCtx.get(); }
|
||||
GrContext* ctx() const { return fCtx; }
|
||||
GrCoverageCountingPathRenderer* ccpr() const { return fCCPR; }
|
||||
|
||||
bool valid() const { return fCCPR && fRTC; }
|
||||
void clear() const { fRTC->clear(nullptr, SK_PMColor4fTRANSPARENT,
|
||||
GrRenderTargetContext::CanClearFullscreen::kYes); }
|
||||
void destroyGrContext() {
|
||||
SkASSERT(fRTC->unique());
|
||||
SkASSERT(fCtx->unique());
|
||||
fRTC.reset();
|
||||
fCCPR = nullptr;
|
||||
fCtx.reset();
|
||||
}
|
||||
void abandonGrContext() { fCtx = nullptr; fCCPR = nullptr; fRTC = nullptr; }
|
||||
|
||||
void drawPath(const SkPath& path, const SkMatrix& matrix = SkMatrix::I()) const {
|
||||
SkASSERT(this->valid());
|
||||
@ -110,7 +102,7 @@ public:
|
||||
}
|
||||
|
||||
fCCPR->testingOnly_drawPathDirectly({
|
||||
fCtx.get(), std::move(paint), &GrUserStencilSettings::kUnused, fRTC.get(), &noClip,
|
||||
fCtx, std::move(paint), &GrUserStencilSettings::kUnused, fRTC.get(), &noClip,
|
||||
&clipBounds, &matrix, &shape, GrAAType::kCoverage, false});
|
||||
}
|
||||
|
||||
@ -130,7 +122,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
sk_sp<GrContext> fCtx;
|
||||
GrContext* fCtx;
|
||||
GrCoverageCountingPathRenderer* fCCPR;
|
||||
sk_sp<GrRenderTargetContext> fRTC;
|
||||
const bool fDoStroke;
|
||||
@ -158,17 +150,17 @@ public:
|
||||
|
||||
this->customizeOptions(&mockOptions, &ctxOptions);
|
||||
|
||||
sk_sp<GrContext> mockContext = GrContext::MakeMock(&mockOptions, ctxOptions);
|
||||
if (!mockContext) {
|
||||
fMockContext = GrContext::MakeMock(&mockOptions, ctxOptions);
|
||||
if (!fMockContext) {
|
||||
ERRORF(reporter, "could not create mock context");
|
||||
return;
|
||||
}
|
||||
if (!mockContext->unique()) {
|
||||
if (!fMockContext->unique()) {
|
||||
ERRORF(reporter, "mock context is not unique");
|
||||
return;
|
||||
}
|
||||
|
||||
CCPRPathDrawer ccpr(skstd::exchange(mockContext, nullptr), reporter, doStroke);
|
||||
CCPRPathDrawer ccpr(fMockContext.get(), reporter, doStroke);
|
||||
if (!ccpr.valid()) {
|
||||
return;
|
||||
}
|
||||
@ -184,6 +176,7 @@ protected:
|
||||
virtual void customizeOptions(GrMockOptions*, GrContextOptions*) {}
|
||||
virtual void onRun(skiatest::Reporter* reporter, CCPRPathDrawer& ccpr) = 0;
|
||||
|
||||
sk_sp<GrContext> fMockContext;
|
||||
SkPath fPath;
|
||||
};
|
||||
|
||||
@ -194,7 +187,7 @@ protected:
|
||||
test.run(reporter, true); \
|
||||
}
|
||||
|
||||
class CCPR_cleanup : public CCPRTest {
|
||||
class GrCCPRTest_cleanup : public CCPRTest {
|
||||
void onRun(skiatest::Reporter* reporter, CCPRPathDrawer& ccpr) override {
|
||||
REPORTER_ASSERT(reporter, SkPathPriv::TestingOnly_unique(fPath));
|
||||
|
||||
@ -219,22 +212,22 @@ class CCPR_cleanup : public CCPRTest {
|
||||
ccpr.drawPath(fPath);
|
||||
ccpr.clipFullscreenRect(fPath);
|
||||
}
|
||||
ccpr.abandonGrContext();
|
||||
REPORTER_ASSERT(reporter, !SkPathPriv::TestingOnly_unique(fPath));
|
||||
|
||||
ccpr.destroyGrContext();
|
||||
fMockContext.reset();
|
||||
REPORTER_ASSERT(reporter, SkPathPriv::TestingOnly_unique(fPath));
|
||||
}
|
||||
};
|
||||
DEF_CCPR_TEST(CCPR_cleanup)
|
||||
DEF_CCPR_TEST(GrCCPRTest_cleanup)
|
||||
|
||||
class CCPR_cleanupWithTexAllocFail : public CCPR_cleanup {
|
||||
class GrCCPRTest_cleanupWithTexAllocFail : public GrCCPRTest_cleanup {
|
||||
void customizeOptions(GrMockOptions* mockOptions, GrContextOptions*) override {
|
||||
mockOptions->fFailTextureAllocations = true;
|
||||
}
|
||||
};
|
||||
DEF_CCPR_TEST(CCPR_cleanupWithTexAllocFail)
|
||||
DEF_CCPR_TEST(GrCCPRTest_cleanupWithTexAllocFail)
|
||||
|
||||
class CCPR_unregisterCulledOps : public CCPRTest {
|
||||
class GrCCPRTest_unregisterCulledOps : public CCPRTest {
|
||||
void onRun(skiatest::Reporter* reporter, CCPRPathDrawer& ccpr) override {
|
||||
REPORTER_ASSERT(reporter, SkPathPriv::TestingOnly_unique(fPath));
|
||||
|
||||
@ -250,12 +243,13 @@ class CCPR_unregisterCulledOps : public CCPRTest {
|
||||
REPORTER_ASSERT(reporter, !SkPathPriv::TestingOnly_unique(fPath));
|
||||
ccpr.clear(); // Clear should delete the CCPR DrawPathsOp.
|
||||
REPORTER_ASSERT(reporter, SkPathPriv::TestingOnly_unique(fPath));
|
||||
ccpr.destroyGrContext(); // Should not crash (DrawPathsOp should have unregistered itself).
|
||||
ccpr.abandonGrContext();
|
||||
fMockContext.reset(); // Should not crash (DrawPathsOp should have unregistered itself).
|
||||
}
|
||||
};
|
||||
DEF_CCPR_TEST(CCPR_unregisterCulledOps)
|
||||
DEF_CCPR_TEST(GrCCPRTest_unregisterCulledOps)
|
||||
|
||||
class CCPR_parseEmptyPath : public CCPRTest {
|
||||
class GrCCPRTest_parseEmptyPath : public CCPRTest {
|
||||
void onRun(skiatest::Reporter* reporter, CCPRPathDrawer& ccpr) override {
|
||||
REPORTER_ASSERT(reporter, SkPathPriv::TestingOnly_unique(fPath));
|
||||
|
||||
@ -289,496 +283,119 @@ class CCPR_parseEmptyPath : public CCPRTest {
|
||||
ccpr.flush();
|
||||
}
|
||||
};
|
||||
DEF_CCPR_TEST(CCPR_parseEmptyPath)
|
||||
|
||||
static int get_mock_texture_id(const GrTexture* texture) {
|
||||
const GrBackendTexture& backingTexture = texture->getBackendTexture();
|
||||
SkASSERT(GrBackendApi::kMock == backingTexture.backend());
|
||||
|
||||
if (!backingTexture.isValid()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
GrMockTextureInfo info;
|
||||
backingTexture.getMockTextureInfo(&info);
|
||||
return info.fID;
|
||||
}
|
||||
|
||||
// Base class for cache path unit tests.
|
||||
class CCPRCacheTest : public CCPRTest {
|
||||
protected:
|
||||
// Registers as an onFlush callback in order to snag the CCPR per-flush resources and note the
|
||||
// texture IDs.
|
||||
class RecordLastMockAtlasIDs : public GrOnFlushCallbackObject {
|
||||
public:
|
||||
RecordLastMockAtlasIDs(sk_sp<GrCoverageCountingPathRenderer> ccpr) : fCCPR(ccpr) {}
|
||||
|
||||
int lastCopyAtlasID() const { return fLastCopyAtlasID; }
|
||||
int lastRenderedAtlasID() const { return fLastRenderedAtlasID; }
|
||||
|
||||
void preFlush(GrOnFlushResourceProvider*, const uint32_t* opListIDs, int numOpListIDs,
|
||||
SkTArray<sk_sp<GrRenderTargetContext>>* out) override {
|
||||
fLastRenderedAtlasID = fLastCopyAtlasID = 0;
|
||||
|
||||
const GrCCPerFlushResources* resources = fCCPR->testingOnly_getCurrentFlushResources();
|
||||
if (!resources) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (const GrTexture* tex = resources->testingOnly_frontCopyAtlasTexture()) {
|
||||
fLastCopyAtlasID = get_mock_texture_id(tex);
|
||||
}
|
||||
if (const GrTexture* tex = resources->testingOnly_frontRenderedAtlasTexture()) {
|
||||
fLastRenderedAtlasID = get_mock_texture_id(tex);
|
||||
}
|
||||
}
|
||||
|
||||
void postFlush(GrDeferredUploadToken, const uint32_t*, int) override {}
|
||||
|
||||
private:
|
||||
sk_sp<GrCoverageCountingPathRenderer> fCCPR;
|
||||
int fLastCopyAtlasID = 0;
|
||||
int fLastRenderedAtlasID = 0;
|
||||
};
|
||||
|
||||
CCPRCacheTest() {
|
||||
static constexpr int primes[11] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31};
|
||||
|
||||
SkRandom rand;
|
||||
for (size_t i = 0; i < SK_ARRAY_COUNT(fPaths); ++i) {
|
||||
int numPts = rand.nextRangeU(GrShape::kMaxKeyFromDataVerbCnt + 1,
|
||||
GrShape::kMaxKeyFromDataVerbCnt * 2);
|
||||
int step;
|
||||
do {
|
||||
step = primes[rand.nextU() % SK_ARRAY_COUNT(primes)];
|
||||
} while (step == numPts);
|
||||
fPaths[i] = sk_tool_utils::make_star(SkRect::MakeLTRB(0,0,1,1), numPts, step);
|
||||
}
|
||||
}
|
||||
|
||||
void drawPathsAndFlush(CCPRPathDrawer& ccpr, const SkMatrix& m) {
|
||||
this->drawPathsAndFlush(ccpr, &m, 1);
|
||||
}
|
||||
void drawPathsAndFlush(CCPRPathDrawer& ccpr, const SkMatrix* matrices, int numMatrices) {
|
||||
// Draw all the paths.
|
||||
for (size_t i = 0; i < SK_ARRAY_COUNT(fPaths); ++i) {
|
||||
ccpr.drawPath(fPaths[i], matrices[i % numMatrices]);
|
||||
}
|
||||
// Re-draw a few paths, to test the case where a cache entry is hit more than once in a
|
||||
// single flush.
|
||||
SkRandom rand;
|
||||
int duplicateIndices[10];
|
||||
for (size_t i = 0; i < SK_ARRAY_COUNT(duplicateIndices); ++i) {
|
||||
duplicateIndices[i] = rand.nextULessThan(SK_ARRAY_COUNT(fPaths));
|
||||
}
|
||||
for (size_t i = 0; i < SK_ARRAY_COUNT(duplicateIndices); ++i) {
|
||||
for (size_t j = 0; j <= i; ++j) {
|
||||
int idx = duplicateIndices[j];
|
||||
ccpr.drawPath(fPaths[idx], matrices[idx % numMatrices]);
|
||||
}
|
||||
}
|
||||
ccpr.flush();
|
||||
}
|
||||
|
||||
private:
|
||||
void customizeOptions(GrMockOptions*, GrContextOptions* ctxOptions) override {
|
||||
ctxOptions->fAllowPathMaskCaching = true;
|
||||
}
|
||||
|
||||
void onRun(skiatest::Reporter* reporter, CCPRPathDrawer& ccpr) final {
|
||||
RecordLastMockAtlasIDs atlasIDRecorder(sk_ref_sp(ccpr.ccpr()));
|
||||
ccpr.ctx()->contextPriv().addOnFlushCallbackObject(&atlasIDRecorder);
|
||||
|
||||
this->onRun(reporter, ccpr, atlasIDRecorder);
|
||||
|
||||
ccpr.ctx()->contextPriv().testingOnly_flushAndRemoveOnFlushCallbackObject(&atlasIDRecorder);
|
||||
}
|
||||
|
||||
virtual void onRun(skiatest::Reporter* reporter, CCPRPathDrawer& ccpr,
|
||||
const RecordLastMockAtlasIDs&) = 0;
|
||||
|
||||
protected:
|
||||
SkPath fPaths[350];
|
||||
};
|
||||
|
||||
// Ensures ccpr always reuses the same atlas texture in the animation use case.
|
||||
class CCPR_cache_animationAtlasReuse : public CCPRCacheTest {
|
||||
void onRun(skiatest::Reporter* reporter, CCPRPathDrawer& ccpr,
|
||||
const RecordLastMockAtlasIDs& atlasIDRecorder) override {
|
||||
SkMatrix m = SkMatrix::MakeTrans(kCanvasSize/2, kCanvasSize/2);
|
||||
m.preScale(80, 80);
|
||||
m.preTranslate(-.5,-.5);
|
||||
this->drawPathsAndFlush(ccpr, m);
|
||||
|
||||
REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastCopyAtlasID());
|
||||
REPORTER_ASSERT(reporter, 0 != atlasIDRecorder.lastRenderedAtlasID());
|
||||
const int atlasID = atlasIDRecorder.lastRenderedAtlasID();
|
||||
|
||||
// Ensures we always reuse the same atlas texture in the animation use case.
|
||||
for (int i = 0; i < 12; ++i) {
|
||||
// 59 is prime, so we will hit every integer modulo 360 before repeating.
|
||||
m.preRotate(59, .5, .5);
|
||||
|
||||
// Go twice. Paths have to get drawn twice with the same matrix before we cache their
|
||||
// atlas. This makes sure that on the subsequent draw, after an atlas has been cached
|
||||
// and is then invalidated since the matrix will change, that the same underlying
|
||||
// texture object is still reused for the next atlas.
|
||||
for (int j = 0; j < 2; ++j) {
|
||||
this->drawPathsAndFlush(ccpr, m);
|
||||
// Nothing should be copied to an 8-bit atlas after just two draws.
|
||||
REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastCopyAtlasID());
|
||||
REPORTER_ASSERT(reporter, atlasIDRecorder.lastRenderedAtlasID() == atlasID);
|
||||
}
|
||||
}
|
||||
|
||||
// Do the last draw again. (On draw 3 they should get copied to an 8-bit atlas.)
|
||||
this->drawPathsAndFlush(ccpr, m);
|
||||
REPORTER_ASSERT(reporter, 0 != atlasIDRecorder.lastCopyAtlasID());
|
||||
REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastRenderedAtlasID());
|
||||
|
||||
// Now double-check that everything continues to hit the cache as expected when the matrix
|
||||
// doesn't change.
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
this->drawPathsAndFlush(ccpr, m);
|
||||
REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastCopyAtlasID());
|
||||
REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastRenderedAtlasID());
|
||||
}
|
||||
}
|
||||
};
|
||||
DEF_CCPR_TEST(CCPR_cache_animationAtlasReuse)
|
||||
|
||||
class CCPR_cache_recycleEntries : public CCPRCacheTest {
|
||||
void onRun(skiatest::Reporter* reporter, CCPRPathDrawer& ccpr,
|
||||
const RecordLastMockAtlasIDs& atlasIDRecorder) override {
|
||||
SkMatrix m = SkMatrix::MakeTrans(kCanvasSize/2, kCanvasSize/2);
|
||||
m.preScale(80, 80);
|
||||
m.preTranslate(-.5,-.5);
|
||||
|
||||
auto cache = ccpr.ccpr()->testingOnly_getPathCache();
|
||||
REPORTER_ASSERT(reporter, cache);
|
||||
|
||||
const auto& lru = cache->testingOnly_getLRU();
|
||||
|
||||
SkTArray<const void*> expectedPtrs;
|
||||
|
||||
// Ensures we always reuse the same atlas texture in the animation use case.
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
// 59 is prime, so we will hit every integer modulo 360 before repeating.
|
||||
m.preRotate(59, .5, .5);
|
||||
|
||||
// Go twice. Paths have to get drawn twice with the same matrix before we cache their
|
||||
// atlas.
|
||||
for (int j = 0; j < 2; ++j) {
|
||||
this->drawPathsAndFlush(ccpr, m);
|
||||
// Nothing should be copied to an 8-bit atlas after just two draws.
|
||||
REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastCopyAtlasID());
|
||||
REPORTER_ASSERT(reporter, 0 != atlasIDRecorder.lastRenderedAtlasID());
|
||||
}
|
||||
|
||||
int idx = 0;
|
||||
for (const GrCCPathCacheEntry* entry : lru) {
|
||||
if (0 == i) {
|
||||
expectedPtrs.push_back(entry);
|
||||
} else {
|
||||
// The same pointer should have been recycled for the new matrix.
|
||||
REPORTER_ASSERT(reporter, entry == expectedPtrs[idx]);
|
||||
}
|
||||
++idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
DEF_CCPR_TEST(CCPR_cache_recycleEntries)
|
||||
|
||||
// Ensures mostly-visible paths get their full mask cached.
|
||||
class CCPR_cache_mostlyVisible : public CCPRCacheTest {
|
||||
void onRun(skiatest::Reporter* reporter, CCPRPathDrawer& ccpr,
|
||||
const RecordLastMockAtlasIDs& atlasIDRecorder) override {
|
||||
SkMatrix matrices[3] = {
|
||||
SkMatrix::MakeScale(kCanvasSize/2, kCanvasSize/2), // Fully visible.
|
||||
SkMatrix::MakeScale(kCanvasSize * 1.25, kCanvasSize * 1.25), // Mostly visible.
|
||||
SkMatrix::MakeScale(kCanvasSize * 1.5, kCanvasSize * 1.5), // Mostly NOT visible.
|
||||
};
|
||||
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
this->drawPathsAndFlush(ccpr, matrices, 3);
|
||||
if (2 == i) {
|
||||
// The mostly-visible paths should still get cached.
|
||||
REPORTER_ASSERT(reporter, 0 != atlasIDRecorder.lastCopyAtlasID());
|
||||
} else {
|
||||
REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastCopyAtlasID());
|
||||
}
|
||||
// Ensure mostly NOT-visible paths never get cached.
|
||||
REPORTER_ASSERT(reporter, 0 != atlasIDRecorder.lastRenderedAtlasID());
|
||||
}
|
||||
|
||||
// Clear the path cache.
|
||||
this->drawPathsAndFlush(ccpr, SkMatrix::I());
|
||||
|
||||
// Now only draw the fully/mostly visible ones.
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
this->drawPathsAndFlush(ccpr, matrices, 2);
|
||||
REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastCopyAtlasID());
|
||||
REPORTER_ASSERT(reporter, 0 != atlasIDRecorder.lastRenderedAtlasID());
|
||||
}
|
||||
|
||||
// On draw 3 they should get copied to an 8-bit atlas.
|
||||
this->drawPathsAndFlush(ccpr, matrices, 2);
|
||||
REPORTER_ASSERT(reporter, 0 != atlasIDRecorder.lastCopyAtlasID());
|
||||
REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastRenderedAtlasID());
|
||||
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
this->drawPathsAndFlush(ccpr, matrices, 2);
|
||||
REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastCopyAtlasID());
|
||||
REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastRenderedAtlasID());
|
||||
}
|
||||
|
||||
// Draw a different part of the path to ensure the full mask was cached.
|
||||
matrices[1].postTranslate(SkScalarFloorToInt(kCanvasSize * -.25f),
|
||||
SkScalarFloorToInt(kCanvasSize * -.25f));
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
this->drawPathsAndFlush(ccpr, matrices, 2);
|
||||
REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastCopyAtlasID());
|
||||
REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastRenderedAtlasID());
|
||||
}
|
||||
}
|
||||
};
|
||||
DEF_CCPR_TEST(CCPR_cache_mostlyVisible)
|
||||
|
||||
// Ensures GrContext::performDeferredCleanup works.
|
||||
class CCPR_cache_deferredCleanup : public CCPRCacheTest {
|
||||
void onRun(skiatest::Reporter* reporter, CCPRPathDrawer& ccpr,
|
||||
const RecordLastMockAtlasIDs& atlasIDRecorder) override {
|
||||
SkMatrix m = SkMatrix::MakeScale(20, 20);
|
||||
int lastRenderedAtlasID = 0;
|
||||
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
this->drawPathsAndFlush(ccpr, m);
|
||||
REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastCopyAtlasID());
|
||||
REPORTER_ASSERT(reporter, 0 != atlasIDRecorder.lastRenderedAtlasID());
|
||||
int renderedAtlasID = atlasIDRecorder.lastRenderedAtlasID();
|
||||
REPORTER_ASSERT(reporter, renderedAtlasID != lastRenderedAtlasID);
|
||||
lastRenderedAtlasID = renderedAtlasID;
|
||||
|
||||
this->drawPathsAndFlush(ccpr, m);
|
||||
REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastCopyAtlasID());
|
||||
REPORTER_ASSERT(reporter, lastRenderedAtlasID == atlasIDRecorder.lastRenderedAtlasID());
|
||||
|
||||
// On draw 3 they should get copied to an 8-bit atlas.
|
||||
this->drawPathsAndFlush(ccpr, m);
|
||||
REPORTER_ASSERT(reporter, 0 != atlasIDRecorder.lastCopyAtlasID());
|
||||
REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastRenderedAtlasID());
|
||||
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
this->drawPathsAndFlush(ccpr, m);
|
||||
REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastCopyAtlasID());
|
||||
REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastRenderedAtlasID());
|
||||
}
|
||||
|
||||
ccpr.ctx()->performDeferredCleanup(std::chrono::milliseconds(0));
|
||||
}
|
||||
}
|
||||
};
|
||||
DEF_CCPR_TEST(CCPR_cache_deferredCleanup)
|
||||
|
||||
// Verifies the cache/hash table internals.
|
||||
class CCPR_cache_hashTable : public CCPRCacheTest {
|
||||
void onRun(skiatest::Reporter* reporter, CCPRPathDrawer& ccpr,
|
||||
const RecordLastMockAtlasIDs& atlasIDRecorder) override {
|
||||
using CoverageType = GrCCAtlas::CoverageType;
|
||||
SkMatrix m = SkMatrix::MakeScale(20, 20);
|
||||
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
this->drawPathsAndFlush(ccpr, m);
|
||||
if (2 == i) {
|
||||
REPORTER_ASSERT(reporter, 0 != atlasIDRecorder.lastCopyAtlasID());
|
||||
} else {
|
||||
REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastCopyAtlasID());
|
||||
}
|
||||
if (i < 2) {
|
||||
REPORTER_ASSERT(reporter, 0 != atlasIDRecorder.lastRenderedAtlasID());
|
||||
} else {
|
||||
REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastRenderedAtlasID());
|
||||
}
|
||||
|
||||
auto cache = ccpr.ccpr()->testingOnly_getPathCache();
|
||||
REPORTER_ASSERT(reporter, cache);
|
||||
|
||||
const auto& hash = cache->testingOnly_getHashTable();
|
||||
const auto& lru = cache->testingOnly_getLRU();
|
||||
int count = 0;
|
||||
for (GrCCPathCacheEntry* entry : lru) {
|
||||
auto* node = hash.find(entry->cacheKey());
|
||||
REPORTER_ASSERT(reporter, node);
|
||||
REPORTER_ASSERT(reporter, node->entry() == entry);
|
||||
REPORTER_ASSERT(reporter, 0 == entry->testingOnly_peekOnFlushRefCnt());
|
||||
if (0 == i) {
|
||||
REPORTER_ASSERT(reporter, !entry->cachedAtlas());
|
||||
} else {
|
||||
const GrCCCachedAtlas* cachedAtlas = entry->cachedAtlas();
|
||||
REPORTER_ASSERT(reporter, cachedAtlas);
|
||||
if (1 == i) {
|
||||
REPORTER_ASSERT(reporter, CoverageType::kFP16_CoverageCount
|
||||
== cachedAtlas->coverageType());
|
||||
} else {
|
||||
REPORTER_ASSERT(reporter, CoverageType::kA8_LiteralCoverage
|
||||
== cachedAtlas->coverageType());
|
||||
}
|
||||
REPORTER_ASSERT(reporter, cachedAtlas->textureKey().isValid());
|
||||
// The actual proxy should not be held past the end of a flush.
|
||||
REPORTER_ASSERT(reporter, !cachedAtlas->getOnFlushProxy());
|
||||
REPORTER_ASSERT(reporter, 0 == cachedAtlas->testingOnly_peekOnFlushRefCnt());
|
||||
}
|
||||
++count;
|
||||
}
|
||||
REPORTER_ASSERT(reporter, hash.count() == count);
|
||||
}
|
||||
}
|
||||
};
|
||||
DEF_CCPR_TEST(CCPR_cache_hashTable)
|
||||
|
||||
// Ensures paths get cached even when using a sporadic flushing pattern and drawing out of order
|
||||
// (a la Chrome tiles).
|
||||
class CCPR_cache_multiFlush : public CCPRCacheTest {
|
||||
void onRun(skiatest::Reporter* reporter, CCPRPathDrawer& ccpr,
|
||||
const RecordLastMockAtlasIDs& atlasIDRecorder) override {
|
||||
static constexpr int kNumPaths = SK_ARRAY_COUNT(fPaths);
|
||||
static constexpr int kBigPrimes[] = {
|
||||
9323, 11059, 22993, 38749, 45127, 53147, 64853, 77969, 83269, 99989};
|
||||
|
||||
SkRandom rand;
|
||||
SkMatrix m = SkMatrix::I();
|
||||
|
||||
for (size_t i = 0; i < SK_ARRAY_COUNT(kBigPrimes); ++i) {
|
||||
int prime = kBigPrimes[i];
|
||||
int endPathIdx = (int)rand.nextULessThan(kNumPaths);
|
||||
int pathIdx = endPathIdx;
|
||||
int nextFlush = rand.nextRangeU(1, 47);
|
||||
for (int j = 0; j < kNumPaths; ++j) {
|
||||
pathIdx = (pathIdx + prime) % kNumPaths;
|
||||
int repeat = rand.nextRangeU(1, 3);
|
||||
for (int k = 0; k < repeat; ++k) {
|
||||
ccpr.drawPath(fPaths[pathIdx], m);
|
||||
}
|
||||
if (nextFlush == j) {
|
||||
ccpr.flush();
|
||||
// The paths are small enough that we should never copy to an A8 atlas.
|
||||
REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastCopyAtlasID());
|
||||
if (i < 2) {
|
||||
REPORTER_ASSERT(reporter, 0 != atlasIDRecorder.lastRenderedAtlasID());
|
||||
} else {
|
||||
REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastRenderedAtlasID());
|
||||
}
|
||||
nextFlush = SkTMin(j + (int)rand.nextRangeU(1, 29), kNumPaths - 1);
|
||||
}
|
||||
}
|
||||
SkASSERT(endPathIdx == pathIdx % kNumPaths);
|
||||
}
|
||||
}
|
||||
};
|
||||
DEF_CCPR_TEST(CCPR_cache_multiFlush)
|
||||
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 CCPR_cache_partialInvalidate : public CCPRCacheTest {
|
||||
class GrCCPRTest_cache : public CCPRTest {
|
||||
void customizeOptions(GrMockOptions*, GrContextOptions* ctxOptions) override {
|
||||
ctxOptions->fAllowPathMaskCaching = true;
|
||||
}
|
||||
|
||||
static constexpr int kPathSize = 4;
|
||||
void onRun(skiatest::Reporter* reporter, CCPRPathDrawer& ccpr) override {
|
||||
static constexpr int kPathSize = 20;
|
||||
SkRandom rand;
|
||||
|
||||
SkPath paths[300];
|
||||
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)]);
|
||||
}
|
||||
|
||||
void onRun(skiatest::Reporter* reporter, CCPRPathDrawer& ccpr,
|
||||
const RecordLastMockAtlasIDs& atlasIDRecorder) override {
|
||||
SkMatrix matrices[2] = {
|
||||
SkMatrix::MakeTrans(5, 5),
|
||||
SkMatrix::MakeTrans(kCanvasSize - kPathSize - 5, kCanvasSize - kPathSize - 5)
|
||||
};
|
||||
matrices[0].preScale(kPathSize, kPathSize);
|
||||
matrices[1].preScale(kPathSize, kPathSize);
|
||||
|
||||
int firstAtlasID = 0;
|
||||
int firstAtlasID = -1;
|
||||
|
||||
for (int iterIdx = 0; iterIdx < 4*3*2; ++iterIdx) {
|
||||
this->drawPathsAndFlush(ccpr, matrices, 2);
|
||||
for (int iterIdx = 0; iterIdx < 10; ++iterIdx) {
|
||||
static constexpr int kNumHitsBeforeStash = 2;
|
||||
static const GrUniqueKey gInvalidUniqueKey;
|
||||
|
||||
// Draw all the paths then flush. Repeat until a new stash occurs.
|
||||
const GrUniqueKey* stashedAtlasKey = &gInvalidUniqueKey;
|
||||
for (int j = 0; j < kNumHitsBeforeStash; ++j) {
|
||||
// Nothing should be stashed until its hit count reaches kNumHitsBeforeStash.
|
||||
REPORTER_ASSERT(reporter, !stashedAtlasKey->isValid());
|
||||
|
||||
for (size_t i = 0; i < SK_ARRAY_COUNT(paths); ++i) {
|
||||
ccpr.drawPath(paths[i], matrices[i % 2]);
|
||||
}
|
||||
ccpr.flush();
|
||||
|
||||
stashedAtlasKey = &ccpr.ccpr()->testingOnly_getStashedAtlasKey();
|
||||
}
|
||||
|
||||
// Figure out the mock backend ID of the atlas texture stashed away by CCPR.
|
||||
GrMockTextureInfo stashedAtlasInfo;
|
||||
stashedAtlasInfo.fID = -1;
|
||||
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 == iterIdx) {
|
||||
// First iteration: just note the ID of the stashed atlas and continue.
|
||||
firstAtlasID = atlasIDRecorder.lastRenderedAtlasID();
|
||||
REPORTER_ASSERT(reporter, 0 != firstAtlasID);
|
||||
REPORTER_ASSERT(reporter, stashedAtlasKey->isValid());
|
||||
firstAtlasID = stashedAtlasInfo.fID;
|
||||
continue;
|
||||
}
|
||||
|
||||
int testIdx = (iterIdx/2) % 3;
|
||||
int repetitionIdx = iterIdx % 2;
|
||||
switch (testIdx) {
|
||||
case 0:
|
||||
if (0 == repetitionIdx) {
|
||||
// This is the big test. New paths were drawn twice last round. On hit 2
|
||||
// (last time), 'firstAtlasID' was cached as a 16-bit atlas. Now, on hit 3,
|
||||
// these paths should be copied out of 'firstAtlasID', and into an A8 atlas.
|
||||
// THEN: we should recycle 'firstAtlasID' and reuse that same texture to
|
||||
// render the new masks.
|
||||
REPORTER_ASSERT(reporter, 0 != atlasIDRecorder.lastCopyAtlasID());
|
||||
REPORTER_ASSERT(reporter,
|
||||
atlasIDRecorder.lastRenderedAtlasID() == firstAtlasID);
|
||||
} else {
|
||||
REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastCopyAtlasID());
|
||||
// This is hit 2 for the new masks. Next time they will be copied to an A8
|
||||
// atlas.
|
||||
REPORTER_ASSERT(reporter,
|
||||
atlasIDRecorder.lastRenderedAtlasID() == firstAtlasID);
|
||||
}
|
||||
|
||||
if (1 == repetitionIdx) {
|
||||
// Integer translates: all path masks stay valid.
|
||||
matrices[0].preTranslate(-1, -1);
|
||||
matrices[1].preTranslate(1, 1);
|
||||
}
|
||||
break;
|
||||
|
||||
switch (iterIdx % 3) {
|
||||
case 1:
|
||||
if (0 == repetitionIdx) {
|
||||
// New paths were drawn twice last round. The third hit (now) they should be
|
||||
// copied to an A8 atlas.
|
||||
REPORTER_ASSERT(reporter, 0 != atlasIDRecorder.lastCopyAtlasID());
|
||||
} else {
|
||||
REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastCopyAtlasID());
|
||||
}
|
||||
|
||||
// This draw should have gotten 100% cache hits; we only did integer translates
|
||||
// last time (or none if it was the first flush). Therefore, everything should
|
||||
// have been cached.
|
||||
REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastRenderedAtlasID());
|
||||
// last time (or none if it was the first flush). Therefore, no atlas should
|
||||
// have been stashed away.
|
||||
REPORTER_ASSERT(reporter, !stashedAtlasKey->isValid());
|
||||
|
||||
if (1 == repetitionIdx) {
|
||||
// Invalidate even path masks.
|
||||
matrices[0].preTranslate(1.6f, 1.4f);
|
||||
}
|
||||
// Invalidate even path masks.
|
||||
matrices[0].preTranslate(1.6f, 1.4f);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
// No new masks to copy from last time; it had 100% cache hits.
|
||||
REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastCopyAtlasID());
|
||||
// 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());
|
||||
|
||||
// Even path masks were invalidated last iteration by a subpixel translate.
|
||||
// They should have been re-rendered this time in the original 'firstAtlasID'
|
||||
// texture.
|
||||
REPORTER_ASSERT(reporter,
|
||||
atlasIDRecorder.lastRenderedAtlasID() == firstAtlasID);
|
||||
// 'firstAtlasID' should be kept as a scratch texture in the resource cache.
|
||||
REPORTER_ASSERT(reporter, stashedAtlasInfo.fID == firstAtlasID);
|
||||
|
||||
if (1 == repetitionIdx) {
|
||||
// Invalidate odd path masks.
|
||||
matrices[1].preTranslate(-1.4f, -1.6f);
|
||||
}
|
||||
// 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(CCPR_cache_partialInvalidate)
|
||||
DEF_CCPR_TEST(GrCCPRTest_cache)
|
||||
|
||||
class CCPR_unrefPerOpListPathsBeforeOps : public CCPRTest {
|
||||
class GrCCPRTest_unrefPerOpListPathsBeforeOps : public CCPRTest {
|
||||
void onRun(skiatest::Reporter* reporter, CCPRPathDrawer& ccpr) override {
|
||||
REPORTER_ASSERT(reporter, SkPathPriv::TestingOnly_unique(fPath));
|
||||
for (int i = 0; i < 10000; ++i) {
|
||||
@ -796,7 +413,7 @@ class CCPR_unrefPerOpListPathsBeforeOps : public CCPRTest {
|
||||
REPORTER_ASSERT(reporter, SkPathPriv::TestingOnly_unique(fPath));
|
||||
}
|
||||
};
|
||||
DEF_CCPR_TEST(CCPR_unrefPerOpListPathsBeforeOps)
|
||||
DEF_CCPR_TEST(GrCCPRTest_unrefPerOpListPathsBeforeOps)
|
||||
|
||||
class CCPRRenderingTest {
|
||||
public:
|
||||
@ -804,7 +421,7 @@ public:
|
||||
if (!ctx->contextPriv().drawingManager()->getCoverageCountingPathRenderer()) {
|
||||
return; // CCPR is not enabled on this GPU.
|
||||
}
|
||||
CCPRPathDrawer ccpr(sk_ref_sp(ctx), reporter, doStroke);
|
||||
CCPRPathDrawer ccpr(ctx, reporter, doStroke);
|
||||
if (!ccpr.valid()) {
|
||||
return;
|
||||
}
|
||||
@ -824,7 +441,7 @@ protected:
|
||||
test.run(reporter, ctxInfo.grContext(), true); \
|
||||
}
|
||||
|
||||
class CCPR_busyPath : public CCPRRenderingTest {
|
||||
class GrCCPRTest_busyPath : public CCPRRenderingTest {
|
||||
void onRun(skiatest::Reporter* reporter, const CCPRPathDrawer& ccpr) const override {
|
||||
static constexpr int kNumBusyVerbs = 1 << 17;
|
||||
ccpr.clear();
|
||||
@ -842,4 +459,4 @@ class CCPR_busyPath : public CCPRRenderingTest {
|
||||
// your platform's GrGLCaps.
|
||||
}
|
||||
};
|
||||
DEF_CCPR_RENDERING_TEST(CCPR_busyPath)
|
||||
DEF_CCPR_RENDERING_TEST(GrCCPRTest_busyPath)
|
||||
|
@ -27,7 +27,6 @@
|
||||
#include "SkString.h"
|
||||
#include "SkTo.h"
|
||||
#include "ccpr/GrCoverageCountingPathRenderer.h"
|
||||
#include "ccpr/GrCCPathCache.h"
|
||||
#include "ops/GrMeshDrawOp.h"
|
||||
#include "text/GrGlyphCache.h"
|
||||
#include "text/GrTextBlobCache.h"
|
||||
@ -279,55 +278,10 @@ void GrCoverageCountingPathRenderer::testingOnly_drawPathDirectly(const DrawPath
|
||||
this->onDrawPath(args);
|
||||
}
|
||||
|
||||
const GrCCPerFlushResources*
|
||||
GrCoverageCountingPathRenderer::testingOnly_getCurrentFlushResources() {
|
||||
SkASSERT(fFlushing);
|
||||
if (fFlushingPaths.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
// All pending paths should share the same resources.
|
||||
const GrCCPerFlushResources* resources = fFlushingPaths.front()->fFlushResources.get();
|
||||
#ifdef SK_DEBUG
|
||||
for (const auto& flushingPaths : fFlushingPaths) {
|
||||
SkASSERT(flushingPaths->fFlushResources.get() == resources);
|
||||
}
|
||||
#endif
|
||||
return resources;
|
||||
const GrUniqueKey& GrCoverageCountingPathRenderer::testingOnly_getStashedAtlasKey() const {
|
||||
return fStashedAtlasKey;
|
||||
}
|
||||
|
||||
const GrCCPathCache* GrCoverageCountingPathRenderer::testingOnly_getPathCache() const {
|
||||
return fPathCache.get();
|
||||
}
|
||||
|
||||
const GrTexture* GrCCPerFlushResources::testingOnly_frontCopyAtlasTexture() const {
|
||||
if (fCopyAtlasStack.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
const GrTextureProxy* proxy = fCopyAtlasStack.front().textureProxy();
|
||||
return (proxy) ? proxy->peekTexture() : nullptr;
|
||||
}
|
||||
|
||||
const GrTexture* GrCCPerFlushResources::testingOnly_frontRenderedAtlasTexture() const {
|
||||
if (fRenderedAtlasStack.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
const GrTextureProxy* proxy = fRenderedAtlasStack.front().textureProxy();
|
||||
return (proxy) ? proxy->peekTexture() : nullptr;
|
||||
}
|
||||
|
||||
const SkTHashTable<GrCCPathCache::HashNode, const GrCCPathCache::Key&>&
|
||||
GrCCPathCache::testingOnly_getHashTable() const {
|
||||
return fHashTable;
|
||||
}
|
||||
|
||||
const SkTInternalLList<GrCCPathCacheEntry>& GrCCPathCache::testingOnly_getLRU() const {
|
||||
return fLRU;
|
||||
}
|
||||
|
||||
int GrCCPathCacheEntry::testingOnly_peekOnFlushRefCnt() const { return fOnFlushRefCnt; }
|
||||
|
||||
int GrCCCachedAtlas::testingOnly_peekOnFlushRefCnt() const { return fOnFlushRefCnt; }
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#define DRAW_OP_TEST_EXTERN(Op) \
|
||||
|
@ -175,12 +175,11 @@ void get_text_path(const SkFont& font, const void* text, size_t length, SkTextEn
|
||||
}
|
||||
|
||||
SkPath make_star(const SkRect& bounds, int numPts, int step) {
|
||||
SkASSERT(numPts != step);
|
||||
SkPath path;
|
||||
path.setFillType(SkPath::kEvenOdd_FillType);
|
||||
path.moveTo(0,-1);
|
||||
for (int i = 1; i < numPts; ++i) {
|
||||
int idx = i*step % numPts;
|
||||
int idx = i*step;
|
||||
SkScalar theta = idx * 2*SK_ScalarPI/numPts + SK_ScalarPI/2;
|
||||
SkScalar x = SkScalarCos(theta);
|
||||
SkScalar y = -SkScalarSin(theta);
|
||||
|
Loading…
Reference in New Issue
Block a user