Reland "Fill intermediate device image with clamped backdrop content"

This reverts commit 35ac48c715.

Reason for revert: guarded needs_subset

Original change's description:
> Revert "Fill intermediate device image with clamped backdrop content"
>
> This reverts commit 25f5e1f838.
>
> Reason for revert: needs_subset is unused for no-gpu+release builds
>
> Original change's description:
> > Fill intermediate device image with clamped backdrop content
> >
> > Adds asShader() to SkSpecialImage that accepts a local matrix and a
> > tilemode. It automatically handles the subsetting of the image data
> > (either by making a new bitmap view, or using GrTexturEffect), as well
> > as modifying the local matrix to account for the subset's offset within
> > the image data.
> >
> > By adding asShader(), SkCanvas can fill a device using drawPaint() to
> > apply the image's tilemode to the entire contents, ensuring that any
> > device bounds clipping from snapping of backdrop filters doesn't leave
> > the default transparent pixels around for later filtering.
> >
> > Additionally, making SkSpecialImage more like a shader will make it
> > easier down the road to continue refactoring the image filter pipeline
> > to be more backend agnostic and allow operations to be performed by
> > filling devices with shaders, instead of relying on FPs directly.
> >
> > Lastly, once SkImage::makeShader() can support subsets on the raster
> > backend, then SkSpecialImage may not be required at all in order to
> > implement the image filtering pipeline...
> >
> > Bug: b/197774543, skia:12784
> > Change-Id: I21dcb22b56b19ff58d246b3c0517bb8a265649bc
> > Reviewed-on: https://skia-review.googlesource.com/c/skia/+/491445
> > Reviewed-by: Brian Salomon <bsalomon@google.com>
> > Commit-Queue: Michael Ludwig <michaelludwig@google.com>
>
> Bug: b/197774543, skia:12784
> Change-Id: Ia9adedaab9378d2679353ab94c7a9589c0ff9e02
> No-Presubmit: true
> No-Tree-Checks: true
> No-Try: true
> Reviewed-on: https://skia-review.googlesource.com/c/skia/+/491978
> Bot-Commit: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com>
> Commit-Queue: Michael Ludwig <michaelludwig@google.com>

Bug: b/197774543, skia:12784
Change-Id: I6034e4ddbe4f6da476d058a78f85f4e2bb6742fa
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/492116
Reviewed-by: Brian Salomon <bsalomon@google.com>
Auto-Submit: Michael Ludwig <michaelludwig@google.com>
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
This commit is contained in:
Michael Ludwig 2022-01-06 20:29:39 -05:00 committed by SkCQ
parent 5bb80e1ee0
commit 54d9447b6a
6 changed files with 148 additions and 27 deletions

View File

