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/base/SkArenaAlloc.h"
14 #include "src/core/SkImageInfoPriv.h"
15 #include "src/core/SkImagePriv.h"
16 #include "src/core/SkMatrixPriv.h"
17 #include "src/core/SkMatrixProvider.h"
18 #include "src/core/SkMatrixUtils.h"
19 #include "src/core/SkPicturePriv.h"
20 #include "src/core/SkRasterPipeline.h"
21 #include "src/core/SkReadBuffer.h"
22 #include "src/core/SkResourceCache.h"
23 #include "src/core/SkVM.h"
24 #include "src/shaders/SkBitmapProcShader.h"
25 #include "src/shaders/SkImageShader.h"
26 #include "src/shaders/SkLocalMatrixShader.h"
27
28 #if defined(SK_GANESH)
29 #include "include/gpu/GrDirectContext.h"
30 #include "include/gpu/GrRecordingContext.h"
31 #include "src/gpu/ganesh/GrCaps.h"
32 #include "src/gpu/ganesh/GrColorInfo.h"
33 #include "src/gpu/ganesh/GrFPArgs.h"
34 #include "src/gpu/ganesh/GrFragmentProcessor.h"
35 #include "src/gpu/ganesh/GrRecordingContextPriv.h"
36 #include "src/gpu/ganesh/SkGr.h"
37 #include "src/gpu/ganesh/effects/GrTextureEffect.h"
38 #include "src/image/SkImage_Base.h"
39 #include "src/shaders/SkLocalMatrixShader.h"
40 #endif
41
42 #if defined(SK_GRAPHITE)
43 #include "src/gpu/graphite/Caps.h"
44 #include "src/gpu/graphite/KeyContext.h"
45 #include "src/gpu/graphite/KeyHelpers.h"
46 #include "src/gpu/graphite/PaintParamsKey.h"
47 #include "src/gpu/graphite/RecorderPriv.h"
48 #endif
49
makeShader(SkTileMode tmx,SkTileMode tmy,SkFilterMode filter,const SkMatrix * localMatrix,const SkRect * tile) const50 sk_sp<SkShader> SkPicture::makeShader(SkTileMode tmx, SkTileMode tmy, SkFilterMode filter,
51 const SkMatrix* localMatrix, const SkRect* tile) const {
52 if (localMatrix && !localMatrix->invert(nullptr)) {
53 return nullptr;
54 }
55 return SkPictureShader::Make(sk_ref_sp(this), tmx, tmy, filter, localMatrix, tile);
56 }
57
58 namespace {
59 static unsigned gImageFromPictureKeyNamespaceLabel;
60
61 struct ImageFromPictureKey : public SkResourceCache::Key {
62 public:
ImageFromPictureKey__anondb89c27f0111::ImageFromPictureKey63 ImageFromPictureKey(SkColorSpace* colorSpace, SkColorType colorType,
64 uint32_t pictureID, const SkRect& subset,
65 SkSize scale, const SkSurfaceProps& surfaceProps)
66 : fColorSpaceXYZHash(colorSpace->toXYZD50Hash())
67 , fColorSpaceTransferFnHash(colorSpace->transferFnHash())
68 , fColorType(static_cast<uint32_t>(colorType))
69 , fSubset(subset)
70 , fScale(scale)
71 , fSurfaceProps(surfaceProps)
72 {
73 static const size_t keySize = sizeof(fColorSpaceXYZHash) +
74 sizeof(fColorSpaceTransferFnHash) +
75 sizeof(fColorType) +
76 sizeof(fSubset) +
77 sizeof(fScale) +
78 sizeof(fSurfaceProps);
79 // This better be packed.
80 SkASSERT(sizeof(uint32_t) * (&fEndOfStruct - &fColorSpaceXYZHash) == keySize);
81 this->init(&gImageFromPictureKeyNamespaceLabel,
82 SkPicturePriv::MakeSharedID(pictureID),
83 keySize);
84 }
85
86 private:
87 uint32_t fColorSpaceXYZHash;
88 uint32_t fColorSpaceTransferFnHash;
89 uint32_t fColorType;
90 SkRect fSubset;
91 SkSize fScale;
92 SkSurfaceProps fSurfaceProps;
93
94 SkDEBUGCODE(uint32_t fEndOfStruct;)
95 };
96
97 struct ImageFromPictureRec : public SkResourceCache::Rec {
ImageFromPictureRec__anondb89c27f0111::ImageFromPictureRec98 ImageFromPictureRec(const ImageFromPictureKey& key, sk_sp<SkImage> image)
99 : fKey(key)
100 , fImage(std::move(image)) {}
101
102 ImageFromPictureKey fKey;
103 sk_sp<SkImage> fImage;
104
getKey__anondb89c27f0111::ImageFromPictureRec105 const Key& getKey() const override { return fKey; }
bytesUsed__anondb89c27f0111::ImageFromPictureRec106 size_t bytesUsed() const override {
107 // Just the record overhead -- the actual pixels are accounted by SkImage_Lazy.
108 return sizeof(fKey) + (size_t)fImage->width() * fImage->height() * 4;
109 }
getCategory__anondb89c27f0111::ImageFromPictureRec110 const char* getCategory() const override { return "bitmap-shader"; }
diagnostic_only_getDiscardable__anondb89c27f0111::ImageFromPictureRec111 SkDiscardableMemory* diagnostic_only_getDiscardable() const override { return nullptr; }
112
Visitor__anondb89c27f0111::ImageFromPictureRec113 static bool Visitor(const SkResourceCache::Rec& baseRec, void* contextShader) {
114 const ImageFromPictureRec& rec = static_cast<const ImageFromPictureRec&>(baseRec);
115 sk_sp<SkImage>* result = reinterpret_cast<sk_sp<SkImage>*>(contextShader);
116
117 *result = rec.fImage;
118 return true;
119 }
120 };
121
122 } // namespace
123
SkPictureShader(sk_sp<SkPicture> picture,SkTileMode tmx,SkTileMode tmy,SkFilterMode filter,const SkRect * tile)124 SkPictureShader::SkPictureShader(sk_sp<SkPicture> picture,
125 SkTileMode tmx,
126 SkTileMode tmy,
127 SkFilterMode filter,
128 const SkRect* tile)
129 : fPicture(std::move(picture))
130 , fTile(tile ? *tile : fPicture->cullRect())
131 , fTmx(tmx)
132 , fTmy(tmy)
133 , fFilter(filter) {}
134
Make(sk_sp<SkPicture> picture,SkTileMode tmx,SkTileMode tmy,SkFilterMode filter,const SkMatrix * lm,const SkRect * tile)135 sk_sp<SkShader> SkPictureShader::Make(sk_sp<SkPicture> picture, SkTileMode tmx, SkTileMode tmy,
136 SkFilterMode filter, const SkMatrix* lm, const SkRect* tile) {
137 if (!picture || picture->cullRect().isEmpty() || (tile && tile->isEmpty())) {
138 return SkShaders::Empty();
139 }
140 return SkLocalMatrixShader::MakeWrapped<SkPictureShader>(lm,
141 std::move(picture),
142 tmx, tmy,
143 filter,
144 tile);
145 }
146
CreateProc(SkReadBuffer & buffer)147 sk_sp<SkFlattenable> SkPictureShader::CreateProc(SkReadBuffer& buffer) {
148 SkMatrix lm;
149 if (buffer.isVersionLT(SkPicturePriv::Version::kNoShaderLocalMatrix)) {
150 buffer.readMatrix(&lm);
151 }
152 auto tmx = buffer.read32LE(SkTileMode::kLastTileMode);
153 auto tmy = buffer.read32LE(SkTileMode::kLastTileMode);
154 SkRect tile = buffer.readRect();
155
156 sk_sp<SkPicture> picture;
157
158 SkFilterMode filter = SkFilterMode::kNearest;
159 if (buffer.isVersionLT(SkPicturePriv::kNoFilterQualityShaders_Version)) {
160 if (buffer.isVersionLT(SkPicturePriv::kPictureShaderFilterParam_Version)) {
161 bool didSerialize = buffer.readBool();
162 if (didSerialize) {
163 picture = SkPicturePriv::MakeFromBuffer(buffer);
164 }
165 } else {
166 unsigned legacyFilter = buffer.read32();
167 if (legacyFilter <= (unsigned)SkFilterMode::kLast) {
168 filter = (SkFilterMode)legacyFilter;
169 }
170 picture = SkPicturePriv::MakeFromBuffer(buffer);
171 }
172 } else {
173 filter = buffer.read32LE(SkFilterMode::kLast);
174 picture = SkPicturePriv::MakeFromBuffer(buffer);
175 }
176 return SkPictureShader::Make(picture, tmx, tmy, filter, &lm, &tile);
177 }
178
flatten(SkWriteBuffer & buffer) const179 void SkPictureShader::flatten(SkWriteBuffer& buffer) const {
180 buffer.write32((unsigned)fTmx);
181 buffer.write32((unsigned)fTmy);
182 buffer.writeRect(fTile);
183 buffer.write32((unsigned)fFilter);
184 SkPicturePriv::Flatten(fPicture, buffer);
185 }
186
ref_or_srgb(SkColorSpace * cs)187 static sk_sp<SkColorSpace> ref_or_srgb(SkColorSpace* cs) {
188 return cs ? sk_ref_sp(cs) : SkColorSpace::MakeSRGB();
189 }
190
191 struct CachedImageInfo {
192 bool success;
193 SkSize tileScale; // Additional scale factors to apply when sampling image.
194 SkMatrix matrixForDraw; // Matrix used to produce an image from the picture
195 SkImageInfo imageInfo;
196 SkSurfaceProps props;
197
MakeCachedImageInfo198 static CachedImageInfo Make(const SkRect& bounds,
199 const SkMatrix& totalM,
200 SkColorType dstColorType,
201 SkColorSpace* dstColorSpace,
202 const int maxTextureSize,
203 const SkSurfaceProps& propsIn) {
204 SkSurfaceProps props = propsIn.cloneWithPixelGeometry(kUnknown_SkPixelGeometry);
205
206 const SkSize scaledSize = [&]() {
207 SkSize size;
208 // Use a rotation-invariant scale
209 if (!totalM.decomposeScale(&size, nullptr)) {
210 SkPoint center = {bounds.centerX(), bounds.centerY()};
211 SkScalar area = SkMatrixPriv::DifferentialAreaScale(totalM, center);
212 if (!SkScalarIsFinite(area) || SkScalarNearlyZero(area)) {
213 size = {1, 1}; // ill-conditioned matrix
214 } else {
215 size.fWidth = size.fHeight = SkScalarSqrt(area);
216 }
217 }
218 size.fWidth *= bounds.width();
219 size.fHeight *= bounds.height();
220
221 // Clamp the tile size to about 4M pixels
222 static const SkScalar kMaxTileArea = 2048 * 2048;
223 SkScalar tileArea = size.width() * size.height();
224 if (tileArea > kMaxTileArea) {
225 SkScalar clampScale = SkScalarSqrt(kMaxTileArea / tileArea);
226 size.set(size.width() * clampScale, size.height() * clampScale);
227 }
228
229 // Scale down the tile size if larger than maxTextureSize for GPU path
230 // or it should fail on create texture
231 if (maxTextureSize) {
232 if (size.width() > maxTextureSize || size.height() > maxTextureSize) {
233 SkScalar downScale = maxTextureSize / std::max(size.width(),
234 size.height());
235 size.set(SkScalarFloorToScalar(size.width() * downScale),
236 SkScalarFloorToScalar(size.height() * downScale));
237 }
238 }
239 return size;
240 }();
241
242 const SkISize tileSize = scaledSize.toCeil();
243 if (tileSize.isEmpty()) {
244 return {false, {}, {}, {}, {}};
245 }
246
247 const SkSize tileScale = {
248 tileSize.width() / bounds.width(), tileSize.height() / bounds.height()
249 };
250 auto imgCS = ref_or_srgb(dstColorSpace);
251 const SkColorType imgCT = SkColorTypeMaxBitsPerChannel(dstColorType) <= 8
252 ? kRGBA_8888_SkColorType
253 : kRGBA_F16Norm_SkColorType;
254
255 return {true,
256 tileScale,
257 SkMatrix::RectToRect(bounds, SkRect::MakeIWH(tileSize.width(), tileSize.height())),
258 SkImageInfo::Make(tileSize, imgCT, kPremul_SkAlphaType, imgCS),
259 props};
260 }
261
makeImageCachedImageInfo262 sk_sp<SkImage> makeImage(sk_sp<SkSurface> surf, const SkPicture* pict) const {
263 if (!surf) {
264 return nullptr;
265 }
266 auto canvas = surf->getCanvas();
267 canvas->concat(matrixForDraw);
268 canvas->drawPicture(pict);
269 return surf->makeImageSnapshot();
270 }
271 };
272
273 // Returns a cached image shader, which wraps a single picture tile at the given
274 // CTM/local matrix. Also adjusts the local matrix for tile scaling.
rasterShader(const SkMatrix & totalM,SkColorType dstColorType,SkColorSpace * dstColorSpace,const SkSurfaceProps & propsIn) const275 sk_sp<SkShader> SkPictureShader::rasterShader(const SkMatrix& totalM,
276 SkColorType dstColorType,
277 SkColorSpace* dstColorSpace,
278 const SkSurfaceProps& propsIn) const {
279 const int maxTextureSize_NotUsedForCPU = 0;
280 CachedImageInfo info = CachedImageInfo::Make(fTile,
281 totalM,
282 dstColorType, dstColorSpace,
283 maxTextureSize_NotUsedForCPU,
284 propsIn);
285 if (!info.success) {
286 return nullptr;
287 }
288
289 ImageFromPictureKey key(info.imageInfo.colorSpace(), info.imageInfo.colorType(),
290 fPicture->uniqueID(), fTile, info.tileScale, info.props);
291
292 sk_sp<SkImage> image;
293 if (!SkResourceCache::Find(key, ImageFromPictureRec::Visitor, &image)) {
294 image = info.makeImage(SkSurface::MakeRaster(info.imageInfo, &info.props), fPicture.get());
295 if (!image) {
296 return nullptr;
297 }
298
299 SkResourceCache::Add(new ImageFromPictureRec(key, image));
300 SkPicturePriv::AddedToCache(fPicture.get());
301 }
302 // Scale the image to the original picture size.
303 auto lm = SkMatrix::Scale(1.f/info.tileScale.width(), 1.f/info.tileScale.height());
304 return image->makeShader(fTmx, fTmy, SkSamplingOptions(fFilter), &lm);
305 }
306
appendStages(const SkStageRec & rec,const MatrixRec & mRec) const307 bool SkPictureShader::appendStages(const SkStageRec& rec, const MatrixRec& mRec) const {
308 // Keep bitmapShader alive by using alloc instead of stack memory
309 auto& bitmapShader = *rec.fAlloc->make<sk_sp<SkShader>>();
310 // We don't check whether the total local matrix is valid here because we have to assume *some*
311 // mapping to make an image. It could be wildly wrong if there is a runtime shader transforming
312 // the coordinates in a manner we don't know about here. However, that is a fundamental problem
313 // with the technique of converting a picture to an image to implement this shader.
314 bitmapShader = this->rasterShader(mRec.totalMatrix(),
315 rec.fDstColorType,
316 rec.fDstCS,
317 rec.fSurfaceProps);
318 if (!bitmapShader) {
319 return false;
320 }
321 return as_SB(bitmapShader)->appendStages(rec, mRec);
322 }
323
program(skvm::Builder * p,skvm::Coord device,skvm::Coord local,skvm::Color paint,const MatrixRec & mRec,const SkColorInfo & dst,skvm::Uniforms * uniforms,SkArenaAlloc * alloc) const324 skvm::Color SkPictureShader::program(skvm::Builder* p,
325 skvm::Coord device,
326 skvm::Coord local,
327 skvm::Color paint,
328 const MatrixRec& mRec,
329 const SkColorInfo& dst,
330 skvm::Uniforms* uniforms,
331 SkArenaAlloc* alloc) const {
332 // TODO: We'll need additional plumbing to get the correct props from our callers.
333 SkSurfaceProps props{};
334
335 // Keep bitmapShader alive by using alloc instead of stack memory
336 auto& bitmapShader = *alloc->make<sk_sp<SkShader>>();
337 bitmapShader = this->rasterShader(mRec.totalMatrix(), dst.colorType(), dst.colorSpace(), props);
338 if (!bitmapShader) {
339 return {};
340 }
341
342 return as_SB(bitmapShader)->program(p, device, local, paint, mRec, dst, uniforms, alloc);
343 }
344
345 /////////////////////////////////////////////////////////////////////////////////////////
346
347 #ifdef SK_ENABLE_LEGACY_SHADERCONTEXT
onMakeContext(const ContextRec & rec,SkArenaAlloc * alloc) const348 SkShaderBase::Context* SkPictureShader::onMakeContext(const ContextRec& rec, SkArenaAlloc* alloc)
349 const {
350 const auto& vm = *rec.fMatrix;
351 const auto* lm = rec.fLocalMatrix;
352 const auto totalM = lm ? SkMatrix::Concat(vm, *lm) : vm;
353 sk_sp<SkShader> bitmapShader = this->rasterShader(totalM, rec.fDstColorType,
354 rec.fDstColorSpace, rec.fProps);
355 if (!bitmapShader) {
356 return nullptr;
357 }
358
359 return as_SB(bitmapShader)->makeContext(rec, alloc);
360 }
361 #endif
362
363 /////////////////////////////////////////////////////////////////////////////////////////
364
365 #if defined(SK_GANESH)
366
367 #include "src/gpu/ganesh/GrProxyProvider.h"
368
asFragmentProcessor(const GrFPArgs & args,const MatrixRec & mRec) const369 std::unique_ptr<GrFragmentProcessor> SkPictureShader::asFragmentProcessor(
370 const GrFPArgs& args, const MatrixRec& mRec) const {
371 auto ctx = args.fContext;
372 SkColorType dstColorType = GrColorTypeToSkColorType(args.fDstColorInfo->colorType());
373 if (dstColorType == kUnknown_SkColorType) {
374 dstColorType = kRGBA_8888_SkColorType;
375 }
376
377 auto dstCS = ref_or_srgb(args.fDstColorInfo->colorSpace());
378
379 auto info = CachedImageInfo::Make(fTile,
380 mRec.totalMatrix(),
381 dstColorType,
382 dstCS.get(),
383 ctx->priv().caps()->maxTextureSize(),
384 args.fSurfaceProps);
385 if (!info.success) {
386 return nullptr;
387 }
388
389 // Gotta be sure the GPU can support our requested colortype (might be FP16)
390 if (!ctx->colorTypeSupportedAsSurface(info.imageInfo.colorType())) {
391 info.imageInfo = info.imageInfo.makeColorType(kRGBA_8888_SkColorType);
392 }
393
394 static const skgpu::UniqueKey::Domain kDomain = skgpu::UniqueKey::GenerateDomain();
395 skgpu::UniqueKey key;
396 std::tuple keyData = {
397 dstCS->toXYZD50Hash(),
398 dstCS->transferFnHash(),
399 static_cast<uint32_t>(dstColorType),
400 fPicture->uniqueID(),
401 fTile,
402 info.tileScale,
403 info.props
404 };
405 skgpu::UniqueKey::Builder builder(&key, kDomain, sizeof(keyData)/sizeof(uint32_t),
406 "Picture Shader Image");
407 memcpy(&builder[0], &keyData, sizeof(keyData));
408 builder.finish();
409
410 GrProxyProvider* provider = ctx->priv().proxyProvider();
411 GrSurfaceProxyView view;
412 if (auto proxy = provider->findOrCreateProxyByUniqueKey(key)) {
413 view = GrSurfaceProxyView(proxy, kTopLeft_GrSurfaceOrigin, skgpu::Swizzle());
414 } else {
415 const int msaaSampleCount = 0;
416 const bool createWithMips = false;
417 auto image = info.makeImage(SkSurface::MakeRenderTarget(ctx,
418 skgpu::Budgeted::kYes,
419 info.imageInfo,
420 msaaSampleCount,
421 kTopLeft_GrSurfaceOrigin,
422 &info.props,
423 createWithMips),
424 fPicture.get());
425 if (!image) {
426 return nullptr;
427 }
428 auto [v, ct] = as_IB(image)->asView(ctx, GrMipmapped::kNo);
429 view = std::move(v);
430 provider->assignUniqueKeyToProxy(key, view.asTextureProxy());
431 }
432
433 const GrSamplerState sampler(static_cast<GrSamplerState::WrapMode>(fTmx),
434 static_cast<GrSamplerState::WrapMode>(fTmy),
435 fFilter);
436 auto fp = GrTextureEffect::Make(std::move(view),
437 kPremul_SkAlphaType,
438 SkMatrix::I(),
439 sampler,
440 *ctx->priv().caps());
441 SkMatrix scale = SkMatrix::Scale(info.tileScale.width(), info.tileScale.height());
442 bool success;
443 std::tie(success, fp) = mRec.apply(std::move(fp), scale);
444 return success ? std::move(fp) : nullptr;
445 }
446 #endif
447
448 #if defined(SK_GRAPHITE)
addToKey(const skgpu::graphite::KeyContext & keyContext,skgpu::graphite::PaintParamsKeyBuilder * builder,skgpu::graphite::PipelineDataGatherer * gatherer) const449 void SkPictureShader::addToKey(const skgpu::graphite::KeyContext& keyContext,
450 skgpu::graphite::PaintParamsKeyBuilder* builder,
451 skgpu::graphite::PipelineDataGatherer* gatherer) const {
452
453 using namespace skgpu::graphite;
454
455 Recorder* recorder = keyContext.recorder();
456 const Caps* caps = recorder->priv().caps();
457
458 // TODO: We'll need additional plumbing to get the correct props from our callers. In
459 // particular we'll need to expand the keyContext to have the surfaceProps, the dstColorType
460 // and dstColorSpace.
461 SkSurfaceProps props{};
462
463 SkMatrix totalM = keyContext.local2Dev().asM33();
464 if (keyContext.localMatrix()) {
465 totalM.preConcat(*keyContext.localMatrix());
466 }
467 CachedImageInfo info = CachedImageInfo::Make(fTile,
468 totalM,
469 /* dstColorType= */ kRGBA_8888_SkColorType,
470 /* dstColorSpace= */ nullptr,
471 caps->maxTextureSize(),
472 props);
473 if (!info.success) {
474 SolidColorShaderBlock::BeginBlock(keyContext, builder, gatherer, {1, 0, 0, 1});
475 builder->endBlock();
476 return;
477 }
478
479 // TODO: right now we're explicitly not caching here. We could expand the ImageProvider
480 // API to include already Graphite-backed images, add a Recorder-local cache or add
481 // rendered-picture images to the global cache.
482 sk_sp<SkImage> img = info.makeImage(SkSurface::MakeGraphite(recorder, info.imageInfo,
483 skgpu::Mipmapped::kNo, &info.props),
484 fPicture.get());
485 if (!img) {
486 SolidColorShaderBlock::BeginBlock(keyContext, builder, gatherer, {1, 0, 0, 1});
487 builder->endBlock();
488 return;
489 }
490
491 const auto shaderLM = SkMatrix::Scale(1.f/info.tileScale.width(), 1.f/info.tileScale.height());
492 sk_sp<SkShader> shader = img->makeShader(fTmx, fTmy, SkSamplingOptions(fFilter), &shaderLM);
493 if (!shader) {
494 SolidColorShaderBlock::BeginBlock(keyContext, builder, gatherer, {1, 0, 0, 1});
495 builder->endBlock();
496 return;
497 }
498
499 as_SB(shader)->addToKey(keyContext, builder, gatherer);
500 }
501 #endif // SK_GRAPHITE
502