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:
parent
9f821489c9
commit
f8a6b5b4b0
64
bench/FilteringBench.cpp
Normal file
64
bench/FilteringBench.cpp
Normal 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}); )
|
@ -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; )
|
||||
|
@ -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",
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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).
|
||||
|
@ -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");
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user