1/* 2 * Copyright 2014 Google Inc. 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 8#include "SkPictureShader.h" 9 10#include "SkArenaAlloc.h" 11#include "SkBitmap.h" 12#include "SkBitmapProcShader.h" 13#include "SkCanvas.h" 14#include "SkColorSpaceXformCanvas.h" 15#include "SkImage.h" 16#include "SkImageShader.h" 17#include "SkMatrixUtils.h" 18#include "SkPicture.h" 19#include "SkPictureImageGenerator.h" 20#include "SkReadBuffer.h" 21#include "SkResourceCache.h" 22 23#if SK_SUPPORT_GPU 24#include "GrCaps.h" 25#include "GrColorSpaceInfo.h" 26#include "GrContext.h" 27#include "GrFragmentProcessor.h" 28#endif 29 30namespace { 31static unsigned gBitmapSkaderKeyNamespaceLabel; 32 33struct BitmapShaderKey : public SkResourceCache::Key { 34public: 35 BitmapShaderKey(sk_sp<SkColorSpace> colorSpace, 36 uint32_t shaderID, 37 const SkRect& tile, 38 SkShader::TileMode tmx, 39 SkShader::TileMode tmy, 40 const SkSize& scale, 41 SkTransferFunctionBehavior blendBehavior) 42 : fColorSpace(std::move(colorSpace)) 43 , fTile(tile) 44 , fTmx(tmx) 45 , fTmy(tmy) 46 , fScale(scale) 47 , fBlendBehavior(blendBehavior) { 48 49 static const size_t keySize = sizeof(fColorSpace) + 50 sizeof(fTile) + 51 sizeof(fTmx) + sizeof(fTmy) + 52 sizeof(fScale) + 53 sizeof(fBlendBehavior); 54 // This better be packed. 55 SkASSERT(sizeof(uint32_t) * (&fEndOfStruct - (uint32_t*)&fColorSpace) == keySize); 56 this->init(&gBitmapSkaderKeyNamespaceLabel, MakeSharedID(shaderID), keySize); 57 } 58 59 static uint64_t MakeSharedID(uint32_t shaderID) { 60 uint64_t sharedID = SkSetFourByteTag('p', 's', 'd', 'r'); 61 return (sharedID << 32) | shaderID; 62 } 63 64private: 65 // TODO: there are some fishy things about using CS sk_sps in the key: 66 // - false negatives: keys are memcmp'ed, so we don't detect equivalent CSs 67 // (SkColorspace::Equals) 68 // - we're keeping the CS alive, even when the client releases it 69 // 70 // Ideally we'd be using unique IDs or some other weak ref + purge mechanism 71 // when the CS is deleted. 72 sk_sp<SkColorSpace> fColorSpace; 73 SkRect fTile; 74 SkShader::TileMode fTmx, fTmy; 75 SkSize fScale; 76 SkTransferFunctionBehavior fBlendBehavior; 77 78 SkDEBUGCODE(uint32_t fEndOfStruct;) 79}; 80 81struct BitmapShaderRec : public SkResourceCache::Rec { 82 BitmapShaderRec(const BitmapShaderKey& key, SkShader* tileShader) 83 : fKey(key) 84 , fShader(SkRef(tileShader)) {} 85 86 BitmapShaderKey fKey; 87 sk_sp<SkShader> fShader; 88 size_t fBitmapBytes; 89 90 const Key& getKey() const override { return fKey; } 91 size_t bytesUsed() const override { 92 // Just the record overhead -- the actual pixels are accounted by SkImageCacherator. 93 return sizeof(fKey) + sizeof(SkImageShader); 94 } 95 const char* getCategory() const override { return "bitmap-shader"; } 96 SkDiscardableMemory* diagnostic_only_getDiscardable() const override { return nullptr; } 97 98 static bool Visitor(const SkResourceCache::Rec& baseRec, void* contextShader) { 99 const BitmapShaderRec& rec = static_cast<const BitmapShaderRec&>(baseRec); 100 sk_sp<SkShader>* result = reinterpret_cast<sk_sp<SkShader>*>(contextShader); 101 102 *result = rec.fShader; 103 104 // The bitmap shader is backed by an image generator, thus it can always re-generate its 105 // pixels if discarded. 106 return true; 107 } 108}; 109 110static int32_t gNextID = 1; 111uint32_t next_id() { 112 int32_t id; 113 do { 114 id = sk_atomic_inc(&gNextID); 115 } while (id == SK_InvalidGenID); 116 return static_cast<uint32_t>(id); 117} 118 119} // namespace 120 121SkPictureShader::SkPictureShader(sk_sp<SkPicture> picture, TileMode tmx, TileMode tmy, 122 const SkMatrix* localMatrix, const SkRect* tile, 123 sk_sp<SkColorSpace> colorSpace) 124 : INHERITED(localMatrix) 125 , fPicture(std::move(picture)) 126 , fTile(tile ? *tile : fPicture->cullRect()) 127 , fTmx(tmx) 128 , fTmy(tmy) 129 , fColorSpace(std::move(colorSpace)) 130 , fUniqueID(next_id()) 131 , fAddedToCache(false) {} 132 133SkPictureShader::~SkPictureShader() { 134 if (fAddedToCache.load()) { 135 SkResourceCache::PostPurgeSharedID(BitmapShaderKey::MakeSharedID(fUniqueID)); 136 } 137} 138 139sk_sp<SkShader> SkPictureShader::Make(sk_sp<SkPicture> picture, TileMode tmx, TileMode tmy, 140 const SkMatrix* localMatrix, const SkRect* tile) { 141 if (!picture || picture->cullRect().isEmpty() || (tile && tile->isEmpty())) { 142 return SkShader::MakeEmptyShader(); 143 } 144 return sk_sp<SkShader>(new SkPictureShader(std::move(picture), tmx, tmy, localMatrix, tile, 145 nullptr)); 146} 147 148sk_sp<SkFlattenable> SkPictureShader::CreateProc(SkReadBuffer& buffer) { 149 SkMatrix lm; 150 buffer.readMatrix(&lm); 151 TileMode mx = (TileMode)buffer.read32(); 152 TileMode my = (TileMode)buffer.read32(); 153 SkRect tile; 154 buffer.readRect(&tile); 155 156 sk_sp<SkPicture> picture; 157 158 bool didSerialize = buffer.readBool(); 159 if (didSerialize) { 160 picture = SkPicture::MakeFromBuffer(buffer); 161 } 162 return SkPictureShader::Make(picture, mx, my, &lm, &tile); 163} 164 165void SkPictureShader::flatten(SkWriteBuffer& buffer) const { 166 buffer.writeMatrix(this->getLocalMatrix()); 167 buffer.write32(fTmx); 168 buffer.write32(fTmy); 169 buffer.writeRect(fTile); 170 171 buffer.writeBool(true); 172 fPicture->flatten(buffer); 173} 174 175// This helper returns two artifacts: 176// 177// 1) a cached image shader, which wraps a single picture tile at the given CTM/local matrix 178// 179// 2) a "composite" local matrix, to be passed down when dispatching createContext(), 180// appendStages() and asFragmentProcessor() in callers 181// 182// The composite local matrix includes the actual local matrix, any inherited/outer local matrix 183// and a scale component (to mape the actual tile bitmap size -> fTile size). 184// 185sk_sp<SkShader> SkPictureShader::refBitmapShader(const SkMatrix& viewMatrix, 186 const SkMatrix* outerLocalMatrix, 187 SkColorSpace* dstColorSpace, 188 SkMatrix* compositeLocalMatrix, 189 const int maxTextureSize) const { 190 SkASSERT(fPicture && !fPicture->cullRect().isEmpty()); 191 192 *compositeLocalMatrix = this->getLocalMatrix(); 193 if (outerLocalMatrix) { 194 compositeLocalMatrix->preConcat(*outerLocalMatrix); 195 } 196 const SkMatrix m = SkMatrix::Concat(viewMatrix, *compositeLocalMatrix); 197 198 // Use a rotation-invariant scale 199 SkPoint scale; 200 // 201 // TODO: replace this with decomposeScale() -- but beware LayoutTest rebaselines! 202 // 203 if (!SkDecomposeUpper2x2(m, nullptr, &scale, nullptr)) { 204 // Decomposition failed, use an approximation. 205 scale.set(SkScalarSqrt(m.getScaleX() * m.getScaleX() + m.getSkewX() * m.getSkewX()), 206 SkScalarSqrt(m.getScaleY() * m.getScaleY() + m.getSkewY() * m.getSkewY())); 207 } 208 SkSize scaledSize = SkSize::Make(SkScalarAbs(scale.x() * fTile.width()), 209 SkScalarAbs(scale.y() * fTile.height())); 210 211 // Clamp the tile size to about 4M pixels 212 static const SkScalar kMaxTileArea = 2048 * 2048; 213 SkScalar tileArea = scaledSize.width() * scaledSize.height(); 214 if (tileArea > kMaxTileArea) { 215 SkScalar clampScale = SkScalarSqrt(kMaxTileArea / tileArea); 216 scaledSize.set(scaledSize.width() * clampScale, 217 scaledSize.height() * clampScale); 218 } 219#if SK_SUPPORT_GPU 220 // Scale down the tile size if larger than maxTextureSize for GPU Path or it should fail on create texture 221 if (maxTextureSize) { 222 if (scaledSize.width() > maxTextureSize || scaledSize.height() > maxTextureSize) { 223 SkScalar downScale = maxTextureSize / SkMaxScalar(scaledSize.width(), scaledSize.height()); 224 scaledSize.set(SkScalarFloorToScalar(scaledSize.width() * downScale), 225 SkScalarFloorToScalar(scaledSize.height() * downScale)); 226 } 227 } 228#endif 229 230 const SkISize tileSize = scaledSize.toCeil(); 231 if (tileSize.isEmpty()) { 232 return SkShader::MakeEmptyShader(); 233 } 234 235 // The actual scale, compensating for rounding & clamping. 236 const SkSize tileScale = SkSize::Make(SkIntToScalar(tileSize.width()) / fTile.width(), 237 SkIntToScalar(tileSize.height()) / fTile.height()); 238 239 // |fColorSpace| will only be set when using an SkColorSpaceXformCanvas to do pre-draw xforms. 240 // This canvas is strictly for legacy mode. A non-null |dstColorSpace| indicates that we 241 // should perform color correct rendering and xform at draw time. 242 SkASSERT(!fColorSpace || !dstColorSpace); 243 sk_sp<SkColorSpace> keyCS = dstColorSpace ? sk_ref_sp(dstColorSpace) : fColorSpace; 244 SkTransferFunctionBehavior blendBehavior = dstColorSpace ? SkTransferFunctionBehavior::kRespect 245 : SkTransferFunctionBehavior::kIgnore; 246 247 sk_sp<SkShader> tileShader; 248 BitmapShaderKey key(std::move(keyCS), 249 fUniqueID, 250 fTile, 251 fTmx, 252 fTmy, 253 tileScale, 254 blendBehavior); 255 256 if (!SkResourceCache::Find(key, BitmapShaderRec::Visitor, &tileShader)) { 257 SkMatrix tileMatrix; 258 tileMatrix.setRectToRect(fTile, SkRect::MakeIWH(tileSize.width(), tileSize.height()), 259 SkMatrix::kFill_ScaleToFit); 260 261 sk_sp<SkImage> tileImage = SkImage::MakeFromGenerator( 262 SkPictureImageGenerator::Make(tileSize, fPicture, &tileMatrix, nullptr, 263 SkImage::BitDepth::kU8, sk_ref_sp(dstColorSpace))); 264 if (!tileImage) { 265 return nullptr; 266 } 267 268 if (fColorSpace) { 269 tileImage = tileImage->makeColorSpace(fColorSpace, SkTransferFunctionBehavior::kIgnore); 270 } 271 272 tileShader = tileImage->makeShader(fTmx, fTmy); 273 274 SkResourceCache::Add(new BitmapShaderRec(key, tileShader.get())); 275 fAddedToCache.store(true); 276 } 277 278 compositeLocalMatrix->preScale(1 / tileScale.width(), 1 / tileScale.height()); 279 280 return tileShader; 281} 282 283bool SkPictureShader::onIsRasterPipelineOnly(const SkMatrix& ctm) const { 284 return SkImageShader::IsRasterPipelineOnly(ctm, kN32_SkColorType, kPremul_SkAlphaType, 285 fTmx, fTmy, this->getLocalMatrix()); 286} 287 288bool SkPictureShader::onAppendStages(const StageRec& rec) const { 289 // Keep bitmapShader alive by using alloc instead of stack memory 290 auto& bitmapShader = *rec.fAlloc->make<sk_sp<SkShader>>(); 291 SkMatrix compositeLocalMatrix; 292 bitmapShader = this->refBitmapShader(rec.fCTM, rec.fLocalM, rec.fDstCS, &compositeLocalMatrix); 293 294 StageRec localRec = rec; 295 localRec.fLocalM = compositeLocalMatrix.isIdentity() ? nullptr : &compositeLocalMatrix; 296 297 return bitmapShader && as_SB(bitmapShader)->appendStages(localRec); 298} 299 300///////////////////////////////////////////////////////////////////////////////////////// 301SkShaderBase::Context* SkPictureShader::onMakeContext(const ContextRec& rec, SkArenaAlloc* alloc) 302const { 303 SkMatrix compositeLocalMatrix; 304 sk_sp<SkShader> bitmapShader = this->refBitmapShader(*rec.fMatrix, 305 rec.fLocalMatrix, 306 rec.fDstColorSpace, 307 &compositeLocalMatrix); 308 if (!bitmapShader) { 309 return nullptr; 310 } 311 312 ContextRec localRec = rec; 313 localRec.fLocalMatrix = compositeLocalMatrix.isIdentity() ? nullptr : &compositeLocalMatrix; 314 315 PictureShaderContext* ctx = 316 alloc->make<PictureShaderContext>(*this, localRec, std::move(bitmapShader), alloc); 317 if (nullptr == ctx->fBitmapShaderContext) { 318 ctx = nullptr; 319 } 320 return ctx; 321} 322 323sk_sp<SkShader> SkPictureShader::onMakeColorSpace(SkColorSpaceXformer* xformer) const { 324 sk_sp<SkColorSpace> dstCS = xformer->dst(); 325 if (SkColorSpace::Equals(dstCS.get(), fColorSpace.get())) { 326 return sk_ref_sp(const_cast<SkPictureShader*>(this)); 327 } 328 329 return sk_sp<SkPictureShader>(new SkPictureShader(fPicture, fTmx, fTmy, &this->getLocalMatrix(), 330 &fTile, std::move(dstCS))); 331} 332 333///////////////////////////////////////////////////////////////////////////////////////// 334 335SkPictureShader::PictureShaderContext::PictureShaderContext( 336 const SkPictureShader& shader, const ContextRec& rec, sk_sp<SkShader> bitmapShader, 337 SkArenaAlloc* alloc) 338 : INHERITED(shader, rec) 339 , fBitmapShader(std::move(bitmapShader)) 340{ 341 fBitmapShaderContext = as_SB(fBitmapShader)->makeContext(rec, alloc); 342 //if fBitmapShaderContext is null, we are invalid 343} 344 345uint32_t SkPictureShader::PictureShaderContext::getFlags() const { 346 SkASSERT(fBitmapShaderContext); 347 return fBitmapShaderContext->getFlags(); 348} 349 350void SkPictureShader::PictureShaderContext::shadeSpan(int x, int y, SkPMColor dstC[], int count) { 351 SkASSERT(fBitmapShaderContext); 352 fBitmapShaderContext->shadeSpan(x, y, dstC, count); 353} 354 355#ifndef SK_IGNORE_TO_STRING 356void SkPictureShader::toString(SkString* str) const { 357 static const char* gTileModeName[SkShader::kTileModeCount] = { 358 "clamp", "repeat", "mirror" 359 }; 360 361 str->appendf("PictureShader: [%f:%f:%f:%f] ", 362 fPicture->cullRect().fLeft, 363 fPicture->cullRect().fTop, 364 fPicture->cullRect().fRight, 365 fPicture->cullRect().fBottom); 366 367 str->appendf("(%s, %s)", gTileModeName[fTmx], gTileModeName[fTmy]); 368 369 this->INHERITED::toString(str); 370} 371#endif 372 373#if SK_SUPPORT_GPU 374std::unique_ptr<GrFragmentProcessor> SkPictureShader::asFragmentProcessor( 375 const GrFPArgs& args) const { 376 int maxTextureSize = 0; 377 if (args.fContext) { 378 maxTextureSize = args.fContext->caps()->maxTextureSize(); 379 } 380 SkMatrix compositeLocalMatrix; 381 sk_sp<SkShader> bitmapShader(this->refBitmapShader(*args.fViewMatrix, args.fLocalMatrix, 382 args.fDstColorSpaceInfo->colorSpace(), 383 &compositeLocalMatrix, 384 maxTextureSize)); 385 if (!bitmapShader) { 386 return nullptr; 387 } 388 389 return as_SB(bitmapShader)->asFragmentProcessor( 390 GrFPArgs(args.fContext, 391 args.fViewMatrix, 392 compositeLocalMatrix.isIdentity() ? nullptr : &compositeLocalMatrix, 393 args.fFilterQuality, 394 args.fDstColorSpaceInfo)); 395} 396#endif 397