@ -924,12 +924,15 @@ void SkCanvas::internalDrawDeviceWithFilter(SkBaseDevice* src,
intermediateDevice->setOrigin(SkM44(srcToIntermediate),
requiredInput.left(), requiredInput.top());
SkMatrix offsetLocalToDevice = intermediateDevice->localToDevice();
offsetLocalToDevice.preTranslate(srcSubset.left(), srcSubset.top());
// We draw with non-AA bilinear since we cover the destination but definitely don't have
// a pixel-aligned transform.
intermediateDevice->drawSpecial(srcImage.get(), offsetLocalToDevice,
SkSamplingOptions{SkFilterMode::kLinear}, {});
// We use drawPaint to fill the entire device with the src input + clamp tiling, which
// extends the backdrop's edge pixels to the parts of 'requiredInput' that map offscreen
// Without this, the intermediateDevice would contain transparent pixels that may then
// infect blurs and other filters with large kernels.
SkPaint imageFill;
imageFill.setShader(srcImage->asShader(SkTileMode::kClamp,
SkSamplingOptions{SkFilterMode::kLinear},
SkMatrix::Translate(srcSubset.topLeft())));
intermediateDevice->drawPaint(imageFill);
filterInput = intermediateDevice->snapSpecial();
// TODO: Like the non-intermediate case, we need to apply the image origin.

View File

@ -10,6 +10,8 @@
#include "include/core/SkBitmap.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkImage.h"
#include "include/core/SkMatrix.h"
#include "include/core/SkTileMode.h"
#include "src/core/SkSpecialSurface.h"
#include "src/core/SkSurfacePriv.h"
#include "src/image/SkImage_Base.h"
@ -22,6 +24,7 @@
#include "src/gpu/GrRecordingContextPriv.h"
#include "src/gpu/GrTextureProxy.h"
#include "src/image/SkImage_Gpu.h"
#include "src/shaders/SkImageShader.h"
#endif
// Currently the raster imagefilters can only handle certain imageinfos. Call this to know if
@ -64,6 +67,10 @@ public:
// already been mapped from the content rect by the non-virtual asImage().
virtual sk_sp<SkImage> onAsImage(const SkIRect* subset) const = 0;
virtual sk_sp<SkShader> onAsShader(SkTileMode,
const SkSamplingOptions&,
const SkMatrix&) const = 0;
virtual sk_sp<SkSurface> onMakeTightSurface(
SkColorType colorType, const SkColorSpace* colorSpace,
const SkISize& size, SkAlphaType at) const = 0;
@ -138,6 +145,21 @@ sk_sp<SkImage> SkSpecialImage::asImage(const SkIRect* subset) const {
}
}
sk_sp<SkShader> SkSpecialImage::asShader(SkTileMode tileMode,
const SkSamplingOptions& sampling,
const SkMatrix& lm) const {
return as_SIB(this)->onAsShader(tileMode, sampling, lm);
}
sk_sp<SkShader> SkSpecialImage::asShader(const SkSamplingOptions& sampling) const {
return this->asShader(sampling, SkMatrix::I());
}
sk_sp<SkShader> SkSpecialImage::asShader(const SkSamplingOptions& sampling,
const SkMatrix& lm) const {
return this->asShader(SkTileMode::kClamp, sampling, lm);
}
#if defined(SK_DEBUG) || SK_SUPPORT_GPU
static bool rect_fits(const SkIRect& rect, int width, int height) {
if (0 == width && 0 == height) {
@ -252,7 +274,19 @@ public:
return fBitmap.asImage();
}
sk_sp<SkSurface> onMakeTightSurface(SkColorType colorType, const SkColorSpace* colorSpace,
sk_sp<SkShader> onAsShader(SkTileMode tileMode,
const SkSamplingOptions& sampling,
const SkMatrix& lm) const override {
// TODO(skbug.com/12784): SkImage::makeShader() doesn't support a subset yet, but SkBitmap
// supports subset views so create the shader from the subset bitmap instead of fBitmap.
SkBitmap subsetBM;
if (!this->getROPixels(&subsetBM)) {
return nullptr;
}
return subsetBM.asImage()->makeShader(tileMode, tileMode, sampling, lm);
}
sk_sp<SkSurface> onMakeTightSurface(SkColorType colorType, const SkColorSpace* colorSpace,
const SkISize& size, SkAlphaType at) const override {
// Ignore the requested color type, the raster backend currently only supports N32
colorType = kN32_SkColorType; // TODO: find ways to allow f16
@ -438,6 +472,23 @@ public:
return wrap_proxy_in_image(fContext, fView, this->colorType(), fAlphaType, fColorSpace);
}
sk_sp<SkShader> onAsShader(SkTileMode tileMode,
const SkSamplingOptions& sampling,
const SkMatrix& lm) const override {
// The special image's logical (0,0) is at its subset's topLeft() so we need to account for
// that in the local matrix used when sampling.
SkMatrix subsetOrigin = SkMatrix::Translate(-this->subset().topLeft());
subsetOrigin.postConcat(lm);
// However, we don't need to modify the subset itself since that is defined with respect to
// the base image, and the local matrix is applied before any tiling/clamping.
const SkRect subset = SkRect::Make(this->subset());
// asImage() w/o a subset makes no copy; create the SkImageShader directly to remember the
// subset used to access the image.
return SkImageShader::MakeSubset(
this->asImage(), subset, tileMode, tileMode, sampling, &subsetOrigin);
}
sk_sp<SkSurface> onMakeTightSurface(SkColorType colorType, const SkColorSpace* colorSpace,
const SkISize& size, SkAlphaType at) const override {
// TODO (michaelludwig): Why does this ignore colorType but onMakeSurface doesn't ignore it?

View File

@ -25,10 +25,13 @@ class SkBitmap;
class SkCanvas;
class SkImage;
struct SkImageInfo;
class SkMatrix;
class SkPaint;
class SkPixmap;
class SkShader;
class SkSpecialSurface;
class SkSurface;
enum class SkTileMode;
enum {
kNeedNewImageUniqueID_SpecialImage = 0
@ -126,8 +129,20 @@ public:
* When the 'subset' parameter is specified the returned image will be tight even if that
* entails a copy! The 'subset' is relative to this special image's content rect.
*/
// TODO: The only version that uses the subset is the tile image filter, and that doesn't need
// to if it can be rewritten to use asShader() and SkTileModes. Similarly, the only use case of
// asImage() w/o a subset is SkImage::makeFiltered() and that could/should return an SkShader so
// that users don't need to worry about correctly applying the subset, etc.
sk_sp<SkImage> asImage(const SkIRect* subset = nullptr) const;
/**
* Create an SkShader that samples the contents of this special image, applying tile mode for
* any sample that falls outside its internal subset.
*/
sk_sp<SkShader> asShader(SkTileMode, const SkSamplingOptions&, const SkMatrix&) const;
sk_sp<SkShader> asShader(const SkSamplingOptions& sampling) const;
sk_sp<SkShader> asShader(const SkSamplingOptions& sampling, const SkMatrix& lm) const;
/**
* If the SpecialImage is backed by a gpu texture, return true.
*/

View File

@ -176,11 +176,9 @@ sk_sp<SkSpecialImage> SkRuntimeImageFilter::onFilterImage(const Context& ctx,
return nullptr;
}
SkMatrix localM = inverse *
SkMatrix::Translate(inputOffset) *
SkMatrix::Translate(-input->subset().topLeft());
SkMatrix localM = inverse * SkMatrix::Translate(inputOffset);
sk_sp<SkShader> inputShader =
input->asImage()->makeShader(SkSamplingOptions(SkFilterMode::kLinear), &localM);
input->asShader(SkSamplingOptions(SkFilterMode::kLinear), localM);
SkASSERT(inputShader);
inputShaders.push_back(std::move(inputShader));
}

View File

@ -67,7 +67,15 @@ static SkTileMode optimize(SkTileMode tm, int dimension) {
#endif
}
// TODO: currently this only *always* used in asFragmentProcessor(), which is excluded on no-gpu
// builds. No-gpu builds only use needs_subset() in asserts, so release+no-gpu doesn't use it, which
// can cause builds to fail if unused warnings are treated as errors.
SK_MAYBE_UNUSED static bool needs_subset(SkImage* img, const SkRect& subset) {
return subset != SkRect::Make(img->dimensions());
}
SkImageShader::SkImageShader(sk_sp<SkImage> img,
const SkRect& subset,
SkTileMode tmx, SkTileMode tmy,
const SkSamplingOptions& sampling,
const SkMatrix* localMatrix,
@ -78,6 +86,7 @@ SkImageShader::SkImageShader(sk_sp<SkImage> img,
, fSampling(sampling)
, fTileModeX(optimize(tmx, fImage->width()))
, fTileModeY(optimize(tmy, fImage->height()))
, fSubset(subset)
, fRaw(raw)
, fClampAsIfUnpremul(clampAsIfUnpremul) {
// These options should never appear together:
@ -132,6 +141,9 @@ sk_sp<SkFlattenable> SkImageShader::CreateProc(SkReadBuffer& buffer) {
bool raw = buffer.isVersionLT(SkPicturePriv::Version::kRawImageShaders) ? false
: buffer.readBool();
// TODO(skbug.com/12784): Subset is not serialized yet; it's only used by special images so it
// will never be written to an SKP.
return raw ? SkImageShader::MakeRaw(std::move(img), tmx, tmy, sampling, &localMatrix)
: SkImageShader::Make(std::move(img), tmx, tmy, sampling, &localMatrix);
}
@ -146,6 +158,10 @@ void SkImageShader::flatten(SkWriteBuffer& buffer) const {
buffer.writeImage(fImage.get());
SkASSERT(fClampAsIfUnpremul == false);
// TODO(skbug.com/12784): Subset is not serialized yet; it's only used by special images so it
// will never be written to an SKP.
SkASSERT(!needs_subset(fImage.get(), fSubset));
buffer.writeBool(fRaw);
}
@ -189,6 +205,7 @@ static bool legacy_shader_can_handle(const SkMatrix& inv) {
SkShaderBase::Context* SkImageShader::onMakeContext(const ContextRec& rec,
SkArenaAlloc* alloc) const {
SkASSERT(!needs_subset(fImage.get(), fSubset)); // TODO(skbug.com/12784)
if (fImage->alphaType() == kUnpremul_SkAlphaType) {
return nullptr;
}
@ -265,19 +282,8 @@ sk_sp<SkShader> SkImageShader::Make(sk_sp<SkImage> image,
const SkSamplingOptions& options,
const SkMatrix* localMatrix,
bool clampAsIfUnpremul) {
auto is_unit = [](float x) {
return x >= 0 && x <= 1;
};
if (options.useCubic) {
if (!is_unit(options.cubic.B) || !is_unit(options.cubic.C)) {
return nullptr;
}
}
if (!image) {
return sk_make_sp<SkEmptyShader>();
}
return sk_sp<SkShader>{new SkImageShader(
image, tmx, tmy, options, localMatrix, /*raw=*/false, clampAsIfUnpremul)};
SkRect subset = image ? SkRect::Make(image->dimensions()) : SkRect::MakeEmpty();
return MakeSubset(std::move(image), subset, tmx, tmy, options, localMatrix, clampAsIfUnpremul);
}
sk_sp<SkShader> SkImageShader::MakeRaw(sk_sp<SkImage> image,
@ -291,7 +297,36 @@ sk_sp<SkShader> SkImageShader::MakeRaw(sk_sp<SkImage> image,
return sk_make_sp<SkEmptyShader>();
}
return sk_sp<SkShader>{new SkImageShader(
image, tmx, tmy, options, localMatrix, /*raw=*/true, /*clampAsIfUnpremul=*/false)};
image, SkRect::Make(image->dimensions()), tmx, tmy, options, localMatrix,
/*raw=*/true, /*clampAsIfUnpremul=*/false)};
}
sk_sp<SkShader> SkImageShader::MakeSubset(sk_sp<SkImage> image,
const SkRect& subset,
SkTileMode tmx, SkTileMode tmy,
const SkSamplingOptions& options,
const SkMatrix* localMatrix,
bool clampAsIfUnpremul) {
auto is_unit = [](float x) {
return x >= 0 && x <= 1;
};
if (options.useCubic) {
if (!is_unit(options.cubic.B) || !is_unit(options.cubic.C)) {
return nullptr;
}
}
if (!image || subset.isEmpty()) {
return sk_make_sp<SkEmptyShader>();
}
// Validate subset and check if we can drop it
if (!SkRect::Make(image->bounds()).contains(subset)) {
return nullptr;
}
// TODO(skbug.com/12784): GPU-only for now since it's only supported in onAsFragmentProcessor()
SkASSERT(!needs_subset(image.get(), subset) || image->isTextureBacked());
return sk_sp<SkShader>{new SkImageShader(
image, subset, tmx, tmy, options, localMatrix, /*raw=*/false, clampAsIfUnpremul)};
}
///////////////////////////////////////////////////////////////////////////////////////////////////
@ -310,10 +345,12 @@ std::unique_ptr<GrFragmentProcessor> SkImageShader::asFragmentProcessor(
}
SkTileMode tileModes[2] = {fTileModeX, fTileModeY};
const SkRect* subset = needs_subset(fImage.get(), fSubset) ? &fSubset : nullptr;
auto fp = as_IB(fImage.get())->asFragmentProcessor(args.fContext,
fSampling,
tileModes,
lmInverse);
lmInverse,
subset);
if (!fp) {
return nullptr;
}
@ -407,6 +444,7 @@ static SkMatrix tweak_inv_matrix(SkFilterMode filter, SkMatrix matrix) {
}
bool SkImageShader::doStages(const SkStageRec& rec, TransformShader* updater) const {
SkASSERT(!needs_subset(fImage.get(), fSubset)); // TODO(skbug.com/12784)
// We only support certain sampling options in stages so far
auto sampling = fSampling;
if (sampling.useCubic) {
@ -705,7 +743,7 @@ skvm::Color SkImageShader::makeProgram(
skvm::Builder* p, skvm::Coord device, skvm::Coord origLocal, skvm::Color paint,
const SkMatrixProvider& matrices, const SkMatrix* localM, const SkColorInfo& dst,
skvm::Uniforms* uniforms, const TransformShader* coordShader, SkArenaAlloc* alloc) const {
SkASSERT(!needs_subset(fImage.get(), fSubset)); // TODO(skbug.com/12784)
SkMatrix baseInv;
if (!this->computeTotalInverse(matrices.localToDevice(), localM, &baseInv)) {
return {};

View File

@ -28,6 +28,16 @@ public:
const SkSamplingOptions&,
const SkMatrix* localMatrix);
// TODO(skbug.com/12784): Requires SkImage to be texture backed, and created SkShader can only
// be used on GPU-backed surfaces.
static sk_sp<SkShader> MakeSubset(sk_sp<SkImage>,
const SkRect& subset,
SkTileMode tmx,
SkTileMode tmy,
const SkSamplingOptions&,
const SkMatrix* localMatrix,
bool clampAsIfUnpremul = false);
bool isOpaque() const override;
#if SK_SUPPORT_GPU
@ -40,6 +50,7 @@ private:
SK_FLATTENABLE_HOOKS(SkImageShader)
SkImageShader(sk_sp<SkImage>,
const SkRect& subset,
SkTileMode tmx,
SkTileMode tmy,
const SkSamplingOptions&,
@ -74,6 +85,11 @@ private:
const SkSamplingOptions fSampling;
const SkTileMode fTileModeX;
const SkTileMode fTileModeY;
// TODO(skbug.com/12784): This is only supported for GPU images currently.
// If subset == (0,0,w,h) of the image, then no subset is applied. Subset will not be empty.
const SkRect fSubset;
const bool fRaw;
const bool fClampAsIfUnpremul;