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