From f8a6b5b4b0d02895f70af4158c47c3069488a64a Mon Sep 17 00:00:00 2001 From: Mike Reed Date: Fri, 10 Jul 2020 08:36:42 -0400 Subject: [PATCH] Impl SkFilterOptions for raster-images Maintains the old and new code-paths: - if the shader was made with explicit filteroptions, use those - if not, infer the filteroptions from the filterquality enum (and the legacy heuristics of sniffing/snapping the ctm) In either case, the bulk of the onProgram() is shared, driving off the (possibly computed locally) filteroptions. bench looks sort like we expect: 509.28 filteroptions_sampling_0_mipmap_0 8888 495.76 filteroptions_sampling_0_mipmap_1 8888 642.52 filteroptions_sampling_0_mipmap_2 8888 942.40 filteroptions_sampling_1_mipmap_0 8888 976.94 filteroptions_sampling_1_mipmap_1 8888 1686.34 filteroptions_sampling_1_mipmap_2 8888 Bug: skia:10344 Change-Id: I77a79f79f640986fdd6b14f163c1a03462c55dc0 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/297561 Commit-Queue: Mike Reed Reviewed-by: Mike Klein --- bench/FilteringBench.cpp | 64 ++++++ gm/showmiplevels.cpp | 53 +++++ gn/bench.gni | 1 + include/core/SkImage.h | 20 ++ src/core/SkBitmapController.cpp | 72 +++++++ src/core/SkBitmapController.h | 23 ++ src/core/SkMipMap.cpp | 25 ++- src/core/SkMipMap.h | 5 +- src/core/SkPicturePriv.h | 5 +- src/image/SkImage.cpp | 7 + src/shaders/SkImageShader.cpp | 363 ++++++++++++++++++++++---------- src/shaders/SkImageShader.h | 25 ++- 12 files changed, 538 insertions(+), 125 deletions(-) create mode 100644 bench/FilteringBench.cpp diff --git a/bench/FilteringBench.cpp b/bench/FilteringBench.cpp new file mode 100644 index 0000000000..9465b5c30d --- /dev/null +++ b/bench/FilteringBench.cpp @@ -0,0 +1,64 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "bench/Benchmark.h" +#include "include/core/SkCanvas.h" +#include "include/core/SkPaint.h" +#include "include/core/SkShader.h" +#include "include/core/SkString.h" +#include "tools/Resources.h" + +class FilteringBench : public Benchmark { +public: + FilteringBench(SkFilterOptions options) : fOptions(options) { + fName.printf("filteroptions_sampling_%d_mipmap_%d", + (int)options.fSampling, (int)options.fMipmap); + } + +protected: + const char* onGetName() override { + return fName.c_str(); + } + + void onDelayedSetup() override { + auto img = GetResourceAsImage("images/ship.png"); + // need to force raster since lazy doesn't support filteroptions yet + img = img->makeRasterImage(); + + fRect = SkRect::MakeIWH(img->width(), img->height()); + fShader = img->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, fOptions); + } + + void onDraw(int loops, SkCanvas* canvas) override { + // scale so we will trigger lerping between levels if we mipmapping + canvas->scale(0.75f, 0.75f); + + SkPaint paint; + paint.setShader(fShader); + for (int i = 0; i < loops; ++i) { + for (int j = 0; j < 10; ++j) { + canvas->drawRect(fRect, paint); + } + } + } + +private: + SkString fName; + SkRect fRect; + sk_sp fShader; + SkFilterOptions fOptions; + + typedef Benchmark INHERITED; +}; + +DEF_BENCH( return new FilteringBench({SkSamplingMode::kLinear, SkMipmapMode::kLinear}); ) +DEF_BENCH( return new FilteringBench({SkSamplingMode::kLinear, SkMipmapMode::kNearest}); ) +DEF_BENCH( return new FilteringBench({SkSamplingMode::kLinear, SkMipmapMode::kNone}); ) + +DEF_BENCH( return new FilteringBench({SkSamplingMode::kNearest, SkMipmapMode::kLinear}); ) +DEF_BENCH( return new FilteringBench({SkSamplingMode::kNearest, SkMipmapMode::kNearest}); ) +DEF_BENCH( return new FilteringBench({SkSamplingMode::kNearest, SkMipmapMode::kNone}); ) diff --git a/gm/showmiplevels.cpp b/gm/showmiplevels.cpp index fe1a2341f6..b90fb13e8d 100644 --- a/gm/showmiplevels.cpp +++ b/gm/showmiplevels.cpp @@ -312,3 +312,56 @@ DEF_GM( return new ShowMipLevels2(255, 255); ) DEF_GM( return new ShowMipLevels2(256, 255); ) DEF_GM( return new ShowMipLevels2(255, 256); ) DEF_GM( return new ShowMipLevels2(256, 256); ) + +#include "tools/Resources.h" + +class ShowMipLevels3 : public skiagm::GM { + sk_sp fImg; + + SkString onShortName() override { return SkString("showmiplevels_explicit"); } + + SkISize onISize() override { return {1290, 990}; } + + SkScalar draw_downscaling(SkCanvas* canvas, SkFilterOptions options) { + SkAutoCanvasRestore acr(canvas, true); + + SkPaint paint; + SkRect r = {0, 0, 150, 150}; + for (float scale = 1; scale >= 0.125f; scale *= 0.75f) { + SkMatrix matrix = SkMatrix::Scale(scale, scale); + paint.setShader(fImg->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, + options, &matrix)); + canvas->drawRect(r, paint); +// r.offset(r.width() + 10, 0); + canvas->translate(r.width() + 10, 0); + } + return 160; + } + + void onOnceBeforeDraw() override { + fImg = GetResourceAsImage("images/ship.png"); + } + + void onDraw(SkCanvas* canvas) override { + canvas->drawColor(0xFFDDDDDD); + + const SkSamplingMode samplings[] = { + SkSamplingMode::kNearest, SkSamplingMode::kLinear + }; + const SkMipmapMode mipmodes[] = { + SkMipmapMode::kNone, SkMipmapMode::kNearest, SkMipmapMode::kLinear + }; + + canvas->translate(10, 10); + for (auto mm : mipmodes) { + for (auto sa : samplings) { + canvas->translate(0, draw_downscaling(canvas, {sa, mm})); + } + canvas->translate(0, 10); + } + } + +private: + typedef skiagm::GM INHERITED; +}; +DEF_GM( return new ShowMipLevels3; ) diff --git a/gn/bench.gni b/gn/bench.gni index a59bcd1cd3..597a10933c 100644 --- a/gn/bench.gni +++ b/gn/bench.gni @@ -46,6 +46,7 @@ bench_sources = [ "$_bench/DrawBitmapAABench.cpp", "$_bench/EncodeBench.cpp", "$_bench/FSRectBench.cpp", + "$_bench/FilteringBench.cpp", "$_bench/FontCacheBench.cpp", "$_bench/GMBench.cpp", "$_bench/GameBench.cpp", diff --git a/include/core/SkImage.h b/include/core/SkImage.h index 341ed0283c..f628adfa86 100644 --- a/include/core/SkImage.h +++ b/include/core/SkImage.h @@ -26,6 +26,7 @@ class SkData; class SkCanvas; class SkImageFilter; class SkImageGenerator; +class SkM44; class SkPaint; class SkPicture; class SkSurface; @@ -35,6 +36,22 @@ class GrContextThreadSafeProxy; struct SkYUVAIndex; +enum class SkSamplingMode { + kNearest, // single sample point (nearest neighbor) + kLinear, // interporate between 2x2 sample points (bilinear interpolation) +}; + +enum class SkMipmapMode { + kNone, // ignore mipmap levels, sample from the "base" + kNearest, // sample from the nearest level + kLinear, // interpolate between the two nearest levels +}; + +struct SkFilterOptions { + SkSamplingMode fSampling; + SkMipmapMode fMipmap; +}; + /** \class SkImage SkImage describes a two dimensional array of pixels to draw. The pixels may be decoded in a raster bitmap, encoded in a SkPicture or compressed data stream, @@ -770,6 +787,9 @@ public: */ bool isOpaque() const { return SkAlphaTypeIsOpaque(this->alphaType()); } + sk_sp makeShader(SkTileMode tmx, SkTileMode tmy, const SkFilterOptions&, + const SkMatrix* localMatrix = nullptr) const; + /** Creates SkShader from SkImage. SkShader dimensions are taken from SkImage. SkShader uses SkTileMode rules to fill drawn area outside SkImage. localMatrix permits transforming SkImage before SkCanvas matrix is applied. diff --git a/src/core/SkBitmapController.cpp b/src/core/SkBitmapController.cpp index 35cd0410e3..ba1df6d0c3 100644 --- a/src/core/SkBitmapController.cpp +++ b/src/core/SkBitmapController.cpp @@ -104,3 +104,75 @@ SkBitmapController::State::State(const SkImage_Base* image, // and will destroy us if it is nullptr. fPixmap.reset(fResultBitmap.info(), fResultBitmap.getPixels(), fResultBitmap.rowBytes()); } + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +SkMipmapAccessor::SkMipmapAccessor(const SkImage_Base* image, const SkMatrix& inv, + SkMipmapMode requestedMode) { + fResolvedMode = requestedMode; + fLowerWeight = 0; + + auto load_upper_from_base = [&]() { + // only do this once + if (fBaseStorage.getPixels() == nullptr) { + (void)image->getROPixels(&fBaseStorage); + fUpper.reset(fBaseStorage.info(), fBaseStorage.getPixels(), fBaseStorage.rowBytes()); + } + }; + + float level = 0; + if (requestedMode != SkMipmapMode::kNone) { + SkSize scale; + if (!inv.decomposeScale(&scale, nullptr)) { + fResolvedMode = SkMipmapMode::kNone; + } else { + level = SkMipMap::ComputeLevel({1/scale.width(), 1/scale.height()}); + if (level <= 0) { + fResolvedMode = SkMipmapMode::kNone; + level = 0; + } + } + } + + int levelNum = sk_float_floor2int(level); + float lowerWeight = level - levelNum; // fract(level) + SkASSERT(levelNum >= 0); + + if (levelNum == 0) { + load_upper_from_base(); + } + // load fCurrMip if needed + if (levelNum > 0 || (fResolvedMode == SkMipmapMode::kLinear && lowerWeight > 0)) { + // try to load from the cache + fCurrMip.reset(SkMipMapCache::FindAndRef(SkBitmapCacheDesc::Make(image))); + if (!fCurrMip) { + fCurrMip.reset(SkMipMapCache::AddAndRef(image)); + if (!fCurrMip) { + load_upper_from_base(); + fResolvedMode = SkMipmapMode::kNone; + } + } + if (fCurrMip) { + SkMipMap::Level levelRec; + + SkASSERT(fResolvedMode != SkMipmapMode::kNone); + if (levelNum > 0) { + if (fCurrMip->getLevel(levelNum - 1, &levelRec)) { + fUpper = levelRec.fPixmap; + } else { + load_upper_from_base(); + fResolvedMode = SkMipmapMode::kNone; + } + } + + if (fResolvedMode == SkMipmapMode::kLinear) { + if (fCurrMip->getLevel(levelNum, &levelRec)) { + fLower = levelRec.fPixmap; + fLowerWeight = lowerWeight; + } else { + fResolvedMode = SkMipmapMode::kNearest; + } + } + } + } +} diff --git a/src/core/SkBitmapController.h b/src/core/SkBitmapController.h index 6e08f94db5..7f76003211 100644 --- a/src/core/SkBitmapController.h +++ b/src/core/SkBitmapController.h @@ -10,6 +10,7 @@ #include "include/core/SkBitmap.h" #include "include/core/SkFilterQuality.h" +#include "include/core/SkImage.h" #include "include/core/SkMatrix.h" #include "src/core/SkBitmapCache.h" #include "src/core/SkMipMap.h" @@ -50,4 +51,26 @@ private: SkBitmapController() = delete; }; +class SkMipmapAccessor : ::SkNoncopyable { +public: + SkMipmapAccessor(const SkImage_Base*, const SkMatrix& inv, SkMipmapMode requestedMode); + + const SkPixmap& level() const { return fUpper; } + // only valid if mode() == kLinear + const SkPixmap& lowerLevel() const { return fLower; } + // 0....1. Will be 1.0 if there is no lowerLevel + float lowerWeight() const { return fLowerWeight; } + SkMipmapMode mode() const { return fResolvedMode; } + +private: + SkPixmap fUpper, + fLower; // only valid for mip_linear + float fLowerWeight; // lower * weight + upper * (1 - weight) + SkMipmapMode fResolvedMode; + + // these manage lifetime for the buffers + SkBitmap fBaseStorage; + sk_sp fCurrMip; +}; + #endif diff --git a/src/core/SkMipMap.cpp b/src/core/SkMipMap.cpp index 225c4a4b4f..97cf59d6c0 100644 --- a/src/core/SkMipMap.cpp +++ b/src/core/SkMipMap.cpp @@ -709,11 +709,9 @@ SkISize SkMipMap::ComputeLevelSize(int baseWidth, int baseHeight, int level) { /////////////////////////////////////////////////////////////////////////////// -bool SkMipMap::extractLevel(const SkSize& scaleSize, Level* levelPtr) const { - if (nullptr == fLevels) { - return false; - } - +// Returns fractional level value. floor(level) is the index of the larger level. +// < 0 means failure. +float SkMipMap::ComputeLevel(SkSize scaleSize) { SkASSERT(scaleSize.width() >= 0 && scaleSize.height() >= 0); #ifndef SK_SUPPORT_LEGACY_ANISOTROPIC_MIPMAP_SCALE @@ -727,17 +725,24 @@ bool SkMipMap::extractLevel(const SkSize& scaleSize, Level* levelPtr) const { #endif if (scale >= SK_Scalar1 || scale <= 0 || !SkScalarIsFinite(scale)) { - return false; + return -1; } SkScalar L = -SkScalarLog2(scale); if (!SkScalarIsFinite(L)) { - return false; + return -1; } SkASSERT(L >= 0); - int level = SkScalarFloorToInt(L); + return L; +} - SkASSERT(level >= 0); +bool SkMipMap::extractLevel(SkSize scaleSize, Level* levelPtr) const { + if (nullptr == fLevels) { + return false; + } + + float L = ComputeLevel(scaleSize); + int level = SkScalarFloorToInt(L); if (level <= 0) { return false; } @@ -779,6 +784,8 @@ bool SkMipMap::getLevel(int index, Level* levelPtr) const { } if (levelPtr) { *levelPtr = fLevels[index]; + // need to augment with our colorspace + levelPtr->fPixmap.setColorSpace(fCS); } return true; } diff --git a/src/core/SkMipMap.h b/src/core/SkMipMap.h index 89312b9dec..ef96ead62f 100644 --- a/src/core/SkMipMap.h +++ b/src/core/SkMipMap.h @@ -43,6 +43,9 @@ public: // the base level. So index 0 represents mipmap level 1. static SkISize ComputeLevelSize(int baseWidth, int baseHeight, int level); + // Computes the fractional level based on the scaling in X and Y. + static float ComputeLevel(SkSize scaleSize); + // We use a block of (possibly discardable) memory to hold an array of Level structs, followed // by the pixel data for each level. On 32-bit platforms, Level would naturally be 4 byte // aligned, so the pixel data could end up with 4 byte alignment. If the pixel data is F16, @@ -52,7 +55,7 @@ public: SkSize fScale; // < 1.0 }; - bool extractLevel(const SkSize& scale, Level*) const; + bool extractLevel(SkSize scale, Level*) const; // countLevels returns the number of mipmap levels generated (which does not // include the base mipmap level). diff --git a/src/core/SkPicturePriv.h b/src/core/SkPicturePriv.h index 67cb931ba3..8ba1ea94da 100644 --- a/src/core/SkPicturePriv.h +++ b/src/core/SkPicturePriv.h @@ -99,11 +99,12 @@ public: kEdgeAAQuadColor4f_Version = 73, kMorphologyTakesScalar_Version = 74, kVerticesUseReadBuffer_Version = 75, - kFilteringInImageShader_Version = 76, + kFilterEnumInImageShader_Version = 76, + kFilterOptionsInImageShader_Version = 77, // Only SKPs within the min/current picture version range (inclusive) can be read. kMin_Version = kTileModeInBlurImageFilter_Version, - kCurrent_Version = kFilteringInImageShader_Version + kCurrent_Version = kFilterOptionsInImageShader_Version }; static_assert(kMin_Version <= 62, "Remove kFontAxes_bad from SkFontDescriptor.cpp"); diff --git a/src/image/SkImage.cpp b/src/image/SkImage.cpp index 63887e3681..6c22f30db5 100644 --- a/src/image/SkImage.cpp +++ b/src/image/SkImage.cpp @@ -127,6 +127,13 @@ sk_sp SkImage::makeShader(SkTileMode tmx, SkTileMode tmy, SkImageShader::kInheritFromPaint); } +sk_sp SkImage::makeShader(SkTileMode tmx, SkTileMode tmy, + const SkFilterOptions& options, + const SkMatrix* localMatrix) const { + return SkImageShader::Make(sk_ref_sp(const_cast(this)), tmx, tmy, + options, localMatrix); +} + sk_sp SkImage::makeShader(SkTileMode tmx, SkTileMode tmy, const SkMatrix* localMatrix, SkFilterQuality filtering) const { return SkImageShader::Make(sk_ref_sp(const_cast(this)), tmx, tmy, localMatrix, diff --git a/src/shaders/SkImageShader.cpp b/src/shaders/SkImageShader.cpp index 30946c6711..95ef5b5861 100755 --- a/src/shaders/SkImageShader.cpp +++ b/src/shaders/SkImageShader.cpp @@ -45,8 +45,24 @@ SkImageShader::SkImageShader(sk_sp img, , fImage(std::move(img)) , fTileModeX(optimize(tmx, fImage->width())) , fTileModeY(optimize(tmy, fImage->height())) - , fFiltering(filtering) + , fFilterEnum(filtering) , fClampAsIfUnpremul(clampAsIfUnpremul) + , fFilterOptions({}) // ignored +{ + SkASSERT(filtering != kUseFilterOptions); +} + +SkImageShader::SkImageShader(sk_sp img, + SkTileMode tmx, SkTileMode tmy, + const SkFilterOptions& options, + const SkMatrix* localMatrix) + : INHERITED(localMatrix) + , fImage(std::move(img)) + , fTileModeX(optimize(tmx, fImage->width())) + , fTileModeY(optimize(tmy, fImage->height())) + , fFilterEnum(FilterEnum::kUseFilterOptions) + , fClampAsIfUnpremul(false) + , fFilterOptions(options) {} // fClampAsIfUnpremul is always false when constructed through public APIs, @@ -56,9 +72,15 @@ sk_sp SkImageShader::CreateProc(SkReadBuffer& buffer) { auto tmx = buffer.read32LE(SkTileMode::kLastTileMode); auto tmy = buffer.read32LE(SkTileMode::kLastTileMode); - FilterEnum filtering = kInheritFromPaint; - if (!buffer.isVersionLT(SkPicturePriv::kFilteringInImageShader_Version)) { - filtering = buffer.read32LE(kInheritFromPaint); + FilterEnum fe = kInheritFromPaint; + if (!buffer.isVersionLT(SkPicturePriv::kFilterEnumInImageShader_Version)) { + fe = buffer.read32LE(kUseFilterOptions); + } + + SkFilterOptions fo = { SkSamplingMode::kNearest, SkMipmapMode::kNone }; + if (!buffer.isVersionLT(SkPicturePriv::kFilterOptionsInImageShader_Version)) { + fo.fSampling = buffer.read32LE(SkSamplingMode::kLinear); + fo.fMipmap = buffer.read32LE(SkMipmapMode::kLinear); } SkMatrix localMatrix; @@ -68,13 +90,17 @@ sk_sp SkImageShader::CreateProc(SkReadBuffer& buffer) { return nullptr; } - return SkImageShader::Make(std::move(img), tmx, tmy, &localMatrix, filtering); + return (fe == kUseFilterOptions) + ? SkImageShader::Make(std::move(img), tmx, tmy, fo, &localMatrix) + : SkImageShader::Make(std::move(img), tmx, tmy, &localMatrix, fe); } void SkImageShader::flatten(SkWriteBuffer& buffer) const { buffer.writeUInt((unsigned)fTileModeX); buffer.writeUInt((unsigned)fTileModeY); - buffer.writeUInt((unsigned)fFiltering); + buffer.writeUInt((unsigned)fFilterEnum); + buffer.writeUInt((unsigned)fFilterOptions.fSampling); + buffer.writeUInt((unsigned)fFilterOptions.fMipmap); buffer.writeMatrix(this->getLocalMatrix()); buffer.writeImage(fImage.get()); SkASSERT(fClampAsIfUnpremul == false); @@ -112,7 +138,11 @@ static bool legacy_shader_can_handle(const SkMatrix& inv) { SkShaderBase::Context* SkImageShader::onMakeContext(const ContextRec& rec, SkArenaAlloc* alloc) const { - SkFilterQuality quality = this->resolveFiltering(rec.fPaint->getFilterQuality()); + if (fFilterEnum == kUseFilterOptions) { + return nullptr; + } + + auto quality = this->resolveFiltering(rec.fPaint->getFilterQuality()); if (quality == kHigh_SkFilterQuality) { return nullptr; @@ -192,6 +222,18 @@ sk_sp SkImageShader::Make(sk_sp image, }; } +sk_sp SkImageShader::Make(sk_sp image, + SkTileMode tmx, SkTileMode tmy, + const SkFilterOptions& options, + const SkMatrix* localMatrix) { + if (!image) { + return sk_make_sp(); + } + return sk_sp{ + new SkImageShader(image, tmx, tmy, options, localMatrix) + }; +} + /////////////////////////////////////////////////////////////////////////////////////////////////// #if SK_SUPPORT_GPU @@ -387,7 +429,16 @@ static void tweak_quality_and_inv_matrix(SkFilterQuality* quality, SkMatrix* mat } bool SkImageShader::doStages(const SkStageRec& rec, SkImageStageUpdater* updater) const { - auto quality = this->resolveFiltering(rec.fPaint.getFilterQuality()); + SkFilterQuality quality; + switch (fFilterEnum) { + case FilterEnum::kUseFilterOptions: return false; // TODO: support these in stages + case FilterEnum::kInheritFromPaint: + quality = rec.fPaint.getFilterQuality(); + break; + default: + quality = (SkFilterQuality)fFilterEnum; + break; + } if (updater && quality == kMedium_SkFilterQuality) { // TODO: medium: recall RequestBitmap and update width/height accordingly @@ -666,35 +717,77 @@ SkStageUpdater* SkImageShader::onAppendUpdatableStages(const SkStageRec& rec) co return this->doStages(rec, updater) ? updater : nullptr; } +enum class SamplingEnum { + kNearest, + kLinear, + kBicubic, +}; + skvm::Color SkImageShader::onProgram(skvm::Builder* p, - skvm::Coord device, skvm::Coord local, skvm::Color paint, + skvm::Coord device, skvm::Coord origLocal, skvm::Color paint, const SkMatrixProvider& matrices, const SkMatrix* localM, - SkFilterQuality quality, const SkColorInfo& dst, + SkFilterQuality paintQuality, const SkColorInfo& dst, skvm::Uniforms* uniforms, SkArenaAlloc* alloc) const { - quality = this->resolveFiltering(quality); - - SkMatrix inv; - if (!this->computeTotalInverse(matrices.localToDevice(), localM, &inv)) { + SkMatrix baseInv; + if (!this->computeTotalInverse(matrices.localToDevice(), localM, &baseInv)) { return {}; } + baseInv.normalizePerspective(); - // We use RequestBitmap() to make sure our SkBitmapController::State lives in the alloc. - // This lets the SkVMBlitter hang on to this state and keep our image alive. - auto state = SkBitmapController::RequestBitmap(as_IB(fImage.get()), inv, quality, alloc); - if (!state) { - return {}; + const SkPixmap *upper = nullptr, + *lower = nullptr; + SkMatrix upperInv; + float lowerWeight = 0; + SamplingEnum sampling = (SamplingEnum)fFilterOptions.fSampling; + + auto post_scale = [&](SkISize level, const SkMatrix& base) { + return SkMatrix::Scale(SkIntToScalar(level.width()) / fImage->width(), + SkIntToScalar(level.height()) / fImage->height()) + * base; + }; + + if (fFilterEnum == kUseFilterOptions) { + auto* access = alloc->make(as_IB(fImage.get()), baseInv, + fFilterOptions.fMipmap); + upper = &access->level(); + upperInv = post_scale(upper->dimensions(), baseInv); + lowerWeight = access->lowerWeight(); + if (lowerWeight > 0) { + lower = &access->lowerLevel(); + } + } else { + // Convert from the filter-quality enum to our working description: + // sampling : nearest, bilerp, bicubic + // miplevel(s) and associated matrices + // + SkFilterQuality quality = paintQuality; + if (fFilterEnum != kInheritFromPaint) { + quality = (SkFilterQuality)fFilterEnum; + } + + // We use RequestBitmap() to make sure our SkBitmapController::State lives in the alloc. + // This lets the SkVMBlitter hang on to this state and keep our image alive. + auto state = SkBitmapController::RequestBitmap(as_IB(fImage.get()), baseInv, quality, alloc); + if (!state) { + return {}; + } + upper = &state->pixmap(); + upperInv = state->invMatrix(); + + quality = state->quality(); + tweak_quality_and_inv_matrix(&quality, &upperInv); + switch (quality) { + case kNone_SkFilterQuality: sampling = SamplingEnum::kNearest; break; + case kLow_SkFilterQuality: sampling = SamplingEnum::kLinear; break; + case kMedium_SkFilterQuality: sampling = SamplingEnum::kLinear; break; + case kHigh_SkFilterQuality: sampling = SamplingEnum::kBicubic; break; + } } - const SkPixmap& pm = state->pixmap(); - inv = state->invMatrix(); - quality = state->quality(); - tweak_quality_and_inv_matrix(&quality, &inv); - inv.normalizePerspective(); - // Apply matrix to convert dst coords to sample center coords. - local = SkShaderBase::ApplyMatrix(p, inv, local, uniforms); + skvm::Coord upperLocal = SkShaderBase::ApplyMatrix(p, upperInv, origLocal, uniforms); // Bail out if sample() can't yet handle our image's color type. - switch (pm.colorType()) { + switch (upper->colorType()) { default: return {}; case kGray_8_SkColorType: case kAlpha_8_SkColorType: @@ -709,26 +802,66 @@ skvm::Color SkImageShader::onProgram(skvm::Builder* p, } // We can exploit image opacity to skip work unpacking alpha channels. - const bool input_is_opaque = SkAlphaTypeIsOpaque(pm.alphaType()) - || SkColorTypeIsAlwaysOpaque(pm.colorType()); + const bool input_is_opaque = SkAlphaTypeIsOpaque(upper->alphaType()) + || SkColorTypeIsAlwaysOpaque(upper->colorType()); // Each call to sample() will try to rewrite the same uniforms over and over, // so remember where we start and reset back there each time. That way each // sample() call uses the same uniform offsets. - const size_t uniforms_before_sample = uniforms->buf.size(); - auto sample = [&](skvm::F32 sx, skvm::F32 sy) -> skvm::Color { - uniforms->buf.resize(uniforms_before_sample); + auto compute_clamp_limit = [&](float limit) { + // Subtract an ulp so the upper clamp limit excludes limit itself. + int bits; + memcpy(&bits, &limit, 4); + return p->uniformF(uniforms->push(bits-1)); + }; + // Except in the simplest case (no mips, no filtering), we reference uniforms + // more than once. To avoid adding/registering them multiple times, we pre-load them + // into a struct (just to logically group them together), based on the "current" + // pixmap (level of a mipmap). + // + struct Uniforms { + skvm::F32 w, iw, i2w, + h, ih, i2h; + + skvm::F32 clamp_w, + clamp_h; + + skvm::Uniform addr; + skvm::I32 rowBytesAsPixels; + + SkColorType colorType; // not a uniform, but needed for each texel sample, + // so we store it here, since it is also dependent on + // the current pixmap (level). + }; + + auto setup_uniforms = [&](const SkPixmap& pm) -> Uniforms { + return { + p->uniformF(uniforms->pushF( pm.width())), + p->uniformF(uniforms->pushF(1.0f/pm.width())), // iff tileX == kRepeat + p->uniformF(uniforms->pushF(0.5f/pm.width())), // iff tileX == kMirror + + p->uniformF(uniforms->pushF( pm.height())), + p->uniformF(uniforms->pushF(1.0f/pm.height())), // iff tileY == kRepeat + p->uniformF(uniforms->pushF(0.5f/pm.height())), // iff tileY == kMirror + + compute_clamp_limit(pm. width()), + compute_clamp_limit(pm.height()), + + uniforms->pushPtr(pm.addr()), + p->uniform32(uniforms->push(pm.rowBytesAsPixels())), + + pm.colorType(), + }; + }; + + auto sample_texel = [&](const Uniforms& u, skvm::F32 sx, skvm::F32 sy) -> skvm::Color { // repeat() and mirror() are written assuming they'll be followed by a [0,scale) clamp. - auto repeat = [&](skvm::F32 v, float scale) { - skvm::F32 S = p->uniformF(uniforms->pushF( scale)), - I = p->uniformF(uniforms->pushF(1.0f/scale)); + auto repeat = [&](skvm::F32 v, skvm::F32 S, skvm::F32 I) { return v - floor(v * I) * S; }; - auto mirror = [&](skvm::F32 v, float scale) { - skvm::F32 S = p->uniformF(uniforms->pushF( scale)), - I2 = p->uniformF(uniforms->pushF(0.5f/scale)); + auto mirror = [&](skvm::F32 v, skvm::F32 S, skvm::F32 I2) { // abs( (v-scale) - (2*scale)*floor((v-scale)*(0.5f/scale)) - scale ) // {---A---} {------------------B------------------} skvm::F32 A = v - S, @@ -738,33 +871,27 @@ skvm::Color SkImageShader::onProgram(skvm::Builder* p, switch (fTileModeX) { case SkTileMode::kDecal: /* handled after gather */ break; case SkTileMode::kClamp: /* we always clamp */ break; - case SkTileMode::kRepeat: sx = repeat(sx, pm.width()); break; - case SkTileMode::kMirror: sx = mirror(sx, pm.width()); break; + case SkTileMode::kRepeat: sx = repeat(sx, u.w, u.iw); break; + case SkTileMode::kMirror: sx = mirror(sx, u.w, u.i2w); break; } switch (fTileModeY) { case SkTileMode::kDecal: /* handled after gather */ break; case SkTileMode::kClamp: /* we always clamp */ break; - case SkTileMode::kRepeat: sy = repeat(sy, pm.height()); break; - case SkTileMode::kMirror: sy = mirror(sy, pm.height()); break; + case SkTileMode::kRepeat: sy = repeat(sy, u.h, u.ih); break; + case SkTileMode::kMirror: sy = mirror(sy, u.h, u.i2h); break; } // Always clamp sample coordinates to [0,width), [0,height), both for memory // safety and to handle the clamps still needed by kClamp, kRepeat, and kMirror. - auto clamp0x = [&](skvm::F32 v, float limit) { - // Subtract an ulp so the upper clamp limit excludes limit itself. - int bits; - memcpy(&bits, &limit, 4); - return clamp(v, 0.0f, p->uniformF(uniforms->push(bits-1))); - }; - skvm::F32 clamped_x = clamp0x(sx, pm. width()), - clamped_y = clamp0x(sy, pm.height()); + skvm::F32 clamped_x = clamp(sx, 0, u.clamp_w), + clamped_y = clamp(sy, 0, u.clamp_h); // Load pixels from pm.addr()[(int)sx + (int)sy*stride]. - skvm::Uniform img = uniforms->pushPtr(pm.addr()); + skvm::Uniform img = u.addr; skvm::I32 index = trunc(clamped_x) + - trunc(clamped_y) * p->uniform32(uniforms->push(pm.rowBytesAsPixels())); + trunc(clamped_y) * u.rowBytesAsPixels; skvm::Color c; - switch (pm.colorType()) { + switch (u.colorType) { default: SkUNREACHABLE; case kGray_8_SkColorType: c.r = c.g = c.b = from_unorm(8, gather8(img, index)); @@ -813,70 +940,84 @@ skvm::Color SkImageShader::onProgram(skvm::Builder* p, return c; }; - skvm::Color c; + auto sample_level = [&](const SkPixmap& pm, const SkMatrix& inv, skvm::Coord local) { + const Uniforms u = setup_uniforms(pm); - if (quality == kNone_SkFilterQuality) { - c = sample(local.x,local.y); - } else if (quality == kLow_SkFilterQuality) { - // Our four sample points are the corners of a logical 1x1 pixel - // box surrounding (x,y) at (0.5,0.5) off-center. - skvm::F32 left = local.x - 0.5f, - top = local.y - 0.5f, - right = local.x + 0.5f, - bottom = local.y + 0.5f; + if (sampling == SamplingEnum::kNearest) { + return sample_texel(u, local.x,local.y); + } else if (sampling == SamplingEnum::kLinear) { + // Our four sample points are the corners of a logical 1x1 pixel + // box surrounding (x,y) at (0.5,0.5) off-center. + skvm::F32 left = local.x - 0.5f, + top = local.y - 0.5f, + right = local.x + 0.5f, + bottom = local.y + 0.5f; - // The fractional parts of right and bottom are our lerp factors in x and y respectively. - skvm::F32 fx = fract(right ), - fy = fract(bottom); + // The fractional parts of right and bottom are our lerp factors in x and y respectively. + skvm::F32 fx = fract(right ), + fy = fract(bottom); - c = lerp(lerp(sample(left,top ), sample(right,top ), fx), - lerp(sample(left,bottom), sample(right,bottom), fx), fy); - } else { - SkASSERT(quality == kHigh_SkFilterQuality); + return lerp(lerp(sample_texel(u, left,top ), sample_texel(u, right,top ), fx), + lerp(sample_texel(u, left,bottom), sample_texel(u, right,bottom), fx), fy); + } else { + SkASSERT(sampling == SamplingEnum::kBicubic); - // All bicubic samples have the same fractional offset (fx,fy) from the center. - // They're either the 16 corners of a 3x3 grid/ surrounding (x,y) at (0.5,0.5) off-center. - skvm::F32 fx = fract(local.x + 0.5f), - fy = fract(local.y + 0.5f); + // All bicubic samples have the same fractional offset (fx,fy) from the center. + // They're either the 16 corners of a 3x3 grid/ surrounding (x,y) at (0.5,0.5) off-center. + skvm::F32 fx = fract(local.x + 0.5f), + fy = fract(local.y + 0.5f); - // See GrCubicEffect for details of these weights. - // TODO: these maybe don't seem right looking at gm/bicubic and GrBicubicEffect. - auto near = [&](skvm::F32 t) { - // 1/18 + 9/18t + 27/18t^2 - 21/18t^3 == t ( t ( -21/18t + 27/18) + 9/18) + 1/18 - return t * (t * (t * (-21/18.0f) + 27/18.0f) + 9/18.0f) + 1/18.0f; - }; - auto far = [&](skvm::F32 t) { - // 0/18 + 0/18*t - 6/18t^2 + 7/18t^3 == t^2 (7/18t - 6/18) - return t * t * (t * (7/18.0f) - 6/18.0f); - }; - const skvm::F32 wx[] = { - far (1.0f - fx), - near(1.0f - fx), - near( fx), - far ( fx), - }; - const skvm::F32 wy[] = { - far (1.0f - fy), - near(1.0f - fy), - near( fy), - far ( fy), - }; + // See GrCubicEffect for details of these weights. + // TODO: these maybe don't seem right looking at gm/bicubic and GrBicubicEffect. + auto near = [&](skvm::F32 t) { + // 1/18 + 9/18t + 27/18t^2 - 21/18t^3 == t ( t ( -21/18t + 27/18) + 9/18) + 1/18 + return t * (t * (t * (-21/18.0f) + 27/18.0f) + 9/18.0f) + 1/18.0f; + }; + auto far = [&](skvm::F32 t) { + // 0/18 + 0/18*t - 6/18t^2 + 7/18t^3 == t^2 (7/18t - 6/18) + return t * t * (t * (7/18.0f) - 6/18.0f); + }; + const skvm::F32 wx[] = { + far (1.0f - fx), + near(1.0f - fx), + near( fx), + far ( fx), + }; + const skvm::F32 wy[] = { + far (1.0f - fy), + near(1.0f - fy), + near( fy), + far ( fy), + }; - c.r = c.g = c.b = c.a = p->splat(0.0f); + skvm::Color c; + c.r = c.g = c.b = c.a = p->splat(0.0f); - skvm::F32 sy = local.y - 1.5f; - for (int j = 0; j < 4; j++, sy += 1.0f) { - skvm::F32 sx = local.x - 1.5f; - for (int i = 0; i < 4; i++, sx += 1.0f) { - skvm::Color s = sample(sx,sy); - skvm::F32 w = wx[i] * wy[j]; + skvm::F32 sy = local.y - 1.5f; + for (int j = 0; j < 4; j++, sy += 1.0f) { + skvm::F32 sx = local.x - 1.5f; + for (int i = 0; i < 4; i++, sx += 1.0f) { + skvm::Color s = sample_texel(u, sx,sy); + skvm::F32 w = wx[i] * wy[j]; - c.r += s.r * w; - c.g += s.g * w; - c.b += s.b * w; - c.a += s.a * w; + c.r += s.r * w; + c.g += s.g * w; + c.b += s.b * w; + c.a += s.a * w; + } } + return c; } + }; + + skvm::Color c = sample_level(*upper, upperInv, upperLocal); + if (lower) { + auto lowerInv = post_scale(lower->dimensions(), baseInv); + auto lowerLocal = SkShaderBase::ApplyMatrix(p, lowerInv, origLocal, uniforms); + // lower * weight + upper * (1 - weight) + c = lerp(c, + sample_level(*lower, lowerInv, lowerLocal), + p->uniformF(uniforms->pushF(lowerWeight))); } // If the input is opaque and we're not in decal mode, that means the output is too. @@ -888,9 +1029,9 @@ skvm::Color SkImageShader::onProgram(skvm::Builder* p, } // Alpha-only images get their color from the paint (already converted to dst color space). - SkColorSpace* cs = pm.colorSpace(); - SkAlphaType at = pm.alphaType(); - if (SkColorTypeIsAlphaOnly(pm.colorType())) { + SkColorSpace* cs = upper->colorSpace(); + SkAlphaType at = upper->alphaType(); + if (SkColorTypeIsAlphaOnly(upper->colorType())) { c.r = paint.r; c.g = paint.g; c.b = paint.b; @@ -899,7 +1040,7 @@ skvm::Color SkImageShader::onProgram(skvm::Builder* p, at = kUnpremul_SkAlphaType; } - if (quality == kHigh_SkFilterQuality) { + if (sampling == SamplingEnum::kBicubic) { // Bicubic filtering naturally produces out of range values on both sides of [0,1]. c.a = clamp01(c.a); diff --git a/src/shaders/SkImageShader.h b/src/shaders/SkImageShader.h index a653a9f332..988b4eb61a 100644 --- a/src/shaders/SkImageShader.h +++ b/src/shaders/SkImageShader.h @@ -24,6 +24,8 @@ public: kHigh, // this is the special value for backward compatibility kInheritFromPaint, + // this signals we should use the new SkFilterOptions + kUseFilterOptions, }; static sk_sp Make(sk_sp, @@ -33,6 +35,12 @@ public: FilterEnum, bool clampAsIfUnpremul = false); + static sk_sp Make(sk_sp, + SkTileMode tmx, + SkTileMode tmy, + const SkFilterOptions&, + const SkMatrix* localMatrix); + bool isOpaque() const override; #if SK_SUPPORT_GPU @@ -48,6 +56,11 @@ private: const SkMatrix* localMatrix, FilterEnum, bool clampAsIfUnpremul); + SkImageShader(sk_sp, + SkTileMode tmx, + SkTileMode tmy, + const SkFilterOptions&, + const SkMatrix* localMatrix); void flatten(SkWriteBuffer&) const override; #ifdef SK_ENABLE_LEGACY_SHADERCONTEXT @@ -66,15 +79,23 @@ private: bool doStages(const SkStageRec&, SkImageStageUpdater* = nullptr) const; SkFilterQuality resolveFiltering(SkFilterQuality paintQuality) const { - return fFiltering == kInheritFromPaint ? paintQuality : (SkFilterQuality)fFiltering; + switch (fFilterEnum) { + case kUseFilterOptions: return kNone_SkFilterQuality; // TODO + case kInheritFromPaint: return paintQuality; + default: break; + } + return (SkFilterQuality)fFilterEnum; } sk_sp fImage; const SkTileMode fTileModeX; const SkTileMode fTileModeY; - const FilterEnum fFiltering; + const FilterEnum fFilterEnum; const bool fClampAsIfUnpremul; + // only use this if fFilterEnum == kUseFilterOptions + SkFilterOptions fFilterOptions; + friend class SkShaderBase; typedef SkShaderBase INHERITED; };