From 9be2d572d45ffd51437cb3935c604cb96eaae4b9 Mon Sep 17 00:00:00 2001 From: Brian Salomon Date: Wed, 27 Apr 2022 15:19:20 -0400 Subject: [PATCH] Add anisotropic option to SkSamplingOptions. Implement on GPU. Bug: skia:13036 Change-Id: I35d760596c4f8faaec27fccf284b70802fcf3f9b Reviewed-on: https://skia-review.googlesource.com/c/skia/+/524757 Commit-Queue: Brian Salomon Reviewed-by: Brian Osman Reviewed-by: Michael Ludwig --- RELEASE_NOTES.txt | 6 + gm/anisotropic.cpp | 135 ++++++++++++++++++++- gm/perspshaders.cpp | 4 +- gm/resizeimagefilter.cpp | 18 +-- gm/tilemodes_scaled.cpp | 5 +- include/core/SkCanvas.h | 4 +- include/core/SkSamplingOptions.h | 21 +++- src/core/SkBitmapProcState.cpp | 1 + src/core/SkCanvas.cpp | 10 +- src/core/SkPicturePriv.h | 5 +- src/core/SkPictureRecord.cpp | 9 +- src/core/SkReadBuffer.cpp | 6 + src/core/SkSamplingPriv.h | 20 ++- src/core/SkWriter32.cpp | 17 +-- src/gpu/ganesh/effects/GrTextureEffect.cpp | 73 ++++++++--- src/gpu/ganesh/v1/BUILD.bazel | 1 + src/gpu/ganesh/v1/Device_drawTexture.cpp | 28 +++-- src/image/BUILD.bazel | 1 + src/image/SkImage.cpp | 14 ++- src/image/SkImage_GpuYUVA.cpp | 11 +- src/image/SkImage_Raster.cpp | 2 +- src/shaders/SkImageShader.cpp | 16 ++- tools/debugger/BUILD.bazel | 1 + tools/debugger/DrawCommand.cpp | 1 + tools/debugger/JsonWriteBuffer.cpp | 1 + 25 files changed, 329 insertions(+), 81 deletions(-) diff --git a/RELEASE_NOTES.txt b/RELEASE_NOTES.txt index 3bf03606e5..2eeb93b3f6 100644 --- a/RELEASE_NOTES.txt +++ b/RELEASE_NOTES.txt @@ -2,6 +2,12 @@ Skia Graphics Release Notes This file includes a list of high level updates for each milestone release. +Milestone 103 +------------- + * SkSamplingOptions now includes anisotropic filtering. Implemented on GPU only. + +* * * + Milestone 102 ------------- * Add glGetFloatv and glSamplerParameterf to GrGLInterface. diff --git a/gm/anisotropic.cpp b/gm/anisotropic.cpp index efc156cb45..0263a9880b 100644 --- a/gm/anisotropic.cpp +++ b/gm/anisotropic.cpp @@ -21,13 +21,39 @@ namespace skiagm { // This GM exercises anisotropic image scaling. class AnisotropicGM : public GM { public: - AnisotropicGM() : fSampling(SkFilterMode::kLinear, SkMipmapMode::kLinear) { + enum class Mode { kLinear, kMip, kAniso }; + + AnisotropicGM(Mode mode) : fMode(mode) { + switch (fMode) { + case Mode::kLinear: + fSampling = SkSamplingOptions(SkFilterMode::kLinear); + break; + case Mode::kMip: + fSampling = SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kLinear); + break; + case Mode::kAniso: + fSampling = SkSamplingOptions::Aniso(16); + break; + } this->setBGColor(0xFFCCCCCC); } protected: - - SkString onShortName() override { return SkString("anisotropic_image_scale_mip"); } + SkString onShortName() override { + SkString name("anisotropic_image_scale_"); + switch (fMode) { + case Mode::kLinear: + name += "linear"; + break; + case Mode::kMip: + name += "mip"; + break; + case Mode::kAniso: + name += "aniso"; + break; + } + return name; + } SkISize onISize() override { return SkISize::Make(2*kImageSize + 3*kSpacer, @@ -112,11 +138,112 @@ private: sk_sp fImage; SkSamplingOptions fSampling; + Mode fMode; using INHERITED = GM; }; ////////////////////////////////////////////////////////////////////////////// -DEF_GM(return new AnisotropicGM;) +DEF_GM(return new AnisotropicGM(AnisotropicGM::Mode::kLinear);) +DEF_GM(return new AnisotropicGM(AnisotropicGM::Mode::kMip);) +DEF_GM(return new AnisotropicGM(AnisotropicGM::Mode::kAniso);) + +////////////////////////////////////////////////////////////////////////////// + +class AnisoMipsGM : public GM { +public: + AnisoMipsGM() = default; + +protected: + SkString onShortName() override { return SkString("anisomips"); } + + SkISize onISize() override { return SkISize::Make(520, 260); } + + sk_sp updateImage(SkSurface* surf, SkColor color) { + surf->getCanvas()->clear(color); + SkPaint paint; + paint.setColor(~color | 0xFF000000); + surf->getCanvas()->drawRect(SkRect::MakeLTRB(surf->width() *2/5.f, + surf->height()*2/5.f, + surf->width() *3/5.f, + surf->height()*3/5.f), + paint); + return surf->makeImageSnapshot()->withDefaultMipmaps(); + } + + void onDraw(SkCanvas* canvas) override { + auto ct = canvas->imageInfo().colorType() == kUnknown_SkColorType + ? kRGBA_8888_SkColorType + : canvas->imageInfo().colorType(); + auto ii = SkImageInfo::Make(kImageSize, + kImageSize, + ct, + kPremul_SkAlphaType, + canvas->imageInfo().refColorSpace()); + // In GPU mode we want a surface that is created with mipmaps to ensure that we exercise the + // case where the SkSurface and SkImage share a texture. If the surface texture isn't + // created with MIPs then asking for a mipmapped image will cause a copy to a mipped + // texture. + sk_sp surface; + if (auto rc = canvas->recordingContext()) { + surface = SkSurface::MakeRenderTarget(rc, + SkBudgeted::kYes, + ii, + 1, + kTopLeft_GrSurfaceOrigin, + /*surfaceProps=*/nullptr, + /*shouldCreateWithMips=*/true); + } else { + surface = canvas->makeSurface(ii); + if (!surface) { // could be a recording canvas. + surface = SkSurface::MakeRaster(ii); + } + } + + static constexpr float kScales[] = {1.f, 0.5f, 0.25f, 0.125f}; + SkColor kColors[] = {0xFFF0F0F0, SK_ColorBLUE, SK_ColorGREEN, SK_ColorRED}; + static const SkSamplingOptions kSampling = SkSamplingOptions::Aniso(16); + + for (bool shader : {false, true}) { + int c = 0; + canvas->save(); + for (float sy : kScales) { + canvas->save(); + for (float sx : kScales) { + canvas->save(); + canvas->scale(sx, sy); + auto image = this->updateImage(surface.get(), kColors[c]); + if (shader) { + SkPaint paint; + paint.setShader(image->makeShader(kSampling)); + canvas->drawRect(SkRect::Make(image->dimensions()), paint); + } else { + canvas->drawImage(image, 0, 0, kSampling); + } + canvas->restore(); + canvas->translate(ii.width() * sx + kPad, 0); + c = (c + 1) % SK_ARRAY_COUNT(kColors); + } + canvas->restore(); + canvas->translate(0, ii.width() * sy + kPad); + } + canvas->restore(); + for (float sx : kScales) { + canvas->translate(ii.width() * sx + kPad, 0); + } + } + } + +private: + inline static constexpr int kImageSize = 128; + inline static constexpr int kPad = 5; + + using INHERITED = GM; +}; + +////////////////////////////////////////////////////////////////////////////// + +DEF_GM(return new AnisoMipsGM();) + } // namespace skiagm diff --git a/gm/perspshaders.cpp b/gm/perspshaders.cpp index 661b251dd0..471e6e2693 100644 --- a/gm/perspshaders.cpp +++ b/gm/perspshaders.cpp @@ -158,10 +158,12 @@ protected: canvas->translate(0, SkIntToScalar(kCellSize)); this->drawRow(canvas, SkSamplingOptions(SkCubicResampler::Mitchell())); canvas->translate(0, SkIntToScalar(kCellSize)); + this->drawRow(canvas, SkSamplingOptions::Aniso(16)); + canvas->translate(0, SkIntToScalar(kCellSize)); } private: inline static constexpr int kCellSize = 50; - inline static constexpr int kNumRows = 4; + inline static constexpr int kNumRows = 5; inline static constexpr int kNumCols = 6; bool fDoAA; diff --git a/gm/resizeimagefilter.cpp b/gm/resizeimagefilter.cpp index d8d493d39c..330b5f2d8e 100644 --- a/gm/resizeimagefilter.cpp +++ b/gm/resizeimagefilter.cpp @@ -66,7 +66,7 @@ protected: } SkISize onISize() override { - return SkISize::Make(520, 100); + return SkISize::Make(630, 100); } void onDraw(SkCanvas* canvas) override { @@ -77,20 +77,15 @@ protected: SkSamplingOptions(SkFilterMode::kLinear), SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kLinear), SkSamplingOptions(SkCubicResampler::Mitchell()), + SkSamplingOptions::Aniso(16), }; const SkRect srcRect = SkRect::MakeWH(96, 96); const SkSize deviceSize = SkSize::Make(16, 16); - this->draw(canvas, srcRect, deviceSize, samplings[0], nullptr); - - canvas->translate(srcRect.width() + SkIntToScalar(10), 0); - this->draw(canvas, srcRect, deviceSize, samplings[1], nullptr); - - canvas->translate(srcRect.width() + SkIntToScalar(10), 0); - this->draw(canvas, srcRect, deviceSize, samplings[2], nullptr); - - canvas->translate(srcRect.width() + SkIntToScalar(10), 0); - this->draw(canvas, srcRect, deviceSize, samplings[3], nullptr); + for (const auto& sampling : samplings) { + this->draw(canvas, srcRect, deviceSize, sampling, nullptr); + canvas->translate(srcRect.width() + SkIntToScalar(10), 0); + } { sk_sp surface(SkSurface::MakeRasterN32Premul(16, 16)); @@ -109,7 +104,6 @@ protected: sk_sp source( SkImageFilters::Image(std::move(image), inRect, outRect, SkSamplingOptions({1/3.0f, 1/3.0f}))); - canvas->translate(srcRect.width() + SkIntToScalar(10), 0); this->draw(canvas, srcRect, deviceSize, samplings[3], std::move(source)); } } diff --git a/gm/tilemodes_scaled.cpp b/gm/tilemodes_scaled.cpp index 2ec02cb654..168a439d10 100644 --- a/gm/tilemodes_scaled.cpp +++ b/gm/tilemodes_scaled.cpp @@ -31,6 +31,7 @@ const SkSamplingOptions gSamplings[] = { SkSamplingOptions(SkFilterMode::kLinear), SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kLinear), SkSamplingOptions(SkCubicResampler::Mitchell()), + SkSamplingOptions::Aniso(16), }; static void makebm(SkBitmap* bm, SkColorType ct, int w, int h) { @@ -81,7 +82,7 @@ protected: return name; } - SkISize onISize() override { return SkISize::Make(880, 760); } + SkISize onISize() override { return SkISize::Make(880, 880); } void onOnceBeforeDraw() override { int size = fPowerOfTwoSize ? kPOTSize : kNPOTSize; @@ -102,7 +103,7 @@ protected: const char* gColorTypeNames[] = { "8888" , "565", "4444" }; - const char* gFilterNames[] = { "None", "Low", "Medium", "High" }; + const char* gFilterNames[] = { "Nearest", "Linear", "Trilinear", "Mitchell", "Aniso" }; constexpr SkTileMode gModes[] = { SkTileMode::kClamp, SkTileMode::kRepeat, SkTileMode::kMirror }; diff --git a/include/core/SkCanvas.h b/include/core/SkCanvas.h index a975fe53f6..0295e0ed01 100644 --- a/include/core/SkCanvas.h +++ b/include/core/SkCanvas.h @@ -1444,8 +1444,8 @@ public: /** \enum SkCanvas::SrcRectConstraint SrcRectConstraint controls the behavior at the edge of source SkRect, provided to drawImageRect() when there is any filtering. If kStrict is set, - then extra code is used to ensure it nevers samples outside of the src-rect. - kStrict_SrcRectConstraint disables the use of mipmaps. + then extra code is used to ensure it never samples outside of the src-rect. + kStrict_SrcRectConstraint disables the use of mipmaps and anisotropic filtering. */ enum SrcRectConstraint { kStrict_SrcRectConstraint, //!< sample only inside bounds; slower diff --git a/include/core/SkSamplingOptions.h b/include/core/SkSamplingOptions.h index 468cf5bef4..9575e479d0 100644 --- a/include/core/SkSamplingOptions.h +++ b/include/core/SkSamplingOptions.h @@ -9,6 +9,8 @@ #define SkImageSampling_DEFINED #include "include/core/SkTypes.h" + +#include #include enum class SkFilterMode { @@ -52,6 +54,7 @@ struct SkCubicResampler { }; struct SK_API SkSamplingOptions { + const int maxAniso = 0; const bool useCubic = false; const SkCubicResampler cubic = {0, 0}; const SkFilterMode filter = SkFilterMode::kNearest; @@ -66,27 +69,35 @@ struct SK_API SkSamplingOptions { } SkSamplingOptions(SkFilterMode fm, SkMipmapMode mm) - : useCubic(false) - , filter(fm) + : filter(fm) , mipmap(mm) {} explicit SkSamplingOptions(SkFilterMode fm) - : useCubic(false) - , filter(fm) + : filter(fm) , mipmap(SkMipmapMode::kNone) {} explicit SkSamplingOptions(const SkCubicResampler& c) : useCubic(true) , cubic(c) {} + static SkSamplingOptions Aniso(int maxAniso) { + return SkSamplingOptions{std::max(maxAniso, 1)}; + } + bool operator==(const SkSamplingOptions& other) const { - return useCubic == other.useCubic + return maxAniso == other.maxAniso + && useCubic == other.useCubic && cubic.B == other.cubic.B && cubic.C == other.cubic.C && filter == other.filter && mipmap == other.mipmap; } bool operator!=(const SkSamplingOptions& other) const { return !(*this == other); } + + bool isAniso() const { return maxAniso != 0; } + +private: + SkSamplingOptions(int maxAniso) : maxAniso(maxAniso) {} }; #endif diff --git a/src/core/SkBitmapProcState.cpp b/src/core/SkBitmapProcState.cpp index 11ad83612f..8c01dbd213 100755 --- a/src/core/SkBitmapProcState.cpp +++ b/src/core/SkBitmapProcState.cpp @@ -184,6 +184,7 @@ bool SkBitmapProcState::init(const SkMatrix& inv, SkAlpha paintAlpha, const SkSamplingOptions& sampling) { SkASSERT(!inv.hasPerspective()); SkASSERT(SkOpts::S32_alpha_D32_filter_DXDY || inv.isScaleTranslate()); + SkASSERT(!sampling.isAniso()); SkASSERT(!sampling.useCubic); SkASSERT(sampling.mipmap != SkMipmapMode::kLinear); diff --git a/src/core/SkCanvas.cpp b/src/core/SkCanvas.cpp index ef8e029df8..d6cf83c32d 100644 --- a/src/core/SkCanvas.cpp +++ b/src/core/SkCanvas.cpp @@ -2262,9 +2262,13 @@ void SkCanvas::onDrawImage2(const SkImage* image, SkScalar x, SkScalar y, static SkSamplingOptions clean_sampling_for_constraint( const SkSamplingOptions& sampling, SkCanvas::SrcRectConstraint constraint) { - if (constraint == SkCanvas::kStrict_SrcRectConstraint && - sampling.mipmap != SkMipmapMode::kNone) { - return SkSamplingOptions(sampling.filter); + if (constraint == SkCanvas::kStrict_SrcRectConstraint) { + if (sampling.mipmap != SkMipmapMode::kNone) { + return SkSamplingOptions(sampling.filter); + } + if (sampling.isAniso()) { + return SkSamplingOptions(SkFilterMode::kLinear); + } } return sampling; } diff --git a/src/core/SkPicturePriv.h b/src/core/SkPicturePriv.h index 013018dadc..aba289476b 100644 --- a/src/core/SkPicturePriv.h +++ b/src/core/SkPicturePriv.h @@ -100,6 +100,8 @@ public: // V88: Add blender to ComposeShader and BlendImageFilter // V89: Deprecated SkClipOps are no longer supported // V90: Private API for backdrop scale factor in SaveLayerRec + // V91: Added raw image shaders + // V92: Added anisotropic filtering to SkSamplingOptions enum Version { kPictureShaderFilterParam_Version = 82, @@ -112,10 +114,11 @@ public: kNoExpandingClipOps = 89, kBackdropScaleFactor = 90, kRawImageShaders = 91, + kAnisotropicFilter = 92, // Only SKPs within the min/current picture version range (inclusive) can be read. kMin_Version = kPictureShaderFilterParam_Version, - kCurrent_Version = kRawImageShaders + kCurrent_Version = kAnisotropicFilter }; }; diff --git a/src/core/SkPictureRecord.cpp b/src/core/SkPictureRecord.cpp index 55681e7bcc..651ff5e29c 100644 --- a/src/core/SkPictureRecord.cpp +++ b/src/core/SkPictureRecord.cpp @@ -523,7 +523,7 @@ void SkPictureRecord::onDrawPath(const SkPath& path, const SkPaint& paint) { void SkPictureRecord::onDrawImage2(const SkImage* image, SkScalar x, SkScalar y, const SkSamplingOptions& sampling, const SkPaint* paint) { // op + paint_index + image_index + x + y - size_t size = 3 * kUInt32Size + 2 * sizeof(SkScalar) + SkSamplingPriv::kFlatSize; + size_t size = 3 * kUInt32Size + 2 * sizeof(SkScalar) + SkSamplingPriv::FlatSize(sampling); size_t initialOffset = this->addDraw(DRAW_IMAGE2, &size); this->addPaintPtr(paint); this->addImage(image); @@ -537,7 +537,8 @@ void SkPictureRecord::onDrawImageRect2(const SkImage* image, const SkRect& src, const SkSamplingOptions& sampling, const SkPaint* paint, SrcRectConstraint constraint) { // id + paint_index + image_index + constraint - size_t size = 3 * kUInt32Size + 2 * sizeof(dst) + SkSamplingPriv::kFlatSize + kUInt32Size; + size_t size = 3 * kUInt32Size + 2 * sizeof(dst) + SkSamplingPriv::FlatSize(sampling) + + kUInt32Size; size_t initialOffset = this->addDraw(DRAW_IMAGE_RECT2, &size); this->addPaintPtr(paint); @@ -684,7 +685,7 @@ void SkPictureRecord::onDrawAtlas2(const SkImage* atlas, const SkRSXform xform[] const SkPaint* paint) { // [op + paint-index + atlas-index + flags + count] + [xform] + [tex] + [*colors + mode] + cull size_t size = 5 * kUInt32Size + count * sizeof(SkRSXform) + count * sizeof(SkRect); - size += SkSamplingPriv::kFlatSize; + size += SkSamplingPriv::FlatSize(sampling); uint32_t flags = 0; if (colors) { flags |= DRAW_ATLAS_HAS_COLORS; @@ -780,7 +781,7 @@ void SkPictureRecord::onDrawEdgeAAImageSet2(const SkCanvas::ImageSetEntry set[], size_t size = 6 * kUInt32Size + sizeof(SkPoint) * totalDstClipCount + kMatrixSize * totalMatrixCount + (4 * kUInt32Size + 2 * sizeof(SkRect) + sizeof(SkScalar)) * count + - SkSamplingPriv::kFlatSize; + SkSamplingPriv::FlatSize(sampling); size_t initialOffset = this->addDraw(DRAW_EDGEAA_IMAGE_SET2, &size); this->addInt(count); this->addPaintPtr(paint); diff --git a/src/core/SkReadBuffer.cpp b/src/core/SkReadBuffer.cpp index 786c16584a..5678a19e45 100644 --- a/src/core/SkReadBuffer.cpp +++ b/src/core/SkReadBuffer.cpp @@ -210,6 +210,12 @@ SkRect SkReadBuffer::readRect() { } SkSamplingOptions SkReadBuffer::readSampling() { + if (!this->isVersionLT(SkPicturePriv::kAnisotropicFilter)) { + int maxAniso = this->readInt(); + if (maxAniso != 0) { + return SkSamplingOptions::Aniso(maxAniso); + } + } if (this->readBool()) { float B = this->readScalar(); float C = this->readScalar(); diff --git a/src/core/SkSamplingPriv.h b/src/core/SkSamplingPriv.h index ae8385cc01..4b57839200 100644 --- a/src/core/SkSamplingPriv.h +++ b/src/core/SkSamplingPriv.h @@ -32,17 +32,31 @@ enum SkMediumAs { class SkSamplingPriv { public: - enum { - kFlatSize = 3 * sizeof(uint32_t) // bool32 + [2 floats | 2 ints] - }; + static size_t FlatSize(const SkSamplingOptions& options) { + size_t size = sizeof(uint32_t); // maxAniso + if (!options.isAniso()) { + size += 3 * sizeof(uint32_t); // bool32 + [2 floats | 2 ints] + } + return size; + } // Returns true if the sampling can be ignored when the CTM is identity. static bool NoChangeWithIdentityMatrix(const SkSamplingOptions& sampling) { // If B == 0, the cubic resampler should have no effect for identity matrices // https://entropymine.com/imageworsener/bicubic/ + // We assume aniso has no effect with an identity transform. return !sampling.useCubic || sampling.cubic.B == 0; } + // Makes a fallback SkSamplingOptions for cases where anisotropic filtering is not allowed. + // anisotropic filtering can access mip levels if present, but we don't add mipmaps to non- + // mipmapped images when the user requests anisotropic. So we shouldn't fall back to a + // sampling that would trigger mip map creation. + static SkSamplingOptions AnisoFallback(bool imageIsMipped) { + auto mm = imageIsMipped ? SkMipmapMode::kLinear : SkMipmapMode::kNone; + return SkSamplingOptions(SkFilterMode::kLinear, mm); + } + static SkSamplingOptions FromFQ(SkLegacyFQ, SkMediumAs = kNearest_SkMediumAs); }; diff --git a/src/core/SkWriter32.cpp b/src/core/SkWriter32.cpp index 363245774f..3b9502bc17 100644 --- a/src/core/SkWriter32.cpp +++ b/src/core/SkWriter32.cpp @@ -18,13 +18,16 @@ void SkWriter32::writeMatrix(const SkMatrix& matrix) { } void SkWriter32::writeSampling(const SkSamplingOptions& sampling) { - this->writeBool(sampling.useCubic); - if (sampling.useCubic) { - this->writeScalar(sampling.cubic.B); - this->writeScalar(sampling.cubic.C); - } else { - this->write32((unsigned)sampling.filter); - this->write32((unsigned)sampling.mipmap); + this->write32(sampling.maxAniso); + if (!sampling.isAniso()) { + this->writeBool(sampling.useCubic); + if (sampling.useCubic) { + this->writeScalar(sampling.cubic.B); + this->writeScalar(sampling.cubic.C); + } else { + this->write32((unsigned)sampling.filter); + this->write32((unsigned)sampling.mipmap); + } } } diff --git a/src/gpu/ganesh/effects/GrTextureEffect.cpp b/src/gpu/ganesh/effects/GrTextureEffect.cpp index d9245efbc7..ad3dd79739 100644 --- a/src/gpu/ganesh/effects/GrTextureEffect.cpp +++ b/src/gpu/ganesh/effects/GrTextureEffect.cpp @@ -58,15 +58,46 @@ GrTextureEffect::Sampling::Sampling(const GrSurfaceProxy& proxy, bool contains(Span r) const { return fA <= r.fA && fB >= r.fB; } }; struct Result1D { - ShaderMode fShaderMode; - Span fShaderSubset; - Span fShaderClamp; - Wrap fHWWrap; + ShaderMode fShaderMode = ShaderMode::kNone; + Span fShaderSubset = {}; + Span fShaderClamp = {}; + Wrap fHWWrap = Wrap::kClamp; }; - auto type = proxy.asTextureProxy()->textureType(); + auto type = proxy.asTextureProxy()->textureType(); auto filter = sampler.filter(); - auto mm = sampler.mipmapMode(); + auto mm = sampler.mipmapMode(); + + // TODO: Use HW border color when available. + // TODO: Move this back into resolve below when aniso is supported with shader-based subsetting. + bool needShaderBorder = false; + if ((sampler.wrapModeX() == Wrap::kClampToBorder || + sampler.wrapModeY() == Wrap::kClampToBorder) && + (!caps.clampToBorderSupport() || border[0] || border[1] || border[2] || border[3])) { + needShaderBorder = true; + } + + // TODO: Right now if we use shader based subsetting for any reason we just completely drop + // aniso. Longer term allow shader subsetting, reusing the special repeat mode LOD selection + // logic for mip maps, and simply don't attempt to restrict ansiso's computed samples to the + // subset. That is use "subsetting" but not "clamping"/insetting in terms of the shader gen + // logic. + bool aniso = sampler.isAniso(); + SkASSERT(!aniso || caps.anisoSupport()); + if (aniso) { + bool anisoSubset = !proxy.backingStoreBoundsRect().contains(subset) && + (!domain || !subset.contains(*domain)); + if (needShaderBorder || anisoSubset) { + MipmapMode newMM = proxy.asTextureProxy()->mipmapped() == GrMipmapped::kYes + ? MipmapMode::kLinear + : MipmapMode::kNone; + sampler = GrSamplerState(sampler.wrapModeX(), + sampler.wrapModeY(), + SkFilterMode::kLinear, + newMM); + aniso = false; + } + } auto resolve = [&](int size, Wrap wrap, Span subset, Span domain, float linearFilterInset) { Result1D r; @@ -119,19 +150,29 @@ GrTextureEffect::Sampling::Sampling(const GrSurfaceProxy& proxy, return r; }; - SkISize dim = proxy.isFullyLazy() ? SkISize{-1, -1} : proxy.backingStoreDimensions(); + Result1D x, y; + if (!aniso) { + SkISize dim = proxy.isFullyLazy() ? SkISize{-1, -1} : proxy.backingStoreDimensions(); - Span subsetX{subset.fLeft, subset.fRight}; - auto domainX = domain ? Span{domain->fLeft, domain->fRight} - : Span{SK_FloatNegativeInfinity, SK_FloatInfinity}; - auto x = resolve(dim.width(), sampler.wrapModeX(), subsetX, domainX, linearFilterInset.fX); + Span subsetX{subset.fLeft, subset.fRight}; + auto domainX = domain ? Span{domain->fLeft, domain->fRight} + : Span{SK_FloatNegativeInfinity, SK_FloatInfinity}; + x = resolve(dim.width(), sampler.wrapModeX(), subsetX, domainX, linearFilterInset.fX); - Span subsetY{subset.fTop, subset.fBottom}; - auto domainY = domain ? Span{domain->fTop, domain->fBottom} - : Span{SK_FloatNegativeInfinity, SK_FloatInfinity}; - auto y = resolve(dim.height(), sampler.wrapModeY(), subsetY, domainY, linearFilterInset.fY); + Span subsetY{subset.fTop, subset.fBottom}; + auto domainY = domain ? Span{domain->fTop, domain->fBottom} + : Span{SK_FloatNegativeInfinity, SK_FloatInfinity}; + y = resolve(dim.height(), sampler.wrapModeY(), subsetY, domainY, linearFilterInset.fY); + } else { + x.fHWWrap = sampler.wrapModeX(); + y.fHWWrap = sampler.wrapModeY(); + } - fHWSampler = {x.fHWWrap, y.fHWWrap, filter, mm}; + fHWSampler = aniso ? GrSamplerState::Aniso(x.fHWWrap, + y.fHWWrap, + sampler.maxAniso(), + proxy.asTextureProxy()->mipmapped()) + : GrSamplerState{x.fHWWrap, y.fHWWrap, filter, mm}; fShaderModes[0] = x.fShaderMode; fShaderModes[1] = y.fShaderMode; fShaderSubset = {x.fShaderSubset.fA, y.fShaderSubset.fA, diff --git a/src/gpu/ganesh/v1/BUILD.bazel b/src/gpu/ganesh/v1/BUILD.bazel index 2061754cbe..a58aab90f8 100644 --- a/src/gpu/ganesh/v1/BUILD.bazel +++ b/src/gpu/ganesh/v1/BUILD.bazel @@ -63,6 +63,7 @@ generated_cc_atom( "//src/core:SkDraw_hdr", "//src/core:SkImagePriv_hdr", "//src/core:SkMaskFilterBase_hdr", + "//src/core:SkSamplingPriv_hdr", "//src/core:SkSpecialImage_hdr", "//src/gpu/ganesh:GrBlurUtils_hdr", "//src/gpu/ganesh:GrCaps_hdr", diff --git a/src/gpu/ganesh/v1/Device_drawTexture.cpp b/src/gpu/ganesh/v1/Device_drawTexture.cpp index 69e5be2b88..ebba0eb9b8 100644 --- a/src/gpu/ganesh/v1/Device_drawTexture.cpp +++ b/src/gpu/ganesh/v1/Device_drawTexture.cpp @@ -13,6 +13,7 @@ #include "src/core/SkDraw.h" #include "src/core/SkImagePriv.h" #include "src/core/SkMaskFilterBase.h" +#include "src/core/SkSamplingPriv.h" #include "src/core/SkSpecialImage.h" #include "src/gpu/ganesh/GrBlurUtils.h" #include "src/gpu/ganesh/GrCaps.h" @@ -323,10 +324,10 @@ ImageDrawMode optimize_sample_area(const SkISize& image, const SkRect* origSrcRe * Checks whether the paint is compatible with using SurfaceDrawContext::drawTexture. It is more * efficient than the SkImage general case. */ -bool can_use_draw_texture(const SkPaint& paint, bool useCubicResampler, SkMipmapMode mm) { +bool can_use_draw_texture(const SkPaint& paint, const SkSamplingOptions& sampling) { return (!paint.getColorFilter() && !paint.getShader() && !paint.getMaskFilter() && - !paint.getImageFilter() && !paint.getBlender() && !useCubicResampler && - mm == SkMipmapMode::kNone); + !paint.getImageFilter() && !paint.getBlender() && !sampling.isAniso() && + !sampling.useCubic && sampling.mipmap == SkMipmapMode::kNone); } SkPMColor4f texture_color(SkColor4f paintColor, float entryAlpha, GrColorType srcColorType, @@ -426,9 +427,7 @@ void draw_image(GrRecordingContext* rContext, SkSamplingOptions sampling, SkTileMode tm = SkTileMode::kClamp) { const SkMatrix& ctm(matrixProvider.localToDevice()); - if (tm == SkTileMode::kClamp && - !image.isYUVA() && - can_use_draw_texture(paint, sampling.useCubic, sampling.mipmap)) { + if (tm == SkTileMode::kClamp && !image.isYUVA() && can_use_draw_texture(paint, sampling)) { // We've done enough checks above to allow us to pass ClampNearest() and not check for // scaling adjustments. auto [view, ct] = image.asView(rContext, GrMipmapped::kNo); @@ -476,7 +475,8 @@ void draw_image(GrRecordingContext* rContext, // Check for optimization to drop the src rect constraint when using linear filtering. // TODO: Just rely on image to handle this. - if (!sampling.useCubic && + if (sampling.isAniso() && + !sampling.useCubic && sampling.filter == SkFilterMode::kLinear && restrictToSubset && sampling.mipmap == SkMipmapMode::kNone && @@ -579,6 +579,9 @@ void draw_tiled_bitmap(GrRecordingContext* rContext, SkCanvas::SrcRectConstraint constraint, SkSamplingOptions sampling, SkTileMode tileMode) { + if (sampling.isAniso()) { + sampling = SkSamplingPriv::AnisoFallback(/*imageIsMipped=*/false); + } SkRect clippedSrcRect = SkRect::Make(clippedSrcIRect); int nx = bitmap.width() / tileSize; @@ -670,7 +673,7 @@ void draw_tiled_bitmap(GrRecordingContext* rContext, SkFilterMode downgrade_to_filter(const SkSamplingOptions& sampling) { SkFilterMode filter = sampling.filter; - if (sampling.useCubic || sampling.mipmap != SkMipmapMode::kNone) { + if (sampling.isAniso() || sampling.useCubic || sampling.mipmap != SkMipmapMode::kNone) { // if we were "fancier" than just bilerp, only do bilerp filter = SkFilterMode::kLinear; } @@ -779,10 +782,11 @@ void Device::drawImageQuad(const SkImage* image, int tileFilterPad; if (sampling.useCubic) { tileFilterPad = GrBicubicEffect::kFilterTexelPad; - } else if (sampling.filter == SkFilterMode::kNearest) { - tileFilterPad = 0; - } else { + } else if (sampling.filter == SkFilterMode::kLinear || sampling.isAniso()) { + // Aniso will fallback to linear filtering in the tiling case. tileFilterPad = 1; + } else { + tileFilterPad = 0; } int maxTileSize = fContext->priv().caps()->maxTextureSize() - 2*tileFilterPad; int tileSize; @@ -842,7 +846,7 @@ void Device::drawEdgeAAImageSet(const SkCanvas::ImageSetEntry set[], int count, const SkSamplingOptions& sampling, const SkPaint& paint, SkCanvas::SrcRectConstraint constraint) { SkASSERT(count > 0); - if (!can_use_draw_texture(paint, sampling.useCubic, sampling.mipmap)) { + if (!can_use_draw_texture(paint, sampling)) { // Send every entry through drawImageQuad() to handle the more complicated paint int dstClipIndex = 0; for (int i = 0; i < count; ++i) { diff --git a/src/image/BUILD.bazel b/src/image/BUILD.bazel index 0c77e89184..83a33d9929 100644 --- a/src/image/BUILD.bazel +++ b/src/image/BUILD.bazel @@ -119,6 +119,7 @@ generated_cc_atom( "//include/gpu:GrYUVABackendTextures_hdr", "//src/core:SkAutoPixmapStorage_hdr", "//src/core:SkMipmap_hdr", + "//src/core:SkSamplingPriv_hdr", "//src/core:SkScopeExit_hdr", "//src/gpu/ganesh:GrClip_hdr", "//src/gpu/ganesh:GrDirectContextPriv_hdr", diff --git a/src/image/SkImage.cpp b/src/image/SkImage.cpp index f4204f0a98..2f34ddf317 100644 --- a/src/image/SkImage.cpp +++ b/src/image/SkImage.cpp @@ -389,10 +389,20 @@ std::unique_ptr SkImage_Base::MakeFragmentProcessorFromView GrBicubicEffect::Direction::kXY, *rContext->priv().caps()); } - if (view.proxy()->asTextureProxy()->mipmapped() == GrMipmapped::kNo) { + if (sampling.isAniso()) { + if (!rContext->priv().caps()->anisoSupport()) { + // Fallback to linear + sampling = SkSamplingPriv::AnisoFallback(view.mipmapped() == GrMipmapped::kYes); + } + } else if (view.mipmapped() == GrMipmapped::kNo) { sampling = SkSamplingOptions(sampling.filter); } - GrSamplerState sampler(wmx, wmy, sampling.filter, sampling.mipmap); + 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), diff --git a/src/image/SkImage_GpuYUVA.cpp b/src/image/SkImage_GpuYUVA.cpp index a79d43a356..0850f79613 100644 --- a/src/image/SkImage_GpuYUVA.cpp +++ b/src/image/SkImage_GpuYUVA.cpp @@ -5,9 +5,7 @@ * found in the LICENSE file. */ -#include -#include -#include +#include "src/image/SkImage_GpuYUVA.h" #include "include/core/SkBitmap.h" #include "include/core/SkYUVAPixmaps.h" @@ -16,6 +14,7 @@ #include "include/gpu/GrYUVABackendTextures.h" #include "src/core/SkAutoPixmapStorage.h" #include "src/core/SkMipmap.h" +#include "src/core/SkSamplingPriv.h" #include "src/core/SkScopeExit.h" #include "src/gpu/ganesh/GrClip.h" #include "src/gpu/ganesh/GrDirectContextPriv.h" @@ -28,7 +27,6 @@ #include "src/gpu/ganesh/effects/GrBicubicEffect.h" #include "src/gpu/ganesh/effects/GrYUVtoRGBEffect.h" #include "src/image/SkImage_Gpu.h" -#include "src/image/SkImage_GpuYUVA.h" static constexpr auto kAssumedColorType = kRGBA_8888_SkColorType; @@ -191,6 +189,11 @@ std::unique_ptr SkImage_GpuYUVA::onAsFragmentProcessor( if (!fContext->priv().matches(context)) { return {}; } + // At least for now we do not attempt aniso filtering on YUVA images. + if (sampling.isAniso()) { + sampling = SkSamplingPriv::AnisoFallback(fYUVAProxies.mipmapped() == GrMipmapped::kYes); + } + auto wmx = SkTileModeToWrapMode(tileModes[0]); auto wmy = SkTileModeToWrapMode(tileModes[1]); GrSamplerState sampler(wmx, wmy, sampling.filter, sampling.mipmap); diff --git a/src/image/SkImage_Raster.cpp b/src/image/SkImage_Raster.cpp index 4d9e71b38d..628bc28530 100644 --- a/src/image/SkImage_Raster.cpp +++ b/src/image/SkImage_Raster.cpp @@ -444,7 +444,7 @@ std::tuple SkImage_Raster::onAsView( if (fPinnedView) { // We ignore the mipmap request here. If the pinned view isn't mipmapped then we will // fallback to bilinear. The pin API is used by Android Framework which does not expose - // mipmapping .Moreover, we're moving towards requiring that images be made with mip levels + // mipmapping . Moreover, we're moving towards requiring that images be made with mip levels // if mipmapping is desired (skbug.com/10411) mipmapped = GrMipmapped::kNo; if (policy != GrImageTexGenPolicy::kDraw) { diff --git a/src/shaders/SkImageShader.cpp b/src/shaders/SkImageShader.cpp index 108e326ad7..4ecc77ae56 100755 --- a/src/shaders/SkImageShader.cpp +++ b/src/shaders/SkImageShader.cpp @@ -220,6 +220,11 @@ SkShaderBase::Context* SkImageShader::onMakeContext(const ContextRec& rec, return nullptr; } + SkSamplingOptions sampling = fSampling; + if (sampling.isAniso()) { + sampling = SkSamplingPriv::AnisoFallback(fImage->hasMipmaps()); + } + auto supported = [](const SkSamplingOptions& sampling) { const std::tuple supported[] = { {SkFilterMode::kNearest, SkMipmapMode::kNone}, // legacy None @@ -233,7 +238,7 @@ SkShaderBase::Context* SkImageShader::onMakeContext(const ContextRec& rec, } return false; }; - if (fSampling.useCubic || !supported(fSampling)) { + if (sampling.useCubic || !supported(sampling)) { return nullptr; } @@ -262,7 +267,7 @@ SkShaderBase::Context* SkImageShader::onMakeContext(const ContextRec& rec, return nullptr; } - return SkBitmapProcLegacyShader::MakeContext(*this, fTileModeX, fTileModeY, fSampling, + return SkBitmapProcLegacyShader::MakeContext(*this, fTileModeX, fTileModeY, sampling, as_IB(fImage.get()), rec, alloc); } #endif @@ -471,6 +476,9 @@ bool SkImageShader::doStages(const SkStageRec& rec, TransformShader* updater) co 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.isAniso()) { + sampling = SkSamplingPriv::AnisoFallback(fImage->hasMipmaps()); + } if (sampling.mipmap == SkMipmapMode::kLinear) { return false; } @@ -777,10 +785,14 @@ skvm::Color SkImageShader::makeProgram( baseInv.normalizePerspective(); auto sampling = fSampling; + if (sampling.isAniso()) { + sampling = SkSamplingPriv::AnisoFallback(fImage->hasMipmaps()); + } auto* access = SkMipmapAccessor::Make(alloc, fImage.get(), baseInv, sampling.mipmap); if (!access) { return {}; } + auto [upper, upperInv] = access->level(); // If we are using a coordShader, then we can't make guesses about the state of the matrix. if (!sampling.useCubic && !coordShader) { diff --git a/tools/debugger/BUILD.bazel b/tools/debugger/BUILD.bazel index 0c89071301..a0b24f5798 100644 --- a/tools/debugger/BUILD.bazel +++ b/tools/debugger/BUILD.bazel @@ -211,6 +211,7 @@ generated_cc_atom( ":JsonWriteBuffer_hdr", "//include/core:SkFlattenable_hdr", "//include/core:SkPoint_hdr", + "//include/core:SkSamplingOptions_hdr", "//include/core:SkString_hdr", "//src/utils:SkJSONWriter_hdr", ], diff --git a/tools/debugger/DrawCommand.cpp b/tools/debugger/DrawCommand.cpp index c533024489..856ce7b6da 100644 --- a/tools/debugger/DrawCommand.cpp +++ b/tools/debugger/DrawCommand.cpp @@ -572,6 +572,7 @@ void DrawCommand::MakeJsonRegion(SkJSONWriter& writer, const SkRegion& region) { void DrawCommand::MakeJsonSampling(SkJSONWriter& writer, const SkSamplingOptions& sampling) { writer.beginObject(); + writer.appendS32("maxAniso", sampling.maxAniso); writer.appendBool("useCubic", sampling.useCubic); writer.appendS32("filter", (int)sampling.filter); writer.appendS32("mipmap", (int)sampling.mipmap); diff --git a/tools/debugger/JsonWriteBuffer.cpp b/tools/debugger/JsonWriteBuffer.cpp index 71fb2a8a65..fed1d8dd32 100644 --- a/tools/debugger/JsonWriteBuffer.cpp +++ b/tools/debugger/JsonWriteBuffer.cpp @@ -9,6 +9,7 @@ #include "include/core/SkFlattenable.h" #include "include/core/SkPoint.h" +#include "include/core/SkSamplingOptions.h" #include "include/core/SkString.h" #include "src/utils/SkJSONWriter.h" #include "tools/debugger/DrawCommand.h"