| /* | |
| * 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 "src/gpu/ganesh/image/GrImageUtils.h" | |
| #include "include/core/SkAlphaType.h" | |
| #include "include/core/SkBitmap.h" | |
| #include "include/core/SkColorSpace.h" | |
| #include "include/core/SkImage.h" | |
| #include "include/core/SkImageInfo.h" | |
| #include "include/core/SkPixmap.h" | |
| #include "include/core/SkRect.h" | |
| #include "include/core/SkSamplingOptions.h" | |
| #include "include/core/SkSize.h" | |
| #include "include/core/SkSurface.h" | |
| #include "include/core/SkTypes.h" | |
| #include "include/core/SkYUVAInfo.h" | |
| #include "include/core/SkYUVAPixmaps.h" | |
| #include "include/gpu/GpuTypes.h" | |
| #include "include/gpu/ganesh/GrBackendSurface.h" | |
| #include "include/gpu/ganesh/GrContextOptions.h" | |
| #include "include/gpu/ganesh/GrRecordingContext.h" | |
| #include "include/gpu/ganesh/GrTypes.h" | |
| #include "include/gpu/ganesh/SkSurfaceGanesh.h" | |
| #include "include/private/SkIDChangeListener.h" | |
| #include "include/private/SkMutex.h" | |
| #include "include/private/gpu/ganesh/GrImageContext.h" | |
| #include "include/private/gpu/ganesh/GrTextureGenerator.h" | |
| #include "include/private/gpu/ganesh/GrTypesPriv.h" | |
| #include "src/core/SkBlurEngine.h" | |
| #include "src/core/SkCachedData.h" | |
| #include "src/core/SkImageFilterCache.h" | |
| #include "src/core/SkImageFilterTypes.h" | |
| #include "src/core/SkSamplingPriv.h" | |
| #include "src/gpu/ResourceKey.h" | |
| #include "src/gpu/SkBackingFit.h" | |
| #include "src/gpu/Swizzle.h" | |
| #include "src/gpu/ganesh/Device.h" | |
| #include "src/gpu/ganesh/GrCaps.h" | |
| #include "src/gpu/ganesh/GrColorSpaceXform.h" | |
| #include "src/gpu/ganesh/GrFragmentProcessor.h" | |
| #include "src/gpu/ganesh/GrImageInfo.h" | |
| #include "src/gpu/ganesh/GrProxyProvider.h" | |
| #include "src/gpu/ganesh/GrRecordingContextPriv.h" | |
| #include "src/gpu/ganesh/GrSamplerState.h" | |
| #include "src/gpu/ganesh/GrSurfaceProxy.h" | |
| #include "src/gpu/ganesh/GrSurfaceProxyView.h" | |
| #include "src/gpu/ganesh/GrTextureProxy.h" | |
| #include "src/gpu/ganesh/GrThreadSafeCache.h" | |
| #include "src/gpu/ganesh/GrYUVATextureProxies.h" | |
| #include "src/gpu/ganesh/SkGr.h" | |
| #include "src/gpu/ganesh/SurfaceDrawContext.h" | |
| #include "src/gpu/ganesh/SurfaceFillContext.h" | |
| #include "src/gpu/ganesh/effects/GrBicubicEffect.h" | |
| #include "src/gpu/ganesh/effects/GrTextureEffect.h" | |
| #include "src/gpu/ganesh/effects/GrYUVtoRGBEffect.h" | |
| #include "src/gpu/ganesh/image/GrMippedBitmap.h" | |
| #include "src/gpu/ganesh/image/SkImage_Ganesh.h" | |
| #include "src/gpu/ganesh/image/SkImage_GaneshBase.h" | |
| #include "src/gpu/ganesh/image/SkImage_RasterPinnable.h" | |
| #include "src/gpu/ganesh/image/SkSpecialImage_Ganesh.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 <array> | |
| #include <string_view> | |
| #include <utility> | |
| class SkDevice; | |
| class SkMatrix; | |
| class SkSurfaceProps; | |
| enum SkColorType : int; | |
| class SkSpecialImage; | |
| namespace skgpu::ganesh { | |
| GrSurfaceProxyView CopyView(GrRecordingContext* context, | |
| GrSurfaceProxyView src, | |
| skgpu::Mipmapped mipmapped, | |
| GrImageTexGenPolicy policy, | |
| std::string_view label) { | |
| skgpu::Budgeted budgeted = policy == GrImageTexGenPolicy::kNew_Uncached_Budgeted | |
| ? skgpu::Budgeted::kYes | |
| : skgpu::Budgeted::kNo; | |
| return GrSurfaceProxyView::Copy(context, | |
| std::move(src), | |
| mipmapped, | |
| SkBackingFit::kExact, | |
| budgeted, | |
| /*label=*/label); | |
| } | |
| std::tuple<GrSurfaceProxyView, GrColorType> RasterAsView(GrRecordingContext* rContext, | |
| const SkImage_Raster* raster, | |
| skgpu::Mipmapped mipmapped, | |
| GrImageTexGenPolicy policy) { | |
| if (policy == GrImageTexGenPolicy::kDraw) { | |
| // If the draw doesn't require mipmaps but this SkImage has them go ahead and make a | |
| // mipmapped texture. There are three reasons for this: | |
| // 1) Avoiding another texture creation if a later draw requires mipmaps. | |
| // 2) Ensuring we upload the bitmap's levels instead of generating on the GPU from the base. | |
| if (raster->hasMipmaps()) { | |
| mipmapped = skgpu::Mipmapped::kYes; | |
| } | |
| return GrMakeCachedBitmapProxyView(rContext, | |
| GrMippedBitmap(raster->bitmap(), raster->refMips()), | |
| /*label=*/"TextureForImageRasterWithPolicyEqualKDraw", | |
| mipmapped); | |
| } | |
| auto budgeted = (policy == GrImageTexGenPolicy::kNew_Uncached_Unbudgeted) | |
| ? skgpu::Budgeted::kNo | |
| : skgpu::Budgeted::kYes; | |
| return GrMakeUncachedBitmapProxyView(rContext, | |
| GrMippedBitmap(raster->bitmap(), raster->refMips()), | |
| mipmapped, | |
| SkBackingFit::kExact, | |
| budgeted); | |
| } | |
| // Returns the GrColorType to use with the GrTextureProxy returned from lockTextureProxy. This | |
| // may be different from the color type on the image in the case where we need up upload CPU | |
| // data to a texture but the GPU doesn't support the format of CPU data. In this case we convert | |
| // the data to RGBA_8888 unorm on the CPU then upload that. | |
| GrColorType ColorTypeOfLockTextureProxy(const GrCaps* caps, SkColorType sct) { | |
| GrColorType ct = SkColorTypeToGrColorType(sct); | |
| GrBackendFormat format = caps->getDefaultBackendFormat(ct, GrRenderable::kNo); | |
| if (!format.isValid()) { | |
| ct = GrColorType::kRGBA_8888; | |
| } | |
| return ct; | |
| } | |
| static GrSurfaceOrigin get_origin(const SkImage_Lazy* img) { | |
| SkASSERT(img->generator()); | |
| if (!img->generator()->isTextureGenerator()) { | |
| return kTopLeft_GrSurfaceOrigin; | |
| } | |
| // origin should be thread safe | |
| return static_cast<const GrTextureGenerator*>(img->generator()->fGenerator.get())->origin(); | |
| } | |
| static GrSurfaceProxyView texture_proxy_view_from_planes(GrRecordingContext* ctx, | |
| const SkImage_Lazy* img, | |
| skgpu::Budgeted budgeted) { | |
| auto supportedDataTypes = SupportedTextureFormats(*ctx); | |
| SkYUVAPixmaps yuvaPixmaps; | |
| sk_sp<SkCachedData> dataStorage = img->getPlanes(supportedDataTypes, &yuvaPixmaps); | |
| if (!dataStorage) { | |
| return {}; | |
| } | |
| std::array<GrSurfaceProxyView, SkYUVAInfo::kMaxPlanes> views = {}; | |
| std::array<GrColorType, SkYUVAInfo::kMaxPlanes> pixmapColorTypes = {}; | |
| for (int i = 0; i < yuvaPixmaps.numPlanes(); ++i) { | |
| // If the sizes of the components are not all the same we choose to create exact-match | |
| // textures for the smaller ones rather than add a texture domain to the draw. | |
| // TODO: revisit this decision to improve texture reuse? | |
| SkBackingFit fit = yuvaPixmaps.plane(i).dimensions() == img->dimensions() | |
| ? SkBackingFit::kApprox | |
| : SkBackingFit::kExact; | |
| // We grab a ref to cached yuv data. When the SkBitmap we create below goes away it will | |
| // call releaseProc which will release this ref. | |
| // DDL TODO: Currently we end up creating a lazy proxy that will hold onto a ref to the | |
| // SkImage in its lambda. This means that we'll keep the ref on the YUV data around for the | |
| // life time of the proxy and not just upload. For non-DDL draws we should look into | |
| // releasing this SkImage after uploads (by deleting the lambda after instantiation). | |
| auto releaseProc = [](void*, void* data) { | |
| auto cachedData = static_cast<SkCachedData*>(data); | |
| SkASSERT(cachedData); | |
| cachedData->unref(); | |
| }; | |
| std::optional<GrMippedBitmap> bitmap = | |
| GrMippedBitmap::Make(yuvaPixmaps.plane(i).info(), | |
| yuvaPixmaps.plane(i).writable_addr(), | |
| yuvaPixmaps.plane(i).rowBytes(), | |
| releaseProc, | |
| SkRef(dataStorage.get())); | |
| if (!bitmap) { | |
| return {}; | |
| } | |
| pixmapColorTypes[i] = SkColorTypeToGrColorType(bitmap->colorType()); | |
| std::tie(views[i], std::ignore) = | |
| GrMakeUncachedBitmapProxyView(ctx, bitmap.value(), skgpu::Mipmapped::kNo, fit); | |
| if (!views[i]) { | |
| return {}; | |
| } | |
| } | |
| // TODO: investigate preallocating mip maps here | |
| GrImageInfo info(SkColorTypeToGrColorType(img->colorType()), | |
| kPremul_SkAlphaType, | |
| /*color space*/ nullptr, | |
| img->dimensions()); | |
| auto sfc = ctx->priv().makeSFC(info, | |
| "ImageLazy_TextureProxyViewFromPlanes", | |
| SkBackingFit::kExact, | |
| 1, | |
| skgpu::Mipmapped::kNo, | |
| GrProtected::kNo, | |
| kTopLeft_GrSurfaceOrigin, | |
| budgeted); | |
| if (!sfc) { | |
| return {}; | |
| } | |
| GrYUVATextureProxies yuvaProxies(yuvaPixmaps.yuvaInfo(), views.data(), pixmapColorTypes.data()); | |
| SkAssertResult(yuvaProxies.isValid()); | |
| std::unique_ptr<GrFragmentProcessor> fp = GrYUVtoRGBEffect::Make( | |
| yuvaProxies, | |
| GrSamplerState::Filter::kNearest, | |
| *ctx->priv().caps()); | |
| // The pixels after yuv->rgb will be in the generator's color space. | |
| // If onMakeColorTypeAndColorSpace has been called then this will not match this image's | |
| // color space. To correct this, apply a color space conversion from the generator's color | |
| // space to this image's color space. | |
| SkColorSpace* srcColorSpace = img->generator()->getInfo().colorSpace(); | |
| SkColorSpace* dstColorSpace = img->colorSpace(); | |
| // If the caller expects the pixels in a different color space than the one from the image, | |
| // apply a color conversion to do this. | |
| fp = GrColorSpaceXformEffect::Make(std::move(fp), | |
| srcColorSpace, kOpaque_SkAlphaType, | |
| dstColorSpace, kOpaque_SkAlphaType); | |
| sfc->fillWithFP(std::move(fp)); | |
| return sfc->readSurfaceView(); | |
| } | |
| static GrSurfaceProxyView generate_picture_texture(GrRecordingContext* ctx, | |
| const SkImage_Picture* img, | |
| skgpu::Mipmapped mipmapped, | |
| GrImageTexGenPolicy texGenPolicy) { | |
| SkASSERT(ctx); | |
| SkASSERT(img); | |
| skgpu::Budgeted budgeted = texGenPolicy == GrImageTexGenPolicy::kNew_Uncached_Unbudgeted | |
| ? skgpu::Budgeted::kNo | |
| : skgpu::Budgeted::kYes; | |
| auto surface = SkSurfaces::RenderTarget(ctx, | |
| budgeted, | |
| img->imageInfo(), | |
| 0, | |
| kTopLeft_GrSurfaceOrigin, | |
| img->props(), | |
| mipmapped == skgpu::Mipmapped::kYes); | |
| if (!surface) { | |
| return {}; | |
| } | |
| img->replay(surface->getCanvas()); | |
| sk_sp<SkImage> image(surface->makeImageSnapshot()); | |
| if (!image) { | |
| return {}; | |
| } | |
| // The surface the image was created from is only local to this function so there will not be a | |
| // need for a copy when getting the view of the image for a future draw. | |
| auto [view, ct] = AsView(ctx, image, mipmapped, /*targetSurface=*/nullptr); | |
| SkASSERT(view); | |
| SkASSERT(mipmapped == skgpu::Mipmapped::kNo || | |
| view.asTextureProxy()->mipmapped() == skgpu::Mipmapped::kYes); | |
| return view; | |
| } | |
| // Returns the texture proxy. We will always cache the generated texture on success. | |
| // We have 4 ways to try to return a texture (in sorted order) | |
| // | |
| // 1. Check the cache for a pre-existing one | |
| // 2. Ask the generator to natively create one | |
| // 3. Ask the generator to return YUV planes, which the GPU can convert | |
| // 4. Ask the generator to return RGB(A) data, which the GPU can convert | |
| GrSurfaceProxyView LockTextureProxyView(GrRecordingContext* rContext, | |
| const SkImage_Lazy* img, | |
| GrImageTexGenPolicy texGenPolicy, | |
| skgpu::Mipmapped mipmapped) { | |
| skgpu::UniqueKey key; | |
| if (texGenPolicy == GrImageTexGenPolicy::kDraw) { | |
| GrMakeKeyFromImageID(&key, img->uniqueID(), SkIRect::MakeSize(img->dimensions())); | |
| } | |
| const GrCaps* caps = rContext->priv().caps(); | |
| GrProxyProvider* proxyProvider = rContext->priv().proxyProvider(); | |
| auto installKey = [&](const GrSurfaceProxyView& view) { | |
| SkASSERT(view && view.asTextureProxy()); | |
| if (key.isValid()) { | |
| auto listener = GrMakeUniqueKeyInvalidationListener(&key, rContext->priv().contextID()); | |
| img->addUniqueIDListener(std::move(listener)); | |
| proxyProvider->assignUniqueKeyToProxy(key, view.asTextureProxy()); | |
| } | |
| }; | |
| auto ct = ColorTypeOfLockTextureProxy(caps, img->colorType()); | |
| // 1. Check the cache for a pre-existing one. | |
| if (key.isValid()) { | |
| auto proxy = proxyProvider->findOrCreateProxyByUniqueKey(key); | |
| if (proxy) { | |
| skgpu::Swizzle swizzle = caps->getReadSwizzle(proxy->backendFormat(), ct); | |
| GrSurfaceOrigin origin = get_origin(img); | |
| GrSurfaceProxyView view(std::move(proxy), origin, swizzle); | |
| if (mipmapped == skgpu::Mipmapped::kNo || | |
| view.asTextureProxy()->mipmapped() == skgpu::Mipmapped::kYes) { | |
| return view; | |
| } else { | |
| // We need a mipped proxy, but we found a cached proxy that wasn't mipped. Thus we | |
| // generate a new mipped surface and copy the original proxy into the base layer. We | |
| // will then let the gpu generate the rest of the mips. | |
| auto mippedView = GrCopyBaseMipMapToView(rContext, view); | |
| if (!mippedView) { | |
| // We failed to make a mipped proxy with the base copied into it. This could | |
| // have been from failure to make the proxy or failure to do the copy. Thus we | |
| // will fall back to just using the non mipped proxy; See skbug.com/40038328. | |
| return view; | |
| } | |
| proxyProvider->removeUniqueKeyFromProxy(view.asTextureProxy()); | |
| installKey(mippedView); | |
| return mippedView; | |
| } | |
| } | |
| } | |
| // 2. Ask the generator to natively create one (if it knows how) | |
| { | |
| if (img->type() == SkImage_Base::Type::kLazyPicture) { | |
| if (auto view = generate_picture_texture(rContext, | |
| static_cast<const SkImage_Picture*>(img), | |
| mipmapped, | |
| texGenPolicy)) { | |
| installKey(view); | |
| return view; | |
| } | |
| // The fallback for this would be to generate a bitmap, but some picture-backed | |
| // images can only be played back on the GPU. | |
| return {}; | |
| } else if (img->generator()->isTextureGenerator()) { | |
| auto sharedGenerator = img->generator(); | |
| SkAutoMutexExclusive mutex(sharedGenerator->fMutex); | |
| auto textureGen = static_cast<GrTextureGenerator*>(sharedGenerator->fGenerator.get()); | |
| if (auto view = textureGen->generateTexture(rContext, | |
| img->imageInfo(), | |
| mipmapped, | |
| texGenPolicy)) { | |
| installKey(view); | |
| return view; | |
| } | |
| } | |
| } | |
| // 3. Ask the generator to return YUV planes, which the GPU can convert. If we will be mipping | |
| // the texture we skip this step so the CPU generate non-planar MIP maps for us. | |
| if (mipmapped == skgpu::Mipmapped::kNo && | |
| !rContext->priv().options().fDisableGpuYUVConversion) { | |
| // TODO: Update to create the mipped surface in the textureProxyViewFromPlanes generator and | |
| // draw the base layer directly into the mipped surface. | |
| skgpu::Budgeted budgeted = texGenPolicy == GrImageTexGenPolicy::kNew_Uncached_Unbudgeted | |
| ? skgpu::Budgeted::kNo | |
| : skgpu::Budgeted::kYes; | |
| auto view = texture_proxy_view_from_planes(rContext, img, budgeted); | |
| if (view) { | |
| installKey(view); | |
| return view; | |
| } | |
| } | |
| // 4. Ask the generator to return a bitmap, which the GPU can convert. | |
| auto hint = texGenPolicy == GrImageTexGenPolicy::kDraw ? SkImage::CachingHint::kAllow_CachingHint | |
| : SkImage::CachingHint::kDisallow_CachingHint; | |
| if (SkBitmap bitmap; img->getROPixels(nullptr, &bitmap, hint)) { | |
| // We always make an uncached bitmap here because we will cache it based on passed in policy | |
| // with *our* key, not a key derived from bitmap. We're just making the proxy here. | |
| auto budgeted = texGenPolicy == GrImageTexGenPolicy::kNew_Uncached_Unbudgeted | |
| ? skgpu::Budgeted::kNo | |
| : skgpu::Budgeted::kYes; | |
| auto view = std::get<0>( | |
| GrMakeUncachedBitmapProxyView(rContext, | |
| GrMippedBitmap(bitmap, as_IB(img)->refMips()), | |
| mipmapped, | |
| SkBackingFit::kExact, | |
| budgeted)); | |
| if (view) { | |
| installKey(view); | |
| return view; | |
| } | |
| } | |
| return {}; | |
| } | |
| static std::tuple<GrSurfaceProxyView, GrColorType> lazy_as_view(GrRecordingContext* context, | |
| const SkImage_Lazy* img, | |
| skgpu::Mipmapped mipmapped, | |
| GrImageTexGenPolicy policy) { | |
| GrColorType ct = ColorTypeOfLockTextureProxy(context->priv().caps(), img->colorType()); | |
| return {LockTextureProxyView(context, img, policy, mipmapped), ct}; | |
| } | |
| std::tuple<GrSurfaceProxyView, GrColorType> AsView(GrRecordingContext* rContext, | |
| const SkImage* img, | |
| skgpu::Mipmapped mipmapped, | |
| GrRenderTargetProxy* targetSurface, | |
| GrImageTexGenPolicy policy) { | |
| SkASSERT(img); | |
| if (!rContext) { | |
| return {}; | |
| } | |
| if (!rContext->priv().caps()->mipmapSupport() || img->dimensions().area() <= 1) { | |
| mipmapped = skgpu::Mipmapped::kNo; | |
| } | |
| auto ib = static_cast<const SkImage_Base*>(img); | |
| if (ib->type() == SkImage_Base::Type::kRaster) { | |
| return skgpu::ganesh::RasterAsView( | |
| rContext, static_cast<const SkImage_Raster*>(ib), mipmapped, policy); | |
| } else if (ib->type() == SkImage_Base::Type::kRasterPinnable) { | |
| auto rp = static_cast<const SkImage_RasterPinnable*>(img); | |
| return rp->asView(rContext, mipmapped, policy); | |
| } else if (ib->isGaneshBacked()) { | |
| auto gb = static_cast<const SkImage_GaneshBase*>(img); | |
| return gb->asView(rContext, mipmapped, policy, targetSurface); | |
| } else if (ib->isLazyGenerated()) { | |
| return lazy_as_view(rContext, static_cast<const SkImage_Lazy*>(ib), mipmapped, policy); | |
| } | |
| SkDEBUGFAIL("Unsupported image type to make a View"); | |
| return {}; | |
| } | |
| static std::unique_ptr<GrFragmentProcessor> make_fp_from_view(GrRecordingContext* rContext, | |
| GrSurfaceProxyView view, | |
| SkAlphaType at, | |
| SkSamplingOptions sampling, | |
| const SkTileMode tileModes[2], | |
| const SkMatrix& m, | |
| const SkRect* subset, | |
| const SkRect* domain) { | |
| if (!view) { | |
| return nullptr; | |
| } | |
| const GrCaps& caps = *rContext->priv().caps(); | |
| auto wmx = SkTileModeToWrapMode(tileModes[0]); | |
| auto wmy = SkTileModeToWrapMode(tileModes[1]); | |
| if (sampling.useCubic) { | |
| if (subset) { | |
| if (domain) { | |
| return GrBicubicEffect::MakeSubset(std::move(view), | |
| at, | |
| m, | |
| wmx, | |
| wmy, | |
| *subset, | |
| *domain, | |
| sampling.cubic, | |
| GrBicubicEffect::Direction::kXY, | |
| *rContext->priv().caps()); | |
| } | |
| return GrBicubicEffect::MakeSubset(std::move(view), | |
| at, | |
| m, | |
| wmx, | |
| wmy, | |
| *subset, | |
| sampling.cubic, | |
| GrBicubicEffect::Direction::kXY, | |
| *rContext->priv().caps()); | |
| } | |
| return GrBicubicEffect::Make(std::move(view), | |
| at, | |
| m, | |
| wmx, | |
| wmy, | |
| sampling.cubic, | |
| GrBicubicEffect::Direction::kXY, | |
| *rContext->priv().caps()); | |
| } | |
| if (sampling.isAniso()) { | |
| if (!rContext->priv().caps()->anisoSupport()) { | |
| // Fallback to linear | |
| sampling = SkSamplingPriv::AnisoFallback(view.mipmapped() == skgpu::Mipmapped::kYes); | |
| } | |
| } else if (view.mipmapped() == skgpu::Mipmapped::kNo) { | |
| sampling = SkSamplingOptions(sampling.filter); | |
| } | |
| GrSamplerState sampler; | |
| if (sampling.isAniso()) { | |
| sampler = GrSamplerState::Aniso(wmx, wmy, sampling.maxAniso, view.mipmapped()); | |
| } else { | |
| sampler = GrSamplerState(wmx, wmy, sampling.filter, sampling.mipmap); | |
| } | |
| if (subset) { | |
| if (domain) { | |
| return GrTextureEffect::MakeSubset( | |
| std::move(view), at, m, sampler, *subset, *domain, caps); | |
| } | |
| return GrTextureEffect::MakeSubset(std::move(view), at, m, sampler, *subset, caps); | |
| } else { | |
| return GrTextureEffect::Make(std::move(view), at, m, sampler, caps); | |
| } | |
| } | |
| std::unique_ptr<GrFragmentProcessor> raster_as_fp(GrRecordingContext* rContext, | |
| const SkImage_Raster* img, | |
| SkSamplingOptions sampling, | |
| const SkTileMode tileModes[2], | |
| const SkMatrix& m, | |
| const SkRect* subset, | |
| const SkRect* domain) { | |
| auto mm = | |
| sampling.mipmap == SkMipmapMode::kNone ? skgpu::Mipmapped::kNo : skgpu::Mipmapped::kYes; | |
| return make_fp_from_view(rContext, | |
| std::get<0>(AsView(rContext, img, mm, /*targetSurface=*/nullptr)), | |
| img->alphaType(), | |
| sampling, | |
| tileModes, | |
| m, | |
| subset, | |
| domain); | |
| } | |
| std::unique_ptr<GrFragmentProcessor> AsFragmentProcessor(SurfaceDrawContext* sdc, | |
| const SkImage* img, | |
| SkSamplingOptions sampling, | |
| const SkTileMode tileModes[2], | |
| const SkMatrix& m, | |
| const SkRect* subset, | |
| const SkRect* domain) { | |
| if (!sdc) { | |
| return {}; | |
| } | |
| GrRecordingContext* rContext = sdc->recordingContext(); | |
| if (!rContext) { | |
| return {}; | |
| } | |
| if (sampling.useCubic && !GrValidCubicResampler(sampling.cubic)) { | |
| return {}; | |
| } | |
| if (sampling.mipmap != SkMipmapMode::kNone && | |
| (!rContext->priv().caps()->mipmapSupport() || img->dimensions().area() <= 1)) { | |
| sampling = SkSamplingOptions(sampling.filter); | |
| } | |
| auto ib = static_cast<const SkImage_Base*>(img); | |
| if (ib->isRasterBacked()) { | |
| return raster_as_fp(rContext, | |
| static_cast<const SkImage_Raster*>(ib), | |
| sampling, | |
| tileModes, | |
| m, | |
| subset, | |
| domain); | |
| } else if (ib->isGaneshBacked()) { | |
| auto gb = static_cast<const SkImage_GaneshBase*>(img); | |
| return gb->asFragmentProcessor(sdc, sampling, tileModes, m, subset, domain); | |
| } else if (ib->isLazyGenerated()) { | |
| // TODO: If the CPU data is extracted as planes return a FP that reconstructs the image from | |
| // the planes. | |
| auto mm = sampling.mipmap == SkMipmapMode::kNone ? skgpu::Mipmapped::kNo | |
| : skgpu::Mipmapped::kYes; | |
| auto rtp = sdc->asRenderTargetProxy(); | |
| return MakeFragmentProcessorFromView(rContext, | |
| std::get<0>(AsView(rContext, img, mm, rtp)), | |
| img->alphaType(), | |
| sampling, | |
| tileModes, | |
| m, | |
| subset, | |
| domain); | |
| } | |
| SkDEBUGFAIL("Unsupported image type to make a FragmentProcessor"); | |
| return {}; | |
| } | |
| std::unique_ptr<GrFragmentProcessor> MakeFragmentProcessorFromView( | |
| GrRecordingContext* rContext, | |
| GrSurfaceProxyView view, | |
| SkAlphaType at, | |
| SkSamplingOptions sampling, | |
| const SkTileMode tileModes[2], | |
| const SkMatrix& m, | |
| const SkRect* subset, | |
| const SkRect* domain) { | |
| if (!view) { | |
| return nullptr; | |
| } | |
| const GrCaps& caps = *rContext->priv().caps(); | |
| auto wmx = SkTileModeToWrapMode(tileModes[0]); | |
| auto wmy = SkTileModeToWrapMode(tileModes[1]); | |
| if (sampling.useCubic) { | |
| if (subset) { | |
| if (domain) { | |
| return GrBicubicEffect::MakeSubset(std::move(view), | |
| at, | |
| m, | |
| wmx, | |
| wmy, | |
| *subset, | |
| *domain, | |
| sampling.cubic, | |
| GrBicubicEffect::Direction::kXY, | |
| *rContext->priv().caps()); | |
| } | |
| return GrBicubicEffect::MakeSubset(std::move(view), | |
| at, | |
| m, | |
| wmx, | |
| wmy, | |
| *subset, | |
| sampling.cubic, | |
| GrBicubicEffect::Direction::kXY, | |
| *rContext->priv().caps()); | |
| } | |
| return GrBicubicEffect::Make(std::move(view), | |
| at, | |
| m, | |
| wmx, | |
| wmy, | |
| sampling.cubic, | |
| GrBicubicEffect::Direction::kXY, | |
| *rContext->priv().caps()); | |
| } | |
| if (sampling.isAniso()) { | |
| if (!rContext->priv().caps()->anisoSupport()) { | |
| // Fallback to linear | |
| sampling = SkSamplingPriv::AnisoFallback(view.mipmapped() == skgpu::Mipmapped::kYes); | |
| } | |
| } else if (view.mipmapped() == skgpu::Mipmapped::kNo) { | |
| sampling = SkSamplingOptions(sampling.filter); | |
| } | |
| GrSamplerState sampler; | |
| if (sampling.isAniso()) { | |
| sampler = GrSamplerState::Aniso(wmx, wmy, sampling.maxAniso, view.mipmapped()); | |
| } else { | |
| sampler = GrSamplerState(wmx, wmy, sampling.filter, sampling.mipmap); | |
| } | |
| if (subset) { | |
| if (domain) { | |
| return GrTextureEffect::MakeSubset(std::move(view), | |
| at, | |
| m, | |
| sampler, | |
| *subset, | |
| *domain, | |
| caps); | |
| } | |
| return GrTextureEffect::MakeSubset(std::move(view), | |
| at, | |
| m, | |
| sampler, | |
| *subset, | |
| caps); | |
| } else { | |
| return GrTextureEffect::Make(std::move(view), at, m, sampler, caps); | |
| } | |
| } | |
| GrSurfaceProxyView FindOrMakeCachedMipmappedView(GrRecordingContext* rContext, | |
| GrSurfaceProxyView view, | |
| uint32_t imageUniqueID) { | |
| SkASSERT(rContext); | |
| SkASSERT(imageUniqueID != SK_InvalidUniqueID); | |
| if (!view || view.proxy()->asTextureProxy()->mipmapped() == skgpu::Mipmapped::kYes) { | |
| return view; | |
| } | |
| GrProxyProvider* proxyProvider = rContext->priv().proxyProvider(); | |
| skgpu::UniqueKey baseKey; | |
| GrMakeKeyFromImageID(&baseKey, imageUniqueID, SkIRect::MakeSize(view.dimensions())); | |
| SkASSERT(baseKey.isValid()); | |
| skgpu::UniqueKey mipmappedKey; | |
| static const skgpu::UniqueKey::Domain kMipmappedDomain = skgpu::UniqueKey::GenerateDomain(); | |
| { // No extra values beyond the domain are required. Must name the var to please clang-tidy. | |
| skgpu::UniqueKey::Builder b(&mipmappedKey, baseKey, kMipmappedDomain, 0); | |
| } | |
| SkASSERT(mipmappedKey.isValid()); | |
| if (sk_sp<GrTextureProxy> cachedMippedView = | |
| proxyProvider->findOrCreateProxyByUniqueKey(mipmappedKey)) { | |
| return {std::move(cachedMippedView), view.origin(), view.swizzle()}; | |
| } | |
| auto copy = GrCopyBaseMipMapToView(rContext, view); | |
| if (!copy) { | |
| return view; | |
| } | |
| // TODO: If we move listeners up from SkImage_Lazy to SkImage_Base then add one here. | |
| proxyProvider->assignUniqueKeyToProxy(mipmappedKey, copy.asTextureProxy()); | |
| return copy; | |
| } | |
| using DataType = SkYUVAPixmapInfo::DataType; | |
| SkYUVAPixmapInfo::SupportedDataTypes SupportedTextureFormats(const GrImageContext& context) { | |
| SkYUVAPixmapInfo::SupportedDataTypes dataTypes; | |
| const auto isValid = [&context](DataType dt, int n) { | |
| return context.defaultBackendFormat(SkYUVAPixmapInfo::DefaultColorTypeForDataType(dt, n), | |
| GrRenderable::kNo).isValid(); | |
| }; | |
| for (int n = 1; n <= 4; ++n) { | |
| if (isValid(DataType::kUnorm8, n)) { | |
| dataTypes.enableDataType(DataType::kUnorm8, n); | |
| } | |
| if (isValid(DataType::kUnorm16, n)) { | |
| dataTypes.enableDataType(DataType::kUnorm16, n); | |
| } | |
| if (isValid(DataType::kFloat16, n)) { | |
| dataTypes.enableDataType(DataType::kFloat16, n); | |
| } | |
| if (isValid(DataType::kUnorm10_Unorm2, n)) { | |
| dataTypes.enableDataType(DataType::kUnorm10_Unorm2, n); | |
| } | |
| } | |
| return dataTypes; | |
| } | |
| } // namespace skgpu::ganesh | |
| namespace skif { | |
| namespace { | |
| class GaneshBackend : | |
| public Backend, | |
| private SkShaderBlurAlgorithm, | |
| private SkBlurEngine { | |
| public: | |
| GaneshBackend(sk_sp<GrRecordingContext> context, | |
| GrSurfaceOrigin origin, | |
| const SkSurfaceProps& surfaceProps, | |
| SkColorType colorType) | |
| : Backend(SkImageFilterCache::Create(SkImageFilterCache::kDefaultTransientSize), | |
| surfaceProps, colorType) | |
| , fContext(std::move(context)) | |
| , fOrigin(origin) {} | |
| // Backend | |
| sk_sp<SkDevice> makeDevice(SkISize size, | |
| sk_sp<SkColorSpace> colorSpace, | |
| const SkSurfaceProps* props) const override { | |
| SkImageInfo imageInfo = SkImageInfo::Make(size, | |
| this->colorType(), | |
| kPremul_SkAlphaType, | |
| std::move(colorSpace)); | |
| return fContext->priv().createDevice(skgpu::Budgeted::kYes, | |
| imageInfo, | |
| SkBackingFit::kApprox, | |
| 1, | |
| skgpu::Mipmapped::kNo, | |
| GrProtected::kNo, | |
| fOrigin, | |
| props ? *props : this->surfaceProps(), | |
| skgpu::ganesh::Device::InitContents::kUninit); | |
| } | |
| sk_sp<SkSpecialImage> makeImage(const SkIRect& subset, sk_sp<SkImage> image) const override { | |
| return SkSpecialImages::MakeFromTextureImage( | |
| fContext.get(), subset, image, this->surfaceProps()); | |
| } | |
| sk_sp<SkImage> getCachedBitmap(const SkBitmap& data) const override { | |
| // This uses the thread safe cache (instead of GrMakeCachedBitmapProxyView) so that image | |
| // filters can be evaluated on other threads with DDLs. | |
| auto threadSafeCache = fContext->priv().threadSafeCache(); | |
| skgpu::UniqueKey key; | |
| SkIRect subset = SkIRect::MakePtSize(data.pixelRefOrigin(), data.dimensions()); | |
| GrMakeKeyFromImageID(&key, data.getGenerationID(), subset); | |
| auto view = threadSafeCache->find(key); | |
| if (!view) { | |
| view = std::get<0>(GrMakeUncachedBitmapProxyView( | |
| fContext.get(), GrMippedBitmap(data))); | |
| if (!view) { | |
| return nullptr; | |
| } | |
| threadSafeCache->add(key, view); | |
| } | |
| return sk_make_sp<SkImage_Ganesh>(fContext, | |
| data.getGenerationID(), | |
| std::move(view), | |
| data.info().colorInfo()); | |
| } | |
| const SkBlurEngine* getBlurEngine() const override { return this; } | |
| // SkBlurEngine | |
| const SkBlurEngine::Algorithm* findAlgorithm(SkSize sigma, | |
| SkColorType colorType) const override { | |
| // GrBlurUtils supports all tile modes and color types | |
| return this; | |
| } | |
| // SkShaderBlurAlgorithm | |
| sk_sp<SkDevice> makeDevice(const SkImageInfo& imageInfo) const override { | |
| return fContext->priv().createDevice(skgpu::Budgeted::kYes, | |
| imageInfo, | |
| SkBackingFit::kApprox, | |
| 1, | |
| skgpu::Mipmapped::kNo, | |
| GrProtected::kNo, | |
| fOrigin, | |
| this->surfaceProps(), | |
| skgpu::ganesh::Device::InitContents::kUninit); | |
| } | |
| private: | |
| sk_sp<GrRecordingContext> fContext; | |
| GrSurfaceOrigin fOrigin; | |
| }; | |
| } // anonymous namespace | |
| sk_sp<Backend> MakeGaneshBackend(sk_sp<GrRecordingContext> context, | |
| GrSurfaceOrigin origin, | |
| const SkSurfaceProps& surfaceProps, | |
| SkColorType colorType) { | |
| SkASSERT(context); | |
| return sk_make_sp<GaneshBackend>(std::move(context), origin, surfaceProps, colorType); | |
| } | |
| } // namespace skif |