diff --git a/include/core/SkPicture.h b/include/core/SkPicture.h index f09f8dd3de..42693eecc1 100644 --- a/include/core/SkPicture.h +++ b/include/core/SkPicture.h @@ -38,6 +38,7 @@ class SkWStream; */ class SK_API SkPicture : public SkRefCnt { public: + ~SkPicture() override; /** Recreates SkPicture that was serialized into a stream. Returns constructed SkPicture if successful; otherwise, returns nullptr. Fails if data does not permit @@ -275,6 +276,7 @@ private: class SkPictureData* backport() const; uint32_t fUniqueID; + mutable std::atomic fAddedToCache{false}; }; #endif diff --git a/src/core/SkPicture.cpp b/src/core/SkPicture.cpp index d3c3af4063..5542fcabc6 100644 --- a/src/core/SkPicture.cpp +++ b/src/core/SkPicture.cpp @@ -18,6 +18,7 @@ #include "src/core/SkPicturePlayback.h" #include "src/core/SkPicturePriv.h" #include "src/core/SkPictureRecord.h" +#include "src/core/SkResourceCache.h" #include // When we read/write the SkPictInfo via a stream, we have a sentinel byte right after the info. @@ -41,6 +42,12 @@ SkPicture::SkPicture() { } while (fUniqueID == 0); } +SkPicture::~SkPicture() { + if (fAddedToCache.load()) { + SkResourceCache::PostPurgeSharedID(SkPicturePriv::MakeSharedID(fUniqueID)); + } +} + static const char kMagic[] = { 's', 'k', 'i', 'a', 'p', 'i', 'c', 't' }; SkPictInfo SkPicture::createHeader() const { diff --git a/src/core/SkPicturePriv.h b/src/core/SkPicturePriv.h index 82ca186dc2..8230efc0f0 100644 --- a/src/core/SkPicturePriv.h +++ b/src/core/SkPicturePriv.h @@ -35,6 +35,15 @@ public: return picture->asSkBigPicture(); } + static uint64_t MakeSharedID(uint32_t pictureID) { + uint64_t sharedID = SkSetFourByteTag('p', 'i', 'c', 't'); + return (sharedID << 32) | pictureID; + } + + static void AddedToCache(const SkPicture* pic) { + pic->fAddedToCache.store(true); + } + // V35: Store SkRect (rather then width & height) in header // V36: Remove (obsolete) alphatype from SkColorTable // V37: Added shadow only option to SkDropShadowImageFilter (last version to record CLEAR) diff --git a/src/shaders/SkPictureShader.cpp b/src/shaders/SkPictureShader.cpp index 99b7516d2a..dc9b00f95c 100644 --- a/src/shaders/SkPictureShader.cpp +++ b/src/shaders/SkPictureShader.cpp @@ -31,8 +31,14 @@ #include "src/gpu/GrFragmentProcessor.h" #include "src/gpu/GrRecordingContextPriv.h" #include "src/gpu/SkGr.h" +#include "src/gpu/effects/GrTextureEffect.h" +#include "src/image/SkImage_Base.h" #endif +#ifdef SK_SUPPORT_LEGACY_PICTURESHADER_MATH +#include "src/shaders/SkPictureShader.cpp.legacy" +#else + sk_sp SkPicture::makeShader(SkTileMode tmx, SkTileMode tmy, SkFilterMode filter, const SkMatrix* localMatrix, const SkRect* tile) const { if (localMatrix && !localMatrix->invert(nullptr)) { @@ -57,80 +63,66 @@ sk_sp SkPicture::makeShader(SkTileMode tmx, SkTileMode tmy, } namespace { -static unsigned gBitmapShaderKeyNamespaceLabel; +static unsigned gImageFromPictureKeyNamespaceLabel; -struct BitmapShaderKey : public SkResourceCache::Key { +struct ImageFromPictureKey : public SkResourceCache::Key { public: - BitmapShaderKey(SkColorSpace* colorSpace, - SkImage::BitDepth bitDepth, - uint32_t shaderID, - const SkSize& scale) + ImageFromPictureKey(SkColorSpace* colorSpace, SkColorType colorType, + uint32_t pictureID, const SkRect& subset, + SkSize scale) : fColorSpaceXYZHash(colorSpace->toXYZD50Hash()) , fColorSpaceTransferFnHash(colorSpace->transferFnHash()) - , fBitDepth(bitDepth) - , fScale(scale) { - + , fColorType(static_cast(colorType)) + , fSubset(subset) + , fScale(scale) + { static const size_t keySize = sizeof(fColorSpaceXYZHash) + sizeof(fColorSpaceTransferFnHash) + - sizeof(fBitDepth) + + sizeof(fColorType) + + sizeof(fSubset) + sizeof(fScale); // This better be packed. SkASSERT(sizeof(uint32_t) * (&fEndOfStruct - &fColorSpaceXYZHash) == keySize); - this->init(&gBitmapShaderKeyNamespaceLabel, MakeSharedID(shaderID), keySize); - } - - static uint64_t MakeSharedID(uint32_t shaderID) { - uint64_t sharedID = SkSetFourByteTag('p', 's', 'd', 'r'); - return (sharedID << 32) | shaderID; + this->init(&gImageFromPictureKeyNamespaceLabel, + SkPicturePriv::MakeSharedID(pictureID), + keySize); } private: - uint32_t fColorSpaceXYZHash; - uint32_t fColorSpaceTransferFnHash; - SkImage::BitDepth fBitDepth; - SkSize fScale; + uint32_t fColorSpaceXYZHash; + uint32_t fColorSpaceTransferFnHash; + uint32_t fColorType; + SkRect fSubset; + SkSize fScale; SkDEBUGCODE(uint32_t fEndOfStruct;) }; -struct BitmapShaderRec : public SkResourceCache::Rec { - BitmapShaderRec(const BitmapShaderKey& key, SkShader* tileShader) +struct ImageFromPictureRec : public SkResourceCache::Rec { + ImageFromPictureRec(const ImageFromPictureKey& key, sk_sp image) : fKey(key) - , fShader(SkRef(tileShader)) {} + , fImage(std::move(image)) {} - BitmapShaderKey fKey; - sk_sp fShader; + ImageFromPictureKey fKey; + sk_sp fImage; const Key& getKey() const override { return fKey; } size_t bytesUsed() const override { // Just the record overhead -- the actual pixels are accounted by SkImage_Lazy. - return sizeof(fKey) + sizeof(SkImageShader); + return sizeof(fKey) + (size_t)fImage->width() * fImage->height() * 4; } const char* getCategory() const override { return "bitmap-shader"; } SkDiscardableMemory* diagnostic_only_getDiscardable() const override { return nullptr; } static bool Visitor(const SkResourceCache::Rec& baseRec, void* contextShader) { - const BitmapShaderRec& rec = static_cast(baseRec); - sk_sp* result = reinterpret_cast*>(contextShader); + const ImageFromPictureRec& rec = static_cast(baseRec); + sk_sp* result = reinterpret_cast*>(contextShader); - *result = rec.fShader; - - // The bitmap shader is backed by an image generator, thus it can always re-generate its - // pixels if discarded. + *result = rec.fImage; return true; } }; -uint32_t next_id() { - static std::atomic nextID{1}; - - uint32_t id; - do { - id = nextID.fetch_add(1, std::memory_order_relaxed); - } while (id == SK_InvalidGenID); - return id; -} - } // namespace SkPictureShader::SkPictureShader(sk_sp picture, SkTileMode tmx, SkTileMode tmy, @@ -140,15 +132,7 @@ SkPictureShader::SkPictureShader(sk_sp picture, SkTileMode tmx, SkTil , fTile(tile ? *tile : fPicture->cullRect()) , fTmx(tmx) , fTmy(tmy) - , fFilter(filter) - , fUniqueID(next_id()) - , fAddedToCache(false) {} - -SkPictureShader::~SkPictureShader() { - if (fAddedToCache.load()) { - SkResourceCache::PostPurgeSharedID(BitmapShaderKey::MakeSharedID(fUniqueID)); - } -} + , fFilter(filter) {} sk_sp SkPictureShader::Make(sk_sp picture, SkTileMode tmx, SkTileMode tmy, FilterEnum filter, const SkMatrix* lm, const SkRect* tile) { @@ -208,101 +192,139 @@ static SkFilterMode quality_to_filter(SkFilterQuality quality) { : SkFilterMode::kLinear; } +static sk_sp ref_or_srgb(SkColorSpace* cs) { + return cs ? sk_ref_sp(cs) : SkColorSpace::MakeSRGB(); +} + +struct CachedImageInfo { + bool success; + SkSize tileScale; + SkMatrix matrixForDraw; + SkImageInfo imageInfo; + + static CachedImageInfo Make(const SkRect& bounds, + const SkMatrix& viewMatrix, + SkTCopyOnFirstWrite* localMatrix, // in/out + SkColorType dstColorType, + SkColorSpace* dstColorSpace, + const int maxTextureSize) { + const SkMatrix m = SkMatrix::Concat(viewMatrix, **localMatrix); + + const SkSize scaledSize = [&]() { + SkSize size; + // Use a rotation-invariant scale + if (!m.decomposeScale(&size, nullptr)) { + size = {1, 1}; + } + size.fWidth *= bounds.width(); + size.fHeight *= bounds.height(); + + // Clamp the tile size to about 4M pixels + static const SkScalar kMaxTileArea = 2048 * 2048; + SkScalar tileArea = size.width() * size.height(); + if (tileArea > kMaxTileArea) { + SkScalar clampScale = SkScalarSqrt(kMaxTileArea / tileArea); + size.set(size.width() * clampScale, size.height() * clampScale); + } + + // Scale down the tile size if larger than maxTextureSize for GPU Path + // or it should fail on create texture + if (maxTextureSize) { + if (size.width() > maxTextureSize || size.height() > maxTextureSize) { + SkScalar downScale = maxTextureSize / std::max(size.width(), + size.height()); + size.set(SkScalarFloorToScalar(size.width() * downScale), + SkScalarFloorToScalar(size.height() * downScale)); + } + } + return size; + }(); + + const SkISize tileSize = scaledSize.toCeil(); + if (tileSize.isEmpty()) { + return {false, {}, {}, {}}; + } + + const SkSize tileScale = { + tileSize.width() / bounds.width(), tileSize.height() / bounds.height() + }; + auto imgCS = ref_or_srgb(dstColorSpace); + const SkColorType imgCT = SkColorTypeMaxBitsPerChannel(dstColorType) <= 8 + ? kRGBA_8888_SkColorType + : kRGBA_F16Norm_SkColorType; + + if (tileScale.width() != 1 || tileScale.height() != 1) { + localMatrix->writable()->preScale(1 / tileScale.width(), 1 / tileScale.height()); + } + + return { + true, + tileScale, + SkMatrix::RectToRect(bounds, SkRect::MakeIWH(tileSize.width(), tileSize.height())), + SkImageInfo::Make(tileSize.width(), tileSize.height(), + imgCT, kPremul_SkAlphaType, imgCS), + }; + } + + sk_sp makeImage(sk_sp surf, const SkPicture* pict) const { + if (!surf) { + return nullptr; + } + auto canvas = surf->getCanvas(); + canvas->concat(matrixForDraw); + canvas->drawPicture(pict); + return surf->makeImageSnapshot(); + } +}; + // Returns a cached image shader, which wraps a single picture tile at the given // CTM/local matrix. Also adjusts the local matrix for tile scaling. -sk_sp SkPictureShader::refBitmapShader(const SkMatrix& viewMatrix, - SkTCopyOnFirstWrite* localMatrix, - SkColorType dstColorType, - SkColorSpace* dstColorSpace, - SkFilterMode paintFilter, - const int maxTextureSize) const { - SkASSERT(fPicture && !fPicture->cullRect().isEmpty()); - - const SkMatrix m = SkMatrix::Concat(viewMatrix, **localMatrix); - - // Use a rotation-invariant scale - SkSize scaledSize; - if (!m.decomposeScale(&scaledSize, nullptr)) { - scaledSize = {1, 1}; - } - scaledSize.fWidth *= fTile.width(); - scaledSize.fHeight *= fTile.height(); - - // Clamp the tile size to about 4M pixels - static const SkScalar kMaxTileArea = 2048 * 2048; - SkScalar tileArea = scaledSize.width() * scaledSize.height(); - if (tileArea > kMaxTileArea) { - SkScalar clampScale = SkScalarSqrt(kMaxTileArea / tileArea); - scaledSize.set(scaledSize.width() * clampScale, - scaledSize.height() * clampScale); - } -#if SK_SUPPORT_GPU - // Scale down the tile size if larger than maxTextureSize for GPU Path or it should fail on create texture - if (maxTextureSize) { - if (scaledSize.width() > maxTextureSize || scaledSize.height() > maxTextureSize) { - SkScalar downScale = maxTextureSize / std::max(scaledSize.width(), scaledSize.height()); - scaledSize.set(SkScalarFloorToScalar(scaledSize.width() * downScale), - SkScalarFloorToScalar(scaledSize.height() * downScale)); - } - } -#endif - - const SkISize tileSize = scaledSize.toCeil(); - if (tileSize.isEmpty()) { +sk_sp SkPictureShader::rasterShader(const SkMatrix& viewMatrix, + SkTCopyOnFirstWrite* localMatrix, + SkColorType dstColorType, + SkColorSpace* dstColorSpace, + SkFilterMode paintFilter) const { + const int maxTextureSize_NotUsedForCPU = 0; + CachedImageInfo info = CachedImageInfo::Make(fTile, viewMatrix, localMatrix, + dstColorType, dstColorSpace, + maxTextureSize_NotUsedForCPU); + if (!info.success) { return nullptr; } - // The actual scale, compensating for rounding & clamping. - const SkSize tileScale = SkSize::Make(SkIntToScalar(tileSize.width()) / fTile.width(), - SkIntToScalar(tileSize.height()) / fTile.height()); + ImageFromPictureKey key(info.imageInfo.colorSpace(), info.imageInfo.colorType(), + fPicture->uniqueID(), fTile, info.tileScale); - - sk_sp imgCS = dstColorSpace ? sk_ref_sp(dstColorSpace): SkColorSpace::MakeSRGB(); - SkImage::BitDepth bitDepth = SkColorTypeMaxBitsPerChannel(dstColorType) >= 16 - ? SkImage::BitDepth::kF16 - : SkImage::BitDepth::kU8; - - BitmapShaderKey key(imgCS.get(), bitDepth, fUniqueID, tileScale); - - sk_sp tileShader; - if (!SkResourceCache::Find(key, BitmapShaderRec::Visitor, &tileShader)) { - SkMatrix tileMatrix = SkMatrix::RectToRect(fTile, SkRect::MakeIWH(tileSize.width(), - tileSize.height())); - - sk_sp tileImage = SkImage::MakeFromPicture(fPicture, tileSize, &tileMatrix, - nullptr, bitDepth, std::move(imgCS)); - if (!tileImage) { + sk_sp image; + if (!SkResourceCache::Find(key, ImageFromPictureRec::Visitor, &image)) { + image = info.makeImage(SkSurface::MakeRaster(info.imageInfo), fPicture.get()); + if (!image) { return nullptr; } - SkFilterMode filter; - if (fFilter == kInheritFromPaint) { - filter = paintFilter; - } else { - filter = (SkFilterMode)fFilter; - } - tileShader = tileImage->makeShader(fTmx, fTmy, SkSamplingOptions(filter), nullptr); - - SkResourceCache::Add(new BitmapShaderRec(key, tileShader.get())); - fAddedToCache.store(true); + SkResourceCache::Add(new ImageFromPictureRec(key, image)); + SkPicturePriv::AddedToCache(fPicture.get()); } + return this->makeShader(image.get(), paintFilter); +} - if (tileScale.width() != 1 || tileScale.height() != 1) { - localMatrix->writable()->preScale(1 / tileScale.width(), 1 / tileScale.height()); +sk_sp SkPictureShader::makeShader(const SkImage* image, SkFilterMode paintFilter) const { + SkFilterMode filter; + if (fFilter == kInheritFromPaint) { + filter = paintFilter; + } else { + filter = (SkFilterMode)fFilter; } - - return tileShader; + return image->makeShader(fTmx, fTmy, SkSamplingOptions(filter), nullptr); } bool SkPictureShader::onAppendStages(const SkStageRec& rec) const { auto lm = this->totalLocalMatrix(rec.fLocalM); - // Keep bitmapShader alive by using alloc instead of stack memory auto& bitmapShader = *rec.fAlloc->make>(); - bitmapShader = this->refBitmapShader(rec.fMatrixProvider.localToDevice(), &lm, - rec.fDstColorType, rec.fDstCS, - quality_to_filter(rec.fPaint.getFilterQuality())); - + bitmapShader = this->rasterShader(rec.fMatrixProvider.localToDevice(), &lm, + rec.fDstColorType, rec.fDstCS, + quality_to_filter(rec.fPaint.getFilterQuality())); if (!bitmapShader) { return false; } @@ -322,9 +344,9 @@ skvm::Color SkPictureShader::onProgram(skvm::Builder* p, // Keep bitmapShader alive by using alloc instead of stack memory auto& bitmapShader = *alloc->make>(); - bitmapShader = this->refBitmapShader(matrices.localToDevice(), &lm, - dst.colorType(), dst.colorSpace(), - quality_to_filter(quality)); + bitmapShader = this->rasterShader(matrices.localToDevice(), &lm, + dst.colorType(), dst.colorSpace(), + quality_to_filter(quality)); if (!bitmapShader) { return {}; } @@ -341,9 +363,9 @@ skvm::Color SkPictureShader::onProgram(skvm::Builder* p, SkShaderBase::Context* SkPictureShader::onMakeContext(const ContextRec& rec, SkArenaAlloc* alloc) const { auto lm = this->totalLocalMatrix(rec.fLocalMatrix); - sk_sp bitmapShader = this->refBitmapShader(*rec.fMatrix, &lm, rec.fDstColorType, - rec.fDstColorSpace, - sampling_to_filter(rec.fPaintSampling)); + sk_sp bitmapShader = this->rasterShader(*rec.fMatrix, &lm, rec.fDstColorType, + rec.fDstColorSpace, + sampling_to_filter(rec.fPaintSampling)); if (!bitmapShader) { return nullptr; } @@ -384,31 +406,73 @@ void SkPictureShader::PictureShaderContext::shadeSpan(int x, int y, SkPMColor ds #if SK_SUPPORT_GPU +#include "src/gpu/GrProxyProvider.h" + std::unique_ptr SkPictureShader::asFragmentProcessor( const GrFPArgs& args) const { - int maxTextureSize = 0; - if (args.fContext) { - maxTextureSize = args.fContext->priv().caps()->maxTextureSize(); - } + auto ctx = args.fContext; auto lm = this->totalLocalMatrix(args.fPreLocalMatrix); SkColorType dstColorType = GrColorTypeToSkColorType(args.fDstColorInfo->colorType()); if (dstColorType == kUnknown_SkColorType) { dstColorType = kRGBA_8888_SkColorType; } - sk_sp bitmapShader( - this->refBitmapShader(args.fMatrixProvider.localToDevice(), &lm, dstColorType, - args.fDstColorInfo->colorSpace(), - sampling_to_filter(args.fSampling), - maxTextureSize)); - if (!bitmapShader) { + + auto dstCS = ref_or_srgb(args.fDstColorInfo->colorSpace()); + auto info = CachedImageInfo::Make(fTile, args.fMatrixProvider.localToDevice(), &lm, + dstColorType, dstCS.get(), + ctx->priv().caps()->maxTextureSize()); + SkMatrix inv; + if (!info.success || !(*lm).invert(&inv)) { return nullptr; } - // We want to *reset* args.fPreLocalMatrix, not compose it. - GrFPArgs newArgs(args.fContext, args.fMatrixProvider, args.fSampling, args.fDstColorInfo); - newArgs.fPreLocalMatrix = lm.get(); + // Gotta be sure the GPU can support our requested colortype (might be FP16) + if (!ctx->colorTypeSupportedAsSurface(info.imageInfo.colorType())) { + info.imageInfo = info.imageInfo.makeColorType(kRGBA_8888_SkColorType); + } - return as_SB(bitmapShader)->asFragmentProcessor(newArgs); + static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain(); + GrUniqueKey key; + GrUniqueKey::Builder builder(&key, kDomain, 10, "Picture Shader Image"); + builder[0] = dstCS->toXYZD50Hash(); + builder[1] = dstCS->transferFnHash(); + builder[2] = static_cast(dstColorType); + builder[3] = fPicture->uniqueID(); + memcpy(&builder[4], &fTile, sizeof(fTile)); // 4,5,6,7 + memcpy(&builder[8], &info.tileScale, sizeof(info.tileScale)); // 8,9 + builder.finish(); + + GrProxyProvider* provider = ctx->priv().proxyProvider(); + GrSurfaceProxyView view; + if (auto proxy = provider->findProxyByUniqueKey(key)) { + view = GrSurfaceProxyView(proxy, kTopLeft_GrSurfaceOrigin, GrSwizzle()); + } else { + const int msaaSampleCount = 0; + const SkSurfaceProps* props = nullptr; + const bool createWithMips = false; + auto image = info.makeImage(SkSurface::MakeRenderTarget(ctx, + SkBudgeted::kYes, + info.imageInfo, + msaaSampleCount, + kTopLeft_GrSurfaceOrigin, + props, + createWithMips), + fPicture.get()); + if (!image) { + return nullptr; + } + auto [v, ct] = as_IB(image)->asView(ctx, GrMipmapped::kNo); + view = std::move(v); + provider->assignUniqueKeyToProxy(key, view.asTextureProxy()); + } + + const GrSamplerState sampler(static_cast(fTmx), + static_cast(fTmy), + sampling_to_filter(args.fSampling)); + return GrTextureEffect::Make(std::move(view), kPremul_SkAlphaType, inv, sampler, + *ctx->priv().caps()); } #endif + +#endif // !legacy diff --git a/src/shaders/SkPictureShader.cpp.legacy b/src/shaders/SkPictureShader.cpp.legacy new file mode 100644 index 0000000000..3358670b2f --- /dev/null +++ b/src/shaders/SkPictureShader.cpp.legacy @@ -0,0 +1,386 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +sk_sp SkPicture::makeShader(SkTileMode tmx, SkTileMode tmy, SkFilterMode filter, + const SkMatrix* localMatrix, const SkRect* tile) const { + if (localMatrix && !localMatrix->invert(nullptr)) { + return nullptr; + } + return SkPictureShader::Make(sk_ref_sp(this), tmx, tmy, (SkPictureShader::FilterEnum)filter, + localMatrix, tile); +} + +sk_sp SkPicture::makeShader(SkTileMode tmx, SkTileMode tmy, const SkMatrix* localMatrix, + const SkRect* tile) const { + if (localMatrix && !localMatrix->invert(nullptr)) { + return nullptr; + } + return SkPictureShader::Make(sk_ref_sp(this), tmx, tmy, SkPictureShader::kInheritFromPaint, + localMatrix, tile); +} + +sk_sp SkPicture::makeShader(SkTileMode tmx, SkTileMode tmy, + const SkMatrix* localMatrix) const { + return this->makeShader(tmx, tmy, localMatrix, nullptr); +} + +namespace { +static unsigned gBitmapShaderKeyNamespaceLabel; + +struct BitmapShaderKey : public SkResourceCache::Key { +public: + BitmapShaderKey(SkColorSpace* colorSpace, + SkImage::BitDepth bitDepth, + uint32_t shaderID, + const SkSize& scale) + : fColorSpaceXYZHash(colorSpace->toXYZD50Hash()) + , fColorSpaceTransferFnHash(colorSpace->transferFnHash()) + , fBitDepth(bitDepth) + , fScale(scale) { + + static const size_t keySize = sizeof(fColorSpaceXYZHash) + + sizeof(fColorSpaceTransferFnHash) + + sizeof(fBitDepth) + + sizeof(fScale); + // This better be packed. + SkASSERT(sizeof(uint32_t) * (&fEndOfStruct - &fColorSpaceXYZHash) == keySize); + this->init(&gBitmapShaderKeyNamespaceLabel, MakeSharedID(shaderID), keySize); + } + + static uint64_t MakeSharedID(uint32_t shaderID) { + uint64_t sharedID = SkSetFourByteTag('p', 's', 'd', 'r'); + return (sharedID << 32) | shaderID; + } + +private: + uint32_t fColorSpaceXYZHash; + uint32_t fColorSpaceTransferFnHash; + SkImage::BitDepth fBitDepth; + SkSize fScale; + + SkDEBUGCODE(uint32_t fEndOfStruct;) +}; + +struct BitmapShaderRec : public SkResourceCache::Rec { + BitmapShaderRec(const BitmapShaderKey& key, SkShader* tileShader) + : fKey(key) + , fShader(SkRef(tileShader)) {} + + BitmapShaderKey fKey; + sk_sp fShader; + + const Key& getKey() const override { return fKey; } + size_t bytesUsed() const override { + // Just the record overhead -- the actual pixels are accounted by SkImage_Lazy. + return sizeof(fKey) + sizeof(SkImageShader); + } + const char* getCategory() const override { return "bitmap-shader"; } + SkDiscardableMemory* diagnostic_only_getDiscardable() const override { return nullptr; } + + static bool Visitor(const SkResourceCache::Rec& baseRec, void* contextShader) { + const BitmapShaderRec& rec = static_cast(baseRec); + sk_sp* result = reinterpret_cast*>(contextShader); + + *result = rec.fShader; + + // The bitmap shader is backed by an image generator, thus it can always re-generate its + // pixels if discarded. + return true; + } +}; + +uint32_t next_id() { + static std::atomic nextID{1}; + + uint32_t id; + do { + id = nextID.fetch_add(1, std::memory_order_relaxed); + } while (id == SK_InvalidGenID); + return id; +} + +} // namespace + +SkPictureShader::SkPictureShader(sk_sp picture, SkTileMode tmx, SkTileMode tmy, + FilterEnum filter, const SkMatrix* localMatrix, const SkRect* tile) + : INHERITED(localMatrix) + , fPicture(std::move(picture)) + , fTile(tile ? *tile : fPicture->cullRect()) + , fTmx(tmx) + , fTmy(tmy) + , fFilter(filter) + , fUniqueID(next_id()) + , fAddedToCache(false) {} + +SkPictureShader::~SkPictureShader() { + if (fAddedToCache.load()) { + SkResourceCache::PostPurgeSharedID(BitmapShaderKey::MakeSharedID(fUniqueID)); + } +} + +sk_sp SkPictureShader::Make(sk_sp picture, SkTileMode tmx, SkTileMode tmy, + FilterEnum filter, const SkMatrix* lm, const SkRect* tile) { + if (!picture || picture->cullRect().isEmpty() || (tile && tile->isEmpty())) { + return SkShaders::Empty(); + } + return sk_sp(new SkPictureShader(std::move(picture), tmx, tmy, filter, lm, tile)); +} + +sk_sp SkPictureShader::CreateProc(SkReadBuffer& buffer) { + SkMatrix lm; + buffer.readMatrix(&lm); + auto tmx = buffer.read32LE(SkTileMode::kLastTileMode); + auto tmy = buffer.read32LE(SkTileMode::kLastTileMode); + SkRect tile = buffer.readRect(); + + sk_sp picture; + FilterEnum filter; + if (buffer.isVersionLT(SkPicturePriv::kPictureShaderFilterParam_Version)) { + filter = kInheritFromPaint; + bool didSerialize = buffer.readBool(); + if (didSerialize) { + picture = SkPicturePriv::MakeFromBuffer(buffer); + } + } else { + filter = buffer.read32LE(SkPictureShader::kLastFilterEnum); + picture = SkPicturePriv::MakeFromBuffer(buffer); + } + return SkPictureShader::Make(picture, tmx, tmy, filter, &lm, &tile); +} + +void SkPictureShader::flatten(SkWriteBuffer& buffer) const { + buffer.writeMatrix(this->getLocalMatrix()); + buffer.write32((unsigned)fTmx); + buffer.write32((unsigned)fTmy); + buffer.writeRect(fTile); + buffer.write32((unsigned)fFilter); + SkPicturePriv::Flatten(fPicture, buffer); +} + +// These are tricky "downscales" -- need to deduce the caller's intention. +// For now, map anything that is not "nearest/none" to kLinear +// +// The "modern" version of pictureshader explicitly takes SkFilterMode. +// The legacy version inherits it from the paint, hence the extra conversions/plumbing +// needed to downscale either filter-quality or sampling (from the paint) if we're in +// legacy mode. When all clients only use the modern/explicit version, we can eliminate +// all of this extra stuff. + +static SkFilterMode sampling_to_filter(const SkSamplingOptions& sampling) { + return sampling == SkSamplingOptions() ? SkFilterMode::kNearest + : SkFilterMode::kLinear; +} + +static SkFilterMode quality_to_filter(SkFilterQuality quality) { + return quality == kNone_SkFilterQuality ? SkFilterMode::kNearest + : SkFilterMode::kLinear; +} + +// Returns a cached image shader, which wraps a single picture tile at the given +// CTM/local matrix. Also adjusts the local matrix for tile scaling. +sk_sp SkPictureShader::refBitmapShader(const SkMatrix& viewMatrix, + SkTCopyOnFirstWrite* localMatrix, + SkColorType dstColorType, + SkColorSpace* dstColorSpace, + SkFilterMode paintFilter, + const int maxTextureSize) const { + SkASSERT(fPicture && !fPicture->cullRect().isEmpty()); + + const SkMatrix m = SkMatrix::Concat(viewMatrix, **localMatrix); + + // Use a rotation-invariant scale + SkSize scaledSize; + if (!m.decomposeScale(&scaledSize, nullptr)) { + scaledSize = {1, 1}; + } + scaledSize.fWidth *= fTile.width(); + scaledSize.fHeight *= fTile.height(); + + // Clamp the tile size to about 4M pixels + static const SkScalar kMaxTileArea = 2048 * 2048; + SkScalar tileArea = scaledSize.width() * scaledSize.height(); + if (tileArea > kMaxTileArea) { + SkScalar clampScale = SkScalarSqrt(kMaxTileArea / tileArea); + scaledSize.set(scaledSize.width() * clampScale, + scaledSize.height() * clampScale); + } +#if SK_SUPPORT_GPU + // Scale down the tile size if larger than maxTextureSize for GPU Path or it should fail on create texture + if (maxTextureSize) { + if (scaledSize.width() > maxTextureSize || scaledSize.height() > maxTextureSize) { + SkScalar downScale = maxTextureSize / std::max(scaledSize.width(), scaledSize.height()); + scaledSize.set(SkScalarFloorToScalar(scaledSize.width() * downScale), + SkScalarFloorToScalar(scaledSize.height() * downScale)); + } + } +#endif + + const SkISize tileSize = scaledSize.toCeil(); + if (tileSize.isEmpty()) { + return nullptr; + } + + // The actual scale, compensating for rounding & clamping. + const SkSize tileScale = SkSize::Make(SkIntToScalar(tileSize.width()) / fTile.width(), + SkIntToScalar(tileSize.height()) / fTile.height()); + + + sk_sp imgCS = dstColorSpace ? sk_ref_sp(dstColorSpace): SkColorSpace::MakeSRGB(); + SkImage::BitDepth bitDepth = SkColorTypeMaxBitsPerChannel(dstColorType) >= 16 + ? SkImage::BitDepth::kF16 + : SkImage::BitDepth::kU8; + + BitmapShaderKey key(imgCS.get(), bitDepth, fUniqueID, tileScale); + + sk_sp tileShader; + if (!SkResourceCache::Find(key, BitmapShaderRec::Visitor, &tileShader)) { + SkMatrix tileMatrix = SkMatrix::RectToRect(fTile, SkRect::MakeIWH(tileSize.width(), + tileSize.height())); + + sk_sp tileImage = SkImage::MakeFromPicture(fPicture, tileSize, &tileMatrix, + nullptr, bitDepth, std::move(imgCS)); + if (!tileImage) { + return nullptr; + } + + SkFilterMode filter; + if (fFilter == kInheritFromPaint) { + filter = paintFilter; + } else { + filter = (SkFilterMode)fFilter; + } + tileShader = tileImage->makeShader(fTmx, fTmy, SkSamplingOptions(filter), nullptr); + + SkResourceCache::Add(new BitmapShaderRec(key, tileShader.get())); + fAddedToCache.store(true); + } + + if (tileScale.width() != 1 || tileScale.height() != 1) { + localMatrix->writable()->preScale(1 / tileScale.width(), 1 / tileScale.height()); + } + + return tileShader; +} + +bool SkPictureShader::onAppendStages(const SkStageRec& rec) const { + auto lm = this->totalLocalMatrix(rec.fLocalM); + + // Keep bitmapShader alive by using alloc instead of stack memory + auto& bitmapShader = *rec.fAlloc->make>(); + bitmapShader = this->refBitmapShader(rec.fMatrixProvider.localToDevice(), &lm, + rec.fDstColorType, rec.fDstCS, + quality_to_filter(rec.fPaint.getFilterQuality())); + + if (!bitmapShader) { + return false; + } + + SkStageRec localRec = rec; + localRec.fLocalM = lm->isIdentity() ? nullptr : lm.get(); + + return as_SB(bitmapShader)->appendStages(localRec); +} + +skvm::Color SkPictureShader::onProgram(skvm::Builder* p, + skvm::Coord device, skvm::Coord local, skvm::Color paint, + const SkMatrixProvider& matrices, const SkMatrix* localM, + SkFilterQuality quality, const SkColorInfo& dst, + skvm::Uniforms* uniforms, SkArenaAlloc* alloc) const { + auto lm = this->totalLocalMatrix(localM); + + // Keep bitmapShader alive by using alloc instead of stack memory + auto& bitmapShader = *alloc->make>(); + bitmapShader = this->refBitmapShader(matrices.localToDevice(), &lm, + dst.colorType(), dst.colorSpace(), + quality_to_filter(quality)); + if (!bitmapShader) { + return {}; + } + + return as_SB(bitmapShader)->program(p, device,local, paint, + matrices,lm, + quality,dst, + uniforms,alloc); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +#ifdef SK_ENABLE_LEGACY_SHADERCONTEXT +SkShaderBase::Context* SkPictureShader::onMakeContext(const ContextRec& rec, SkArenaAlloc* alloc) +const { + auto lm = this->totalLocalMatrix(rec.fLocalMatrix); + sk_sp bitmapShader = this->refBitmapShader(*rec.fMatrix, &lm, rec.fDstColorType, + rec.fDstColorSpace, + sampling_to_filter(rec.fPaintSampling)); + if (!bitmapShader) { + return nullptr; + } + + ContextRec localRec = rec; + localRec.fLocalMatrix = lm->isIdentity() ? nullptr : lm.get(); + + PictureShaderContext* ctx = + alloc->make(*this, localRec, std::move(bitmapShader), alloc); + if (nullptr == ctx->fBitmapShaderContext) { + ctx = nullptr; + } + return ctx; +} +#endif + +///////////////////////////////////////////////////////////////////////////////////////// + +SkPictureShader::PictureShaderContext::PictureShaderContext( + const SkPictureShader& shader, const ContextRec& rec, sk_sp bitmapShader, + SkArenaAlloc* alloc) + : INHERITED(shader, rec) + , fBitmapShader(std::move(bitmapShader)) +{ + fBitmapShaderContext = as_SB(fBitmapShader)->makeContext(rec, alloc); + //if fBitmapShaderContext is null, we are invalid +} + +uint32_t SkPictureShader::PictureShaderContext::getFlags() const { + SkASSERT(fBitmapShaderContext); + return fBitmapShaderContext->getFlags(); +} + +void SkPictureShader::PictureShaderContext::shadeSpan(int x, int y, SkPMColor dstC[], int count) { + SkASSERT(fBitmapShaderContext); + fBitmapShaderContext->shadeSpan(x, y, dstC, count); +} + +#if SK_SUPPORT_GPU + +std::unique_ptr SkPictureShader::asFragmentProcessor( + const GrFPArgs& args) const { + int maxTextureSize = 0; + if (args.fContext) { + maxTextureSize = args.fContext->priv().caps()->maxTextureSize(); + } + + auto lm = this->totalLocalMatrix(args.fPreLocalMatrix); + SkColorType dstColorType = GrColorTypeToSkColorType(args.fDstColorInfo->colorType()); + if (dstColorType == kUnknown_SkColorType) { + dstColorType = kRGBA_8888_SkColorType; + } + sk_sp bitmapShader( + this->refBitmapShader(args.fMatrixProvider.localToDevice(), &lm, dstColorType, + args.fDstColorInfo->colorSpace(), + sampling_to_filter(args.fSampling), + maxTextureSize)); + if (!bitmapShader) { + return nullptr; + } + + // We want to *reset* args.fPreLocalMatrix, not compose it. + GrFPArgs newArgs(args.fContext, args.fMatrixProvider, args.fSampling, args.fDstColorInfo); + newArgs.fPreLocalMatrix = lm.get(); + + return as_SB(bitmapShader)->asFragmentProcessor(newArgs); +} +#endif diff --git a/src/shaders/SkPictureShader.h b/src/shaders/SkPictureShader.h index 530e618a6a..6d1199ccbd 100644 --- a/src/shaders/SkPictureShader.h +++ b/src/shaders/SkPictureShader.h @@ -16,6 +16,10 @@ class SkArenaAlloc; class SkBitmap; class SkPicture; +#ifdef SK_SUPPORT_LEGACY_PICTURESHADER_MATH +#include "src/shaders/SkPictureShader.h.legacy" +#else + /* * An SkPictureShader can be used to draw SkPicture-based patterns. * @@ -24,8 +28,6 @@ class SkPicture; */ class SkPictureShader : public SkShaderBase { public: - ~SkPictureShader() override; - enum FilterEnum { kNearest, // SkFilterMode::kNearest kLinear, // SkFilterMode::kLinear @@ -60,10 +62,11 @@ private: SkPictureShader(sk_sp, SkTileMode, SkTileMode, FilterEnum, const SkMatrix*, const SkRect*); - sk_sp refBitmapShader(const SkMatrix&, SkTCopyOnFirstWrite* localMatrix, - SkColorType dstColorType, SkColorSpace* dstColorSpace, - SkFilterMode paintFilter, - const int maxTextureSize = 0) const; + sk_sp rasterShader(const SkMatrix&, SkTCopyOnFirstWrite* localMatrix, + SkColorType dstColorType, SkColorSpace* dstColorSpace, + SkFilterMode paintFilter) const; + + sk_sp makeShader(const SkImage*, SkFilterMode paintFilter) const; class PictureShaderContext : public Context { public: @@ -86,10 +89,8 @@ private: SkTileMode fTmx, fTmy; FilterEnum fFilter; - const uint32_t fUniqueID; - mutable std::atomic fAddedToCache; - using INHERITED = SkShaderBase; }; +#endif // !legacy #endif // SkPictureShader_DEFINED diff --git a/src/shaders/SkPictureShader.h.legacy b/src/shaders/SkPictureShader.h.legacy new file mode 100644 index 0000000000..6bc49b759a --- /dev/null +++ b/src/shaders/SkPictureShader.h.legacy @@ -0,0 +1,82 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* + * An SkPictureShader can be used to draw SkPicture-based patterns. + * + * The SkPicture is first rendered into a tile, which is then used to shade the area according + * to specified tiling rules. + */ +class SkPictureShader : public SkShaderBase { +public: + ~SkPictureShader() override; + + enum FilterEnum { + kNearest, // SkFilterMode::kNearest + kLinear, // SkFilterMode::kLinear + kInheritFromPaint, + + kLastFilterEnum = kInheritFromPaint, + }; + + static sk_sp Make(sk_sp, SkTileMode, SkTileMode, FilterEnum, + const SkMatrix*, const SkRect*); + +#if SK_SUPPORT_GPU + std::unique_ptr asFragmentProcessor(const GrFPArgs&) const override; +#endif + +protected: + SkPictureShader(SkReadBuffer&); + void flatten(SkWriteBuffer&) const override; + bool onAppendStages(const SkStageRec&) const override; + skvm::Color onProgram(skvm::Builder*, skvm::Coord device, skvm::Coord local, skvm::Color paint, + const SkMatrixProvider&, const SkMatrix* localM, + SkFilterQuality quality, const SkColorInfo& dst, + skvm::Uniforms* uniforms, SkArenaAlloc* alloc) const override; + +#ifdef SK_ENABLE_LEGACY_SHADERCONTEXT + Context* onMakeContext(const ContextRec&, SkArenaAlloc*) const override; +#endif + +private: + SK_FLATTENABLE_HOOKS(SkPictureShader) + + SkPictureShader(sk_sp, SkTileMode, SkTileMode, FilterEnum, + const SkMatrix*, const SkRect*); + + sk_sp refBitmapShader(const SkMatrix&, SkTCopyOnFirstWrite* localMatrix, + SkColorType dstColorType, SkColorSpace* dstColorSpace, + SkFilterMode paintFilter, + const int maxTextureSize = 0) const; + + class PictureShaderContext : public Context { + public: + PictureShaderContext( + const SkPictureShader&, const ContextRec&, sk_sp bitmapShader, SkArenaAlloc*); + + uint32_t getFlags() const override; + + void shadeSpan(int x, int y, SkPMColor dstC[], int count) override; + + sk_sp fBitmapShader; + SkShaderBase::Context* fBitmapShaderContext; + void* fBitmapShaderContextStorage; + + using INHERITED = Context; + }; + + sk_sp fPicture; + SkRect fTile; + SkTileMode fTmx, fTmy; + FilterEnum fFilter; + + const uint32_t fUniqueID; + mutable std::atomic fAddedToCache; + + using INHERITED = SkShaderBase; +}; diff --git a/tests/PictureShaderTest.cpp b/tests/PictureShaderTest.cpp index 1698724b2b..0408faf35e 100644 --- a/tests/PictureShaderTest.cpp +++ b/tests/PictureShaderTest.cpp @@ -46,3 +46,69 @@ DEF_TEST(PictureShader_caching, reporter) { // All but the local ref should be gone now. REPORTER_ASSERT(reporter, picture->unique()); } + +#ifndef SK_SUPPORT_LEGACY_PICTURESHADER_MATH + +#include "src/core/SkPicturePriv.h" +#include "src/core/SkResourceCache.h" + +/* + * Check caching of picture-shaders + * - we do cache the underlying image (i.e. there is a cache entry) + * - there is only 1 entry, even with differing tile modes + * - after deleting the picture, the cache entry is purged + */ +DEF_TEST(PictureShader_caching2, reporter) { + auto picture = []() { + SkPictureRecorder recorder; + recorder.beginRecording(100, 100)->drawColor(SK_ColorGREEN); + return recorder.finishRecordingAsPicture(); + }(); + REPORTER_ASSERT(reporter, picture->unique()); + + struct Data { + uint64_t sharedID; + int counter; + } data = { + SkPicturePriv::MakeSharedID(picture->uniqueID()), + 0, + }; + + auto counter = [](const SkResourceCache::Rec& rec, void* dataPtr) { + if (rec.getKey().getSharedID() == ((Data*)dataPtr)->sharedID) { + ((Data*)dataPtr)->counter += 1; + } + }; + + SkResourceCache::VisitAll(counter, &data); + REPORTER_ASSERT(reporter, data.counter == 0); + + // Draw with a view variants of picture-shaders that all use the same picture. + // Only expect 1 cache entry for all (since same CTM for all). + sk_sp surface = SkSurface::MakeRasterN32Premul(100, 100); + for (SkTileMode m : { + SkTileMode::kClamp, SkTileMode::kRepeat, SkTileMode::kRepeat, SkTileMode::kDecal + }) { + SkPaint paint; + paint.setShader(picture->makeShader(m, m)); + surface->getCanvas()->drawPaint(paint); + } + + // Don't expect any additional refs on the picture + REPORTER_ASSERT(reporter, picture->unique()); + + // Check that we did cache something, but only 1 thing + data.counter = 0; + SkResourceCache::VisitAll(counter, &data); + REPORTER_ASSERT(reporter, data.counter == 1); + + // Now delete the picture, and check the we purge the cache entry + + picture.reset(); + SkResourceCache::CheckMessages(); + + data.counter = 0; + SkResourceCache::VisitAll(counter, &data); + REPORTER_ASSERT(reporter, data.counter == 0); +} +#endif