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 "src/shaders/SkPictureShader.h"
9
10#include "include/core/SkBitmap.h"
11#include "include/core/SkCanvas.h"
12#include "include/core/SkImage.h"
13#include "src/core/SkArenaAlloc.h"
14#include "src/core/SkMatrixProvider.h"
15#include "src/core/SkMatrixUtils.h"
16#include "src/core/SkPicturePriv.h"
17#include "src/core/SkReadBuffer.h"
18#include "src/core/SkResourceCache.h"
19#include "src/core/SkVM.h"
20#include "src/shaders/SkBitmapProcShader.h"
21#include "src/shaders/SkImageShader.h"
22#include <atomic>
23
24#if SK_SUPPORT_GPU
25#include "include/gpu/GrDirectContext.h"
26#include "include/gpu/GrRecordingContext.h"
27#include "src/gpu/GrCaps.h"
28#include "src/gpu/GrColorInfo.h"
29#include "src/gpu/GrFragmentProcessor.h"
30#include "src/gpu/GrRecordingContextPriv.h"
31#include "src/gpu/SkGr.h"
32#endif
33
34sk_sp<SkShader> SkPicture::makeShader(SkTileMode tmx, SkTileMode tmy, const SkMatrix* localMatrix,
35 const SkRect* tile) const {
36 if (localMatrix && !localMatrix->invert(nullptr)) {
37 return nullptr;
38 }
39 return SkPictureShader::Make(sk_ref_sp(this), tmx, tmy, localMatrix, tile);
40}
41
42sk_sp<SkShader> SkPicture::makeShader(SkTileMode tmx, SkTileMode tmy,
43 const SkMatrix* localMatrix) const {
44 return this->makeShader(tmx, tmy, localMatrix, nullptr);
45}
46
47namespace {
48static unsigned gBitmapShaderKeyNamespaceLabel;
49
50struct BitmapShaderKey : public SkResourceCache::Key {
51public:
52 BitmapShaderKey(SkColorSpace* colorSpace,
53 SkImage::BitDepth bitDepth,
54 uint32_t shaderID,
55 const SkSize& scale)
56 : fColorSpaceXYZHash(colorSpace->toXYZD50Hash())
57 , fColorSpaceTransferFnHash(colorSpace->transferFnHash())
58 , fBitDepth(bitDepth)
59 , fScale(scale) {
60
61 static const size_t keySize = sizeof(fColorSpaceXYZHash) +
62 sizeof(fColorSpaceTransferFnHash) +
63 sizeof(fBitDepth) +
64 sizeof(fScale);
65 // This better be packed.
66 SkASSERT(sizeof(uint32_t) * (&fEndOfStruct - &fColorSpaceXYZHash) == keySize);
67 this->init(&gBitmapShaderKeyNamespaceLabel, MakeSharedID(shaderID), keySize);
68 }
69
70 static uint64_t MakeSharedID(uint32_t shaderID) {
71 uint64_t sharedID = SkSetFourByteTag('p', 's', 'd', 'r');
72 return (sharedID << 32) | shaderID;
73 }
74
75private:
76 uint32_t fColorSpaceXYZHash;
77 uint32_t fColorSpaceTransferFnHash;
78 SkImage::BitDepth fBitDepth;
79 SkSize fScale;
80
81 SkDEBUGCODE(uint32_t fEndOfStruct;)
82};
83
84struct BitmapShaderRec : public SkResourceCache::Rec {
85 BitmapShaderRec(const BitmapShaderKey& key, SkShader* tileShader)
86 : fKey(key)
87 , fShader(SkRef(tileShader)) {}
88
89 BitmapShaderKey fKey;
90 sk_sp<SkShader> fShader;
91
92 const Key& getKey() const override { return fKey; }
93 size_t bytesUsed() const override {
94 // Just the record overhead -- the actual pixels are accounted by SkImage_Lazy.
95 return sizeof(fKey) + sizeof(SkImageShader);
96 }
97 const char* getCategory() const override { return "bitmap-shader"; }
98 SkDiscardableMemory* diagnostic_only_getDiscardable() const override { return nullptr; }
99
100 static bool Visitor(const SkResourceCache::Rec& baseRec, void* contextShader) {
101 const BitmapShaderRec& rec = static_cast<const BitmapShaderRec&>(baseRec);
102 sk_sp<SkShader>* result = reinterpret_cast<sk_sp<SkShader>*>(contextShader);
103
104 *result = rec.fShader;
105
106 // The bitmap shader is backed by an image generator, thus it can always re-generate its
107 // pixels if discarded.
108 return true;
109 }
110};
111
112uint32_t next_id() {
113 static std::atomic<uint32_t> nextID{1};
114
115 uint32_t id;
116 do {
117 id = nextID++;
118 } while (id == SK_InvalidGenID);
119 return id;
120}
121
122} // namespace
123
124SkPictureShader::SkPictureShader(sk_sp<SkPicture> picture, SkTileMode tmx, SkTileMode tmy,
125 const SkMatrix* localMatrix, const SkRect* tile)
126 : INHERITED(localMatrix)
127 , fPicture(std::move(picture))
128 , fTile(tile ? *tile : fPicture->cullRect())
129 , fTmx(tmx)
130 , fTmy(tmy)
131 , fUniqueID(next_id())
132 , fAddedToCache(false) {}
133
134SkPictureShader::~SkPictureShader() {
135 if (fAddedToCache.load()) {
136 SkResourceCache::PostPurgeSharedID(BitmapShaderKey::MakeSharedID(fUniqueID));
137 }
138}
139
140sk_sp<SkShader> SkPictureShader::Make(sk_sp<SkPicture> picture, SkTileMode tmx, SkTileMode tmy,
141 const SkMatrix* localMatrix, const SkRect* tile) {
142 if (!picture || picture->cullRect().isEmpty() || (tile && tile->isEmpty())) {
143 return SkShaders::Empty();
144 }
145 return sk_sp<SkShader>(new SkPictureShader(std::move(picture), tmx, tmy, localMatrix, tile));
146}
147
148sk_sp<SkFlattenable> SkPictureShader::CreateProc(SkReadBuffer& buffer) {
149 SkMatrix lm;
150 buffer.readMatrix(&lm);
151 auto tmx = buffer.read32LE(SkTileMode::kLastTileMode);
152 auto tmy = buffer.read32LE(SkTileMode::kLastTileMode);
153 SkRect tile;
154 buffer.readRect(&tile);
155
156 sk_sp<SkPicture> picture;
157
158 bool didSerialize = buffer.readBool();
159 if (didSerialize) {
160 picture = SkPicturePriv::MakeFromBuffer(buffer);
161 }
162 return SkPictureShader::Make(picture, tmx, tmy, &lm, &tile);
163}
164
165void SkPictureShader::flatten(SkWriteBuffer& buffer) const {
166 buffer.writeMatrix(this->getLocalMatrix());
167 buffer.write32((unsigned)fTmx);
168 buffer.write32((unsigned)fTmy);
169 buffer.writeRect(fTile);
170
171 buffer.writeBool(true);
172 SkPicturePriv::Flatten(fPicture, buffer);
173}
174
175// Returns a cached image shader, which wraps a single picture tile at the given
176// CTM/local matrix. Also adjusts the local matrix for tile scaling.
177sk_sp<SkShader> SkPictureShader::refBitmapShader(const SkMatrix& viewMatrix,
178 SkTCopyOnFirstWrite<SkMatrix>* localMatrix,
179 SkColorType dstColorType,
180 SkColorSpace* dstColorSpace,
181 const int maxTextureSize) const {
182 SkASSERT(fPicture && !fPicture->cullRect().isEmpty());
183
184 const SkMatrix m = SkMatrix::Concat(viewMatrix, **localMatrix);
185
186 // Use a rotation-invariant scale
187 SkPoint scale;
188 //
189 // TODO: replace this with decomposeScale() -- but beware LayoutTest rebaselines!
190 //
191 if (!SkDecomposeUpper2x2(m, nullptr, &scale, nullptr)) {
192 // Decomposition failed, use an approximation.
193 scale.set(SkScalarSqrt(m.getScaleX() * m.getScaleX() + m.getSkewX() * m.getSkewX()),
194 SkScalarSqrt(m.getScaleY() * m.getScaleY() + m.getSkewY() * m.getSkewY()));
195 }
196 SkSize scaledSize = SkSize::Make(SkScalarAbs(scale.x() * fTile.width()),
197 SkScalarAbs(scale.y() * fTile.height()));
198
199 // Clamp the tile size to about 4M pixels
200 static const SkScalar kMaxTileArea = 2048 * 2048;
201 SkScalar tileArea = scaledSize.width() * scaledSize.height();
202 if (tileArea > kMaxTileArea) {
203 SkScalar clampScale = SkScalarSqrt(kMaxTileArea / tileArea);
204 scaledSize.set(scaledSize.width() * clampScale,
205 scaledSize.height() * clampScale);
206 }
207#if SK_SUPPORT_GPU
208 // Scale down the tile size if larger than maxTextureSize for GPU Path or it should fail on create texture
209 if (maxTextureSize) {
210 if (scaledSize.width() > maxTextureSize || scaledSize.height() > maxTextureSize) {
211 SkScalar downScale = maxTextureSize / std::max(scaledSize.width(), scaledSize.height());
212 scaledSize.set(SkScalarFloorToScalar(scaledSize.width() * downScale),
213 SkScalarFloorToScalar(scaledSize.height() * downScale));
214 }
215 }
216#endif
217
218 const SkISize tileSize = scaledSize.toCeil();
219 if (tileSize.isEmpty()) {
220 return SkShaders::Empty();
221 }
222
223 // The actual scale, compensating for rounding & clamping.
224 const SkSize tileScale = SkSize::Make(SkIntToScalar(tileSize.width()) / fTile.width(),
225 SkIntToScalar(tileSize.height()) / fTile.height());
226
227
228 sk_sp<SkColorSpace> imgCS = dstColorSpace ? sk_ref_sp(dstColorSpace): SkColorSpace::MakeSRGB();
229 SkImage::BitDepth bitDepth =
230 dstColorType >= kRGBA_F16Norm_SkColorType
231 ? SkImage::BitDepth::kF16 : SkImage::BitDepth::kU8;
232
233 BitmapShaderKey key(imgCS.get(), bitDepth, fUniqueID, tileScale);
234
235 sk_sp<SkShader> tileShader;
236 if (!SkResourceCache::Find(key, BitmapShaderRec::Visitor, &tileShader)) {
237 SkMatrix tileMatrix;
238 tileMatrix.setRectToRect(fTile, SkRect::MakeIWH(tileSize.width(), tileSize.height()),
239 SkMatrix::kFill_ScaleToFit);
240
241 sk_sp<SkImage> tileImage = SkImage::MakeFromPicture(fPicture, tileSize, &tileMatrix,
242 nullptr, bitDepth, std::move(imgCS));
243 if (!tileImage) {
244 return nullptr;
245 }
246
247 tileShader = tileImage->makeShader(fTmx, fTmy);
248
249 SkResourceCache::Add(new BitmapShaderRec(key, tileShader.get()));
250 fAddedToCache.store(true);
251 }
252
253 if (tileScale.width() != 1 || tileScale.height() != 1) {
254 localMatrix->writable()->preScale(1 / tileScale.width(), 1 / tileScale.height());
255 }
256
257 return tileShader;
258}
259
260bool SkPictureShader::onAppendStages(const SkStageRec& rec) const {
261 auto lm = this->totalLocalMatrix(rec.fLocalM);
262
263 // Keep bitmapShader alive by using alloc instead of stack memory
264 auto& bitmapShader = *rec.fAlloc->make<sk_sp<SkShader>>();
265 bitmapShader = this->refBitmapShader(rec.fMatrixProvider.localToDevice(), &lm,
266 rec.fDstColorType, rec.fDstCS);
267
268 if (!bitmapShader) {
269 return false;
270 }
271
272 SkStageRec localRec = rec;
273 localRec.fLocalM = lm->isIdentity() ? nullptr : lm.get();
274
275 return as_SB(bitmapShader)->appendStages(localRec);
276}
277
278skvm::Color SkPictureShader::onProgram(skvm::Builder* p,
279 skvm::Coord device, skvm::Coord local, skvm::Color paint,
280 const SkMatrixProvider& matrices, const SkMatrix* localM,
281 SkFilterQuality quality, const SkColorInfo& dst,
282 skvm::Uniforms* uniforms, SkArenaAlloc* alloc) const {
283 auto lm = this->totalLocalMatrix(localM);
284
285 // Keep bitmapShader alive by using alloc instead of stack memory
286 auto& bitmapShader = *alloc->make<sk_sp<SkShader>>();
287 bitmapShader = this->refBitmapShader(matrices.localToDevice(), &lm,
288 dst.colorType(), dst.colorSpace());
289 if (!bitmapShader) {
290 return {};
291 }
292
293 return as_SB(bitmapShader)->program(p, device,local, paint,
294 matrices,lm,
295 quality,dst,
296 uniforms,alloc);
297}
298
299/////////////////////////////////////////////////////////////////////////////////////////
300
301#ifdef SK_ENABLE_LEGACY_SHADERCONTEXT
302SkShaderBase::Context* SkPictureShader::onMakeContext(const ContextRec& rec, SkArenaAlloc* alloc)
303const {
304 auto lm = this->totalLocalMatrix(rec.fLocalMatrix);
305 sk_sp<SkShader> bitmapShader = this->refBitmapShader(*rec.fMatrix, &lm, rec.fDstColorType,
306 rec.fDstColorSpace);
307 if (!bitmapShader) {
308 return nullptr;
309 }
310
311 ContextRec localRec = rec;
312 localRec.fLocalMatrix = lm->isIdentity() ? nullptr : lm.get();
313
314 PictureShaderContext* ctx =
315 alloc->make<PictureShaderContext>(*this, localRec, std::move(bitmapShader), alloc);
316 if (nullptr == ctx->fBitmapShaderContext) {
317 ctx = nullptr;
318 }
319 return ctx;
320}
321#endif
322
323/////////////////////////////////////////////////////////////////////////////////////////
324
325SkPictureShader::PictureShaderContext::PictureShaderContext(
326 const SkPictureShader& shader, const ContextRec& rec, sk_sp<SkShader> bitmapShader,
327 SkArenaAlloc* alloc)
328 : INHERITED(shader, rec)
329 , fBitmapShader(std::move(bitmapShader))
330{
331 fBitmapShaderContext = as_SB(fBitmapShader)->makeContext(rec, alloc);
332 //if fBitmapShaderContext is null, we are invalid
333}
334
335uint32_t SkPictureShader::PictureShaderContext::getFlags() const {
336 SkASSERT(fBitmapShaderContext);
337 return fBitmapShaderContext->getFlags();
338}
339
340void SkPictureShader::PictureShaderContext::shadeSpan(int x, int y, SkPMColor dstC[], int count) {
341 SkASSERT(fBitmapShaderContext);
342 fBitmapShaderContext->shadeSpan(x, y, dstC, count);
343}
344
345#if SK_SUPPORT_GPU
346
347std::unique_ptr<GrFragmentProcessor> SkPictureShader::asFragmentProcessor(
348 const GrFPArgs& args) const {
349 int maxTextureSize = 0;
350 if (args.fContext) {
351 maxTextureSize = args.fContext->priv().caps()->maxTextureSize();
352 }
353
354 auto lm = this->totalLocalMatrix(args.fPreLocalMatrix);
355 SkColorType dstColorType = GrColorTypeToSkColorType(args.fDstColorInfo->colorType());
356 if (dstColorType == kUnknown_SkColorType) {
357 dstColorType = kRGBA_8888_SkColorType;
358 }
359 sk_sp<SkShader> bitmapShader(
360 this->refBitmapShader(args.fMatrixProvider.localToDevice(), &lm, dstColorType,
361 args.fDstColorInfo->colorSpace(), maxTextureSize));
362 if (!bitmapShader) {
363 return nullptr;
364 }
365
366 // We want to *reset* args.fPreLocalMatrix, not compose it.
367 GrFPArgs newArgs(args.fContext, args.fMatrixProvider, args.fFilterQuality, args.fDstColorInfo);
368 newArgs.fPreLocalMatrix = lm.get();
369
370 return as_SB(bitmapShader)->asFragmentProcessor(newArgs);
371}
372#endif
373