Cache image behind picture-shader

PictureShader = picture + tiling + depth/colorspace + filtering [+ scale]

Today we cache the imageshader that is used to rendering. However, the
key for that cache is the pictureshader's ID itself... which means if
we have several, all using the same picture (but maybe diff tiling) we
would create dup cache entries.

Idea:
1. only cache the image (rastered picture), not an imageShader
2. key the cache on the picture's ID, not the shader's

Several implications of this:

1. Should get more cache reuse, since we don't care about the
shader's ID (which is just wrapping a picture+tiling, etc.)

2. We also eliminate the indirection of creating a PictureImage. Instead
we're creating real (pixel) images, and caching those. This removes one
extra layer of "cache".


Idea: when we cache something for pict
Change-Id: I51cf4e9bff3c91ce1872876597d3d565039d8c7a
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/377844
Commit-Queue: Mike Reed <reed@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
This commit is contained in:
Mike Reed 2021-03-04 13:40:44 -05:00 committed by Skia Commit-Bot
parent 3316796acb
commit ff83dda8cd
8 changed files with 774 additions and 157 deletions

View File

@ -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<bool> fAddedToCache{false};
};
#endif

View File

@ -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 <atomic>
// 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 {

View File

@ -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)

View File

@ -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<SkShader> 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<SkShader> 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<uint32_t>(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<SkImage> image)
: fKey(key)
, fShader(SkRef(tileShader)) {}
, fImage(std::move(image)) {}
BitmapShaderKey fKey;
sk_sp<SkShader> fShader;
ImageFromPictureKey fKey;
sk_sp<SkImage> 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<const BitmapShaderRec&>(baseRec);
sk_sp<SkShader>* result = reinterpret_cast<sk_sp<SkShader>*>(contextShader);
const ImageFromPictureRec& rec = static_cast<const ImageFromPictureRec&>(baseRec);
sk_sp<SkImage>* result = reinterpret_cast<sk_sp<SkImage>*>(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<uint32_t> 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<SkPicture> picture, SkTileMode tmx, SkTileMode tmy,
@ -140,15 +132,7 @@ SkPictureShader::SkPictureShader(sk_sp<SkPicture> 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<SkShader> SkPictureShader::Make(sk_sp<SkPicture> 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<SkColorSpace> 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<SkMatrix>* 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<SkImage> makeImage(sk_sp<SkSurface> 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<SkShader> SkPictureShader::refBitmapShader(const SkMatrix& viewMatrix,
SkTCopyOnFirstWrite<SkMatrix>* 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<SkShader> SkPictureShader::rasterShader(const SkMatrix& viewMatrix,
SkTCopyOnFirstWrite<SkMatrix>* 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<SkColorSpace> 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<SkShader> tileShader;
if (!SkResourceCache::Find(key, BitmapShaderRec::Visitor, &tileShader)) {
SkMatrix tileMatrix = SkMatrix::RectToRect(fTile, SkRect::MakeIWH(tileSize.width(),
tileSize.height()));
sk_sp<SkImage> tileImage = SkImage::MakeFromPicture(fPicture, tileSize, &tileMatrix,
nullptr, bitDepth, std::move(imgCS));
if (!tileImage) {
sk_sp<SkImage> 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<SkShader> 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<sk_sp<SkShader>>();
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<sk_sp<SkShader>>();
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<SkShader> bitmapShader = this->refBitmapShader(*rec.fMatrix, &lm, rec.fDstColorType,
rec.fDstColorSpace,
sampling_to_filter(rec.fPaintSampling));
sk_sp<SkShader> 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<GrFragmentProcessor> 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<SkShader> 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<uint32_t>(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<GrSamplerState::WrapMode>(fTmx),
static_cast<GrSamplerState::WrapMode>(fTmy),
sampling_to_filter(args.fSampling));
return GrTextureEffect::Make(std::move(view), kPremul_SkAlphaType, inv, sampler,
*ctx->priv().caps());
}
#endif
#endif // !legacy

View File

@ -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<SkShader> 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<SkShader> 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<SkShader> 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<SkShader> 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<const BitmapShaderRec&>(baseRec);
sk_sp<SkShader>* result = reinterpret_cast<sk_sp<SkShader>*>(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<uint32_t> 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<SkPicture> 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<SkShader> SkPictureShader::Make(sk_sp<SkPicture> 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<SkShader>(new SkPictureShader(std::move(picture), tmx, tmy, filter, lm, tile));
}
sk_sp<SkFlattenable> 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<SkPicture> 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<SkShader> SkPictureShader::refBitmapShader(const SkMatrix& viewMatrix,
SkTCopyOnFirstWrite<SkMatrix>* 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<SkColorSpace> 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<SkShader> tileShader;
if (!SkResourceCache::Find(key, BitmapShaderRec::Visitor, &tileShader)) {
SkMatrix tileMatrix = SkMatrix::RectToRect(fTile, SkRect::MakeIWH(tileSize.width(),
tileSize.height()));
sk_sp<SkImage> 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<sk_sp<SkShader>>();
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<sk_sp<SkShader>>();
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<SkShader> 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<PictureShaderContext>(*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<SkShader> 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<GrFragmentProcessor> 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<SkShader> 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

View File

@ -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<SkPicture>, SkTileMode, SkTileMode, FilterEnum,
const SkMatrix*, const SkRect*);
sk_sp<SkShader> refBitmapShader(const SkMatrix&, SkTCopyOnFirstWrite<SkMatrix>* localMatrix,
SkColorType dstColorType, SkColorSpace* dstColorSpace,
SkFilterMode paintFilter,
const int maxTextureSize = 0) const;
sk_sp<SkShader> rasterShader(const SkMatrix&, SkTCopyOnFirstWrite<SkMatrix>* localMatrix,
SkColorType dstColorType, SkColorSpace* dstColorSpace,
SkFilterMode paintFilter) const;
sk_sp<SkShader> 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<bool> fAddedToCache;
using INHERITED = SkShaderBase;
};
#endif // !legacy
#endif // SkPictureShader_DEFINED

View File

@ -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<SkShader> Make(sk_sp<SkPicture>, SkTileMode, SkTileMode, FilterEnum,
const SkMatrix*, const SkRect*);
#if SK_SUPPORT_GPU
std::unique_ptr<GrFragmentProcessor> 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<SkPicture>, SkTileMode, SkTileMode, FilterEnum,
const SkMatrix*, const SkRect*);
sk_sp<SkShader> refBitmapShader(const SkMatrix&, SkTCopyOnFirstWrite<SkMatrix>* localMatrix,
SkColorType dstColorType, SkColorSpace* dstColorSpace,
SkFilterMode paintFilter,
const int maxTextureSize = 0) const;
class PictureShaderContext : public Context {
public:
PictureShaderContext(
const SkPictureShader&, const ContextRec&, sk_sp<SkShader> bitmapShader, SkArenaAlloc*);
uint32_t getFlags() const override;
void shadeSpan(int x, int y, SkPMColor dstC[], int count) override;
sk_sp<SkShader> fBitmapShader;
SkShaderBase::Context* fBitmapShaderContext;
void* fBitmapShaderContextStorage;
using INHERITED = Context;
};
sk_sp<SkPicture> fPicture;
SkRect fTile;
SkTileMode fTmx, fTmy;
FilterEnum fFilter;
const uint32_t fUniqueID;
mutable std::atomic<bool> fAddedToCache;
using INHERITED = SkShaderBase;
};

View File

@ -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<SkSurface> 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