1 /*
2 * Copyright 2023 Google LLC
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 "include/core/SkBitmap.h"
9 #include "include/core/SkCanvas.h"
10 #include "include/core/SkColorSpace.h"
11 #include "include/core/SkImage.h"
12 #include "include/core/SkSurface.h"
13 #include "include/core/SkYUVAInfo.h"
14 #include "include/core/SkYUVAPixmaps.h"
15 #include "include/gpu/GpuTypes.h"
16 #include "include/gpu/graphite/BackendTexture.h"
17 #include "include/gpu/graphite/Image.h"
18 #include "include/gpu/graphite/Recorder.h"
19 #include "include/gpu/graphite/Surface.h"
20 #include "include/gpu/graphite/YUVABackendTextures.h"
21 #include "include/private/base/SkMutex.h"
22 #include "src/core/SkImageFilterTypes.h"
23 #include "src/core/SkImageFilter_Base.h"
24 #include "src/gpu/RefCntedCallback.h"
25 #include "src/gpu/graphite/Caps.h"
26 #include "src/gpu/graphite/Image_Base_Graphite.h"
27 #include "src/gpu/graphite/Image_Graphite.h"
28 #include "src/gpu/graphite/Image_YUVA_Graphite.h"
29 #include "src/gpu/graphite/Log.h"
30 #include "src/gpu/graphite/RecorderPriv.h"
31 #include "src/gpu/graphite/ResourceProvider.h"
32 #include "src/gpu/graphite/Surface_Graphite.h"
33 #include "src/gpu/graphite/Texture.h"
34 #include "src/gpu/graphite/TextureProxy.h"
35 #include "src/gpu/graphite/TextureProxyView.h"
36 #include "src/gpu/graphite/TextureUtils.h"
37 #include "src/image/SkImage_Base.h"
38 #include "src/image/SkImage_Lazy.h"
39 #include "src/image/SkImage_Picture.h"
40 #include "src/image/SkImage_Raster.h"
41
42 #include <string>
43
44 namespace SkImages {
45
46 using namespace skgpu::graphite;
47
validate_backend_texture(const skgpu::graphite::Caps * caps,const skgpu::graphite::BackendTexture & texture,const SkColorInfo & info)48 static bool validate_backend_texture(const skgpu::graphite::Caps* caps,
49 const skgpu::graphite::BackendTexture& texture,
50 const SkColorInfo& info) {
51 if (!texture.isValid() || texture.dimensions().width() <= 0 ||
52 texture.dimensions().height() <= 0) {
53 return false;
54 }
55
56 if (!SkColorInfoIsValid(info)) {
57 return false;
58 }
59
60 if (!caps->isTexturable(texture.info())) {
61 return false;
62 }
63
64 return caps->areColorTypeAndTextureInfoCompatible(info.colorType(), texture.info());
65 }
66
WrapTexture(Recorder * recorder,const BackendTexture & backendTex,SkColorType ct,SkAlphaType at,sk_sp<SkColorSpace> cs,skgpu::Origin origin,GenerateMipmapsFromBase genMipmaps,TextureReleaseProc releaseP,ReleaseContext releaseC,std::string_view label)67 sk_sp<SkImage> WrapTexture(Recorder* recorder,
68 const BackendTexture& backendTex,
69 SkColorType ct,
70 SkAlphaType at,
71 sk_sp<SkColorSpace> cs,
72 skgpu::Origin origin,
73 GenerateMipmapsFromBase genMipmaps,
74 TextureReleaseProc releaseP,
75 ReleaseContext releaseC,
76 std::string_view label) {
77 auto releaseHelper = skgpu::RefCntedCallback::Make(releaseP, releaseC);
78
79 if (!recorder) {
80 return nullptr;
81 }
82
83 const Caps* caps = recorder->priv().caps();
84
85 SkColorInfo info(ct, at, std::move(cs));
86
87 if (!validate_backend_texture(caps, backendTex, info)) {
88 return nullptr;
89 }
90
91 if (label.empty()) {
92 label = "WrappedImage";
93 }
94
95 sk_sp<Texture> texture =
96 recorder->priv().resourceProvider()->createWrappedTexture(backendTex, std::move(label));
97 if (!texture) {
98 SKGPU_LOG_W("Texture creation failed");
99 return nullptr;
100 }
101 texture->setReleaseCallback(std::move(releaseHelper));
102
103 sk_sp<TextureProxy> proxy = TextureProxy::Wrap(std::move(texture));
104 SkASSERT(proxy);
105
106 skgpu::Swizzle swizzle = caps->getReadSwizzle(ct, backendTex.info());
107 TextureProxyView view(std::move(proxy), swizzle, origin);
108
109 if (genMipmaps == GenerateMipmapsFromBase::kYes) {
110 if (view.proxy()->mipmapped() == skgpu::Mipmapped::kNo) {
111 SKGPU_LOG_W("Failed SkImage:::WrapTexture because asked to generate mipmaps for "
112 "nonmipmapped texture");
113 return nullptr;
114 }
115 if (!GenerateMipmaps(recorder, view.refProxy(), info)) {
116 SKGPU_LOG_W("Failed SkImage::WrapTexture. Could not generate mipmaps.");
117 return nullptr;
118 }
119 }
120
121 return sk_make_sp<skgpu::graphite::Image>(view, info);
122 }
123
WrapTexture(Recorder * recorder,const BackendTexture & backendTex,SkColorType ct,SkAlphaType at,sk_sp<SkColorSpace> cs,skgpu::Origin origin,TextureReleaseProc releaseP,ReleaseContext releaseC,std::string_view label)124 sk_sp<SkImage> WrapTexture(Recorder* recorder,
125 const BackendTexture& backendTex,
126 SkColorType ct,
127 SkAlphaType at,
128 sk_sp<SkColorSpace> cs,
129 skgpu::Origin origin,
130 TextureReleaseProc releaseP,
131 ReleaseContext releaseC,
132 std::string_view label) {
133 return WrapTexture(recorder,
134 backendTex,
135 ct,
136 at,
137 std::move(cs),
138 origin,
139 SkImages::GenerateMipmapsFromBase::kNo,
140 releaseP,
141 releaseC,
142 std::move(label));
143 }
144
WrapTexture(Recorder * recorder,const BackendTexture & backendTex,SkColorType ct,SkAlphaType at,sk_sp<SkColorSpace> cs,TextureReleaseProc releaseP,ReleaseContext releaseC,std::string_view label)145 sk_sp<SkImage> WrapTexture(Recorder* recorder,
146 const BackendTexture& backendTex,
147 SkColorType ct,
148 SkAlphaType at,
149 sk_sp<SkColorSpace> cs,
150 TextureReleaseProc releaseP,
151 ReleaseContext releaseC,
152 std::string_view label) {
153 return WrapTexture(recorder,
154 backendTex,
155 ct,
156 at,
157 std::move(cs),
158 skgpu::Origin::kTopLeft,
159 SkImages::GenerateMipmapsFromBase::kNo,
160 releaseP,
161 releaseC,
162 std::move(label));
163 }
164
PromiseTextureFrom(Recorder * recorder,SkISize dimensions,const TextureInfo & textureInfo,const SkColorInfo & colorInfo,skgpu::Origin origin,Volatile isVolatile,GraphitePromiseTextureFulfillProc fulfillProc,GraphitePromiseImageReleaseProc imageReleaseProc,GraphitePromiseTextureReleaseProc textureReleaseProc,GraphitePromiseImageContext imageContext,std::string_view label)165 sk_sp<SkImage> PromiseTextureFrom(Recorder* recorder,
166 SkISize dimensions,
167 const TextureInfo& textureInfo,
168 const SkColorInfo& colorInfo,
169 skgpu::Origin origin,
170 Volatile isVolatile,
171 GraphitePromiseTextureFulfillProc fulfillProc,
172 GraphitePromiseImageReleaseProc imageReleaseProc,
173 GraphitePromiseTextureReleaseProc textureReleaseProc,
174 GraphitePromiseImageContext imageContext,
175 std::string_view label) {
176 // Our contract is that we will always call the _image_ release proc even on failure.
177 // We use the helper to convey the imageContext, so we need to ensure Make doesn't fail.
178 imageReleaseProc = imageReleaseProc ? imageReleaseProc : [](void*) {};
179 auto releaseHelper = skgpu::RefCntedCallback::Make(imageReleaseProc, imageContext);
180
181 if (!recorder) {
182 SKGPU_LOG_W("Null Recorder");
183 return nullptr;
184 }
185
186 const Caps* caps = recorder->priv().caps();
187
188 SkImageInfo info = SkImageInfo::Make(dimensions, colorInfo);
189 if (!SkImageInfoIsValid(info)) {
190 SKGPU_LOG_W("Invalid SkImageInfo");
191 return nullptr;
192 }
193
194 if (!caps->areColorTypeAndTextureInfoCompatible(colorInfo.colorType(), textureInfo)) {
195 SKGPU_LOG_W("Incompatible SkColorType and TextureInfo");
196 return nullptr;
197 }
198
199 // Non-YUVA promise images use the 'imageContext' for both the release proc and fulfill proc.
200 sk_sp<TextureProxy> proxy = MakePromiseImageLazyProxy(caps,
201 dimensions,
202 textureInfo,
203 isVolatile,
204 std::move(releaseHelper),
205 fulfillProc,
206 imageContext,
207 textureReleaseProc,
208 std::move(label));
209 if (!proxy) {
210 return nullptr;
211 }
212
213 skgpu::Swizzle swizzle = caps->getReadSwizzle(colorInfo.colorType(), textureInfo);
214 TextureProxyView view(std::move(proxy), swizzle, origin);
215 return sk_make_sp<Image>(view, colorInfo);
216 }
217
PromiseTextureFrom(Recorder * recorder,SkISize dimensions,const TextureInfo & textureInfo,const SkColorInfo & colorInfo,Volatile isVolatile,GraphitePromiseTextureFulfillProc fulfillProc,GraphitePromiseImageReleaseProc imageReleaseProc,GraphitePromiseTextureReleaseProc textureReleaseProc,GraphitePromiseImageContext imageContext)218 sk_sp<SkImage> PromiseTextureFrom(Recorder* recorder,
219 SkISize dimensions,
220 const TextureInfo& textureInfo,
221 const SkColorInfo& colorInfo,
222 Volatile isVolatile,
223 GraphitePromiseTextureFulfillProc fulfillProc,
224 GraphitePromiseImageReleaseProc imageReleaseProc,
225 GraphitePromiseTextureReleaseProc textureReleaseProc,
226 GraphitePromiseImageContext imageContext) {
227 return PromiseTextureFrom(recorder,
228 dimensions,
229 textureInfo,
230 colorInfo,
231 skgpu::Origin::kTopLeft,
232 isVolatile,
233 fulfillProc,
234 imageReleaseProc,
235 textureReleaseProc,
236 imageContext);
237 }
238
PromiseTextureFromYUVA(skgpu::graphite::Recorder * recorder,const YUVABackendTextureInfo & backendTextureInfo,sk_sp<SkColorSpace> imageColorSpace,skgpu::graphite::Volatile isVolatile,GraphitePromiseTextureFulfillProc fulfillProc,GraphitePromiseImageReleaseProc imageReleaseProc,GraphitePromiseTextureReleaseProc textureReleaseProc,GraphitePromiseImageContext imageContext,GraphitePromiseTextureFulfillContext planeContexts[],std::string_view label)239 sk_sp<SkImage> PromiseTextureFromYUVA(skgpu::graphite::Recorder* recorder,
240 const YUVABackendTextureInfo& backendTextureInfo,
241 sk_sp<SkColorSpace> imageColorSpace,
242 skgpu::graphite::Volatile isVolatile,
243 GraphitePromiseTextureFulfillProc fulfillProc,
244 GraphitePromiseImageReleaseProc imageReleaseProc,
245 GraphitePromiseTextureReleaseProc textureReleaseProc,
246 GraphitePromiseImageContext imageContext,
247 GraphitePromiseTextureFulfillContext planeContexts[],
248 std::string_view label) {
249 // Our contract is that we will always call the _image_ release proc even on failure.
250 // We use the helper to convey the imageContext, so we need to ensure Make doesn't fail.
251 auto releaseHelper = skgpu::RefCntedCallback::Make(imageReleaseProc, imageContext);
252 if (!recorder) {
253 return nullptr;
254 }
255 // Precompute the dimensions for all promise texture planes
256 SkISize planeDimensions[SkYUVAInfo::kMaxPlanes];
257 if (!backendTextureInfo.yuvaInfo().planeDimensions(planeDimensions)) {
258 return nullptr;
259 }
260
261 std::string labelStr(label);
262 if (labelStr.empty()) {
263 labelStr = "Wrapped_PromiseYUVPlane";
264 } else {
265 labelStr += "_PromiseYUVPlane";
266 }
267
268 TextureProxyView planes[SkYUVAInfo::kMaxPlanes];
269 for (int i = 0; i < backendTextureInfo.numPlanes(); ++i) {
270 sk_sp<TextureProxy> lazyProxy = MakePromiseImageLazyProxy(
271 recorder->priv().caps(),
272 planeDimensions[i],
273 backendTextureInfo.planeTextureInfo(i),
274 isVolatile,
275 releaseHelper,
276 fulfillProc,
277 planeContexts[i],
278 textureReleaseProc,
279 labelStr);
280 // Promise YUVA images assume the default rgba swizzle.
281 planes[i] = TextureProxyView(std::move(lazyProxy));
282 }
283 return Image_YUVA::Make(recorder->priv().caps(), backendTextureInfo.yuvaInfo(),
284 SkSpan(planes), std::move(imageColorSpace));
285 }
286
SubsetTextureFrom(skgpu::graphite::Recorder * recorder,const SkImage * img,const SkIRect & subset,SkImage::RequiredProperties props)287 sk_sp<SkImage> SubsetTextureFrom(skgpu::graphite::Recorder* recorder,
288 const SkImage* img,
289 const SkIRect& subset,
290 SkImage::RequiredProperties props) {
291 if (!recorder || !img) {
292 return nullptr;
293 }
294 auto subsetImg = img->makeSubset(recorder, subset, props);
295 return SkImages::TextureFromImage(recorder, subsetImg, props);
296 }
297
MakeWithFilter(skgpu::graphite::Recorder * recorder,sk_sp<SkImage> src,const SkImageFilter * filter,const SkIRect & subset,const SkIRect & clipBounds,SkIRect * outSubset,SkIPoint * offset)298 sk_sp<SkImage> MakeWithFilter(skgpu::graphite::Recorder* recorder,
299 sk_sp<SkImage> src,
300 const SkImageFilter* filter,
301 const SkIRect& subset,
302 const SkIRect& clipBounds,
303 SkIRect* outSubset,
304 SkIPoint* offset) {
305 if (!recorder || !src || !filter) {
306 return nullptr;
307 }
308
309 sk_sp<skif::Backend> backend = skif::MakeGraphiteBackend(recorder, {}, src->colorType());
310 return as_IFB(filter)->makeImageWithFilter(std::move(backend),
311 std::move(src),
312 subset,
313 clipBounds,
314 outSubset,
315 offset);
316 }
317
generate_picture_texture(skgpu::graphite::Recorder * recorder,const SkImage_Picture * img,const SkImageInfo & info,SkImage::RequiredProperties requiredProps)318 static sk_sp<SkImage> generate_picture_texture(skgpu::graphite::Recorder* recorder,
319 const SkImage_Picture* img,
320 const SkImageInfo& info,
321 SkImage::RequiredProperties requiredProps) {
322 auto mm = requiredProps.fMipmapped ? skgpu::Mipmapped::kYes : skgpu::Mipmapped::kNo;
323 // Use a non-budgeted surface since the image wrapping the surface's texture will be owned by
324 // the client.
325 sk_sp<Surface> surface = Surface::Make(recorder,
326 info,
327 "LazySkImagePictureTexture",
328 skgpu::Budgeted::kNo,
329 mm,
330 SkBackingFit::kExact,
331 img->props());
332
333 if (!surface) {
334 SKGPU_LOG_E("Failed to create Surface");
335 return nullptr;
336 }
337
338 img->replay(surface->getCanvas());
339 // If the surface was created with mipmaps, they will be automatically generated when flushing
340 // the tasks when 'surface' goes out of scope.
341 return surface->asImage();
342 }
343
344 /*
345 * We only have 2 ways to create a Graphite-backed image.
346 *
347 * 1. Ask the generator to natively create one
348 * 2. Ask the generator to return RGB(A) data, which the GPU can convert
349 */
make_texture_image_from_lazy(skgpu::graphite::Recorder * recorder,const SkImage_Lazy * img,SkImage::RequiredProperties requiredProps)350 static sk_sp<SkImage> make_texture_image_from_lazy(skgpu::graphite::Recorder* recorder,
351 const SkImage_Lazy* img,
352 SkImage::RequiredProperties requiredProps) {
353 // 1. Ask the generator to natively create one.
354 {
355 if (img->type() == SkImage_Base::Type::kLazyPicture) {
356 sk_sp<SkImage> newImage =
357 generate_picture_texture(recorder,
358 static_cast<const SkImage_Picture*>(img),
359 img->imageInfo(),
360 requiredProps);
361 if (newImage) {
362 SkASSERT(as_IB(newImage)->isGraphiteBacked());
363 return newImage;
364 }
365 }
366 // There is not an analog to GrTextureGenerator for Graphite yet, but if there was,
367 // we would want to call it here.
368 }
369
370 // 2. Ask the generator to return a bitmap, which the GPU can convert.
371 {
372 SkBitmap bitmap;
373 if (img->getROPixels(nullptr, &bitmap, SkImage_Lazy::CachingHint::kDisallow_CachingHint)) {
374 return skgpu::graphite::MakeFromBitmap(recorder,
375 img->imageInfo().colorInfo(),
376 bitmap,
377 nullptr,
378 skgpu::Budgeted::kNo,
379 requiredProps,
380 "LazySkImageBitmapTexture");
381 }
382 }
383
384 return nullptr;
385 }
386
TextureFromImage(skgpu::graphite::Recorder * recorder,const SkImage * image,SkImage::RequiredProperties requiredProps)387 sk_sp<SkImage> TextureFromImage(skgpu::graphite::Recorder* recorder,
388 const SkImage* image,
389 SkImage::RequiredProperties requiredProps) {
390 if (!recorder || !image) {
391 return nullptr;
392 }
393 if (image->dimensions().area() <= 1) {
394 requiredProps.fMipmapped = false;
395 }
396
397 auto ib = as_IB(image);
398 SkASSERT(!ib->isGaneshBacked());
399
400 if (ib->isRasterBacked()) {
401 auto raster = static_cast<const SkImage_Raster*>(ib);
402 return skgpu::graphite::MakeFromBitmap(recorder,
403 raster->imageInfo().colorInfo(),
404 raster->bitmap(),
405 raster->refMips(),
406 skgpu::Budgeted::kNo,
407 requiredProps,
408 "RasterBitmapTexture");
409 }
410 if (ib->isLazyGenerated()) {
411 return make_texture_image_from_lazy(
412 recorder, static_cast<const SkImage_Lazy*>(ib), requiredProps);
413 }
414 SkASSERT(ib->isGraphiteBacked());
415 return ib->makeSubset(recorder, ib->bounds(), requiredProps);
416 }
417
TextureFromYUVAPixmaps(Recorder * recorder,const SkYUVAPixmaps & pixmaps,SkImage::RequiredProperties requiredProps,bool limitToMaxTextureSize,sk_sp<SkColorSpace> imageColorSpace,std::string_view label)418 sk_sp<SkImage> TextureFromYUVAPixmaps(Recorder* recorder,
419 const SkYUVAPixmaps& pixmaps,
420 SkImage::RequiredProperties requiredProps,
421 bool limitToMaxTextureSize,
422 sk_sp<SkColorSpace> imageColorSpace,
423 std::string_view label) {
424 if (!recorder) {
425 return nullptr;
426 }
427
428 // Determine if we have to resize the pixmaps
429 const int maxTextureSize = recorder->priv().caps()->maxTextureSize();
430 const int maxDim = std::max(pixmaps.yuvaInfo().width(), pixmaps.yuvaInfo().height());
431
432 SkYUVAPixmapInfo finalInfo = pixmaps.pixmapsInfo();
433 if (maxDim > maxTextureSize) {
434 if (!limitToMaxTextureSize) {
435 return nullptr;
436 }
437 float scale = static_cast<float>(maxTextureSize) / maxDim;
438 SkISize newDimensions = {
439 std::min(static_cast<int>(pixmaps.yuvaInfo().width() * scale), maxTextureSize),
440 std::min(static_cast<int>(pixmaps.yuvaInfo().height() * scale), maxTextureSize)};
441 finalInfo = SkYUVAPixmapInfo(pixmaps.yuvaInfo().makeDimensions(newDimensions),
442 pixmaps.dataType(),
443 /*rowBytes=*/nullptr);
444 }
445
446 std::string labelStr(label);
447 if (labelStr.empty()) {
448 labelStr = "YUVRasterBitmapPlane";
449 } else {
450 labelStr += "_YUVBitmapPlane";
451 }
452
453 auto mipmapped = requiredProps.fMipmapped ? skgpu::Mipmapped::kYes : skgpu::Mipmapped::kNo;
454 TextureProxyView planes[SkYUVAInfo::kMaxPlanes];
455 for (int i = 0; i < finalInfo.yuvaInfo().numPlanes(); ++i) {
456 SkBitmap bmp;
457 if (maxDim > maxTextureSize) {
458 // Rescale the data before uploading
459 if (!bmp.tryAllocPixels(finalInfo.planeInfo(i)) ||
460 !pixmaps.plane(i).scalePixels(bmp.pixmap(), SkFilterMode::kLinear)) {
461 return nullptr;
462 }
463 } else {
464 // Use original data to upload
465 if (!bmp.installPixels(pixmaps.plane(i))) {
466 return nullptr;
467 }
468 }
469
470 auto [view, _] = MakeBitmapProxyView(recorder, bmp, /*mipmapsIn=*/nullptr,
471 mipmapped, skgpu::Budgeted::kNo,
472 labelStr);
473 planes[i] = std::move(view);
474 }
475 return Image_YUVA::Make(recorder->priv().caps(), finalInfo.yuvaInfo(),
476 SkSpan(planes), std::move(imageColorSpace));
477 }
478
TextureFromYUVATextures(Recorder * recorder,const YUVABackendTextures & yuvaTextures,sk_sp<SkColorSpace> imageColorSpace,TextureReleaseProc releaseP,ReleaseContext releaseC,std::string_view label)479 sk_sp<SkImage> TextureFromYUVATextures(Recorder* recorder,
480 const YUVABackendTextures& yuvaTextures,
481 sk_sp<SkColorSpace> imageColorSpace,
482 TextureReleaseProc releaseP,
483 ReleaseContext releaseC,
484 std::string_view label) {
485 auto releaseHelper = skgpu::RefCntedCallback::Make(releaseP, releaseC);
486 if (!recorder) {
487 return nullptr;
488 }
489
490 std::string labelStr(label);
491 if (labelStr.empty()) {
492 labelStr = "Wrapped_YUVPlane";
493 } else {
494 labelStr += "_YUVPlane";
495 }
496
497 TextureProxyView planes[SkYUVAInfo::kMaxPlanes];
498 for (int i = 0; i < yuvaTextures.yuvaInfo().numPlanes(); ++i) {
499 sk_sp<Texture> texture = recorder->priv().resourceProvider()->createWrappedTexture(
500 yuvaTextures.planeTexture(i), labelStr);
501 if (!texture) {
502 SKGPU_LOG_W("Failed to wrap backend texture for YUVA plane %d", i);
503 return nullptr;
504 }
505 texture->setReleaseCallback(releaseHelper);
506 planes[i] = TextureProxyView(TextureProxy::Wrap(std::move(texture)));
507 }
508
509 return Image_YUVA::Make(recorder->priv().caps(), yuvaTextures.yuvaInfo(),
510 SkSpan(planes), std::move(imageColorSpace));
511 }
512
TextureFromYUVAImages(Recorder * recorder,const SkYUVAInfo & yuvaInfo,SkSpan<const sk_sp<SkImage>> images,sk_sp<SkColorSpace> imageColorSpace)513 sk_sp<SkImage> TextureFromYUVAImages(Recorder* recorder,
514 const SkYUVAInfo& yuvaInfo,
515 SkSpan<const sk_sp<SkImage>> images,
516 sk_sp<SkColorSpace> imageColorSpace) {
517 // This factory is just a view of the images, so does not actually trigger any work on the
518 // recorder. It is just used to provide the Caps.
519 return Image_YUVA::WrapImages(recorder->priv().caps(), yuvaInfo, images, imageColorSpace);
520 }
521
522 } // namespace SkImages
523