• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2015 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #include "src/shaders/SkImageShader.h"
9 
10 #include "src/base/SkArenaAlloc.h"
11 #include "src/core/SkColorSpacePriv.h"
12 #include "src/core/SkColorSpaceXformSteps.h"
13 #include "src/core/SkImageInfoPriv.h"
14 #include "src/core/SkMatrixPriv.h"
15 #include "src/core/SkMatrixProvider.h"
16 #include "src/core/SkMipmapAccessor.h"
17 #include "src/core/SkOpts.h"
18 #include "src/core/SkRasterPipeline.h"
19 #include "src/core/SkReadBuffer.h"
20 #include "src/core/SkVM.h"
21 #include "src/core/SkWriteBuffer.h"
22 #include "src/image/SkImage_Base.h"
23 #include "src/shaders/SkBitmapProcShader.h"
24 #include "src/shaders/SkLocalMatrixShader.h"
25 #include "src/shaders/SkTransformShader.h"
26 
27 #if defined(SK_GRAPHITE)
28 #include "src/gpu/graphite/ImageUtils.h"
29 #include "src/gpu/graphite/Image_Graphite.h"
30 #include "src/gpu/graphite/KeyContext.h"
31 #include "src/gpu/graphite/KeyHelpers.h"
32 #include "src/gpu/graphite/PaintParamsKey.h"
33 #include "src/gpu/graphite/ReadWriteSwizzle.h"
34 #include "src/gpu/graphite/TextureProxyView.h"
35 
36 
swizzle_class_to_read_enum(const skgpu::Swizzle & swizzle)37 static skgpu::graphite::ReadSwizzle swizzle_class_to_read_enum(const skgpu::Swizzle& swizzle) {
38     if (swizzle == skgpu::Swizzle::RGBA()) {
39         return skgpu::graphite::ReadSwizzle::kRGBA;
40     } else if (swizzle == skgpu::Swizzle::RGB1()) {
41         return skgpu::graphite::ReadSwizzle::kRGB1;
42     } else if (swizzle == skgpu::Swizzle("rrrr")) {
43         return skgpu::graphite::ReadSwizzle::kRRRR;
44     } else if (swizzle == skgpu::Swizzle("rrr1")) {
45         return skgpu::graphite::ReadSwizzle::kRRR1;
46     } else if (swizzle == skgpu::Swizzle::BGRA()) {
47         return skgpu::graphite::ReadSwizzle::kBGRA;
48     } else {
49         SkDebugf("Encountered unsupported read swizzle. Defaulting to RGBA.");
50         return skgpu::graphite::ReadSwizzle::kRGBA;
51     }
52 }
53 #endif
54 
CubicResamplerMatrix(float B,float C)55 SkM44 SkImageShader::CubicResamplerMatrix(float B, float C) {
56 #if 0
57     constexpr SkM44 kMitchell = SkM44( 1.f/18.f, -9.f/18.f,  15.f/18.f,  -7.f/18.f,
58                                       16.f/18.f,  0.f/18.f, -36.f/18.f,  21.f/18.f,
59                                        1.f/18.f,  9.f/18.f,  27.f/18.f, -21.f/18.f,
60                                        0.f/18.f,  0.f/18.f,  -6.f/18.f,   7.f/18.f);
61 
62     constexpr SkM44 kCatmull = SkM44(0.0f, -0.5f,  1.0f, -0.5f,
63                                      1.0f,  0.0f, -2.5f,  1.5f,
64                                      0.0f,  0.5f,  2.0f, -1.5f,
65                                      0.0f,  0.0f, -0.5f,  0.5f);
66 
67     if (B == 1.0f/3 && C == 1.0f/3) {
68         return kMitchell;
69     }
70     if (B == 0 && C == 0.5f) {
71         return kCatmull;
72     }
73 #endif
74     return SkM44(    (1.f/6)*B, -(3.f/6)*B - C,       (3.f/6)*B + 2*C,    - (1.f/6)*B - C,
75                  1 - (2.f/6)*B,              0, -3 + (12.f/6)*B +   C,  2 - (9.f/6)*B - C,
76                      (1.f/6)*B,  (3.f/6)*B + C,  3 - (15.f/6)*B - 2*C, -2 + (9.f/6)*B + C,
77                              0,              0,                    -C,      (1.f/6)*B + C);
78 }
79 
80 /**
81  *  We are faster in clamp, so always use that tiling when we can.
82  */
optimize(SkTileMode tm,int dimension)83 static SkTileMode optimize(SkTileMode tm, int dimension) {
84     SkASSERT(dimension > 0);
85 #ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
86     // need to update frameworks/base/libs/hwui/tests/unit/SkiaBehaviorTests.cpp:55 to allow
87     // for transforming to clamp.
88     return tm;
89 #else
90     // mirror and repeat on a 1px axis are the same as clamping, but decal will still transition to
91     // transparent black.
92     return (tm != SkTileMode::kDecal && dimension == 1) ? SkTileMode::kClamp : tm;
93 #endif
94 }
95 
96 // TODO: currently this only *always* used in asFragmentProcessor(), which is excluded on no-gpu
97 // builds. No-gpu builds only use needs_subset() in asserts, so release+no-gpu doesn't use it, which
98 // can cause builds to fail if unused warnings are treated as errors.
needs_subset(SkImage * img,const SkRect & subset)99 [[maybe_unused]] static bool needs_subset(SkImage* img, const SkRect& subset) {
100     return subset != SkRect::Make(img->dimensions());
101 }
102 
SkImageShader(sk_sp<SkImage> img,const SkRect & subset,SkTileMode tmx,SkTileMode tmy,const SkSamplingOptions & sampling,bool raw,bool clampAsIfUnpremul)103 SkImageShader::SkImageShader(sk_sp<SkImage> img,
104                              const SkRect& subset,
105                              SkTileMode tmx, SkTileMode tmy,
106                              const SkSamplingOptions& sampling,
107                              bool raw,
108                              bool clampAsIfUnpremul)
109         : fImage(std::move(img))
110         , fSampling(sampling)
111         , fTileModeX(optimize(tmx, fImage->width()))
112         , fTileModeY(optimize(tmy, fImage->height()))
113         , fSubset(subset)
114         , fRaw(raw)
115         , fClampAsIfUnpremul(clampAsIfUnpremul) {
116     // These options should never appear together:
117     SkASSERT(!fRaw || !fClampAsIfUnpremul);
118 
119     // Bicubic filtering of raw image shaders would add a surprising clamp - so we don't support it
120     SkASSERT(!fRaw || !fSampling.useCubic);
121 }
122 
123 // just used for legacy-unflattening
124 enum class LegacyFilterEnum {
125     kNone,
126     kLow,
127     kMedium,
128     kHigh,
129     // this is the special value for backward compatibility
130     kInheritFromPaint,
131     // this signals we should use the new SkFilterOptions
132     kUseFilterOptions,
133     // use cubic and ignore FilterOptions
134     kUseCubicResampler,
135 
136     kLast = kUseCubicResampler,
137 };
138 
139 // fClampAsIfUnpremul is always false when constructed through public APIs,
140 // so there's no need to read or write it here.
141 
CreateProc(SkReadBuffer & buffer)142 sk_sp<SkFlattenable> SkImageShader::CreateProc(SkReadBuffer& buffer) {
143     auto tmx = buffer.read32LE<SkTileMode>(SkTileMode::kLastTileMode);
144     auto tmy = buffer.read32LE<SkTileMode>(SkTileMode::kLastTileMode);
145 
146     SkSamplingOptions sampling;
147     bool readSampling = true;
148     if (buffer.isVersionLT(SkPicturePriv::kNoFilterQualityShaders_Version) &&
149         !buffer.readBool() /* legacy has_sampling */)
150     {
151         readSampling = false;
152         // we just default to Nearest in sampling
153     }
154     if (readSampling) {
155         sampling = buffer.readSampling();
156     }
157 
158     SkMatrix localMatrix;
159     if (buffer.isVersionLT(SkPicturePriv::Version::kNoShaderLocalMatrix)) {
160         buffer.readMatrix(&localMatrix);
161     }
162     sk_sp<SkImage> img = buffer.readImage();
163     if (!img) {
164         return nullptr;
165     }
166 
167     bool raw = buffer.isVersionLT(SkPicturePriv::Version::kRawImageShaders) ? false
168                                                                             : buffer.readBool();
169 
170     // TODO(skbug.com/12784): Subset is not serialized yet; it's only used by special images so it
171     // will never be written to an SKP.
172 
173     return raw ? SkImageShader::MakeRaw(std::move(img), tmx, tmy, sampling, &localMatrix)
174                : SkImageShader::Make(std::move(img), tmx, tmy, sampling, &localMatrix);
175 }
176 
flatten(SkWriteBuffer & buffer) const177 void SkImageShader::flatten(SkWriteBuffer& buffer) const {
178     buffer.writeUInt((unsigned)fTileModeX);
179     buffer.writeUInt((unsigned)fTileModeY);
180 
181     buffer.writeSampling(fSampling);
182 
183     buffer.writeImage(fImage.get());
184     SkASSERT(fClampAsIfUnpremul == false);
185 
186     // TODO(skbug.com/12784): Subset is not serialized yet; it's only used by special images so it
187     // will never be written to an SKP.
188     SkASSERT(!needs_subset(fImage.get(), fSubset));
189 
190     buffer.writeBool(fRaw);
191 }
192 
isOpaque() const193 bool SkImageShader::isOpaque() const {
194     return fImage->isOpaque() &&
195            fTileModeX != SkTileMode::kDecal && fTileModeY != SkTileMode::kDecal;
196 }
197 
198 #ifdef SK_ENABLE_LEGACY_SHADERCONTEXT
199 
legacy_shader_can_handle(const SkMatrix & inv)200 static bool legacy_shader_can_handle(const SkMatrix& inv) {
201     SkASSERT(!inv.hasPerspective());
202 
203     // Scale+translate methods are always present, but affine might not be.
204     if (!SkOpts::S32_alpha_D32_filter_DXDY && !inv.isScaleTranslate()) {
205         return false;
206     }
207 
208     // legacy code uses SkFixed 32.32, so ensure the inverse doesn't map device coordinates
209     // out of range.
210     const SkScalar max_dev_coord = 32767.0f;
211     const SkRect src = inv.mapRect(SkRect::MakeWH(max_dev_coord, max_dev_coord));
212 
213     // take 1/4 of max signed 32bits so we have room to subtract local values
214     const SkScalar max_fixed32dot32 = float(SK_MaxS32) * 0.25f;
215     if (!SkRect::MakeLTRB(-max_fixed32dot32, -max_fixed32dot32,
216                           +max_fixed32dot32, +max_fixed32dot32).contains(src)) {
217         return false;
218     }
219 
220     // legacy shader impl should be able to handle these matrices
221     return true;
222 }
223 
onMakeContext(const ContextRec & rec,SkArenaAlloc * alloc) const224 SkShaderBase::Context* SkImageShader::onMakeContext(const ContextRec& rec,
225                                                     SkArenaAlloc* alloc) const {
226     SkASSERT(!needs_subset(fImage.get(), fSubset)); // TODO(skbug.com/12784)
227     if (fImage->alphaType() == kUnpremul_SkAlphaType) {
228         return nullptr;
229     }
230     if (fImage->colorType() != kN32_SkColorType) {
231         return nullptr;
232     }
233     if (fTileModeX != fTileModeY) {
234         return nullptr;
235     }
236     if (fTileModeX == SkTileMode::kDecal || fTileModeY == SkTileMode::kDecal) {
237         return nullptr;
238     }
239 
240     SkSamplingOptions sampling = fSampling;
241     if (sampling.isAniso()) {
242         sampling = SkSamplingPriv::AnisoFallback(fImage->hasMipmaps());
243     }
244 
245     auto supported = [](const SkSamplingOptions& sampling) {
246         const std::tuple<SkFilterMode,SkMipmapMode> supported[] = {
247             {SkFilterMode::kNearest, SkMipmapMode::kNone},    // legacy None
248             {SkFilterMode::kLinear,  SkMipmapMode::kNone},    // legacy Low
249             {SkFilterMode::kLinear,  SkMipmapMode::kNearest}, // legacy Medium
250         };
251         for (auto [f, m] : supported) {
252             if (sampling.filter == f && sampling.mipmap == m) {
253                 return true;
254             }
255         }
256         return false;
257     };
258     if (sampling.useCubic || !supported(sampling)) {
259         return nullptr;
260     }
261 
262     // SkBitmapProcShader stores bitmap coordinates in a 16bit buffer,
263     // so it can't handle bitmaps larger than 65535.
264     //
265     // We back off another bit to 32767 to make small amounts of
266     // intermediate math safe, e.g. in
267     //
268     //     SkFixed fx = ...;
269     //     fx = tile(fx + SK_Fixed1);
270     //
271     // we want to make sure (fx + SK_Fixed1) never overflows.
272     if (fImage-> width() > 32767 ||
273         fImage->height() > 32767) {
274         return nullptr;
275     }
276 
277     SkMatrix inv;
278     if (!this->computeTotalInverse(*rec.fMatrix, rec.fLocalMatrix, &inv) ||
279         !legacy_shader_can_handle(inv)) {
280         return nullptr;
281     }
282 
283     if (!rec.isLegacyCompatible(fImage->colorSpace())) {
284         return nullptr;
285     }
286 
287     return SkBitmapProcLegacyShader::MakeContext(*this, fTileModeX, fTileModeY, sampling,
288                                                  as_IB(fImage.get()), rec, alloc);
289 }
290 #endif
291 
onIsAImage(SkMatrix * texM,SkTileMode xy[]) const292 SkImage* SkImageShader::onIsAImage(SkMatrix* texM, SkTileMode xy[]) const {
293     if (texM) {
294         *texM = SkMatrix::I();
295     }
296     if (xy) {
297         xy[0] = fTileModeX;
298         xy[1] = fTileModeY;
299     }
300     return const_cast<SkImage*>(fImage.get());
301 }
302 
Make(sk_sp<SkImage> image,SkTileMode tmx,SkTileMode tmy,const SkSamplingOptions & options,const SkMatrix * localMatrix,bool clampAsIfUnpremul)303 sk_sp<SkShader> SkImageShader::Make(sk_sp<SkImage> image,
304                                     SkTileMode tmx, SkTileMode tmy,
305                                     const SkSamplingOptions& options,
306                                     const SkMatrix* localMatrix,
307                                     bool clampAsIfUnpremul) {
308     SkRect subset = image ? SkRect::Make(image->dimensions()) : SkRect::MakeEmpty();
309     return MakeSubset(std::move(image), subset, tmx, tmy, options, localMatrix, clampAsIfUnpremul);
310 }
311 
MakeRaw(sk_sp<SkImage> image,SkTileMode tmx,SkTileMode tmy,const SkSamplingOptions & options,const SkMatrix * localMatrix)312 sk_sp<SkShader> SkImageShader::MakeRaw(sk_sp<SkImage> image,
313                                        SkTileMode tmx, SkTileMode tmy,
314                                        const SkSamplingOptions& options,
315                                        const SkMatrix* localMatrix) {
316     if (options.useCubic) {
317         return nullptr;
318     }
319     if (!image) {
320         return SkShaders::Empty();
321     }
322     auto subset = SkRect::Make(image->dimensions());
323     return SkLocalMatrixShader::MakeWrapped<SkImageShader>(localMatrix,
324                                                            image,
325                                                            subset,
326                                                            tmx, tmy,
327                                                            options,
328                                                            /*raw=*/true,
329                                                            /*clampAsIfUnpremul=*/false);
330 }
331 
MakeSubset(sk_sp<SkImage> image,const SkRect & subset,SkTileMode tmx,SkTileMode tmy,const SkSamplingOptions & options,const SkMatrix * localMatrix,bool clampAsIfUnpremul)332 sk_sp<SkShader> SkImageShader::MakeSubset(sk_sp<SkImage> image,
333                                           const SkRect& subset,
334                                           SkTileMode tmx, SkTileMode tmy,
335                                           const SkSamplingOptions& options,
336                                           const SkMatrix* localMatrix,
337                                           bool clampAsIfUnpremul) {
338     auto is_unit = [](float x) {
339         return x >= 0 && x <= 1;
340     };
341     if (options.useCubic) {
342         if (!is_unit(options.cubic.B) || !is_unit(options.cubic.C)) {
343             return nullptr;
344         }
345     }
346     if (!image || subset.isEmpty()) {
347         return SkShaders::Empty();
348     }
349 
350     // Validate subset and check if we can drop it
351     if (!SkRect::Make(image->bounds()).contains(subset)) {
352         return nullptr;
353     }
354     // TODO(skbug.com/12784): GPU-only for now since it's only supported in onAsFragmentProcessor()
355     SkASSERT(!needs_subset(image.get(), subset) || image->isTextureBacked());
356     return SkLocalMatrixShader::MakeWrapped<SkImageShader>(localMatrix,
357                                                            std::move(image),
358                                                            subset,
359                                                            tmx, tmy,
360                                                            options,
361                                                            /*raw=*/false,
362                                                            clampAsIfUnpremul);
363 }
364 
365 ///////////////////////////////////////////////////////////////////////////////////////////////////
366 
367 #if defined(SK_GANESH)
368 
369 #include "src/gpu/ganesh/GrColorInfo.h"
370 #include "src/gpu/ganesh/GrFPArgs.h"
371 #include "src/gpu/ganesh/effects/GrBlendFragmentProcessor.h"
372 
373 std::unique_ptr<GrFragmentProcessor>
asFragmentProcessor(const GrFPArgs & args,const MatrixRec & mRec) const374 SkImageShader::asFragmentProcessor(const GrFPArgs& args, const MatrixRec& mRec) const {
375     SkTileMode tileModes[2] = {fTileModeX, fTileModeY};
376     const SkRect* subset = needs_subset(fImage.get(), fSubset) ? &fSubset : nullptr;
377     auto fp = as_IB(fImage.get())->asFragmentProcessor(args.fContext,
378                                                        fSampling,
379                                                        tileModes,
380                                                        SkMatrix::I(),
381                                                        subset);
382     if (!fp) {
383         return nullptr;
384     }
385 
386     bool success;
387     std::tie(success, fp) = mRec.apply(std::move(fp));
388     if (!success) {
389         return nullptr;
390     }
391 
392     if (!fRaw) {
393         fp = GrColorSpaceXformEffect::Make(std::move(fp),
394                                            fImage->colorSpace(),
395                                            fImage->alphaType(),
396                                            args.fDstColorInfo->colorSpace(),
397                                            kPremul_SkAlphaType);
398 
399         if (fImage->isAlphaOnly()) {
400             fp = GrBlendFragmentProcessor::Make<SkBlendMode::kDstIn>(std::move(fp), nullptr);
401         }
402     }
403 
404     return fp;
405 }
406 
407 #endif
408 
409 #if defined(SK_GRAPHITE)
addToKey(const skgpu::graphite::KeyContext & keyContext,skgpu::graphite::PaintParamsKeyBuilder * builder,skgpu::graphite::PipelineDataGatherer * gatherer) const410 void SkImageShader::addToKey(const skgpu::graphite::KeyContext& keyContext,
411                              skgpu::graphite::PaintParamsKeyBuilder* builder,
412                              skgpu::graphite::PipelineDataGatherer* gatherer) const {
413     using namespace skgpu::graphite;
414 
415     ImageShaderBlock::ImageData imgData(fSampling, fTileModeX, fTileModeY, fSubset,
416                                         ReadSwizzle::kRGBA);
417 
418     if (!fRaw) {
419         imgData.fSteps = SkColorSpaceXformSteps(fImage->colorSpace(),
420                                                 fImage->alphaType(),
421                                                 keyContext.dstColorInfo().colorSpace(),
422                                                 keyContext.dstColorInfo().alphaType());
423 
424         // TODO: add alpha-only image handling here
425     }
426 
427     auto [ imageToDraw, newSampling ] = skgpu::graphite::GetGraphiteBacked(keyContext.recorder(),
428                                                                            fImage.get(),
429                                                                            fSampling);
430 
431     if (imageToDraw) {
432         imgData.fSampling = newSampling;
433         skgpu::Mipmapped mipmapped = (newSampling.mipmap != SkMipmapMode::kNone)
434                                          ? skgpu::Mipmapped::kYes : skgpu::Mipmapped::kNo;
435 
436         auto [view, _] = as_IB(imageToDraw)->asView(keyContext.recorder(), mipmapped);
437         imgData.fTextureProxy = view.refProxy();
438         imgData.fReadSwizzle = swizzle_class_to_read_enum(view.swizzle());
439     }
440 
441     ImageShaderBlock::BeginBlock(keyContext, builder, gatherer, &imgData);
442     builder->endBlock();
443 }
444 #endif
445 
446 ///////////////////////////////////////////////////////////////////////////////////////////////////
447 #include "src/core/SkImagePriv.h"
448 
SkMakeBitmapShaderForPaint(const SkPaint & paint,const SkBitmap & src,SkTileMode tmx,SkTileMode tmy,const SkSamplingOptions & sampling,const SkMatrix * localMatrix,SkCopyPixelsMode mode)449 sk_sp<SkShader> SkMakeBitmapShaderForPaint(const SkPaint& paint, const SkBitmap& src,
450                                            SkTileMode tmx, SkTileMode tmy,
451                                            const SkSamplingOptions& sampling,
452                                            const SkMatrix* localMatrix, SkCopyPixelsMode mode) {
453     auto s = SkImageShader::Make(SkMakeImageFromRasterBitmap(src, mode),
454                                  tmx, tmy, sampling, localMatrix);
455     if (!s) {
456         return nullptr;
457     }
458     if (SkColorTypeIsAlphaOnly(src.colorType()) && paint.getShader()) {
459         // Compose the image shader with the paint's shader. Alpha images+shaders should output the
460         // texture's alpha multiplied by the shader's color. DstIn (d*sa) will achieve this with
461         // the source image and dst shader (MakeBlend takes dst first, src second).
462         s = SkShaders::Blend(SkBlendMode::kDstIn, paint.refShader(), std::move(s));
463     }
464     return s;
465 }
466 
RegisterFlattenables()467 void SkShaderBase::RegisterFlattenables() { SK_REGISTER_FLATTENABLE(SkImageShader); }
468 
469 namespace {
470 
471 struct MipLevelHelper {
472     SkPixmap pm;
473     SkMatrix inv;
474     SkRasterPipeline_GatherCtx* gather;
475     SkRasterPipeline_TileCtx* limitX;
476     SkRasterPipeline_TileCtx* limitY;
477     SkRasterPipeline_DecalTileCtx* decalCtx = nullptr;
478 
allocAndInit__anon9b340be60311::MipLevelHelper479     void allocAndInit(SkArenaAlloc* alloc,
480                       const SkSamplingOptions& sampling,
481                       SkTileMode tileModeX,
482                       SkTileMode tileModeY) {
483         gather = alloc->make<SkRasterPipeline_GatherCtx>();
484         gather->pixels = pm.addr();
485         gather->stride = pm.rowBytesAsPixels();
486         gather->width = pm.width();
487         gather->height = pm.height();
488 
489         if (sampling.useCubic) {
490             SkImageShader::CubicResamplerMatrix(sampling.cubic.B, sampling.cubic.C)
491                     .getColMajor(gather->weights);
492         }
493 
494         limitX = alloc->make<SkRasterPipeline_TileCtx>();
495         limitY = alloc->make<SkRasterPipeline_TileCtx>();
496         limitX->scale = pm.width();
497         limitX->invScale = 1.0f / pm.width();
498         limitY->scale = pm.height();
499         limitY->invScale = 1.0f / pm.height();
500 
501         // We would like an image that is mapped 1:1 with device pixels but at a half pixel offset
502         // to select every pixel from the src image once. Our rasterizer biases upward. That is a
503         // rect from 0.5...1.5 fills pixel 1 and not pixel 0. So we make exact integer pixel sample
504         // values select the pixel to the left/above the integer value.
505         //
506         // Note that a mirror mapping between canvas and image space will not have this property -
507         // on one side of the image a row/column will be skipped and one repeated on the other side.
508         //
509         // The GM nearest_half_pixel_image tests both of the above scenarios.
510         //
511         // The implementation of SkTileMode::kMirror also modifies integer pixel snapping to create
512         // consistency when the sample coords are running backwards and must account for gather
513         // modification we perform here. The GM mirror_tile tests this.
514         if (!sampling.useCubic && sampling.filter == SkFilterMode::kNearest) {
515             gather->roundDownAtInteger = true;
516             limitX->mirrorBiasDir = limitY->mirrorBiasDir = 1;
517         }
518 
519         if (tileModeX == SkTileMode::kDecal || tileModeY == SkTileMode::kDecal) {
520             decalCtx = alloc->make<SkRasterPipeline_DecalTileCtx>();
521             decalCtx->limit_x = limitX->scale;
522             decalCtx->limit_y = limitY->scale;
523 
524             // When integer sample coords snap left/up then we want the right/bottom edge of the
525             // image bounds to be inside the image rather than the left/top edge, that is (0, w]
526             // rather than [0, w).
527             if (gather->roundDownAtInteger) {
528                 decalCtx->inclusiveEdge_x = decalCtx->limit_x;
529                 decalCtx->inclusiveEdge_y = decalCtx->limit_y;
530             }
531         }
532     }
533 };
534 
535 }  // namespace
536 
tweak_sampling(SkSamplingOptions sampling,const SkMatrix & matrix)537 static SkSamplingOptions tweak_sampling(SkSamplingOptions sampling, const SkMatrix& matrix) {
538     SkFilterMode filter = sampling.filter;
539 
540     // When the matrix is just an integer translate, bilerp == nearest neighbor.
541     if (filter == SkFilterMode::kLinear &&
542             matrix.getType() <= SkMatrix::kTranslate_Mask &&
543             matrix.getTranslateX() == (int)matrix.getTranslateX() &&
544             matrix.getTranslateY() == (int)matrix.getTranslateY()) {
545         filter = SkFilterMode::kNearest;
546     }
547 
548     return SkSamplingOptions(filter, sampling.mipmap);
549 }
550 
appendStages(const SkStageRec & rec,const MatrixRec & mRec) const551 bool SkImageShader::appendStages(const SkStageRec& rec, const MatrixRec& mRec) const {
552     SkASSERT(!needs_subset(fImage.get(), fSubset));  // TODO(skbug.com/12784)
553 
554     // We only support certain sampling options in stages so far
555     auto sampling = fSampling;
556     if (sampling.isAniso()) {
557         sampling = SkSamplingPriv::AnisoFallback(fImage->hasMipmaps());
558     }
559 
560     SkRasterPipeline* p = rec.fPipeline;
561     SkArenaAlloc* alloc = rec.fAlloc;
562 
563     SkMatrix baseInv;
564     // If the total matrix isn't valid then we will always access the base MIP level.
565     if (mRec.totalMatrixIsValid()) {
566         if (!mRec.totalInverse(&baseInv)) {
567             return false;
568         }
569         baseInv.normalizePerspective();
570     }
571 
572     SkASSERT(!sampling.useCubic || sampling.mipmap == SkMipmapMode::kNone);
573     auto* access = SkMipmapAccessor::Make(alloc, fImage.get(), baseInv, sampling.mipmap);
574     if (!access) {
575         return false;
576     }
577 
578     MipLevelHelper upper;
579     std::tie(upper.pm, upper.inv) = access->level();
580 
581     if (!sampling.useCubic) {
582         // TODO: can tweak_sampling sometimes for cubic too when B=0
583         if (mRec.totalMatrixIsValid()) {
584             sampling = tweak_sampling(sampling, SkMatrix::Concat(upper.inv, baseInv));
585         }
586     }
587 
588     if (!mRec.apply(rec, upper.inv)) {
589         return false;
590     }
591 
592     upper.allocAndInit(alloc, sampling, fTileModeX, fTileModeY);
593 
594     MipLevelHelper lower;
595     SkRasterPipeline_MipmapCtx* mipmapCtx = nullptr;
596     float lowerWeight = access->lowerWeight();
597     if (lowerWeight > 0) {
598         std::tie(lower.pm, lower.inv) = access->lowerLevel();
599         mipmapCtx = alloc->make<SkRasterPipeline_MipmapCtx>();
600         mipmapCtx->lowerWeight = lowerWeight;
601         mipmapCtx->scaleX = static_cast<float>(lower.pm.width()) / upper.pm.width();
602         mipmapCtx->scaleY = static_cast<float>(lower.pm.height()) / upper.pm.height();
603 
604         lower.allocAndInit(alloc, sampling, fTileModeX, fTileModeY);
605 
606         p->append(SkRasterPipelineOp::mipmap_linear_init, mipmapCtx);
607     }
608 
609     const bool decalBothAxes = fTileModeX == SkTileMode::kDecal && fTileModeY == SkTileMode::kDecal;
610 
611     auto append_tiling_and_gather = [&](const MipLevelHelper* level) {
612         if (decalBothAxes) {
613             p->append(SkRasterPipelineOp::decal_x_and_y,  level->decalCtx);
614         } else {
615             switch (fTileModeX) {
616                 case SkTileMode::kClamp: /* The gather_xxx stage will clamp for us. */
617                     break;
618                 case SkTileMode::kMirror:
619                     p->append(SkRasterPipelineOp::mirror_x, level->limitX);
620                     break;
621                 case SkTileMode::kRepeat:
622                     p->append(SkRasterPipelineOp::repeat_x, level->limitX);
623                     break;
624                 case SkTileMode::kDecal:
625                     p->append(SkRasterPipelineOp::decal_x, level->decalCtx);
626                     break;
627             }
628             switch (fTileModeY) {
629                 case SkTileMode::kClamp: /* The gather_xxx stage will clamp for us. */
630                     break;
631                 case SkTileMode::kMirror:
632                     p->append(SkRasterPipelineOp::mirror_y, level->limitY);
633                     break;
634                 case SkTileMode::kRepeat:
635                     p->append(SkRasterPipelineOp::repeat_y, level->limitY);
636                     break;
637                 case SkTileMode::kDecal:
638                     p->append(SkRasterPipelineOp::decal_y, level->decalCtx);
639                     break;
640             }
641         }
642 
643         void* ctx = level->gather;
644         switch (level->pm.colorType()) {
645             case kAlpha_8_SkColorType:      p->append(SkRasterPipelineOp::gather_a8,    ctx); break;
646             case kA16_unorm_SkColorType:    p->append(SkRasterPipelineOp::gather_a16,   ctx); break;
647             case kA16_float_SkColorType:    p->append(SkRasterPipelineOp::gather_af16,  ctx); break;
648             case kRGB_565_SkColorType:      p->append(SkRasterPipelineOp::gather_565,   ctx); break;
649             case kARGB_4444_SkColorType:    p->append(SkRasterPipelineOp::gather_4444,  ctx); break;
650             case kR8G8_unorm_SkColorType:   p->append(SkRasterPipelineOp::gather_rg88,  ctx); break;
651             case kR16G16_unorm_SkColorType: p->append(SkRasterPipelineOp::gather_rg1616,ctx); break;
652             case kR16G16_float_SkColorType: p->append(SkRasterPipelineOp::gather_rgf16, ctx); break;
653             case kRGBA_8888_SkColorType:    p->append(SkRasterPipelineOp::gather_8888,  ctx); break;
654 
655             case kRGBA_1010102_SkColorType:
656                 p->append(SkRasterPipelineOp::gather_1010102, ctx);
657                 break;
658 
659             case kR16G16B16A16_unorm_SkColorType:
660                 p->append(SkRasterPipelineOp::gather_16161616, ctx);
661                 break;
662 
663             case kRGBA_F16Norm_SkColorType:
664             case kRGBA_F16_SkColorType:     p->append(SkRasterPipelineOp::gather_f16,   ctx); break;
665             case kRGBA_F32_SkColorType:     p->append(SkRasterPipelineOp::gather_f32,   ctx); break;
666 
667             case kGray_8_SkColorType:       p->append(SkRasterPipelineOp::gather_a8,    ctx);
668                                             p->append(SkRasterPipelineOp::alpha_to_gray    ); break;
669 
670             case kR8_unorm_SkColorType:     p->append(SkRasterPipelineOp::gather_a8,    ctx);
671                                             p->append(SkRasterPipelineOp::alpha_to_red     ); break;
672 
673             case kRGB_888x_SkColorType:     p->append(SkRasterPipelineOp::gather_8888,  ctx);
674                                             p->append(SkRasterPipelineOp::force_opaque     ); break;
675 
676             case kBGRA_1010102_SkColorType:
677                 p->append(SkRasterPipelineOp::gather_1010102, ctx);
678                 p->append(SkRasterPipelineOp::swap_rb);
679                 break;
680 
681             case kRGB_101010x_SkColorType:
682                 p->append(SkRasterPipelineOp::gather_1010102, ctx);
683                 p->append(SkRasterPipelineOp::force_opaque);
684                 break;
685 
686             case kBGR_101010x_XR_SkColorType:
687                 SkASSERT(false);
688                 break;
689 
690             case kBGR_101010x_SkColorType:
691                 p->append(SkRasterPipelineOp::gather_1010102, ctx);
692                 p->append(SkRasterPipelineOp::force_opaque);
693                 p->append(SkRasterPipelineOp::swap_rb);
694                 break;
695 
696             case kBGRA_8888_SkColorType:
697                 p->append(SkRasterPipelineOp::gather_8888, ctx);
698                 p->append(SkRasterPipelineOp::swap_rb);
699                 break;
700 
701             case kSRGBA_8888_SkColorType:
702                 p->append(SkRasterPipelineOp::gather_8888, ctx);
703                 p->append_transfer_function(*skcms_sRGB_TransferFunction());
704                 break;
705 
706             case kUnknown_SkColorType: SkASSERT(false);
707         }
708         if (level->decalCtx) {
709             p->append(SkRasterPipelineOp::check_decal_mask, level->decalCtx);
710         }
711     };
712 
713     auto append_misc = [&] {
714         SkColorSpace* cs = upper.pm.colorSpace();
715         SkAlphaType   at = upper.pm.alphaType();
716 
717         // Color for alpha-only images comes from the paint.
718         if (SkColorTypeIsAlphaOnly(upper.pm.colorType()) && !fRaw) {
719             SkColor4f rgb = rec.fPaint.getColor4f();
720             p->append_set_rgb(alloc, rgb);
721 
722             cs = sk_srgb_singleton();
723             at = kUnpremul_SkAlphaType;
724         }
725 
726         // Bicubic filtering naturally produces out of range values on both sides of [0,1].
727         if (sampling.useCubic) {
728             p->append(at == kUnpremul_SkAlphaType || fClampAsIfUnpremul
729                           ? SkRasterPipelineOp::clamp_01
730                           : SkRasterPipelineOp::clamp_gamut);
731         }
732 
733         // Transform color space and alpha type to match shader convention (dst CS, premul alpha).
734         if (!fRaw) {
735             alloc->make<SkColorSpaceXformSteps>(cs, at, rec.fDstCS, kPremul_SkAlphaType)->apply(p);
736         }
737 
738         return true;
739     };
740 
741     // Check for fast-path stages.
742     // TODO: Could we use the fast-path stages for each level when doing linear mipmap filtering?
743     SkColorType ct = upper.pm.colorType();
744     if (true
745         && (ct == kRGBA_8888_SkColorType || ct == kBGRA_8888_SkColorType)
746         && !sampling.useCubic && sampling.filter == SkFilterMode::kLinear
747         && sampling.mipmap != SkMipmapMode::kLinear
748         && fTileModeX == SkTileMode::kClamp && fTileModeY == SkTileMode::kClamp) {
749 
750         p->append(SkRasterPipelineOp::bilerp_clamp_8888, upper.gather);
751         if (ct == kBGRA_8888_SkColorType) {
752             p->append(SkRasterPipelineOp::swap_rb);
753         }
754         return append_misc();
755     }
756     if (true
757         && (ct == kRGBA_8888_SkColorType || ct == kBGRA_8888_SkColorType)
758         && sampling.useCubic
759         && fTileModeX == SkTileMode::kClamp && fTileModeY == SkTileMode::kClamp) {
760 
761         p->append(SkRasterPipelineOp::bicubic_clamp_8888, upper.gather);
762         if (ct == kBGRA_8888_SkColorType) {
763             p->append(SkRasterPipelineOp::swap_rb);
764         }
765         return append_misc();
766     }
767 
768     // This context can be shared by both levels when doing linear mipmap filtering
769     SkRasterPipeline_SamplerCtx* sampler = alloc->make<SkRasterPipeline_SamplerCtx>();
770 
771     auto sample = [&](SkRasterPipelineOp setup_x,
772                       SkRasterPipelineOp setup_y,
773                       const MipLevelHelper* level) {
774         p->append(setup_x, sampler);
775         p->append(setup_y, sampler);
776         append_tiling_and_gather(level);
777         p->append(SkRasterPipelineOp::accumulate, sampler);
778     };
779 
780     auto sample_level = [&](const MipLevelHelper* level) {
781         if (sampling.useCubic) {
782             CubicResamplerMatrix(sampling.cubic.B, sampling.cubic.C).getColMajor(sampler->weights);
783 
784             p->append(SkRasterPipelineOp::bicubic_setup, sampler);
785 
786             sample(SkRasterPipelineOp::bicubic_n3x, SkRasterPipelineOp::bicubic_n3y, level);
787             sample(SkRasterPipelineOp::bicubic_n1x, SkRasterPipelineOp::bicubic_n3y, level);
788             sample(SkRasterPipelineOp::bicubic_p1x, SkRasterPipelineOp::bicubic_n3y, level);
789             sample(SkRasterPipelineOp::bicubic_p3x, SkRasterPipelineOp::bicubic_n3y, level);
790 
791             sample(SkRasterPipelineOp::bicubic_n3x, SkRasterPipelineOp::bicubic_n1y, level);
792             sample(SkRasterPipelineOp::bicubic_n1x, SkRasterPipelineOp::bicubic_n1y, level);
793             sample(SkRasterPipelineOp::bicubic_p1x, SkRasterPipelineOp::bicubic_n1y, level);
794             sample(SkRasterPipelineOp::bicubic_p3x, SkRasterPipelineOp::bicubic_n1y, level);
795 
796             sample(SkRasterPipelineOp::bicubic_n3x, SkRasterPipelineOp::bicubic_p1y, level);
797             sample(SkRasterPipelineOp::bicubic_n1x, SkRasterPipelineOp::bicubic_p1y, level);
798             sample(SkRasterPipelineOp::bicubic_p1x, SkRasterPipelineOp::bicubic_p1y, level);
799             sample(SkRasterPipelineOp::bicubic_p3x, SkRasterPipelineOp::bicubic_p1y, level);
800 
801             sample(SkRasterPipelineOp::bicubic_n3x, SkRasterPipelineOp::bicubic_p3y, level);
802             sample(SkRasterPipelineOp::bicubic_n1x, SkRasterPipelineOp::bicubic_p3y, level);
803             sample(SkRasterPipelineOp::bicubic_p1x, SkRasterPipelineOp::bicubic_p3y, level);
804             sample(SkRasterPipelineOp::bicubic_p3x, SkRasterPipelineOp::bicubic_p3y, level);
805 
806             p->append(SkRasterPipelineOp::move_dst_src);
807         } else if (sampling.filter == SkFilterMode::kLinear) {
808             p->append(SkRasterPipelineOp::bilinear_setup, sampler);
809 
810             sample(SkRasterPipelineOp::bilinear_nx, SkRasterPipelineOp::bilinear_ny, level);
811             sample(SkRasterPipelineOp::bilinear_px, SkRasterPipelineOp::bilinear_ny, level);
812             sample(SkRasterPipelineOp::bilinear_nx, SkRasterPipelineOp::bilinear_py, level);
813             sample(SkRasterPipelineOp::bilinear_px, SkRasterPipelineOp::bilinear_py, level);
814 
815             p->append(SkRasterPipelineOp::move_dst_src);
816         } else {
817             append_tiling_and_gather(level);
818         }
819     };
820 
821     sample_level(&upper);
822 
823     if (mipmapCtx) {
824         p->append(SkRasterPipelineOp::mipmap_linear_update, mipmapCtx);
825         sample_level(&lower);
826         p->append(SkRasterPipelineOp::mipmap_linear_finish, mipmapCtx);
827     }
828 
829     return append_misc();
830 }
831 
program(skvm::Builder * p,skvm::Coord device,skvm::Coord origLocal,skvm::Color paint,const MatrixRec & mRec,const SkColorInfo & dst,skvm::Uniforms * uniforms,SkArenaAlloc * alloc) const832 skvm::Color SkImageShader::program(skvm::Builder* p,
833                                    skvm::Coord device,
834                                    skvm::Coord origLocal,
835                                    skvm::Color paint,
836                                    const MatrixRec& mRec,
837                                    const SkColorInfo& dst,
838                                    skvm::Uniforms* uniforms,
839                                    SkArenaAlloc* alloc) const {
840     SkASSERT(!needs_subset(fImage.get(), fSubset));  // TODO(skbug.com/12784)
841 
842     auto sampling = fSampling;
843     if (sampling.isAniso()) {
844         sampling = SkSamplingPriv::AnisoFallback(fImage->hasMipmaps());
845     }
846 
847     SkMatrix baseInv;
848     // If the total matrix isn't valid then we will always access the base MIP level.
849     if (mRec.totalMatrixIsValid()) {
850         if (!mRec.totalInverse(&baseInv)) {
851             return {};
852         }
853         baseInv.normalizePerspective();
854     }
855 
856     SkASSERT(!sampling.useCubic || sampling.mipmap == SkMipmapMode::kNone);
857     auto* access = SkMipmapAccessor::Make(alloc, fImage.get(), baseInv, sampling.mipmap);
858     if (!access) {
859         return {};
860     }
861 
862     SkPixmap upper;
863     SkMatrix upperInv;
864     std::tie(upper, upperInv) = access->level();
865 
866     if (!sampling.useCubic) {
867         // TODO: can tweak_sampling sometimes for cubic too when B=0
868         if (mRec.totalMatrixIsValid()) {
869             sampling = tweak_sampling(sampling, SkMatrix::Concat(upperInv, baseInv));
870         }
871     }
872 
873     SkPixmap lowerPixmap;
874     SkMatrix lowerInv;
875     SkPixmap* lower = nullptr;
876     float lowerWeight = access->lowerWeight();
877     if (lowerWeight > 0) {
878         std::tie(lowerPixmap, lowerInv) = access->lowerLevel();
879         lower = &lowerPixmap;
880     }
881 
882     skvm::Coord upperLocal = origLocal;
883     if (!mRec.apply(p, &upperLocal, uniforms, upperInv).has_value()) {
884         return {};
885     }
886 
887     // We can exploit image opacity to skip work unpacking alpha channels.
888     const bool input_is_opaque = SkAlphaTypeIsOpaque(upper.alphaType())
889                               || SkColorTypeIsAlwaysOpaque(upper.colorType());
890 
891     // Each call to sample() will try to rewrite the same uniforms over and over,
892     // so remember where we start and reset back there each time.  That way each
893     // sample() call uses the same uniform offsets.
894 
895     auto compute_clamp_limit = [&](float limit) {
896         // Subtract an ulp so the upper clamp limit excludes limit itself.
897         int bits;
898         memcpy(&bits, &limit, 4);
899         return p->uniformF(uniforms->push(bits-1));
900     };
901 
902     // Except in the simplest case (no mips, no filtering), we reference uniforms
903     // more than once. To avoid adding/registering them multiple times, we pre-load them
904     // into a struct (just to logically group them together), based on the "current"
905     // pixmap (level of a mipmap).
906     //
907     struct Uniforms {
908         skvm::F32   w, iw, i2w,
909                     h, ih, i2h;
910 
911         skvm::F32   clamp_w,
912                     clamp_h;
913 
914         skvm::Uniform addr;
915         skvm::I32     rowBytesAsPixels;
916 
917         skvm::PixelFormat pixelFormat;  // not a uniform, but needed for each texel sample,
918                                         // so we store it here, since it is also dependent on
919                                         // the current pixmap (level).
920     };
921 
922     auto setup_uniforms = [&](const SkPixmap& pm) -> Uniforms {
923         skvm::PixelFormat pixelFormat = skvm::SkColorType_to_PixelFormat(pm.colorType());
924         return {
925             p->uniformF(uniforms->pushF(     pm.width())),
926             p->uniformF(uniforms->pushF(1.0f/pm.width())), // iff tileX == kRepeat
927             p->uniformF(uniforms->pushF(0.5f/pm.width())), // iff tileX == kMirror
928 
929             p->uniformF(uniforms->pushF(     pm.height())),
930             p->uniformF(uniforms->pushF(1.0f/pm.height())), // iff tileY == kRepeat
931             p->uniformF(uniforms->pushF(0.5f/pm.height())), // iff tileY == kMirror
932 
933             compute_clamp_limit(pm. width()),
934             compute_clamp_limit(pm.height()),
935 
936             uniforms->pushPtr(pm.addr()),
937             p->uniform32(uniforms->push(pm.rowBytesAsPixels())),
938 
939             pixelFormat,
940         };
941     };
942 
943     auto sample_texel = [&](const Uniforms& u, skvm::F32 sx, skvm::F32 sy) -> skvm::Color {
944         // repeat() and mirror() are written assuming they'll be followed by a [0,scale) clamp.
945         auto repeat = [&](skvm::F32 v, skvm::F32 S, skvm::F32 I) {
946             return v - floor(v * I) * S;
947         };
948         auto mirror = [&](skvm::F32 v, skvm::F32 S, skvm::F32 I2) {
949             // abs( (v-scale) - (2*scale)*floor((v-scale)*(0.5f/scale)) - scale )
950             //      {---A---}   {------------------B------------------}
951             skvm::F32 A = v - S,
952                       B = (S + S) * floor(A * I2);
953             return abs(A - B - S);
954         };
955         switch (fTileModeX) {
956             case SkTileMode::kDecal:  /* handled after gather */ break;
957             case SkTileMode::kClamp:  /*    we always clamp   */ break;
958             case SkTileMode::kRepeat: sx = repeat(sx, u.w, u.iw);  break;
959             case SkTileMode::kMirror: sx = mirror(sx, u.w, u.i2w); break;
960         }
961         switch (fTileModeY) {
962             case SkTileMode::kDecal:  /* handled after gather */  break;
963             case SkTileMode::kClamp:  /*    we always clamp   */  break;
964             case SkTileMode::kRepeat: sy = repeat(sy, u.h, u.ih);  break;
965             case SkTileMode::kMirror: sy = mirror(sy, u.h, u.i2h); break;
966         }
967 
968         // Always clamp sample coordinates to [0,width), [0,height), both for memory
969         // safety and to handle the clamps still needed by kClamp, kRepeat, and kMirror.
970         skvm::F32 clamped_x = clamp(sx, 0, u.clamp_w),
971                   clamped_y = clamp(sy, 0, u.clamp_h);
972 
973         // Load pixels from pm.addr()[(int)sx + (int)sy*stride].
974         skvm::I32 index = trunc(clamped_x) +
975                           trunc(clamped_y) * u.rowBytesAsPixels;
976         skvm::Color c = gather(u.pixelFormat, u.addr, index);
977 
978         // If we know the image is opaque, jump right to alpha = 1.0f, skipping work to unpack it.
979         if (input_is_opaque) {
980             c.a = p->splat(1.0f);
981         }
982 
983         // Mask away any pixels that we tried to sample outside the bounds in kDecal.
984         if (fTileModeX == SkTileMode::kDecal || fTileModeY == SkTileMode::kDecal) {
985             skvm::I32 mask = p->splat(~0);
986             if (fTileModeX == SkTileMode::kDecal) { mask &= (sx == clamped_x); }
987             if (fTileModeY == SkTileMode::kDecal) { mask &= (sy == clamped_y); }
988             c.r = pun_to_F32(p->bit_and(mask, pun_to_I32(c.r)));
989             c.g = pun_to_F32(p->bit_and(mask, pun_to_I32(c.g)));
990             c.b = pun_to_F32(p->bit_and(mask, pun_to_I32(c.b)));
991             c.a = pun_to_F32(p->bit_and(mask, pun_to_I32(c.a)));
992             // Notice that even if input_is_opaque, c.a might now be 0.
993         }
994 
995         return c;
996     };
997 
998     auto sample_level = [&](const SkPixmap& pm, skvm::Coord local) {
999         const Uniforms u = setup_uniforms(pm);
1000 
1001         if (sampling.useCubic) {
1002             // All bicubic samples have the same fractional offset (fx,fy) from the center.
1003             // They're either the 16 corners of a 3x3 grid/ surrounding (x,y) at (0.5,0.5) off-center.
1004             skvm::F32 fx = fract(local.x + 0.5f),
1005                       fy = fract(local.y + 0.5f);
1006             skvm::F32 wx[4],
1007                       wy[4];
1008 
1009             SkM44 weights = CubicResamplerMatrix(sampling.cubic.B, sampling.cubic.C);
1010 
1011             auto dot = [](const skvm::F32 a[], const skvm::F32 b[]) {
1012                 return a[0]*b[0] + a[1]*b[1] + a[2]*b[2] + a[3]*b[3];
1013             };
1014             const skvm::F32 tmpx[] =  { p->splat(1.0f), fx, fx*fx, fx*fx*fx };
1015             const skvm::F32 tmpy[] =  { p->splat(1.0f), fy, fy*fy, fy*fy*fy };
1016 
1017             for (int row = 0; row < 4; ++row) {
1018                 SkV4 r = weights.row(row);
1019                 skvm::F32 ru[] = {
1020                     p->uniformF(uniforms->pushF(r[0])),
1021                     p->uniformF(uniforms->pushF(r[1])),
1022                     p->uniformF(uniforms->pushF(r[2])),
1023                     p->uniformF(uniforms->pushF(r[3])),
1024                 };
1025                 wx[row] = dot(ru, tmpx);
1026                 wy[row] = dot(ru, tmpy);
1027             }
1028 
1029             skvm::Color c;
1030             c.r = c.g = c.b = c.a = p->splat(0.0f);
1031 
1032             skvm::F32 sy = local.y - 1.5f;
1033             for (int j = 0; j < 4; j++, sy += 1.0f) {
1034                 skvm::F32 sx = local.x - 1.5f;
1035                 for (int i = 0; i < 4; i++, sx += 1.0f) {
1036                     skvm::Color s = sample_texel(u, sx,sy);
1037                     skvm::F32   w = wx[i] * wy[j];
1038 
1039                     c.r += s.r * w;
1040                     c.g += s.g * w;
1041                     c.b += s.b * w;
1042                     c.a += s.a * w;
1043                 }
1044             }
1045             return c;
1046         } else if (sampling.filter == SkFilterMode::kLinear) {
1047             // Our four sample points are the corners of a logical 1x1 pixel
1048             // box surrounding (x,y) at (0.5,0.5) off-center.
1049             skvm::F32 left   = local.x - 0.5f,
1050                       top    = local.y - 0.5f,
1051                       right  = local.x + 0.5f,
1052                       bottom = local.y + 0.5f;
1053 
1054             // The fractional parts of right and bottom are our lerp factors in x and y respectively.
1055             skvm::F32 fx = fract(right ),
1056                       fy = fract(bottom);
1057 
1058             return lerp(lerp(sample_texel(u, left,top   ), sample_texel(u, right,top   ), fx),
1059                         lerp(sample_texel(u, left,bottom), sample_texel(u, right,bottom), fx), fy);
1060         } else {
1061             SkASSERT(sampling.filter == SkFilterMode::kNearest);
1062             // Our rasterizer biases upward. That is a rect from 0.5...1.5 fills pixel 1 and not
1063             // pixel 0. To make an image that is mapped 1:1 with device pixels but at a half pixel
1064             // offset select every pixel from the src image once we make exact integer pixel sample
1065             // values round down not up. Note that a mirror mapping will not have this property.
1066             local.x = skvm::pun_to_F32(skvm::pun_to_I32(local.x) - 1);
1067             local.y = skvm::pun_to_F32(skvm::pun_to_I32(local.y) - 1);
1068             return sample_texel(u, local.x,local.y);
1069         }
1070     };
1071 
1072     skvm::Color c = sample_level(upper, upperLocal);
1073     if (lower) {
1074         skvm::Coord lowerLocal = origLocal;
1075         if (!mRec.apply(p, &lowerLocal, uniforms, lowerInv)) {
1076             return {};
1077         }
1078         // lower * weight + upper * (1 - weight)
1079         c = lerp(c,
1080                  sample_level(*lower, lowerLocal),
1081                  p->uniformF(uniforms->pushF(lowerWeight)));
1082     }
1083 
1084     // If the input is opaque and we're not in decal mode, that means the output is too.
1085     // Forcing *a to 1.0 here will retroactively skip any work we did to interpolate sample alphas.
1086     if (input_is_opaque
1087             && fTileModeX != SkTileMode::kDecal
1088             && fTileModeY != SkTileMode::kDecal) {
1089         c.a = p->splat(1.0f);
1090     }
1091 
1092     // Alpha-only images get their color from the paint (already converted to dst color space).
1093     SkColorSpace* cs = upper.colorSpace();
1094     SkAlphaType   at = upper.alphaType();
1095     if (SkColorTypeIsAlphaOnly(upper.colorType()) && !fRaw) {
1096         c.r = paint.r;
1097         c.g = paint.g;
1098         c.b = paint.b;
1099 
1100         cs = dst.colorSpace();
1101         at = kUnpremul_SkAlphaType;
1102     }
1103 
1104     if (sampling.useCubic) {
1105         // Bicubic filtering naturally produces out of range values on both sides of [0,1].
1106         c.a = clamp01(c.a);
1107 
1108         skvm::F32 limit = (at == kUnpremul_SkAlphaType || fClampAsIfUnpremul)
1109                         ? p->splat(1.0f)
1110                         : c.a;
1111         c.r = clamp(c.r, 0.0f, limit);
1112         c.g = clamp(c.g, 0.0f, limit);
1113         c.b = clamp(c.b, 0.0f, limit);
1114     }
1115 
1116     return fRaw ? c
1117                 : SkColorSpaceXformSteps{cs, at, dst.colorSpace(), dst.alphaType()}.program(
1118                           p, uniforms, c);
1119 }
1120