| /* | |
| * Copyright 2023 Google LLC | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #include "include/core/SkBitmap.h" | |
| #include "include/core/SkCanvas.h" | |
| #include "include/core/SkColorSpace.h" | |
| #include "include/core/SkImage.h" | |
| #include "include/core/SkSurface.h" | |
| #include "include/core/SkYUVAInfo.h" | |
| #include "include/core/SkYUVAPixmaps.h" | |
| #include "include/gpu/GpuTypes.h" | |
| #include "include/gpu/graphite/BackendTexture.h" | |
| #include "include/gpu/graphite/Image.h" | |
| #include "include/gpu/graphite/Recorder.h" | |
| #include "include/gpu/graphite/Surface.h" | |
| #include "include/gpu/graphite/YUVABackendTextures.h" | |
| #include "include/private/SkAttributes.h" | |
| #include "include/private/SkLog.h" | |
| #include "src/core/SkImageFilterTypes.h" | |
| #include "src/core/SkImageFilter_Base.h" | |
| #include "src/gpu/RefCntedCallback.h" | |
| #include "src/gpu/graphite/Caps.h" | |
| #include "src/gpu/graphite/Image_Base_Graphite.h" | |
| #include "src/gpu/graphite/Image_Graphite.h" | |
| #include "src/gpu/graphite/Image_YUVA_Graphite.h" | |
| #include "src/gpu/graphite/RecorderPriv.h" | |
| #include "src/gpu/graphite/ResourceProvider.h" | |
| #include "src/gpu/graphite/Surface_Graphite.h" | |
| #include "src/gpu/graphite/Texture.h" | |
| #include "src/gpu/graphite/TextureInfoPriv.h" | |
| #include "src/gpu/graphite/TextureProxy.h" | |
| #include "src/gpu/graphite/TextureProxyView.h" | |
| #include "src/gpu/graphite/TextureUtils.h" | |
| #include "src/image/SkImage_Base.h" | |
| #include "src/image/SkImage_Lazy.h" | |
| #include "src/image/SkImage_Picture.h" | |
| #include "src/image/SkImage_Raster.h" | |
| #include <string> | |
| namespace SkImages { | |
| using namespace skgpu::graphite; | |
| namespace { | |
| bool validate_backend_texture(const Caps* caps, | |
| const BackendTexture& texture, | |
| const SkColorInfo& info) { | |
| if (!texture.isValid() || texture.dimensions().width() <= 0 || | |
| texture.dimensions().height() <= 0) { | |
| return false; | |
| } | |
| if (!SkColorInfoIsValid(info)) { | |
| return false; | |
| } | |
| if (!caps->isTexturable(texture.info())) { | |
| return false; | |
| } | |
| return AreColorTypeAndFormatCompatible(info.colorType(), | |
| TextureInfoPriv::ViewFormat(texture.info())); | |
| } | |
| } // anonymous namespace | |
| sk_sp<SkImage> 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) { | |
| auto releaseHelper = skgpu::RefCntedCallback::Make(releaseP, releaseC); | |
| if (!recorder) { | |
| return nullptr; | |
| } | |
| const Caps* caps = recorder->priv().caps(); | |
| SkColorInfo info(ct, at, std::move(cs)); | |
| if (!validate_backend_texture(caps, backendTex, info)) { | |
| return nullptr; | |
| } | |
| if (label.empty()) { | |
| label = "WrappedImage"; | |
| } | |
| sk_sp<Texture> texture = | |
| recorder->priv().resourceProvider()->createWrappedTexture(backendTex, label); | |
| if (!texture) { | |
| SKIA_LOG_W("Texture creation failed"); | |
| return nullptr; | |
| } | |
| texture->setReleaseCallback(std::move(releaseHelper)); | |
| sk_sp<TextureProxy> proxy = TextureProxy::Wrap(std::move(texture)); | |
| SkASSERT(proxy); | |
| skgpu::Swizzle swizzle = ReadSwizzleForColorType(ct, proxy->format()); | |
| TextureProxyView view(std::move(proxy), swizzle, origin); | |
| if (genMipmaps == GenerateMipmapsFromBase::kYes) { | |
| if (view.proxy()->mipmapped() == skgpu::Mipmapped::kNo) { | |
| SKIA_LOG_W("Failed SkImage:::WrapTexture because asked to generate mipmaps for " | |
| "nonmipmapped texture"); | |
| return nullptr; | |
| } | |
| if (!GenerateMipmaps(recorder, /*drawContext=*/nullptr, view.refProxy())) { | |
| SKIA_LOG_W("Failed SkImage::WrapTexture. Could not generate mipmaps."); | |
| return nullptr; | |
| } | |
| } | |
| return sk_make_sp<Image>(view, info); | |
| } | |
| sk_sp<SkImage> WrapTexture(Recorder* recorder, | |
| const BackendTexture& backendTex, | |
| SkAlphaType at, | |
| sk_sp<SkColorSpace> cs, | |
| skgpu::Origin origin, | |
| GenerateMipmapsFromBase genMipmaps, | |
| TextureReleaseProc releaseP, | |
| ReleaseContext releaseC, | |
| std::string_view label) { | |
| auto releaseHelper = skgpu::RefCntedCallback::Make(releaseP, releaseC); | |
| if (!recorder) { | |
| return nullptr; | |
| } | |
| const Caps* caps = recorder->priv().caps(); | |
| TextureFormat format = TextureInfoPriv::ViewFormat(backendTex.info()); | |
| auto [ct, _] = TextureFormatColorTypeInfo(format); | |
| // TODO(michaelludwig): Generalize this function for promise images and wrapped surfaces. | |
| // The ambiguity for single-channel textures (R) being interpreted at a high-level in Skia as | |
| // alpha-only (which impacts effect generation) or just as red data is resolved by looking at | |
| // the requested alpha type. If the alpha type is premul/unpremul we assume the texture should | |
| // be interpreted as an alpha texture; if it's opaque then it'll be red. | |
| if (at == kPremul_SkAlphaType || at == kUnpremul_SkAlphaType) { | |
| switch (ct) { | |
| case kR8_unorm_SkColorType: ct = kAlpha_8_SkColorType; break; | |
| case kR16_unorm_SkColorType: ct = kA16_unorm_SkColorType; break; | |
| case kR16_float_SkColorType: ct = kA16_float_SkColorType; break; | |
| default: break; // no adjustment | |
| } | |
| } | |
| skgpu::Swizzle swizzle = ReadSwizzleForColorType(ct, format); | |
| // An unknown alpha type needs to be forced to opaque by a swizzle if the texture format won't | |
| // do it for us automatically. Once we force it to opaque, we can report kOpaque for the | |
| // higher-level SkImage's alpha type so Skia's logic can benefit from our swizzling. | |
| if (at == kUnknown_SkAlphaType) { | |
| at = kOpaque_SkAlphaType; | |
| if (SkToBool(TextureFormatChannelMask(format) & kAlpha_SkColorChannelFlag) && | |
| swizzle[3] != '1') { | |
| swizzle = skgpu::Swizzle::Concat(swizzle, skgpu::Swizzle::RGB1()); | |
| // Patch `ct` if possible: | |
| switch (ct) { | |
| case kRGBA_8888_SkColorType: ct = kRGB_888x_SkColorType; break; | |
| case kRGBA_1010102_SkColorType: ct = kRGB_101010x_SkColorType; break; | |
| case kBGRA_1010102_SkColorType: ct = kBGR_101010x_SkColorType; break; | |
| case kRGBA_F16_SkColorType: ct = kRGB_F16F16F16x_SkColorType; break; | |
| default: break; // no further adjustment | |
| } | |
| } | |
| } | |
| SkColorInfo info(ct, at, std::move(cs)); | |
| if (!validate_backend_texture(caps, backendTex, info)) { | |
| return nullptr; | |
| } | |
| if (label.empty()) { | |
| label = "WrappedImage"; | |
| } | |
| sk_sp<Texture> texture = | |
| recorder->priv().resourceProvider()->createWrappedTexture(backendTex, label); | |
| if (!texture) { | |
| SKIA_LOG_W("Texture creation failed"); | |
| return nullptr; | |
| } | |
| texture->setReleaseCallback(std::move(releaseHelper)); | |
| TextureProxyView view(TextureProxy::Wrap(std::move(texture)), swizzle, origin); | |
| if (genMipmaps == GenerateMipmapsFromBase::kYes) { | |
| if (view.proxy()->mipmapped() == skgpu::Mipmapped::kNo) { | |
| SKIA_LOG_W("Failed SkImage:::WrapTexture because asked to generate mipmaps for " | |
| "nonmipmapped texture"); | |
| return nullptr; | |
| } | |
| if (!GenerateMipmaps(recorder, /*drawContext=*/nullptr, view.refProxy())) { | |
| SKIA_LOG_W("Failed SkImage::WrapTexture. Could not generate mipmaps."); | |
| return nullptr; | |
| } | |
| } | |
| return sk_make_sp<skgpu::graphite::Image>(view, info); | |
| } | |
| sk_sp<SkImage> 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) { | |
| return WrapTexture(recorder, | |
| backendTex, | |
| ct, | |
| at, | |
| std::move(cs), | |
| origin, | |
| SkImages::GenerateMipmapsFromBase::kNo, | |
| releaseP, | |
| releaseC, | |
| label); | |
| } | |
| sk_sp<SkImage> WrapTexture(Recorder* recorder, | |
| const BackendTexture& backendTex, | |
| SkColorType ct, | |
| SkAlphaType at, | |
| sk_sp<SkColorSpace> cs, | |
| TextureReleaseProc releaseP, | |
| ReleaseContext releaseC, | |
| std::string_view label) { | |
| return WrapTexture(recorder, | |
| backendTex, | |
| ct, | |
| at, | |
| std::move(cs), | |
| skgpu::Origin::kTopLeft, | |
| SkImages::GenerateMipmapsFromBase::kNo, | |
| releaseP, | |
| releaseC, | |
| label); | |
| } | |
| sk_sp<SkImage> 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) { | |
| // Our contract is that we will always call the _image_ release proc even on failure. | |
| // We use the helper to convey the imageContext, so we need to ensure Make doesn't fail. | |
| imageReleaseProc = imageReleaseProc ? imageReleaseProc : [](void*) {}; | |
| auto releaseHelper = skgpu::RefCntedCallback::Make(imageReleaseProc, imageContext); | |
| if (!recorder) { | |
| SKIA_LOG_W("Null Recorder"); | |
| return nullptr; | |
| } | |
| const Caps* caps = recorder->priv().caps(); | |
| SkImageInfo info = SkImageInfo::Make(dimensions, colorInfo); | |
| if (!SkImageInfoIsValid(info)) { | |
| SKIA_LOG_W("Invalid SkImageInfo"); | |
| return nullptr; | |
| } | |
| const TextureFormat format = TextureInfoPriv::ViewFormat(textureInfo); | |
| if (!AreColorTypeAndFormatCompatible(colorInfo.colorType(), format)) { | |
| SKIA_LOG_W("Incompatible SkColorType and TextureInfo"); | |
| return nullptr; | |
| } | |
| // Non-YUVA promise images use the 'imageContext' for both the release proc and fulfill proc. | |
| sk_sp<TextureProxy> proxy = MakePromiseImageLazyProxy(caps, | |
| dimensions, | |
| textureInfo, | |
| isVolatile, | |
| std::move(releaseHelper), | |
| fulfillProc, | |
| imageContext, | |
| textureReleaseProc, | |
| label); | |
| if (!proxy) { | |
| return nullptr; | |
| } | |
| skgpu::Swizzle swizzle = ReadSwizzleForColorType(colorInfo.colorType(), format); | |
| TextureProxyView view(std::move(proxy), swizzle, origin); | |
| return sk_make_sp<Image>(view, colorInfo); | |
| } | |
| sk_sp<SkImage> PromiseTextureFrom(Recorder* recorder, | |
| SkISize dimensions, | |
| const TextureInfo& textureInfo, | |
| const SkColorInfo& colorInfo, | |
| Volatile isVolatile, | |
| GraphitePromiseTextureFulfillProc fulfillProc, | |
| GraphitePromiseImageReleaseProc imageReleaseProc, | |
| GraphitePromiseTextureReleaseProc textureReleaseProc, | |
| GraphitePromiseImageContext imageContext) { | |
| return PromiseTextureFrom(recorder, | |
| dimensions, | |
| textureInfo, | |
| colorInfo, | |
| skgpu::Origin::kTopLeft, | |
| isVolatile, | |
| fulfillProc, | |
| imageReleaseProc, | |
| textureReleaseProc, | |
| imageContext); | |
| } | |
| sk_sp<SkImage> PromiseTextureFromYUVA(Recorder* recorder, | |
| const YUVABackendTextureInfo& backendTextureInfo, | |
| sk_sp<SkColorSpace> imageColorSpace, | |
| Volatile isVolatile, | |
| GraphitePromiseTextureFulfillProc fulfillProc, | |
| GraphitePromiseImageReleaseProc imageReleaseProc, | |
| GraphitePromiseTextureReleaseProc textureReleaseProc, | |
| GraphitePromiseImageContext imageContext, | |
| GraphitePromiseTextureFulfillContext planeContexts[], | |
| std::string_view label) { | |
| // Our contract is that we will always call the _image_ release proc even on failure. | |
| // We use the helper to convey the imageContext, so we need to ensure Make doesn't fail. | |
| auto releaseHelper = skgpu::RefCntedCallback::Make(imageReleaseProc, imageContext); | |
| if (!recorder) { | |
| return nullptr; | |
| } | |
| // Precompute the dimensions for all promise texture planes | |
| SkISize planeDimensions[SkYUVAInfo::kMaxPlanes]; | |
| if (!backendTextureInfo.yuvaInfo().planeDimensions(planeDimensions)) { | |
| return nullptr; | |
| } | |
| std::string labelStr(label); | |
| if (labelStr.empty()) { | |
| labelStr = "Wrapped_PromiseYUVPlane"; | |
| } else { | |
| labelStr += "_PromiseYUVPlane"; | |
| } | |
| TextureProxyView planes[SkYUVAInfo::kMaxPlanes]; | |
| for (int i = 0; i < backendTextureInfo.numPlanes(); ++i) { | |
| sk_sp<TextureProxy> lazyProxy = | |
| MakePromiseImageLazyProxy(recorder->priv().caps(), | |
| SK_UNSAFE_TODO(planeDimensions[i]), | |
| backendTextureInfo.planeTextureInfo(i), | |
| isVolatile, | |
| releaseHelper, | |
| fulfillProc, | |
| SK_UNSAFE_TODO(planeContexts[i]), | |
| textureReleaseProc, | |
| labelStr); | |
| // Promise YUVA images assume the default rgba swizzle. | |
| SK_UNSAFE_TODO(planes[i]) = TextureProxyView(std::move(lazyProxy)); | |
| } | |
| return Image_YUVA::Make(recorder->priv().caps(), backendTextureInfo.yuvaInfo(), | |
| SkSpan(planes), std::move(imageColorSpace)); | |
| } | |
| sk_sp<SkImage> SubsetTextureFrom(Recorder* recorder, | |
| const SkImage* img, | |
| const SkIRect& subset, | |
| SkImage::RequiredProperties props) { | |
| if (!recorder || !img) { | |
| return nullptr; | |
| } | |
| auto subsetImg = img->makeSubset(recorder, subset, props); | |
| return SkImages::TextureFromImage(recorder, subsetImg, props); | |
| } | |
| sk_sp<SkImage> MakeWithFilter(Recorder* recorder, | |
| sk_sp<SkImage> src, | |
| const SkImageFilter* filter, | |
| const SkIRect& subset, | |
| const SkIRect& clipBounds, | |
| SkIRect* outSubset, | |
| SkIPoint* offset) { | |
| if (!recorder || !src || !filter) { | |
| return nullptr; | |
| } | |
| sk_sp<skif::Backend> backend = skif::MakeGraphiteBackend(recorder, {}, src->colorType()); | |
| sk_sp<SkImage> image = as_IFB(filter)->makeImageWithFilter(std::move(backend), | |
| std::move(src), | |
| subset, | |
| clipBounds, | |
| outSubset, | |
| offset); | |
| // The skif backend creates budgeted, scratch textures. This is what we want most of the time, | |
| // but for the final result image returned from MakeWithFilter(), it needs to be a non-budgeted | |
| // non-shareable texture (i.e. matching what we return from the other factory methods). | |
| if (image) { | |
| SkASSERT(as_IB(image)->isGraphiteBacked()); | |
| image = static_cast<Image_Base*>(image.get())->makeNonBudgeted(recorder); | |
| } | |
| return image; | |
| } | |
| static sk_sp<SkImage> generate_picture_texture(Recorder* recorder, | |
| const SkImage_Picture* img, | |
| const SkImageInfo& info, | |
| SkImage::RequiredProperties requiredProps) { | |
| auto mm = requiredProps.fMipmapped ? skgpu::Mipmapped::kYes : skgpu::Mipmapped::kNo; | |
| // Use a non-budgeted surface since the image wrapping the surface's texture will be owned by | |
| // the client. | |
| sk_sp<Surface> surface = Surface::Make(recorder, | |
| info, | |
| "LazySkImagePictureTexture", | |
| skgpu::Budgeted::kNo, | |
| mm, | |
| SkBackingFit::kExact, | |
| img->props()); | |
| if (!surface) { | |
| SKIA_LOG_E("Failed to create Surface"); | |
| return nullptr; | |
| } | |
| img->replay(surface->getCanvas()); | |
| // If the surface was created with mipmaps, they will be automatically generated when flushing | |
| // the tasks when 'surface' goes out of scope. | |
| return surface->asImage(); | |
| } | |
| sk_sp<SkImage> make_from_bitmap(Recorder* recorder, | |
| const SkColorInfo& colorInfo, | |
| const SkBitmap& bitmap, | |
| sk_sp<SkMipmap> mipmaps, | |
| skgpu::Budgeted budgeted, | |
| SkImage::RequiredProperties requiredProps, | |
| std::string_view label) { | |
| auto mm = requiredProps.fMipmapped ? skgpu::Mipmapped::kYes : skgpu::Mipmapped::kNo; | |
| auto view = MakeBitmapProxyView(recorder, bitmap, std::move(mipmaps), mm, budgeted, label); | |
| if (!view) { | |
| return nullptr; | |
| } | |
| SkASSERT(!requiredProps.fMipmapped || view.proxy()->mipmapped() == skgpu::Mipmapped::kYes); | |
| return sk_make_sp<Image>(std::move(view), colorInfo); | |
| } | |
| /* | |
| * We only have 2 ways to create a Graphite-backed image. | |
| * | |
| * 1. Ask the generator to natively create one | |
| * 2. Ask the generator to return RGB(A) data, which the GPU can convert | |
| */ | |
| static sk_sp<SkImage> make_texture_image_from_lazy(Recorder* recorder, | |
| const SkImage_Lazy* img, | |
| SkImage::RequiredProperties requiredProps) { | |
| // 1. Ask the generator to natively create one. | |
| { | |
| if (img->type() == SkImage_Base::Type::kLazyPicture) { | |
| sk_sp<SkImage> newImage = | |
| generate_picture_texture(recorder, | |
| static_cast<const SkImage_Picture*>(img), | |
| img->imageInfo(), | |
| requiredProps); | |
| if (newImage) { | |
| SkASSERT(as_IB(newImage)->isGraphiteBacked()); | |
| return newImage; | |
| } | |
| // The fallback for this would be to generate a bitmap, but some picture-backed | |
| // images can only be played back on the GPU. | |
| return nullptr; | |
| } | |
| // There is not an analog to GrTextureGenerator for Graphite yet, but if there was, | |
| // we would want to call it here. | |
| } | |
| // 2. Ask the generator to return a bitmap, which the GPU can convert. | |
| { | |
| SkBitmap bitmap; | |
| if (img->getROPixels(nullptr, &bitmap, SkImage_Lazy::CachingHint::kDisallow_CachingHint)) { | |
| return make_from_bitmap(recorder, | |
| img->imageInfo().colorInfo(), | |
| bitmap, | |
| nullptr, | |
| skgpu::Budgeted::kNo, | |
| requiredProps, | |
| "LazySkImageBitmapTexture"); | |
| } | |
| } | |
| return nullptr; | |
| } | |
| sk_sp<SkImage> TextureFromImage(Recorder* recorder, | |
| const SkImage* image, | |
| SkImage::RequiredProperties requiredProps) { | |
| if (!recorder || !image) { | |
| return nullptr; | |
| } | |
| if (image->dimensions().area() <= 1) { | |
| requiredProps.fMipmapped = false; | |
| } | |
| auto ib = as_IB(image); | |
| SkASSERT(!ib->isGaneshBacked()); | |
| if (ib->isRasterBacked()) { | |
| auto raster = static_cast<const SkImage_Raster*>(ib); | |
| return make_from_bitmap(recorder, | |
| raster->imageInfo().colorInfo(), | |
| raster->bitmap(), | |
| raster->refMips(), | |
| skgpu::Budgeted::kNo, | |
| requiredProps, | |
| "RasterBitmapTexture"); | |
| } | |
| if (ib->isLazyGenerated()) { | |
| return make_texture_image_from_lazy( | |
| recorder, static_cast<const SkImage_Lazy*>(ib), requiredProps); | |
| } | |
| SkASSERT(ib->isGraphiteBacked()); | |
| return ib->makeSubset(recorder, ib->bounds(), requiredProps); | |
| } | |
| sk_sp<SkImage> TextureFromYUVAPixmaps(Recorder* recorder, | |
| const SkYUVAPixmaps& pixmaps, | |
| SkImage::RequiredProperties requiredProps, | |
| bool limitToMaxTextureSize, | |
| sk_sp<SkColorSpace> imageColorSpace, | |
| std::string_view label) { | |
| if (!recorder) { | |
| return nullptr; | |
| } | |
| // Determine if we have to resize the pixmaps | |
| const int maxTextureSize = recorder->priv().caps()->maxTextureSize(); | |
| const int maxDim = std::max(pixmaps.yuvaInfo().width(), pixmaps.yuvaInfo().height()); | |
| SkYUVAPixmapInfo finalInfo = pixmaps.pixmapsInfo(); | |
| if (maxDim > maxTextureSize) { | |
| if (!limitToMaxTextureSize) { | |
| return nullptr; | |
| } | |
| float scale = static_cast<float>(maxTextureSize) / maxDim; | |
| SkISize newDimensions = { | |
| std::min(static_cast<int>(pixmaps.yuvaInfo().width() * scale), maxTextureSize), | |
| std::min(static_cast<int>(pixmaps.yuvaInfo().height() * scale), maxTextureSize)}; | |
| finalInfo = SkYUVAPixmapInfo(pixmaps.yuvaInfo().makeDimensions(newDimensions), | |
| pixmaps.dataType(), | |
| /*rowBytes=*/nullptr); | |
| } | |
| std::string labelStr(label); | |
| if (labelStr.empty()) { | |
| labelStr = "YUVRasterBitmapPlane"; | |
| } else { | |
| labelStr += "_YUVBitmapPlane"; | |
| } | |
| auto mipmapped = requiredProps.fMipmapped ? skgpu::Mipmapped::kYes : skgpu::Mipmapped::kNo; | |
| TextureProxyView planes[SkYUVAInfo::kMaxPlanes]; | |
| for (int i = 0; i < finalInfo.yuvaInfo().numPlanes(); ++i) { | |
| SkBitmap bmp; | |
| if (maxDim > maxTextureSize) { | |
| // Rescale the data before uploading | |
| if (!bmp.tryAllocPixels(finalInfo.planeInfo(i)) || | |
| !pixmaps.plane(i).scalePixels(bmp.pixmap(), SkFilterMode::kLinear)) { | |
| return nullptr; | |
| } | |
| } else { | |
| // Use original data to upload | |
| if (!bmp.installPixels(pixmaps.plane(i))) { | |
| return nullptr; | |
| } | |
| } | |
| auto view = MakeBitmapProxyView(recorder, bmp, /*mipmapsIn=*/nullptr, mipmapped, | |
| skgpu::Budgeted::kNo, labelStr); | |
| SK_UNSAFE_TODO(planes[i]) = std::move(view); | |
| } | |
| return Image_YUVA::Make(recorder->priv().caps(), finalInfo.yuvaInfo(), | |
| SkSpan(planes), std::move(imageColorSpace)); | |
| } | |
| sk_sp<SkImage> TextureFromYUVATextures(Recorder* recorder, | |
| const YUVABackendTextures& yuvaTextures, | |
| sk_sp<SkColorSpace> imageColorSpace, | |
| TextureReleaseProc releaseP, | |
| ReleaseContext releaseC, | |
| std::string_view label) { | |
| auto releaseHelper = skgpu::RefCntedCallback::Make(releaseP, releaseC); | |
| if (!recorder) { | |
| return nullptr; | |
| } | |
| std::string labelStr(label); | |
| if (labelStr.empty()) { | |
| labelStr = "Wrapped_YUVPlane"; | |
| } else { | |
| labelStr += "_YUVPlane"; | |
| } | |
| TextureProxyView planes[SkYUVAInfo::kMaxPlanes]; | |
| for (int i = 0; i < yuvaTextures.yuvaInfo().numPlanes(); ++i) { | |
| sk_sp<Texture> texture = recorder->priv().resourceProvider()->createWrappedTexture( | |
| yuvaTextures.planeTexture(i), labelStr); | |
| if (!texture) { | |
| SKIA_LOG_W("Failed to wrap backend texture for YUVA plane %d", i); | |
| return nullptr; | |
| } | |
| texture->setReleaseCallback(releaseHelper); | |
| SK_UNSAFE_TODO(planes[i]) = TextureProxyView(TextureProxy::Wrap(std::move(texture))); | |
| } | |
| return Image_YUVA::Make(recorder->priv().caps(), yuvaTextures.yuvaInfo(), | |
| SkSpan(planes), std::move(imageColorSpace)); | |
| } | |
| sk_sp<SkImage> TextureFromYUVAImages(Recorder* recorder, | |
| const SkYUVAInfo& yuvaInfo, | |
| SkSpan<const sk_sp<SkImage>> images, | |
| sk_sp<SkColorSpace> imageColorSpace) { | |
| // This factory is just a view of the images, so does not actually trigger any work on the | |
| // recorder. It is just used to provide the Caps. | |
| return Image_YUVA::WrapImages(recorder->priv().caps(), yuvaInfo, images, imageColorSpace); | |
| } | |
| } // namespace SkImages |