| /* | |
| * Copyright 2022 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/graphite/TextureUtils.h" | |
| #include "include/core/SkBitmap.h" | |
| #include "include/core/SkCanvas.h" | |
| #include "include/core/SkColorSpace.h" | |
| #include "include/core/SkPaint.h" | |
| #include "include/core/SkSurface.h" | |
| #include "include/effects/SkRuntimeEffect.h" | |
| #include "include/private/SkAttributes.h" | |
| #include "include/private/SkTArray.h" | |
| #include "src/core/SkBlurEngine.h" | |
| #include "src/core/SkCompressedDataUtils.h" | |
| #include "src/core/SkDevice.h" | |
| #include "src/core/SkImageFilterCache.h" | |
| #include "src/core/SkImageFilterTypes.h" | |
| #include "src/core/SkMipmap.h" | |
| #include "src/core/SkSamplingPriv.h" | |
| #include "src/core/SkTraceEvent.h" | |
| #include "src/image/SkImage_Base.h" | |
| #include "include/gpu/graphite/BackendTexture.h" | |
| #include "include/gpu/graphite/Context.h" | |
| #include "include/gpu/graphite/GraphiteTypes.h" | |
| #include "include/gpu/graphite/Image.h" | |
| #include "include/gpu/graphite/ImageProvider.h" | |
| #include "include/gpu/graphite/Recorder.h" | |
| #include "include/gpu/graphite/Recording.h" | |
| #include "include/gpu/graphite/Surface.h" | |
| #include "include/private/SkLog.h" | |
| #include "src/gpu/BlurUtils.h" | |
| #include "src/gpu/RefCntedCallback.h" | |
| #include "src/gpu/SkBackingFit.h" | |
| #include "src/gpu/graphite/Buffer.h" | |
| #include "src/gpu/graphite/Caps.h" | |
| #include "src/gpu/graphite/CommandBuffer.h" | |
| #include "src/gpu/graphite/Device.h" | |
| #include "src/gpu/graphite/DrawContext.h" | |
| #include "src/gpu/graphite/Image_Base_Graphite.h" | |
| #include "src/gpu/graphite/Image_Graphite.h" | |
| #include "src/gpu/graphite/RecorderPriv.h" | |
| #include "src/gpu/graphite/ResourceProvider.h" | |
| #include "src/gpu/graphite/ResourceTypes.h" | |
| #include "src/gpu/graphite/RuntimeEffectDictionary.h" | |
| #include "src/gpu/graphite/SpecialImage_Graphite.h" | |
| #include "src/gpu/graphite/Surface_Graphite.h" | |
| #include "src/gpu/graphite/Texture.h" | |
| #include "src/gpu/graphite/TextureInfoPriv.h" | |
| #include "src/gpu/graphite/task/CopyTask.h" | |
| #include "src/gpu/graphite/task/SynchronizeToCpuTask.h" | |
| #include "src/gpu/graphite/task/UploadTask.h" | |
| #include <array> | |
| using SkImages::GraphitePromiseTextureFulfillProc; | |
| using SkImages::GraphitePromiseTextureFulfillContext; | |
| using SkImages::GraphitePromiseTextureReleaseProc; | |
| namespace skgpu::graphite { | |
| namespace { | |
| // We choose a fallback color type that will map to the same texture format as `dstCT` but will be | |
| // considered renderable (assuming the format is supported). This allows the content to be | |
| // rendered and then we "cast" back to the original requested color type for the image view of the | |
| // scratch surface. | |
| // | |
| // This function takes in the `srcCT` as well because it can enable some fallbacks that are not | |
| // otherwise possible (e.g. gray -> red -> gray). | |
| SkColorType renderable_colortype(SkColorType srcCT, SkColorType dstCT) { | |
| // This mapping only includes color types that are often deemed non-renderable because of | |
| // semantics (e.g. can't blend into an alpha channel that is meant to be masked during sampling, | |
| // or can't render gray from an arbitrary RGB source). | |
| // | |
| // We intentionally don't support falling back from one color type to another if it changes | |
| // the underlying data type. It is better to fail the operation to signal to the client that | |
| // the action isn't possible and let them choose what sort of fallback should happen. | |
| switch (dstCT) { | |
| // For these types we can always render with the alpha channel and not worry about | |
| // blending because every draw operation uses kSrc and we're filling all pixels. The | |
| // image view will then still use an rgb1 swizzle to hide any bad alpha data from the | |
| // original image. | |
| case kRGB_101010x_SkColorType: return kRGBA_1010102_SkColorType; | |
| case kBGR_101010x_SkColorType: return kBGRA_1010102_SkColorType; | |
| case kRGB_F16F16F16x_SkColorType: return kRGBA_F16_SkColorType; | |
| case kRGBA_F16Norm_SkColorType: return kRGBA_F16_SkColorType; | |
| // While it is the case that a BGRA format can be used with a kRGB_888x colortype, we | |
| // don't look at srcCT to guess the format and switch to kBGRA_8888. In the event that | |
| // this copied image will be read back to the CPU, it's best to match the color type's | |
| // channel ordering. | |
| case kRGB_888x_SkColorType: return kRGBA_8888_SkColorType; | |
| // Normally kGray is never renderable from arbitrary RGB color data because calculating the | |
| // luminance/gray level is a dot product. However, if the source color type is also gray, | |
| // then the only channel we care about is R, which is renderable. After rendering to R, | |
| // the image view's swizzle can splat that out to produce grayscale. | |
| case kGray_8_SkColorType: | |
| if (srcCT == kGray_8_SkColorType) { | |
| return kR8_unorm_SkColorType; | |
| } else { | |
| return kUnknown_SkColorType; | |
| } | |
| default: | |
| // If this color type isn't renderable or doesn't map to a supported format, then there | |
| // isn't any other fallback that can happen. | |
| return dstCT; | |
| } | |
| } | |
| SkAlphaType renderable_alphatype(SkAlphaType srcAT, SkAlphaType dstAT) { | |
| switch (srcAT) { | |
| case kUnknown_SkAlphaType: | |
| // The src image will be forced opaque as part of sampling, so the output pixels will | |
| // be guaranteed opaque (can upgrade requested kPremul or kUnpremul to kOpaque since the | |
| // RGB values are unchanged). | |
| return kOpaque_SkAlphaType; | |
| case kOpaque_SkAlphaType: | |
| // The src image claims to be opaque, so the output pixels should be opaque. | |
| return kOpaque_SkAlphaType; | |
| case kPremul_SkAlphaType: | |
| if (dstAT == kUnpremul_SkAlphaType) { | |
| // If the requested dst AT is kUnpremul, allow rendering directly to a kUnpremul | |
| // scratch surface using kSrc blending. | |
| return kUnpremul_SkAlphaType; | |
| } | |
| // Always render to kPremul, regardless of the requested dst AT. If the dst AT was | |
| // kPremul this is a no-op. If it was kOpaque, SkColorSpaceXformSteps treats it as the | |
| // src AT, so it's also a no-op. If it was kUnknown, the image view will presumably be | |
| // sampled with a masked opaque alpha channel, so producing premul RGB values emulates | |
| // having blended with solid black. | |
| return kPremul_SkAlphaType; | |
| case kUnpremul_SkAlphaType: | |
| // If the requested dst AT is kPremul, then keep that so the premultiply is performed | |
| // during the copy conversion. In all other cases, switch to kOpaque so that we are | |
| // deemed renderable and color conversion in SkColorSpaceXformSteps produces a no-op. | |
| // Since the only actual rendering to this surface will be pixel-filling with kSrc | |
| // blending, this alpha type manipulation is valid. | |
| return dstAT == kPremul_SkAlphaType ? kPremul_SkAlphaType : kOpaque_SkAlphaType; | |
| } | |
| SkUNREACHABLE; | |
| } | |
| SkAlphaType final_alphatype(SkAlphaType srcAT, SkAlphaType renderedAT) { | |
| // Assuming `renderedAT` was the result of calling `renderable_alphatype` for `srcAT` and some | |
| // `dstAT`, the final AT to use for the image view is almost always `renderedAT` because it is | |
| // either more accurate (propogates src opaque-ness into the copy's alpha type), a no-op (it | |
| // was premul), or unpremul output was requested for premul input and that's not supported so | |
| // we need to reflect the copy as premul still in its image info. | |
| // | |
| // The only exception is for when both srcAT and dstAT were unpremul, in which case | |
| // `renderedAT` is manipulated to be kOpaque for rendering but in actuality the output remains | |
| // unpremul and that should be reflected in the final image info as well. | |
| return (srcAT == kUnpremul_SkAlphaType && renderedAT == kOpaque_SkAlphaType) ? | |
| kUnpremul_SkAlphaType : renderedAT; | |
| } | |
| SkColorInfo make_renderable(const SkColorInfo& srcInfo, const SkColorInfo& dstInfo) { | |
| return dstInfo.makeColorType(renderable_colortype(srcInfo.colorType(), dstInfo.colorType())) | |
| .makeAlphaType(renderable_alphatype(srcInfo.alphaType(), dstInfo.alphaType())); | |
| } | |
| bool valid_client_provided_image(const SkImage* clientProvided, | |
| const SkImage* original, | |
| SkImage::RequiredProperties requiredProps) { | |
| if (!clientProvided || | |
| !as_IB(clientProvided)->isGraphiteBacked() || | |
| original->dimensions() != clientProvided->dimensions() || | |
| original->alphaType() != clientProvided->alphaType()) { | |
| return false; | |
| } | |
| uint32_t origChannels = SkColorTypeChannelFlags(original->colorType()); | |
| uint32_t clientChannels = SkColorTypeChannelFlags(clientProvided->colorType()); | |
| if ((origChannels & clientChannels) != origChannels) { | |
| return false; | |
| } | |
| // We require provided images to have a TopLeft origin | |
| auto graphiteImage = static_cast<const Image*>(clientProvided); | |
| if (graphiteImage->textureProxyView().origin() != Origin::kTopLeft) { | |
| SKIA_LOG_E("Client provided image must have a TopLeft origin."); | |
| return false; | |
| } | |
| return true; | |
| } | |
| // This class is the lazy instantiation callback for promise images. It manages calling the | |
| // client's Fulfill, ImageRelease, and TextureRelease procs. | |
| class PromiseLazyInstantiateCallback { | |
| public: | |
| PromiseLazyInstantiateCallback(sk_sp<RefCntedCallback> releaseHelper, | |
| GraphitePromiseTextureFulfillProc fulfillProc, | |
| GraphitePromiseTextureFulfillContext fulfillContext, | |
| GraphitePromiseTextureReleaseProc textureReleaseProc, | |
| std::string_view label) | |
| : fReleaseHelper(std::move(releaseHelper)) | |
| , fFulfillProc(fulfillProc) | |
| , fFulfillContext(fulfillContext) | |
| , fTextureReleaseProc(textureReleaseProc) | |
| , fLabel(label) { | |
| } | |
| PromiseLazyInstantiateCallback(PromiseLazyInstantiateCallback&&) = default; | |
| PromiseLazyInstantiateCallback(const PromiseLazyInstantiateCallback&) { | |
| // Because we get wrapped in std::function we must be copyable. But we should never | |
| // be copied. | |
| SkASSERT(false); | |
| } | |
| PromiseLazyInstantiateCallback& operator=(PromiseLazyInstantiateCallback&&) = default; | |
| PromiseLazyInstantiateCallback& operator=(const PromiseLazyInstantiateCallback&) { | |
| SkASSERT(false); | |
| return *this; | |
| } | |
| sk_sp<Texture> operator()(ResourceProvider* resourceProvider) { | |
| // Invoke the fulfill proc to get the promised backend texture. | |
| auto [ backendTexture, textureReleaseCtx ] = fFulfillProc(fFulfillContext); | |
| if (!backendTexture.isValid()) { | |
| SKIA_LOG_W("FulfillProc returned an invalid backend texture"); | |
| return nullptr; | |
| } | |
| sk_sp<RefCntedCallback> textureReleaseCB = RefCntedCallback::Make(fTextureReleaseProc, | |
| textureReleaseCtx); | |
| sk_sp<Texture> texture = resourceProvider->createWrappedTexture(backendTexture, | |
| std::move(fLabel)); | |
| if (!texture) { | |
| SKIA_LOG_W("Failed to wrap BackendTexture returned by fulfill proc"); | |
| return nullptr; | |
| } | |
| texture->setReleaseCallback(std::move(textureReleaseCB)); | |
| return texture; | |
| } | |
| private: | |
| sk_sp<RefCntedCallback> fReleaseHelper; | |
| GraphitePromiseTextureFulfillProc fFulfillProc; | |
| GraphitePromiseTextureFulfillContext fFulfillContext; | |
| GraphitePromiseTextureReleaseProc fTextureReleaseProc; | |
| std::string fLabel; | |
| }; | |
| } // anonymous namespace | |
| TextureProxyView MakeBitmapProxyView(Recorder* recorder, | |
| const SkBitmap& bitmap, | |
| sk_sp<SkMipmap> mipmapsIn, | |
| Mipmapped mipmapped, | |
| Budgeted budgeted, | |
| std::string_view label) { | |
| // Adjust params based on input and Caps | |
| const Caps* caps = recorder->priv().caps(); | |
| const SkColorType ct = bitmap.info().colorType(); | |
| if (bitmap.dimensions().area() <= 1) { | |
| mipmapped = Mipmapped::kNo; | |
| } | |
| Protected isProtected = recorder->priv().isProtected(); | |
| auto textureInfo = caps->getDefaultSampledTextureInfo(ct, mipmapped, isProtected, | |
| Renderable::kNo); | |
| if (!textureInfo.isValid()) { | |
| return {}; | |
| } | |
| if (!SkImageInfoIsValid(bitmap.info())) { | |
| return {}; | |
| } | |
| int mipLevelCount = (mipmapped == Mipmapped::kYes) ? | |
| SkMipmap::ComputeLevelCount(bitmap.width(), bitmap.height()) + 1 : 1; | |
| // setup MipLevels | |
| sk_sp<SkMipmap> mipmaps = mipmapsIn; | |
| skia_private::STArray<16, MipLevel> levels(mipLevelCount); | |
| levels.push_back({bitmap.getPixels(), bitmap.rowBytes()}); // base level is always included | |
| if (mipLevelCount > 1) { | |
| if (!mipmaps && !(mipmaps = sk_sp<SkMipmap>(SkMipmap::Build(bitmap.pixmap(), nullptr)))) { | |
| SKIA_LOG_E("Generating mipmaps failed"); | |
| return {}; | |
| } | |
| SkASSERT(mipLevelCount == mipmaps->countLevels() + 1); | |
| for (int i = 1; i < mipLevelCount; ++i) { | |
| SkMipmap::Level mipLevel; | |
| SkAssertResult(mipmaps->getLevel(i - 1, &mipLevel)); | |
| levels.push_back({mipLevel.fPixmap.addr(), mipLevel.fPixmap.rowBytes()}); | |
| } | |
| } | |
| // Create proxy | |
| sk_sp<TextureProxy> proxy = TextureProxy::Make(caps, | |
| recorder->priv().resourceProvider(), | |
| bitmap.dimensions(), | |
| textureInfo, | |
| budgeted, | |
| label); | |
| if (!proxy) { | |
| return {}; | |
| } | |
| const TextureFormat format = proxy->format(); | |
| SkASSERT(AreColorTypeAndFormatCompatible(ct, format)); | |
| SkASSERT(mipmapped == Mipmapped::kNo || proxy->mipmapped() == Mipmapped::kYes); | |
| const Swizzle swizzle = ReadSwizzleForColorType(ct, format); | |
| TextureProxyView view{std::move(proxy), swizzle}; | |
| // Src and dst colorInfo are the same | |
| const SkColorInfo& colorInfo = bitmap.info().colorInfo(); | |
| // Add upload to the root upload list. These bitmaps are uploaded to unique textures so there is | |
| // no need to coordinate resource sharing. It is better to then group them into a single task | |
| // at the start of the Recording. | |
| const SkIRect dimensions = SkIRect::MakeSize(bitmap.dimensions()); | |
| // Move the view into `uploadSource` so that it is the unique holder of the proxy | |
| UploadSource uploadSource = UploadSource::Make( | |
| recorder->priv().caps(), std::move(view), colorInfo, colorInfo, levels, dimensions); | |
| if (!uploadSource.isValid()) { | |
| SKIA_LOG_E("MakeBitmapProxyView: Could not create UploadSource"); | |
| return {}; | |
| } | |
| if (uploadSource.attemptUploadOnhost()) { | |
| return uploadSource.view(); | |
| } | |
| // else it failed or was unavailable, so use a task on the root upload list | |
| if (!recorder->priv().rootUploadList()->recordUpload(recorder, | |
| uploadSource, | |
| std::make_unique<ImageUploadContext>())) { | |
| SKIA_LOG_E("MakeBitmapProxyView: Could not create UploadInstance"); | |
| return {}; | |
| } | |
| return uploadSource.view(); | |
| } | |
| sk_sp<TextureProxy> MakePromiseImageLazyProxy( | |
| const Caps* caps, | |
| SkISize dimensions, | |
| TextureInfo textureInfo, | |
| Volatile isVolatile, | |
| sk_sp<RefCntedCallback> releaseHelper, | |
| GraphitePromiseTextureFulfillProc fulfillProc, | |
| GraphitePromiseTextureFulfillContext fulfillContext, | |
| GraphitePromiseTextureReleaseProc textureReleaseProc, | |
| std::string_view label) { | |
| SkASSERT(!dimensions.isEmpty()); | |
| SkASSERT(releaseHelper); | |
| if (!fulfillProc) { | |
| return nullptr; | |
| } | |
| PromiseLazyInstantiateCallback callback{std::move(releaseHelper), fulfillProc, | |
| fulfillContext, textureReleaseProc, label}; | |
| // Proxies for promise images are assumed to always be destined for a client's SkImage so | |
| // are never considered budgeted. | |
| return TextureProxy::MakeLazy(caps, dimensions, textureInfo, Budgeted::kNo, isVolatile, | |
| std::move(callback)); | |
| } | |
| size_t ComputeSize(SkISize dimensions, const TextureInfo& info) { | |
| TextureFormat format = TextureInfoPriv::ViewFormat(info); | |
| SkTextureCompressionType compression = TextureFormatCompressionType(format); | |
| size_t colorSize = 0; | |
| if (compression != SkTextureCompressionType::kNone) { | |
| colorSize = SkCompressedFormatDataSize(compression, | |
| dimensions, | |
| info.mipmapped() == Mipmapped::kYes); | |
| } else { | |
| // TODO(b/401016699): Add logic to handle multiplanar formats | |
| int bytesPerPixel = TextureFormatBytesPerBlock(format); | |
| colorSize = (size_t)dimensions.width() * dimensions.height() * bytesPerPixel; | |
| } | |
| size_t finalSize = colorSize * (uint8_t) info.sampleCount(); | |
| if (info.mipmapped() == Mipmapped::kYes) { | |
| finalSize += colorSize/3; | |
| } | |
| return finalSize; | |
| } | |
| sk_sp<Image> CopyAsDraw(Recorder* recorder, | |
| DrawContext* drawContext, | |
| const SkImage* image, | |
| const SkIRect& subset, | |
| const SkColorInfo& dstColorInfo, | |
| Budgeted budgeted, | |
| Mipmapped mipmapped, | |
| SkBackingFit backingFit, | |
| std::string_view label) { | |
| // NOTE: This info may not exactly match `dstColorInfo` but will be castable back to | |
| // `dstColorInfo` when we create the Image view. | |
| SkImageInfo dstInfo = SkImageInfo::Make( | |
| subset.size(), make_renderable(image->imageInfo().colorInfo(), dstColorInfo)); | |
| // The surface goes out of scope when we return, so it can be scratch, but it may or may | |
| // not be budgeted depending on how the copied image is used (or returned to the client). | |
| sk_sp<Surface> surface = | |
| Surface::MakeScratch(recorder, dstInfo, label, budgeted, mipmapped, backingFit, | |
| /*allowUnpremul=*/true); | |
| if (!surface) { | |
| return nullptr; | |
| } | |
| SkPaint paint; | |
| paint.setBlendMode(SkBlendMode::kSrc); | |
| surface->getCanvas()->drawImage(image, -subset.left(), -subset.top(), | |
| SkFilterMode::kNearest, &paint); | |
| surface->flushToDrawContext(drawContext); | |
| // Get the image with the actual requested color type and the final alpha type. | |
| return surface->asImage(dstColorInfo.colorType(), | |
| final_alphatype(image->alphaType(), dstInfo.alphaType())); | |
| } | |
| sk_sp<Image> RescaleImage(Recorder* recorder, | |
| const Image_Base* srcImage, | |
| SkIRect srcIRect, | |
| const SkImageInfo& dstInfo, | |
| SkImage::RescaleGamma rescaleGamma, | |
| SkImage::RescaleMode rescaleMode) { | |
| TRACE_EVENT0("skia.gpu", TRACE_FUNC); | |
| TRACE_EVENT_INSTANT2("skia.gpu", "RescaleImage Src", TRACE_EVENT_SCOPE_THREAD, | |
| "width", srcIRect.width(), "height", srcIRect.height()); | |
| TRACE_EVENT_INSTANT2("skia.gpu", "RescaleImage Dst", TRACE_EVENT_SCOPE_THREAD, | |
| "width", dstInfo.width(), "height", dstInfo.height()); | |
| // Other than the final step, rescaling will be performed in the source color type and color | |
| // space, possibly with a linear gamma adjustment (although changing the SkColorSpace can be | |
| // applied to the result of make_renderable). | |
| SkColorInfo stepInfo = make_renderable(srcImage->imageInfo().colorInfo(), | |
| srcImage->imageInfo().colorInfo()); | |
| // Make a Surface *exactly* (barring renderable adjustments) matching dstInfo for the final | |
| // scaling step. | |
| SkColorInfo renderableDstInfo = | |
| make_renderable(srcImage->imageInfo().colorInfo(), dstInfo.colorInfo()); | |
| sk_sp<Surface> dst = Surface::MakeScratch( | |
| recorder, | |
| SkImageInfo::Make(dstInfo.dimensions(), renderableDstInfo), | |
| "RescaleDstTexture", | |
| Budgeted::kYes, | |
| Mipmapped::kNo, | |
| SkBackingFit::kExact, | |
| /*allowUnpremul=*/true); | |
| if (!dst) { | |
| return nullptr; | |
| } | |
| SkRect srcRect = SkRect::Make(srcIRect); | |
| SkRect dstRect = SkRect::Make(dstInfo.dimensions()); | |
| SkISize finalSize = SkISize::Make(dstRect.width(), dstRect.height()); | |
| if (finalSize == srcIRect.size()) { | |
| rescaleGamma = Image::RescaleGamma::kSrc; | |
| rescaleMode = Image::RescaleMode::kNearest; | |
| } | |
| // Within a rescaling pass tempInput is read from and tempOutput is written to. | |
| // At the end of the pass tempOutput's texture is wrapped and assigned to tempInput. | |
| sk_sp<Image_Base> tempInput = sk_ref_sp(srcImage); | |
| sk_sp<Surface> tempOutput; | |
| // Assume we should ignore the rescale linear request if the surface has no color space since | |
| // it's unclear how we'd linearize from an unknown color space. | |
| if (rescaleGamma == Image::RescaleGamma::kLinear && | |
| stepInfo.colorSpace() && | |
| !stepInfo.colorSpace()->gammaIsLinear()) { | |
| // Draw the src image into a new surface with linear gamma, and make that the new tempInput | |
| sk_sp<SkColorSpace> linearGamma = stepInfo.colorSpace()->makeLinearGamma(); | |
| SkImageInfo gammaDstInfo = SkImageInfo::Make(srcIRect.size(), | |
| stepInfo.makeColorSpace(linearGamma)); | |
| tempOutput = Surface::MakeScratch(recorder, | |
| gammaDstInfo, | |
| "RescaleLinearGammaTexture"); | |
| if (!tempOutput) { | |
| return nullptr; | |
| } | |
| SkCanvas* gammaDst = tempOutput->getCanvas(); | |
| SkRect gammaDstRect = SkRect::Make(srcIRect.size()); | |
| SkPaint paint; | |
| paint.setBlendMode(SkBlendMode::kSrc); | |
| gammaDst->drawImageRect(tempInput, srcRect, gammaDstRect, | |
| SkSamplingOptions(SkFilterMode::kNearest), &paint, | |
| SkCanvas::kStrict_SrcRectConstraint); | |
| tempInput = tempOutput->asImage(); | |
| srcRect = gammaDstRect; | |
| stepInfo = gammaDstInfo.colorInfo(); // remaining steps output linear gamma too | |
| } | |
| do { | |
| SkISize nextDims = finalSize; | |
| if (rescaleMode != Image::RescaleMode::kNearest && | |
| rescaleMode != Image::RescaleMode::kLinear) { | |
| if (srcRect.width() > finalSize.width()) { | |
| nextDims.fWidth = std::max((srcRect.width() + 1)/2, (float)finalSize.width()); | |
| } else if (srcRect.width() < finalSize.width()) { | |
| nextDims.fWidth = std::min(srcRect.width()*2, (float)finalSize.width()); | |
| } | |
| if (srcRect.height() > finalSize.height()) { | |
| nextDims.fHeight = std::max((srcRect.height() + 1)/2, (float)finalSize.height()); | |
| } else if (srcRect.height() < finalSize.height()) { | |
| nextDims.fHeight = std::min(srcRect.height()*2, (float)finalSize.height()); | |
| } | |
| } | |
| SkRect stepDstRect; | |
| if (nextDims == finalSize) { | |
| // The final surface's color info is `renderableDstInfo` | |
| tempOutput = dst; | |
| stepDstRect = dstRect; | |
| } else { | |
| tempOutput = Surface::MakeScratch(recorder, | |
| SkImageInfo::Make(nextDims, stepInfo), | |
| "RescaleImageTempTexture"); | |
| if (!tempOutput) { | |
| return nullptr; | |
| } | |
| stepDstRect = SkRect::Make(tempOutput->imageInfo().dimensions()); | |
| } | |
| SkSamplingOptions samplingOptions; | |
| if (rescaleMode == Image::RescaleMode::kRepeatedCubic) { | |
| samplingOptions = SkSamplingOptions(SkCubicResampler::CatmullRom()); | |
| } else { | |
| samplingOptions = (rescaleMode == Image::RescaleMode::kNearest) ? | |
| SkSamplingOptions(SkFilterMode::kNearest) : | |
| SkSamplingOptions(SkFilterMode::kLinear); | |
| } | |
| SkPaint paint; | |
| paint.setBlendMode(SkBlendMode::kSrc); | |
| tempOutput->getCanvas()->drawImageRect(tempInput, srcRect, stepDstRect, samplingOptions, | |
| &paint, SkCanvas::kStrict_SrcRectConstraint); | |
| tempInput = tempOutput->asImage(); | |
| srcRect = SkRect::Make(nextDims); | |
| } while (srcRect.width() != finalSize.width() || srcRect.height() != finalSize.height()); | |
| // Cast back to the requested color type and final alpha type now that rendering is finished. | |
| return dst->asImage(dstInfo.colorType(), | |
| final_alphatype(srcImage->alphaType(), renderableDstInfo.alphaType())); | |
| } | |
| bool GenerateMipmaps(Recorder* recorder, DrawContext* drawContext, sk_sp<TextureProxy> texture) { | |
| SkASSERT(texture->mipmapped() == Mipmapped::kYes); | |
| // GenerateMipmaps uses Surface and Image to generate mipmaps by drawing each level at 1/2 | |
| // scale compared to the last level and then copying from the scratch surface into `texture`. | |
| // Surface and Image require SkColorInfo but it does not matter what is chosen so long as the | |
| // final color management results in the identity function (including read/write swizzles). | |
| // This ensures that whatever the original color handling of `texture` is, the regenerated | |
| // levels will match. To do this, we use these settings for both surface and images | |
| // - the default color type for the texture's format | |
| // - kOpaque_SkAlphaType to disable all premul/unpremul conversions and remain renderable, | |
| // (which is okay since all our drawing uses kSrc blending anyways) | |
| // - provide no SkColorSpace | |
| // | |
| // NOTE: In the future, if we were generate mipmaps in linear gamma, we would need to know about | |
| // the original image's color space and alpha type. We would also have to implement a custom | |
| // filtering shader that sampled the base level several times with nearest filtering, convert | |
| // each sample to linear+premul space, average them, and then convert that to the source color | |
| // space and alpha type. | |
| auto [colorType, _] = TextureFormatColorTypeInfo(texture->format()); | |
| SkColorInfo colorInfo{colorType, kOpaque_SkAlphaType, /*cs=*/nullptr}; | |
| // Since we are creating the color info from the default color type for the texture format, | |
| // it should match what we'd expect from make_renderable already. | |
| SkASSERT(make_renderable(colorInfo, colorInfo) == colorInfo); | |
| // Configure swizzle for the initial image to match what happens in Surface::asImage() | |
| auto imgSwizzle = ReadSwizzleForColorType(colorInfo.colorType(), texture->format()); | |
| sk_sp<SkImage> scratchImg(new Image(TextureProxyView(texture, imgSwizzle), colorInfo)); | |
| // Alternate between two scratch surfaces to avoid reading from and writing to a texture in the | |
| // same pass. The dimensions of the first usages of the two scratch textures will be 1/2 and 1/4 | |
| // those of the original texture, respectively. | |
| SkISize srcSize = texture->dimensions(); | |
| sk_sp<Surface> scratchSurfaces[2]; | |
| for (int i = 0; i < 2; ++i) { | |
| SK_UNSAFE_TODO(scratchSurfaces[i]) = Surface::MakeScratch( | |
| recorder, | |
| SkImageInfo::Make(SkISize::Make(std::max(1, srcSize.width() >> (i + 1)), | |
| std::max(1, srcSize.height() >> (i + 1))), | |
| colorInfo), | |
| "GenerateMipmapsScratchTexture", | |
| Budgeted::kYes, | |
| Mipmapped::kNo, | |
| SkBackingFit::kApprox); | |
| if (SK_UNSAFE_TODO(!scratchSurfaces[i])) { | |
| return false; | |
| } | |
| } | |
| // Within a rescaling pass scratchImg is read from and a scratch surface is written to. | |
| // At the end of the pass the scratch surface's texture is wrapped and assigned to scratchImg. | |
| for (int mipLevel = 1; srcSize.width() > 1 || srcSize.height() > 1; ++mipLevel) { | |
| const SkISize dstSize = SkISize::Make(std::max(srcSize.width() >> 1, 1), | |
| std::max(srcSize.height() >> 1, 1)); | |
| Surface* scratchSurface = scratchSurfaces[(mipLevel - 1) & 1].get(); | |
| SkPaint paint; | |
| paint.setBlendMode(SkBlendMode::kSrc); | |
| scratchSurface->getCanvas()->drawImageRect(scratchImg, | |
| SkRect::Make(srcSize), | |
| SkRect::Make(dstSize), | |
| SkFilterMode::kLinear, | |
| &paint, | |
| SkCanvas::kStrict_SrcRectConstraint); | |
| // Make sure the rescaling draw finishes before copying the results. | |
| scratchSurface->flushToDrawContext(drawContext); | |
| sk_sp<CopyTextureToTextureTask> copyTask = CopyTextureToTextureTask::Make( | |
| static_cast<const Surface*>(scratchSurface)->target().refProxy(), | |
| SkIRect::MakeSize(dstSize), | |
| texture, | |
| {0, 0}, | |
| mipLevel); | |
| if (!copyTask) { | |
| return false; | |
| } | |
| if (drawContext) { | |
| drawContext->recordDependency(std::move(copyTask)); | |
| } else { | |
| recorder->priv().add(std::move(copyTask)); | |
| } | |
| scratchImg = scratchSurface->asImage(); | |
| srcSize = dstSize; | |
| } | |
| return true; | |
| } | |
| std::pair<sk_sp<SkImage>, SkSamplingOptions> GetGraphiteBacked(Recorder* recorder, | |
| const SkImage* imageIn, | |
| SkSamplingOptions sampling) { | |
| Mipmapped mipmapped = (sampling.mipmap != SkMipmapMode::kNone) | |
| ? Mipmapped::kYes : Mipmapped::kNo; | |
| if (imageIn->dimensions().area() <= 1 && mipmapped == Mipmapped::kYes) { | |
| mipmapped = Mipmapped::kNo; | |
| sampling = SkSamplingOptions(SkFilterMode::kLinear); | |
| } | |
| sk_sp<SkImage> result; | |
| if (as_IB(imageIn)->isGraphiteBacked()) { | |
| result = sk_ref_sp(imageIn); | |
| // If the preexisting Graphite-backed image isn't texturable or doesn't have the required | |
| // mipmaps, we drop down the sampling | |
| const auto graphiteImage = static_cast<const Image_Base*>(result.get()); | |
| bool isTexturable = true; | |
| for (const TextureProxyView& view : graphiteImage->textureProxyViews()) { | |
| if (view && !recorder->priv().caps()->isTexturable(view.proxy()->textureInfo())) { | |
| isTexturable = false; | |
| break; | |
| } | |
| } | |
| if (!isTexturable) { | |
| sampling = SkSamplingOptions(SkFilterMode::kNearest); | |
| } else if (mipmapped == Mipmapped::kYes && !result->hasMipmaps()) { | |
| sampling = SkSamplingOptions(SkFilterMode::kLinear); | |
| } | |
| } else { | |
| auto clientImageProvider = recorder->clientImageProvider(); | |
| result = clientImageProvider->findOrCreate( | |
| recorder, imageIn, {mipmapped == Mipmapped::kYes}); | |
| if (!valid_client_provided_image( | |
| result.get(), imageIn, {mipmapped == Mipmapped::kYes})) { | |
| // The client did not fulfill the ImageProvider contract so drop the image. | |
| result = nullptr; | |
| } | |
| } | |
| if (sampling.isAniso() && result) { | |
| sampling = SkSamplingPriv::AnisoFallback(result->hasMipmaps()); | |
| } | |
| return { result, sampling }; | |
| } | |
| TextureProxyView AsView(const SkImage* image) { | |
| if (!image) { | |
| return {}; | |
| } | |
| if (!as_IB(image)->isGraphiteBacked()) { | |
| return {}; | |
| } | |
| // A YUVA image (even if backed by graphite textures) is not a single texture | |
| if (as_IB(image)->isYUVA()) { | |
| return {}; | |
| } | |
| auto gi = reinterpret_cast<const Image*>(image); | |
| return gi->textureProxyView(); | |
| } | |
| SkColorType ComputeShaderCoverageMaskTargetFormat(const Caps* caps) { | |
| // GPU compute coverage mask renderers need to bind the mask texture as a storage binding, which | |
| // support a limited set of color formats. In general, we use RGBA8 if Alpha8 can't be | |
| // supported. | |
| if (caps->isStorage(caps->getDefaultStorageTextureInfo(kAlpha_8_SkColorType))) { | |
| return kAlpha_8_SkColorType; | |
| } | |
| return kRGBA_8888_SkColorType; | |
| } | |
| } // namespace skgpu::graphite | |
| namespace skif { | |
| namespace { | |
| // TODO(michaelludwig): The skgpu::BlurUtils effects will be migrated to src/core to implement a | |
| // shader BlurEngine that can be shared by rastr, Ganesh, and Graphite. This is blocked by having | |
| // skif::FilterResult handle the resizing to the max supported sigma. | |
| class GraphiteBackend : | |
| public Backend, | |
| private SkShaderBlurAlgorithm, | |
| private SkBlurEngine { | |
| public: | |
| GraphiteBackend(skgpu::graphite::Recorder* recorder, | |
| const SkSurfaceProps& surfaceProps, | |
| SkColorType colorType) | |
| : Backend(SkImageFilterCache::Create(SkImageFilterCache::kDefaultTransientSize), | |
| surfaceProps, colorType) | |
| , fRecorder(recorder) {} | |
| // 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 skgpu::graphite::Device::Make(fRecorder, | |
| imageInfo, | |
| skgpu::Budgeted::kYes, | |
| skgpu::Mipmapped::kNo, | |
| SkBackingFit::kApprox, | |
| props ? *props : this->surfaceProps(), | |
| skgpu::graphite::LoadOp::kDiscard, | |
| "ImageFilterResult"); | |
| } | |
| sk_sp<SkSpecialImage> makeImage(const SkIRect& subset, sk_sp<SkImage> image) const override { | |
| return SkSpecialImages::MakeGraphite(fRecorder, subset, image, this->surfaceProps()); | |
| } | |
| sk_sp<SkImage> getCachedBitmap(const SkBitmap& data) const override { | |
| auto proxy = skgpu::graphite::RecorderPriv::CreateCachedProxy(fRecorder, data, | |
| "ImageFilterCachedBitmap"); | |
| if (!proxy) { | |
| return nullptr; | |
| } | |
| const SkColorInfo& colorInfo = data.info().colorInfo(); | |
| skgpu::Swizzle swizzle = skgpu::graphite::ReadSwizzleForColorType( | |
| colorInfo.colorType(), proxy->format()); | |
| return sk_make_sp<skgpu::graphite::Image>( | |
| skgpu::graphite::TextureProxyView(std::move(proxy), swizzle), | |
| colorInfo); | |
| } | |
| const SkBlurEngine* getBlurEngine() const override { return this; } | |
| // SkBlurEngine | |
| const SkBlurEngine::Algorithm* findAlgorithm(SkSize sigma, | |
| SkColorType colorType) const override { | |
| // The runtime effect blurs handle all tilemodes and color types | |
| return this; | |
| } | |
| // SkShaderBlurAlgorithm | |
| sk_sp<SkDevice> makeDevice(const SkImageInfo& imageInfo) const override { | |
| return skgpu::graphite::Device::Make(fRecorder, | |
| imageInfo, | |
| skgpu::Budgeted::kYes, | |
| skgpu::Mipmapped::kNo, | |
| SkBackingFit::kApprox, | |
| this->surfaceProps(), | |
| skgpu::graphite::LoadOp::kDiscard, | |
| "EvalBlurTexture"); | |
| } | |
| private: | |
| skgpu::graphite::Recorder* fRecorder; | |
| }; | |
| } // anonymous namespace | |
| sk_sp<Backend> MakeGraphiteBackend(skgpu::graphite::Recorder* recorder, | |
| const SkSurfaceProps& surfaceProps, | |
| SkColorType colorType) { | |
| SkASSERT(recorder); | |
| return sk_make_sp<GraphiteBackend>(recorder, surfaceProps, colorType); | |
| } | |
| } // namespace skif |