Add GPU implementation of rescaling for SkSurface::asyncRescaleAndRead.
The only new capability added is bicubic downscaling but this opens up
more possibilities to add specializations to reduce passes in the GPU
implementation.
Bug: skia:8962
Change-Id: I4ff2e89b24df9dc15988d31ed10a3316b018e583
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/215445
Reviewed-by: Greg Daniel <egdaniel@google.com>
Commit-Queue: Brian Salomon <bsalomon@google.com>
diff --git a/src/image/SkSurface.cpp b/src/image/SkSurface.cpp
index a50f581..63c7bc4 100644
--- a/src/image/SkSurface.cpp
+++ b/src/image/SkSurface.cpp
@@ -88,13 +88,109 @@
}
}
-void SkSurface_Base::onAsyncReadPixels(const SkImageInfo& info, int srcX, int srcY,
- ReadPixelsCallback callback, ReadPixelsContext context) {
- SkASSERT(SkIRect::MakeWH(this->width(), this->height())
- .contains(SkIRect::MakeXYWH(srcX, srcY, info.width(), info.height())));
+void SkSurface_Base::onAsyncRescaleAndReadPixels(const SkImageInfo& info, const SkIRect& srcRect,
+ SkSurface::RescaleGamma rescaleGamma,
+ SkFilterQuality rescaleQuality,
+ SkSurface::ReadPixelsCallback callback,
+ SkSurface::ReadPixelsContext context) {
+ int srcW = srcRect.width();
+ int srcH = srcRect.height();
+ float sx = (float)info.width() / srcW;
+ float sy = (float)info.height() / srcH;
+ // How many bilerp/bicubic steps to do in X and Y. + means upscaling, - means downscaling.
+ int stepsX;
+ int stepsY;
+ if (rescaleQuality > kNone_SkFilterQuality) {
+ stepsX = static_cast<int>((sx > 1.f) ? std::ceil(std::log2f(sx))
+ : std::floor(std::log2f(sx)));
+ stepsY = static_cast<int>((sy > 1.f) ? std::ceil(std::log2f(sy))
+ : std::floor(std::log2f(sy)));
+ } else {
+ stepsX = sx != 1.f;
+ stepsY = sy != 1.f;
+ }
+
+ SkPaint paint;
+ paint.setBlendMode(SkBlendMode::kSrc);
+ if (stepsX < 0 || stepsY < 0) {
+ // Don't trigger MIP generation. We don't currently have a way to trigger bicubic for
+ // downscaling draws.
+ rescaleQuality = std::min(rescaleQuality, kLow_SkFilterQuality);
+ }
+ paint.setFilterQuality(rescaleQuality);
+ sk_sp<SkSurface> src(SkRef(this));
+ int srcX = srcRect.fLeft;
+ int srcY = srcRect.fTop;
+ SkCanvas::SrcRectConstraint constraint = SkCanvas::kStrict_SrcRectConstraint;
+ // 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 == SkSurface::RescaleGamma::kLinear &&
+ this->getCanvas()->imageInfo().colorSpace() &&
+ !this->getCanvas()->imageInfo().colorSpace()->gammaIsLinear()) {
+ auto cs = this->getCanvas()->imageInfo().colorSpace()->makeLinearGamma();
+ // Promote to F16 color type to preserve precision.
+ auto ii = SkImageInfo::Make(srcW, srcH, kRGBA_F16_SkColorType,
+ this->getCanvas()->imageInfo().alphaType(), std::move(cs));
+ auto linearSurf = this->makeSurface(ii);
+ if (!linearSurf) {
+ // Maybe F16 isn't supported? Try again with original color type.
+ ii = ii.makeColorType(this->getCanvas()->imageInfo().colorType());
+ linearSurf = this->makeSurface(ii);
+ if (!linearSurf) {
+ callback(context, nullptr, 0);
+ return;
+ }
+ }
+ this->draw(linearSurf->getCanvas(), -srcX, -srcY, &paint);
+ src = std::move(linearSurf);
+ srcX = 0;
+ srcY = 0;
+ constraint = SkCanvas::kFast_SrcRectConstraint;
+ }
+ while (stepsX || stepsY) {
+ int nextW = info.width();
+ int nextH = info.height();
+ if (stepsX < 0) {
+ nextW = info.width() << (-stepsX - 1);
+ stepsX++;
+ } else if (stepsX != 0) {
+ if (stepsX > 1) {
+ nextW = srcW * 2;
+ }
+ --stepsX;
+ }
+ if (stepsY < 0) {
+ nextH = info.height() << (-stepsY - 1);
+ stepsY++;
+ } else if (stepsY != 0) {
+ if (stepsY > 1) {
+ nextH = srcH * 2;
+ }
+ --stepsY;
+ }
+ auto ii = src->getCanvas()->imageInfo().makeWH(nextW, nextH);
+ if (!stepsX && !stepsY) {
+ // Might as well fold conversion to final info in the last step.
+ ii = info;
+ }
+ auto next = this->makeSurface(ii);
+ if (!next) {
+ callback(context, nullptr, 0);
+ return;
+ }
+ next->getCanvas()->drawImageRect(
+ src->makeImageSnapshot(), SkIRect::MakeXYWH(srcX, srcY, srcW, srcH),
+ SkRect::MakeWH((float)nextW, (float)nextH), &paint, constraint);
+ src = std::move(next);
+ srcX = srcY = 0;
+ srcW = nextW;
+ srcH = nextH;
+ constraint = SkCanvas::kFast_SrcRectConstraint;
+ }
+
SkAutoPixmapStorage pm;
pm.alloc(info);
- if (this->readPixels(pm, srcX, srcY)) {
+ if (src->readPixels(pm, srcX, srcY)) {
callback(context, pm.addr(), pm.rowBytes());
} else {
callback(context, nullptr, 0);
@@ -230,101 +326,8 @@
callback(context, nullptr, 0);
return;
}
- int srcW = srcRect.width();
- int srcH = srcRect.height();
- float sx = (float)info.width() / srcW;
- float sy = (float)info.height() / srcH;
- // How many bilerp/bicubic steps to do in X and Y. + means upscaling, - means downscaling.
- int stepsX;
- int stepsY;
- if (rescaleQuality > kNone_SkFilterQuality) {
- stepsX = static_cast<int>((sx > 1.f) ? std::ceil(std::log2f(sx))
- : std::floor(std::log2f(sx)));
- stepsY = static_cast<int>((sy > 1.f) ? std::ceil(std::log2f(sy))
- : std::floor(std::log2f(sy)));
- } else {
- stepsX = sx != 1.f;
- stepsY = sy != 1.f;
- }
-
- SkPaint paint;
- paint.setBlendMode(SkBlendMode::kSrc);
- if (stepsX < 0 || stepsY < 0) {
- // Don't trigger MIP generation. We don't currently have a way to trigger bicubic for
- // downscaling draws.
- rescaleQuality = std::min(rescaleQuality, kLow_SkFilterQuality);
- }
- paint.setFilterQuality(rescaleQuality);
- sk_sp<SkSurface> src(SkRef(this));
- int srcX = srcRect.fLeft;
- int srcY = srcRect.fTop;
- SkCanvas::SrcRectConstraint constraint = SkCanvas::kStrict_SrcRectConstraint;
- // 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 == SkSurface::RescaleGamma::kLinear &&
- this->getCanvas()->imageInfo().colorSpace() &&
- !this->getCanvas()->imageInfo().colorSpace()->gammaIsLinear()) {
- auto cs = this->getCanvas()->imageInfo().colorSpace()->makeLinearGamma();
- // Promote to F16 color type to preserve precision.
- auto ii = SkImageInfo::Make(srcW, srcH, kRGBA_F16_SkColorType,
- this->getCanvas()->imageInfo().alphaType(), std::move(cs));
- auto linearSurf = this->makeSurface(ii);
- if (!linearSurf) {
- // Maybe F16 isn't supported? Try again with original color type.
- ii = ii.makeColorType(this->getCanvas()->imageInfo().colorType());
- linearSurf = this->makeSurface(ii);
- if (!linearSurf) {
- callback(context, nullptr, 0);
- return;
- }
- }
- this->draw(linearSurf->getCanvas(), -srcX, -srcY, &paint);
- src = std::move(linearSurf);
- srcX = 0;
- srcY = 0;
- constraint = SkCanvas::kFast_SrcRectConstraint;
- }
- while (stepsX || stepsY) {
- int nextW = info.width();
- int nextH = info.height();
- if (stepsX < 0) {
- nextW = info.width() << (-stepsX - 1);
- stepsX++;
- } else if (stepsX != 0) {
- if (stepsX > 1) {
- nextW = srcW * 2;
- }
- --stepsX;
- }
- if (stepsY < 0) {
- nextH = info.height() << (-stepsY - 1);
- stepsY++;
- } else if (stepsY != 0) {
- if (stepsY > 1) {
- nextH = srcH * 2;
- }
- --stepsY;
- }
- auto ii = src->getCanvas()->imageInfo().makeWH(nextW, nextH);
- if (!stepsX && !stepsY) {
- // Might as well fold conversion to final info in the last step.
- ii = info;
- }
- auto next = this->makeSurface(ii);
- if (!next) {
- callback(context, nullptr, 0);
- return;
- }
- next->getCanvas()->drawImageRect(
- src->makeImageSnapshot(), SkIRect::MakeXYWH(srcX, srcY, srcW, srcH),
- SkRect::MakeWH((float)nextW, (float)nextH), &paint, constraint);
- src = std::move(next);
- srcX = srcY = 0;
- srcW = nextW;
- srcH = nextH;
- constraint = SkCanvas::kFast_SrcRectConstraint;
- }
- static_cast<SkSurface_Base*>(src.get())->onAsyncReadPixels(info, srcX, srcY, callback, context);
+ asSB(this)->onAsyncRescaleAndReadPixels(info, srcRect, rescaleGamma, rescaleQuality, callback,
+ context);
}
void SkSurface::writePixels(const SkPixmap& pmap, int x, int y) {