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 <reed@google.com>
Reviewed-by: Mike Klein <mtklein@google.com>
This commit is contained in:
Mike Reed 2020-07-10 08:36:42 -04:00 committed by Skia Commit-Bot
parent 9f821489c9
commit f8a6b5b4b0
12 changed files with 538 additions and 125 deletions

64
bench/FilteringBench.cpp Normal file
View File

@ -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<SkShader> 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}); )

View File

@ -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<SkImage> 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; )

View File

@ -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",

View File

@ -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<SkShader> 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.

View File

@ -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;
}
}
}
}
}

View File

@ -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<const SkMipMap> fCurrMip;
};
#endif

View File

@ -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;
}

View File

@ -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).

View File

@ -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");

View File

@ -127,6 +127,13 @@ sk_sp<SkShader> SkImage::makeShader(SkTileMode tmx, SkTileMode tmy,
SkImageShader::kInheritFromPaint);
}
sk_sp<SkShader> SkImage::makeShader(SkTileMode tmx, SkTileMode tmy,
const SkFilterOptions& options,
const SkMatrix* localMatrix) const {
return SkImageShader::Make(sk_ref_sp(const_cast<SkImage*>(this)), tmx, tmy,
options, localMatrix);
}
sk_sp<SkShader> SkImage::makeShader(SkTileMode tmx, SkTileMode tmy,
const SkMatrix* localMatrix, SkFilterQuality filtering) const {
return SkImageShader::Make(sk_ref_sp(const_cast<SkImage*>(this)), tmx, tmy, localMatrix,

View File

@ -45,8 +45,24 @@ SkImageShader::SkImageShader(sk_sp<SkImage> 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<SkImage> 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<SkFlattenable> SkImageShader::CreateProc(SkReadBuffer& buffer) {
auto tmx = buffer.read32LE<SkTileMode>(SkTileMode::kLastTileMode);
auto tmy = buffer.read32LE<SkTileMode>(SkTileMode::kLastTileMode);
FilterEnum filtering = kInheritFromPaint;
if (!buffer.isVersionLT(SkPicturePriv::kFilteringInImageShader_Version)) {
filtering = buffer.read32LE<FilterEnum>(kInheritFromPaint);
FilterEnum fe = kInheritFromPaint;
if (!buffer.isVersionLT(SkPicturePriv::kFilterEnumInImageShader_Version)) {
fe = buffer.read32LE<FilterEnum>(kUseFilterOptions);
}
SkFilterOptions fo = { SkSamplingMode::kNearest, SkMipmapMode::kNone };
if (!buffer.isVersionLT(SkPicturePriv::kFilterOptionsInImageShader_Version)) {
fo.fSampling = buffer.read32LE<SkSamplingMode>(SkSamplingMode::kLinear);
fo.fMipmap = buffer.read32LE<SkMipmapMode>(SkMipmapMode::kLinear);
}
SkMatrix localMatrix;
@ -68,13 +90,17 @@ sk_sp<SkFlattenable> 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<SkShader> SkImageShader::Make(sk_sp<SkImage> image,
};
}
sk_sp<SkShader> SkImageShader::Make(sk_sp<SkImage> image,
SkTileMode tmx, SkTileMode tmy,
const SkFilterOptions& options,
const SkMatrix* localMatrix) {
if (!image) {
return sk_make_sp<SkEmptyShader>();
}
return sk_sp<SkShader>{
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<SkMipmapAccessor>(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);

View File

@ -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<SkShader> Make(sk_sp<SkImage>,
@ -33,6 +35,12 @@ public:
FilterEnum,
bool clampAsIfUnpremul = false);
static sk_sp<SkShader> Make(sk_sp<SkImage>,
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<SkImage>,
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<SkImage> 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;
};