Add 2x2 bilinear downscale steps to GrSurfaceContext::rescale.
Currently just uses 2x2 configuration but could be adapted easily
to support other sample position layouts.
Change-Id: If857bac5d1fab1b6ee04694e445d0f19431646f1
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/292079
Commit-Queue: Brian Salomon <bsalomon@google.com>
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
diff --git a/src/gpu/GrSurfaceContext.cpp b/src/gpu/GrSurfaceContext.cpp
index 9805dd8..ca5be2b 100644
--- a/src/gpu/GrSurfaceContext.cpp
+++ b/src/gpu/GrSurfaceContext.cpp
@@ -7,6 +7,7 @@
#include "src/gpu/GrSurfaceContext.h"
+#include "include/effects/SkRuntimeEffect.h"
#include "include/private/GrRecordingContext.h"
#include "src/core/SkAutoPixmapStorage.h"
#include "src/gpu/GrAuditTrail.h"
@@ -22,6 +23,7 @@
#include "src/gpu/GrSurfacePriv.h"
#include "src/gpu/SkGr.h"
#include "src/gpu/effects/GrBicubicEffect.h"
+#include "src/gpu/effects/GrSkSLFP.h"
#define ASSERT_SINGLE_OWNER \
SkDEBUGCODE(GrSingleOwner::AutoEnforce debug_SingleOwner(this->singleOwner());)
@@ -500,6 +502,61 @@
this->readSurfaceView(), dstPoint);
}
+template <int N> static std::unique_ptr<GrSkSLFP> make_avg_effect(GrRecordingContext* context) {
+ static sk_sp<SkRuntimeEffect> gEffect;
+ static SkString gName;
+ if (!gEffect) {
+ SkString string;
+ for (int i = 0; i < N; ++i) {
+ string.appendf("in fragmentProcessor child%d;\n", i);
+ }
+ string.append("void main(float2 p, inout half4 color) {\n");
+ for (int i = 0; i < N; ++i) {
+ string.appendf(" color %c= sample(child%d, p);\n", i ? '+' : ' ', i);
+ }
+ string.appendf(" color /= half(%d);\n"
+ "}\n", N);
+ gEffect = std::get<0>(SkRuntimeEffect::Make(std::move(string)));
+ SkASSERT(gEffect);
+ gName.printf("Avg%d", N);
+ }
+ return GrSkSLFP::Make(context, gEffect, gName.c_str(), nullptr);
+}
+
+template <int NX, int NY>
+static std::unique_ptr<GrFragmentProcessor> make_multibilerp_effect(GrRecordingContext* context,
+ GrSurfaceProxyView srcView,
+ SkAlphaType alphaType,
+ const SkIRect& srcRect,
+ const SkISize& dstSize) {
+ auto effect = make_avg_effect<NX*NY>(context);
+
+ // scale factors.
+ float sx = static_cast<float>(srcRect.width()) /dstSize.width(),
+ sy = static_cast<float>(srcRect.height())/dstSize.height();
+ // spacing between bilerp samples.
+ float dx = sx/NX,
+ dy = sy/NY;
+ // offset in src from back projection of dst pixel center to left/upper-most bilerp sample.
+ float x0 = (dx - sx)/2,
+ y0 = (dy - sy)/2;
+
+ const auto& caps = *context->priv().caps();
+ for (int j = 0; j < NY; ++j) {
+ for (int i = 0; i < NX; ++i) {
+ float tx = x0 + i*dx,
+ ty = y0 + j*dy;
+ SkMatrix m = SkMatrix::Scale(sx, sy);
+ m.postTranslate(tx + srcRect.x(), ty + srcRect.y());
+ SkRect domain = SkRect::Make(srcRect).makeInset(sx/2, sy/2).makeOffset(tx, ty);
+ effect->addChild(GrTextureEffect::MakeSubset(srcView, alphaType, m,
+ GrSamplerState::Filter::kBilerp,
+ SkRect::Make(srcRect), domain, caps));
+ }
+ }
+ return std::move(effect);
+}
+
std::unique_ptr<GrRenderTargetContext> GrSurfaceContext::rescale(
const GrImageInfo& info,
GrSurfaceOrigin origin,
@@ -563,22 +620,42 @@
srcRect = SkIRect::MakeSize(srcRect.size());
}
- while (srcRect.size() != info.dimensions()) {
- SkISize nextDims = info.dimensions();
- if (rescaleQuality != kNone_SkFilterQuality) {
- if (srcRect.width() > info.width()) {
- nextDims.fWidth = std::max((srcRect.width() + 1)/2, info.width());
- } else if (srcRect.width() < info.width()) {
- nextDims.fWidth = std::min(srcRect.width()*2, info.width());
- }
- if (srcRect.height() > info.height()) {
- nextDims.fHeight = std::max((srcRect.height() + 1)/2, info.height());
- } else if (srcRect.height() < info.height()) {
- nextDims.fHeight = std::min(srcRect.height()*2, info.height());
- }
+ enum StepType {
+ kNearest,
+ kBilinear,
+ kFourTapBilinear,
+ kBicubic,
+ };
+ auto determineNextStep = [rescaleQuality, finalSize = info.dimensions()](SkISize srcSize) {
+ if (rescaleQuality == kNone_SkFilterQuality) {
+ return std::tuple(StepType::kNearest, finalSize);
}
+ SkISize nextSize;
+ if (srcSize.width() > finalSize.width()) {
+ nextSize.fWidth = std::max((srcSize.width() + 1)/2, finalSize.width());
+ } else {
+ nextSize.fWidth = std::min(srcSize.width()*2, finalSize.width());
+ }
+ if (srcSize.height() > finalSize.height()) {
+ nextSize.fHeight = std::max((srcSize.height() + 1)/2, finalSize.height());
+ } else {
+ nextSize.fHeight = std::min(srcSize.height()*2, finalSize.height());
+ }
+ if (rescaleQuality == kHigh_SkFilterQuality) {
+ return std::tuple(StepType::kBicubic, nextSize);
+ }
+ // See if we can do multiple bilinear steps in one.
+ if (nextSize.width() > finalSize.width() && nextSize.height() > finalSize.height()) {
+ nextSize = {std::max((nextSize.width() + 1)/2, finalSize.width()),
+ std::max((nextSize.height() + 1)/2, finalSize.height())};
+ return std::tuple{StepType::kFourTapBilinear, nextSize};
+ }
+ return std::tuple{StepType::kBilinear, nextSize};
+ };
+ while (srcRect.size() != info.dimensions()) {
+ auto [stepType, nextDims] = determineNextStep(srcRect.size());
auto input = tempA ? tempA.get() : this;
- GrColorType colorType = input->colorInfo().colorType();
+ auto colorType = input->colorInfo().colorType();
auto cs = input->colorInfo().refColorSpace();
sk_sp<GrColorSpaceXform> xform;
auto prevAlphaType = input->colorInfo().alphaType();
@@ -595,43 +672,49 @@
if (!tempB) {
return nullptr;
}
- auto dstRect = SkRect::Make(nextDims);
- if (rescaleQuality == kHigh_SkFilterQuality) {
- SkMatrix matrix;
- matrix.setScaleTranslate((float)srcRect.width()/nextDims.width(),
- (float)srcRect.height()/nextDims.height(),
- srcRect.x(),
- srcRect.y());
- std::unique_ptr<GrFragmentProcessor> fp;
- auto dir = GrBicubicEffect::Direction::kXY;
- if (nextDims.width() == srcRect.width()) {
- dir = GrBicubicEffect::Direction::kY;
- } else if (nextDims.height() == srcRect.height()) {
- dir = GrBicubicEffect::Direction::kX;
+ std::unique_ptr<GrFragmentProcessor> srcFP;
+ switch (stepType) {
+ case StepType::kBicubic: {
+ SkMatrix matrix;
+ matrix.setScaleTranslate((float)srcRect.width() /nextDims.width(),
+ (float)srcRect.height()/nextDims.height(),
+ srcRect.x(),
+ srcRect.y());
+ auto dir = GrBicubicEffect::Direction::kXY;
+ if (nextDims.width() == srcRect.width()) {
+ dir = GrBicubicEffect::Direction::kY;
+ } else if (nextDims.height() == srcRect.height()) {
+ dir = GrBicubicEffect::Direction::kX;
+ }
+ static constexpr GrSamplerState::WrapMode kWM = GrSamplerState::WrapMode::kClamp;
+ srcFP = GrBicubicEffect::MakeSubset(std::move(texView), prevAlphaType, matrix, kWM,
+ kWM, SkRect::Make(srcRect), dir, *this->caps());
+ break;
}
- static constexpr GrSamplerState::WrapMode kWM = GrSamplerState::WrapMode::kClamp;
- fp = GrBicubicEffect::MakeSubset(std::move(texView), prevAlphaType, matrix, kWM, kWM,
- SkRect::Make(srcRect), dir, *this->caps());
- if (xform) {
- fp = GrColorSpaceXformEffect::Make(std::move(fp), std::move(xform));
+ case StepType::kFourTapBilinear:
+ srcFP = make_multibilerp_effect<2, 2>(fContext, texView, srcAlphaType, srcRect,
+ nextDims);
+ break;
+ default: {
+ auto filter = stepType == StepType::kNearest ? GrSamplerState::Filter::kNearest
+ : GrSamplerState::Filter::kBilerp;
+ float sx = static_cast<float>(srcRect.width()) /nextDims.width(),
+ sy = static_cast<float>(srcRect.height())/nextDims.height();
+ SkMatrix matrix;
+ matrix.setScaleTranslate(sx, sy, srcRect.x(), srcRect.y());
+ SkRect domain = SkRect::Make(srcRect).makeInset(sx/2, sy/2);
+ srcFP = GrTextureEffect::MakeSubset(std::move(texView), srcAlphaType, matrix,
+ filter, SkRect::Make(srcRect), domain,
+ *this->caps());
+ break;
}
- GrPaint paint;
- paint.addColorFragmentProcessor(std::move(fp));
- paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
- tempB->fillRectToRect(nullptr, std::move(paint), GrAA::kNo, SkMatrix::I(), dstRect,
- dstRect);
- } else {
- auto filter = rescaleQuality == kNone_SkFilterQuality ? GrSamplerState::Filter::kNearest
- : GrSamplerState::Filter::kBilerp;
- // Minimizing draw with integer coord src and dev rects can always be kFast.
- auto constraint = SkCanvas::SrcRectConstraint::kStrict_SrcRectConstraint;
- if (nextDims.width() <= srcRect.width() && nextDims.height() <= srcRect.height()) {
- constraint = SkCanvas::SrcRectConstraint::kFast_SrcRectConstraint;
- }
- tempB->drawTexture(nullptr, std::move(texView), srcAlphaType, filter, SkBlendMode::kSrc,
- SK_PMColor4fWHITE, SkRect::Make(srcRect), dstRect, GrAA::kNo,
- GrQuadAAFlags::kNone, constraint, SkMatrix::I(), std::move(xform));
}
+ GrPaint paint;
+ paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
+ srcFP = GrColorSpaceXformEffect::Make(std::move(srcFP), std::move(xform));
+ paint.addColorFragmentProcessor(std::move(srcFP));
+ tempB->drawRect(nullptr, std::move(paint), GrAA::kNo, SkMatrix::I(),
+ SkRect::Make(nextDims));
texView = tempB->readSurfaceView();
tempA = std::move(tempB);
srcRect = SkIRect::MakeSize(nextDims);