Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
GrGradientShader.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2018 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
9
13#include "src/base/SkMathPriv.h"
23#include "src/gpu/ganesh/SkGr.h"
29
30using namespace skia_private;
31
33
34// Intervals smaller than this (that aren't hard stops) on low-precision-only devices force us to
35// use the textured gradient
37
38// Each cache entry costs 1K or 2K of RAM. Each bitmap will be 1x256 at either 32bpp or 64bpp.
39static const int kMaxNumCachedGradientBitmaps = 32;
40static const int kGradientTextureSize = 256;
41
42// NOTE: signature takes raw pointers to the color/pos arrays and a count to make it easy for
43// MakeColorizer to transparently take care of hard stops at the end points of the gradient.
44static std::unique_ptr<GrFragmentProcessor> make_textured_colorizer(
45 const SkPMColor4f* colors,
46 const SkScalar* positions,
47 int count,
48 bool colorsAreOpaque,
49 const SkGradientShader::Interpolation& interpolation,
50 const SkColorSpace* intermediateColorSpace,
51 const SkColorSpace* dstColorSpace,
52 const GrFPArgs& args) {
54
55 // Use 8888 or F16, depending on the destination config.
56 // TODO: Use 1010102 for opaque gradients, at least if destination is 1010102?
58 if (GrColorTypeIsWiderThan(args.fDstColorInfo->colorType(), 8)) {
59 auto f16Format = args.fContext->priv().caps()->getDefaultBackendFormat(
60 GrColorType::kRGBA_F16, GrRenderable::kNo);
61 if (f16Format.isValid()) {
63 }
64 }
65 SkAlphaType alphaType = static_cast<bool>(interpolation.fInPremul) ? kPremul_SkAlphaType
67
69 gCache.getGradient(colors,
70 positions,
71 count,
72 colorsAreOpaque,
73 interpolation,
74 intermediateColorSpace,
75 dstColorSpace,
77 alphaType,
78 &bitmap);
79 SkASSERT(1 == bitmap.height() && SkIsPow2(bitmap.width()));
80 SkASSERT(bitmap.isImmutable());
81
82 auto view = std::get<0>(GrMakeCachedBitmapProxyView(
83 args.fContext, bitmap, /*label=*/"MakeTexturedColorizer", skgpu::Mipmapped::kNo));
84 if (!view) {
85 SkDebugf("Gradient won't draw. Could not create texture.");
86 return nullptr;
87 }
88
89 auto m = SkMatrix::Scale(view.width(), 1.f);
90 return GrTextureEffect::Make(std::move(view), alphaType, m, GrSamplerState::Filter::kLinear);
91}
92
93static std::unique_ptr<GrFragmentProcessor> make_single_interval_colorizer(const SkPMColor4f& start,
94 const SkPMColor4f& end) {
96 "uniform half4 start;"
97 "uniform half4 end;"
98 "half4 main(float2 coord) {"
99 // Clamping and/or wrapping was already handled by the parent shader so the output
100 // color is a simple lerp.
101 "return mix(start, end, half(coord.x));"
102 "}"
103 );
104 return GrSkSLFP::Make(effect, "SingleIntervalColorizer", /*inputFP=*/nullptr,
106 "start", start,
107 "end", end);
108}
109
110static std::unique_ptr<GrFragmentProcessor> make_dual_interval_colorizer(const SkPMColor4f& c0,
111 const SkPMColor4f& c1,
112 const SkPMColor4f& c2,
113 const SkPMColor4f& c3,
114 float threshold) {
116 "uniform float4 scale[2];"
117 "uniform float4 bias[2];"
118 "uniform half threshold;"
119
120 "half4 main(float2 coord) {"
121 "half t = half(coord.x);"
122
123 "float4 s, b;"
124 "if (t < threshold) {"
125 "s = scale[0];"
126 "b = bias[0];"
127 "} else {"
128 "s = scale[1];"
129 "b = bias[1];"
130 "}"
131
132 "return half4(t * s + b);"
133 "}"
134 );
135
136 // Derive scale and biases from the 4 colors and threshold
137 Vec4 vc0 = Vec4::Load(c0.vec());
138 Vec4 vc1 = Vec4::Load(c1.vec());
139 Vec4 vc2 = Vec4::Load(c2.vec());
140 Vec4 vc3 = Vec4::Load(c3.vec());
141
142 const Vec4 scale[2] = {(vc1 - vc0) / threshold,
143 (vc3 - vc2) / (1 - threshold)};
144 const Vec4 bias[2] = {vc0,
145 vc2 - threshold * scale[1]};
146 return GrSkSLFP::Make(effect, "DualIntervalColorizer", /*inputFP=*/nullptr,
148 "scale", SkSpan(scale),
149 "bias", SkSpan(bias),
150 "threshold", threshold);
151}
152
153// The "unrolled" colorizer contains hand-written nested ifs which perform a binary search.
154// This works on ES2 hardware that doesn't support non-constant array indexes.
155// However, to keep code size under control, we are limited to a small number of stops.
156static constexpr int kMaxUnrolledColorCount = 16;
158
159static std::unique_ptr<GrFragmentProcessor> make_unrolled_colorizer(int intervalCount,
160 const SkPMColor4f* scale,
161 const SkPMColor4f* bias,
162 SkRect thresholds1_7,
163 SkRect thresholds9_13) {
164 SkASSERT(intervalCount >= 1 && intervalCount <= 8);
165
167 static const SkRuntimeEffect* effects[kMaxUnrolledIntervalCount];
168
169 once[intervalCount - 1]([intervalCount] {
170 SkString sksl;
171
172 // The 7 threshold positions that define the boundaries of the 8 intervals (excluding t = 0,
173 // and t = 1) are packed into two half4s instead of having up to 7 separate scalar uniforms.
174 // For low interval counts, the extra components are ignored in the shader, but the uniform
175 // simplification is worth it. It is assumed thresholds are provided in increasing value,
176 // mapped as:
177 // - thresholds1_7.x = boundary between (0,1) and (2,3) -> 1_2
178 // - .y = boundary between (2,3) and (4,5) -> 3_4
179 // - .z = boundary between (4,5) and (6,7) -> 5_6
180 // - .w = boundary between (6,7) and (8,9) -> 7_8
181 // - thresholds9_13.x = boundary between (8,9) and (10,11) -> 9_10
182 // - .y = boundary between (10,11) and (12,13) -> 11_12
183 // - .z = boundary between (12,13) and (14,15) -> 13_14
184 // - .w = unused
185 sksl.append("uniform half4 thresholds1_7, thresholds9_13;");
186
187 // With the current hardstop detection threshold of 0.00024, the maximum scale and bias
188 // values will be on the order of 4k (since they divide by dt). That is well outside the
189 // precision capabilities of half floats, which can lead to inaccurate gradient calculations
190 sksl.appendf("uniform float4 scale[%d];", intervalCount);
191 sksl.appendf("uniform float4 bias[%d];", intervalCount);
192
193 // Explicit binary search for the proper interval that t falls within. The interval
194 // count checks are constant expressions, which are then optimized to the minimal number
195 // of branches for the specific interval count.
196 sksl.appendf(
197 "half4 main(float2 coord) {"
198 "half t = half(coord.x);"
199 "float4 s, b;"
200 // thresholds1_7.w is mid point for intervals (0,7) and (8,15)
201 "if (%d <= 4 || t < thresholds1_7.w) {"
202 // thresholds1_7.y is mid point for intervals (0,3) and (4,7)
203 "if (%d <= 2 || t < thresholds1_7.y) {"
204 // thresholds1_7.x is mid point for intervals (0,1) and (2,3)
205 "if (%d <= 1 || t < thresholds1_7.x) {"
206 "%s" // s = scale[0]; b = bias[0];
207 "} else {"
208 "%s" // s = scale[1]; b = bias[1];
209 "}"
210 "} else {"
211 // thresholds1_7.z is mid point for intervals (4,5) and (6,7)
212 "if (%d <= 3 || t < thresholds1_7.z) {"
213 "%s" // s = scale[2]; b = bias[2];
214 "} else {"
215 "%s" // s = scale[3]; b = bias[3];
216 "}"
217 "}"
218 "} else {"
219 // thresholds9_13.y is mid point for intervals (8,11) and (12,15)
220 "if (%d <= 6 || t < thresholds9_13.y) {"
221 // thresholds9_13.x is mid point for intervals (8,9) and (10,11)
222 "if (%d <= 5 || t < thresholds9_13.x) {"
223 "%s"
224 "} else {"
225 "%s" // s = scale[5]; b = bias[5];
226 "}"
227 "} else {"
228 // thresholds9_13.z is mid point for intervals (12,13) and (14,15)
229 "if (%d <= 7 || t < thresholds9_13.z) {"
230 "%s" // s = scale[6]; b = bias[6];
231 "} else {"
232 "%s" // s = scale[7]; b = bias[7];
233 "}"
234 "}"
235 "}"
236 "return t * s + b;"
237 "}"
238 , intervalCount,
239 intervalCount,
240 intervalCount,
241 (intervalCount <= 0) ? "" : "s = scale[0]; b = bias[0];",
242 (intervalCount <= 1) ? "" : "s = scale[1]; b = bias[1];",
243 intervalCount,
244 (intervalCount <= 2) ? "" : "s = scale[2]; b = bias[2];",
245 (intervalCount <= 3) ? "" : "s = scale[3]; b = bias[3];",
246 intervalCount,
247 intervalCount,
248 (intervalCount <= 4) ? "" : "s = scale[4]; b = bias[4];",
249 (intervalCount <= 5) ? "" : "s = scale[5]; b = bias[5];",
250 intervalCount,
251 (intervalCount <= 6) ? "" : "s = scale[6]; b = bias[6];",
252 (intervalCount <= 7) ? "" : "s = scale[7]; b = bias[7];");
253
254 auto result = SkRuntimeEffect::MakeForShader(std::move(sksl));
255 SkASSERTF(result.effect, "%s", result.errorText.c_str());
256 effects[intervalCount - 1] = result.effect.release();
257 });
258
259 return GrSkSLFP::Make(effects[intervalCount - 1], "UnrolledBinaryColorizer",
260 /*inputFP=*/nullptr, GrSkSLFP::OptFlags::kNone,
261 "thresholds1_7", thresholds1_7,
262 "thresholds9_13", thresholds9_13,
263 "scale", SkSpan(scale, intervalCount),
264 "bias", SkSpan(bias, intervalCount));
265}
266
267// The "looping" colorizer uses a real loop to binary-search the array of gradient stops.
268static constexpr int kMaxLoopingColorCount = 128;
270
271static std::unique_ptr<GrFragmentProcessor> make_looping_colorizer(int intervalCount,
272 const SkPMColor4f* scale,
273 const SkPMColor4f* bias,
274 const SkScalar* thresholds) {
275 SkASSERT(intervalCount >= 1 && intervalCount <= kMaxLoopingIntervalCount);
276 SkASSERT((intervalCount & 3) == 0); // intervals are required to come in groups of four
277 int intervalChunks = intervalCount / 4;
278 int cacheIndex = (size_t)intervalChunks - 1;
279
280 struct EffectCacheEntry {
281 SkOnce once;
282 const SkRuntimeEffect* effect;
283 };
284
285 static EffectCacheEntry effectCache[kMaxLoopingIntervalCount / 4];
286 SkASSERT(cacheIndex >= 0 && cacheIndex < (int)std::size(effectCache));
287 EffectCacheEntry* cacheEntry = &effectCache[cacheIndex];
288
289 cacheEntry->once([intervalCount, intervalChunks, cacheEntry] {
290 SkString sksl;
291
292 // Binary search for the interval that `t` falls within. We can precalculate the number of
293 // loop iterations we need, and we know `t` will always be in range, so we can just loop a
294 // fixed number of times and can be guaranteed to have found the proper element.
295 //
296 // Threshold values are stored in half4s to keep them compact, so the last two rounds of
297 // binary search are hand-unrolled to allow them to use swizzles.
298 //
299 // Note that this colorizer is also designed to handle the case of exactly 4 intervals (a
300 // single chunk). In this case, the binary search for-loop will optimize away entirely, as
301 // it can be proven to execute zero times. We also optimize away the calculation of `4 *
302 // chunk` near the end via an if statement, as the result will always be in chunk 0.
303 int loopCount = SkNextLog2(intervalChunks);
304 sksl.appendf(
305 "#version 300\n" // important space to separate token.
306 "uniform half4 thresholds[%d];"
307 "uniform float4 scale[%d];"
308 "uniform float4 bias[%d];"
309
310 "half4 main(float2 coord) {"
311 "half t = half(coord.x);"
312
313 // Choose a chunk from thresholds via binary search in a loop.
314 "int low = 0;"
315 "int high = %d;"
316 "int chunk = %d;"
317 "for (int loop = 0; loop < %d; ++loop) {"
318 "if (t < thresholds[chunk].w) {"
319 "high = chunk;"
320 "} else {"
321 "low = chunk + 1;"
322 "}"
323 "chunk = (low + high) / 2;"
324 "}"
325
326 // Choose the final position via explicit 4-way binary search.
327 "int pos;"
328 "if (t < thresholds[chunk].y) {"
329 "pos = (t < thresholds[chunk].x) ? 0 : 1;"
330 "} else {"
331 "pos = (t < thresholds[chunk].z) ? 2 : 3;"
332 "}"
333 "if (%d > 0) {"
334 "pos += 4 * chunk;"
335 "}"
336 "return t * scale[pos] + bias[pos];"
337 "}"
338 , /* thresholds: */ intervalChunks,
339 /* scale: */ intervalCount,
340 /* bias: */ intervalCount,
341 /* high: */ intervalChunks - 1,
342 /* chunk: */ (intervalChunks - 1) / 2,
343 /* loopCount: */ loopCount,
344 /* if (loopCount > 0): */ loopCount);
345
346 auto result = SkRuntimeEffect::MakeForShader(std::move(sksl));
347 SkASSERTF(result.effect, "%s", result.errorText.c_str());
348 cacheEntry->effect = result.effect.release();
349 });
350
351 return GrSkSLFP::Make(cacheEntry->effect, "LoopingBinaryColorizer",
352 /*inputFP=*/nullptr, GrSkSLFP::OptFlags::kNone,
353 "thresholds", SkSpan((const SkV4*)thresholds, intervalChunks),
354 "scale", SkSpan(scale, intervalCount),
355 "bias", SkSpan(bias, intervalCount));
356}
357
358// Converts an input array of {colors, positions} into an array of {scales, biases, thresholds}.
359// The length of the result array may differ from the input due to hard-stops or empty intervals.
360int build_intervals(int inputLength,
361 const SkPMColor4f* inColors,
362 const SkScalar* inPositions,
363 int outputLength,
364 SkPMColor4f* outScales,
365 SkPMColor4f* outBiases,
366 SkScalar* outThresholds) {
367 // Depending on how the positions resolve into hard stops or regular stops, the number of
368 // intervals specified by the number of colors/positions can change. For instance, a plain
369 // 3 color gradient is two intervals, but a 4 color gradient with a hard stop is also
370 // two intervals. At the most extreme end, an 8 interval gradient made entirely of hard
371 // stops has 16 colors.
372 int intervalCount = 0;
373 for (int i = 0; i < inputLength - 1; i++) {
374 if (intervalCount >= outputLength) {
375 // Already reached our output limit, and haven't run out of color stops. This gradient
376 // cannot be represented without more intervals.
377 return 0;
378 }
379
380 SkScalar t0 = inPositions[i];
381 SkScalar t1 = inPositions[i + 1];
382 SkScalar dt = t1 - t0;
383 // If the interval is empty, skip to the next interval. This will automatically create
384 // distinct hard stop intervals as needed. It also protects against malformed gradients
385 // that have repeated hard stops at the very beginning that are effectively unreachable.
386 if (SkScalarNearlyZero(dt)) {
387 continue;
388 }
389
390 Vec4 c0 = Vec4::Load(inColors[i].vec());
391 Vec4 c1 = Vec4::Load(inColors[i + 1].vec());
392 Vec4 scale = (c1 - c0) / dt;
393 Vec4 bias = c0 - t0 * scale;
394
395 scale.store(outScales + intervalCount);
396 bias.store(outBiases + intervalCount);
397 outThresholds[intervalCount] = t1;
398 intervalCount++;
399 }
400 return intervalCount;
401}
402
403static std::unique_ptr<GrFragmentProcessor> make_unrolled_binary_colorizer(
404 const SkPMColor4f* colors, const SkScalar* positions, int count) {
406 // Definitely cannot represent this gradient configuration
407 return nullptr;
408 }
409
412 SkScalar thresholds[kMaxUnrolledIntervalCount] = {};
413 int intervalCount = build_intervals(count, colors, positions,
414 kMaxUnrolledIntervalCount, scales, biases, thresholds);
415 if (intervalCount <= 0) {
416 return nullptr;
417 }
418
419 SkRect thresholds1_7 = {thresholds[0], thresholds[1], thresholds[2], thresholds[3]},
420 thresholds9_13 = {thresholds[4], thresholds[5], thresholds[6], 0.0};
421
422 return make_unrolled_colorizer(intervalCount, scales, biases, thresholds1_7, thresholds9_13);
423}
424
425static std::unique_ptr<GrFragmentProcessor> make_looping_binary_colorizer(const SkPMColor4f* colors,
426 const SkScalar* positions,
427 int count) {
429 // Definitely cannot represent this gradient configuration
430 return nullptr;
431 }
432
435 SkScalar thresholds[kMaxLoopingIntervalCount] = {};
436 int intervalCount = build_intervals(count, colors, positions,
437 kMaxLoopingIntervalCount, scales, biases, thresholds);
438 if (intervalCount <= 0) {
439 return nullptr;
440 }
441
442 // We round up the number of intervals to the next power of two. This reduces the number of
443 // unique shaders and doesn't require any additional GPU processing power, but this does waste a
444 // handful of uniforms.
445 int roundedSize = std::max(4, SkNextPow2(intervalCount));
446 SkASSERT(roundedSize <= kMaxLoopingIntervalCount);
447 for (; intervalCount < roundedSize; ++intervalCount) {
448 thresholds[intervalCount] = thresholds[intervalCount - 1];
449 scales[intervalCount] = scales[intervalCount - 1];
450 biases[intervalCount] = biases[intervalCount - 1];
451 }
452
453 return make_looping_colorizer(intervalCount, scales, biases, thresholds);
454}
455
456// Analyze the shader's color stops and positions and chooses an appropriate colorizer to represent
457// the gradient.
458static std::unique_ptr<GrFragmentProcessor> make_uniform_colorizer(const SkPMColor4f* colors,
459 const SkScalar* positions,
460 int count,
461 bool premul,
462 const GrFPArgs& args) {
463 // If there are hard stops at the beginning or end, the first and/or last color should be
464 // ignored by the colorizer since it should only be used in a clamped border color. By detecting
465 // and removing these stops at the beginning, it makes optimizing the remaining color stops
466 // simpler.
467
468 // SkGradientBaseShader guarantees that pos[0] == 0 by adding a default value.
469 bool bottomHardStop = SkScalarNearlyEqual(positions[0], positions[1]);
470 // The same is true for pos[end] == 1
471 bool topHardStop = SkScalarNearlyEqual(positions[count - 2], positions[count - 1]);
472
473 if (bottomHardStop) {
474 colors++;
475 positions++;
476 count--;
477 }
478 if (topHardStop) {
479 count--;
480 }
481
482 // Two remaining colors means a single interval from 0 to 1
483 // (but it may have originally been a 3 or 4 color gradient with 1-2 hard stops at the ends)
484 if (count == 2) {
485 return make_single_interval_colorizer(colors[0], colors[1]);
486 }
487
488 const GrShaderCaps* caps = args.fContext->priv().caps()->shaderCaps();
489 auto intervalsExceedPrecisionLimit = [&]() -> bool {
490 // The remaining analytic colorizers use scale*t+bias, and the scale/bias values can become
491 // quite large when thresholds are close (but still outside the hardstop limit). If float
492 // isn't 32-bit, output can be incorrect if the thresholds are too close together. However,
493 // the analytic shaders are higher quality, so they can be used with lower precision
494 // hardware when the thresholds are not ill-conditioned.
495 if (!caps->fFloatIs32Bits) {
496 // Could run into problems. Check if thresholds are close together (with a limit of .01,
497 // so that scales will be less than 100, which leaves 4 decimals of precision on
498 // 16-bit).
499 for (int i = 0; i < count - 1; i++) {
500 SkScalar dt = SkScalarAbs(positions[i] - positions[i + 1]);
501 if (dt <= kLowPrecisionIntervalLimit && dt > SK_ScalarNearlyZero) {
502 return true;
503 }
504 }
505 }
506 return false;
507 };
508
509 auto makeDualIntervalColorizer = [&]() -> std::unique_ptr<GrFragmentProcessor> {
510 // The dual-interval colorizer uses the same principles as the binary-search colorizer, but
511 // is limited to exactly 2 intervals.
512 if (count == 3) {
513 // Must be a dual interval gradient, where the middle point is at 1 and the
514 // two intervals share the middle color stop.
515 return make_dual_interval_colorizer(colors[0], colors[1],
516 colors[1], colors[2],
517 positions[1]);
518 }
519 if (count == 4 && SkScalarNearlyEqual(positions[1], positions[2])) {
520 // Two separate intervals that join at the same threshold position
521 return make_dual_interval_colorizer(colors[0], colors[1],
522 colors[2], colors[3],
523 positions[1]);
524 }
525 // The gradient can't be represented in only two intervals.
526 return nullptr;
527 };
528
529 int binaryColorizerLimit = caps->fNonconstantArrayIndexSupport ? kMaxLoopingColorCount
531 if ((count <= binaryColorizerLimit) && !intervalsExceedPrecisionLimit()) {
532 // The dual-interval colorizer uses the same principles as the binary-search colorizer, but
533 // is limited to exactly 2 intervals.
534 std::unique_ptr<GrFragmentProcessor> colorizer = makeDualIntervalColorizer();
535 if (colorizer) {
536 return colorizer;
537 }
538 // Attempt to create an analytic colorizer that uses a binary-search loop.
539 colorizer = caps->fNonconstantArrayIndexSupport
540 ? make_looping_binary_colorizer(colors, positions, count)
541 : make_unrolled_binary_colorizer(colors, positions, count);
542 if (colorizer) {
543 return colorizer;
544 }
545 }
546
547 // This gradient is too complex for our uniform colorizers. The calling code will fall back to
548 // creating a textured colorizer, instead.
549 return nullptr;
550}
551
552// This top-level effect implements clamping on the layout coordinate and requires specifying the
553// border colors that are used when outside the clamped boundary. Gradients with the
554// SkTileMode::kClamp should use the colors at their first and last stop (after adding default stops
555// for t=0,t=1) as the border color. This will automatically replicate the edge color, even when
556// there is a hard stop.
557//
558// The SkTileMode::kDecal can be produced by specifying transparent black as the border colors,
559// regardless of the gradient's stop colors.
560static std::unique_ptr<GrFragmentProcessor> make_clamped_gradient(
561 std::unique_ptr<GrFragmentProcessor> colorizer,
562 std::unique_ptr<GrFragmentProcessor> gradLayout,
563 SkPMColor4f leftBorderColor,
564 SkPMColor4f rightBorderColor,
565 bool colorsAreOpaque) {
567 "uniform shader colorizer;"
568 "uniform shader gradLayout;"
569
570 "uniform half4 leftBorderColor;" // t < 0.0
571 "uniform half4 rightBorderColor;" // t > 1.0
572
573 "uniform int layoutPreservesOpacity;" // specialized
574
575 "half4 main(float2 coord) {"
576 "half4 t = gradLayout.eval(coord);"
577 "half4 outColor;"
578
579 // If t.x is below 0, use the left border color without invoking the child processor.
580 // If any t.x is above 1, use the right border color. Otherwise, t is in the [0, 1]
581 // range assumed by the colorizer FP, so delegate to the child processor.
582 "if (!bool(layoutPreservesOpacity) && t.y < 0) {"
583 // layout has rejected this fragment (rely on sksl to remove this branch if the
584 // layout FP preserves opacity is false)
585 "outColor = half4(0);"
586 "} else if (t.x < 0) {"
587 "outColor = leftBorderColor;"
588 "} else if (t.x > 1.0) {"
589 "outColor = rightBorderColor;"
590 "} else {"
591 // Always sample from (x, 0), discarding y, since the layout FP can use y as a
592 // side-channel.
593 "outColor = colorizer.eval(t.x0);"
594 "}"
595 "return outColor;"
596 "}"
597 );
598
599 // If the layout does not preserve opacity, remove the opaque optimization,
600 // but otherwise respect the provided color opacity state (which should take
601 // into account the opacity of the border colors).
602 bool layoutPreservesOpacity = gradLayout->preservesOpaqueInput();
604 if (colorsAreOpaque && layoutPreservesOpacity) {
606 }
607
608 return GrSkSLFP::Make(effect, "ClampedGradient", /*inputFP=*/nullptr, optFlags,
609 "colorizer", GrSkSLFP::IgnoreOptFlags(std::move(colorizer)),
610 "gradLayout", GrSkSLFP::IgnoreOptFlags(std::move(gradLayout)),
611 "leftBorderColor", leftBorderColor,
612 "rightBorderColor", rightBorderColor,
613 "layoutPreservesOpacity",
614 GrSkSLFP::Specialize<int>(layoutPreservesOpacity));
615}
616
617static std::unique_ptr<GrFragmentProcessor> make_tiled_gradient(
618 const GrFPArgs& args,
619 std::unique_ptr<GrFragmentProcessor> colorizer,
620 std::unique_ptr<GrFragmentProcessor> gradLayout,
621 bool mirror,
622 bool colorsAreOpaque) {
624 "uniform shader colorizer;"
625 "uniform shader gradLayout;"
626
627 "uniform int mirror;" // specialized
628 "uniform int layoutPreservesOpacity;" // specialized
629 "uniform int useFloorAbsWorkaround;" // specialized
630
631 "half4 main(float2 coord) {"
632 "half4 t = gradLayout.eval(coord);"
633
634 "if (!bool(layoutPreservesOpacity) && t.y < 0) {"
635 // layout has rejected this fragment (rely on sksl to remove this branch if the
636 // layout FP preserves opacity is false)
637 "return half4(0);"
638 "} else {"
639 "if (bool(mirror)) {"
640 "half t_1 = t.x - 1;"
641 "half tiled_t = t_1 - 2 * floor(t_1 * 0.5) - 1;"
642 "if (bool(useFloorAbsWorkaround)) {"
643 // At this point the expected value of tiled_t should between -1 and 1, so
644 // this clamp has no effect other than to break up the floor and abs calls
645 // and make sure the compiler doesn't merge them back together.
646 "tiled_t = clamp(tiled_t, -1, 1);"
647 "}"
648 "t.x = abs(tiled_t);"
649 "} else {"
650 // Simple repeat mode
651 "t.x = fract(t.x);"
652 "}"
653
654 // Always sample from (x, 0), discarding y, since the layout FP can use y as a
655 // side-channel.
656 "half4 outColor = colorizer.eval(t.x0);"
657 "return outColor;"
658 "}"
659 "}"
660 );
661
662 // If the layout does not preserve opacity, remove the opaque optimization,
663 // but otherwise respect the provided color opacity state (which should take
664 // into account the opacity of the border colors).
665 bool layoutPreservesOpacity = gradLayout->preservesOpaqueInput();
667 if (colorsAreOpaque && layoutPreservesOpacity) {
669 }
670 const bool useFloorAbsWorkaround =
671 args.fContext->priv().caps()->shaderCaps()->fMustDoOpBetweenFloorAndAbs;
672
673 return GrSkSLFP::Make(effect, "TiledGradient", /*inputFP=*/nullptr, optFlags,
674 "colorizer", GrSkSLFP::IgnoreOptFlags(std::move(colorizer)),
675 "gradLayout", GrSkSLFP::IgnoreOptFlags(std::move(gradLayout)),
676 "mirror", GrSkSLFP::Specialize<int>(mirror),
677 "layoutPreservesOpacity",
678 GrSkSLFP::Specialize<int>(layoutPreservesOpacity),
679 "useFloorAbsWorkaround",
680 GrSkSLFP::Specialize<int>(useFloorAbsWorkaround));
681}
682
683static std::unique_ptr<GrFragmentProcessor> make_interpolated_to_dst(
684 std::unique_ptr<GrFragmentProcessor> gradient,
685 const SkGradientShader::Interpolation& interpolation,
686 SkColorSpace* intermediateColorSpace,
687 const GrColorInfo& dstInfo,
688 bool allOpaque) {
690
691 // If these values change, you will need to edit sksl_shared
692 static_assert(static_cast<int>(ColorSpace::kDestination) == 0);
693 static_assert(static_cast<int>(ColorSpace::kSRGBLinear) == 1);
694 static_assert(static_cast<int>(ColorSpace::kLab) == 2);
695 static_assert(static_cast<int>(ColorSpace::kOKLab) == 3);
696 static_assert(static_cast<int>(ColorSpace::kOKLabGamutMap) == 4);
697 static_assert(static_cast<int>(ColorSpace::kLCH) == 5);
698 static_assert(static_cast<int>(ColorSpace::kOKLCH) == 6);
699 static_assert(static_cast<int>(ColorSpace::kOKLCHGamutMap) == 7);
700 static_assert(static_cast<int>(ColorSpace::kSRGB) == 8);
701 static_assert(static_cast<int>(ColorSpace::kHSL) == 9);
702 static_assert(static_cast<int>(ColorSpace::kHWB) == 10);
703
705 "uniform int colorSpace;" // specialized
706 "uniform int do_unpremul;" // specialized
707
708 "half4 main(half4 color) {"
709 "return $interpolated_to_rgb_unpremul(color, colorSpace, do_unpremul);"
710 "}"
711 );
712
713 // Are we interpreting premul colors? We use this later to decide if we need to inject a final
714 // premultiplication step.
715 bool inputPremul = static_cast<bool>(interpolation.fInPremul);
716
717 switch (interpolation.fColorSpace) {
718 case ColorSpace::kLab:
719 case ColorSpace::kOKLab:
720 case ColorSpace::kOKLabGamutMap:
721 case ColorSpace::kLCH:
722 case ColorSpace::kOKLCH:
723 case ColorSpace::kOKLCHGamutMap:
724 case ColorSpace::kHSL:
725 case ColorSpace::kHWB:
726 // In these exotic spaces, unpremul the colors if necessary (no need to do this if
727 // they're all opaque), and then convert them to the intermediate SkColorSpace
728 gradient = GrSkSLFP::Make(effect, "GradientCS", std::move(gradient),
730 "colorSpace", GrSkSLFP::Specialize<int>(
731 static_cast<int>(interpolation.fColorSpace)),
732 "do_unpremul", GrSkSLFP::Specialize<int>(
733 inputPremul && !allOpaque));
734 // We just forced the colors back to unpremul. Remember that for below
735 inputPremul = false;
736 break;
737 default:
738 break;
739 }
740
741 // Now transform from intermediate to destination color space. There are two tricky things here:
742 // 1) Normally, we'd pass dstInfo to the transform effect. However, if someone is rendering to
743 // a non-color managed surface (nullptr dst color space), and they chose to interpolate in
744 // any of the exotic spaces, that transform would do nothing, and leave the colors in
745 // whatever intermediate space we chose. That could even be something like XYZ, which will
746 // produce nonsense. So, in this particular case, we break Skia's rules, and treat a null
747 // destination as sRGB.
748 SkColorSpace* dstColorSpace = dstInfo.colorSpace() ? dstInfo.colorSpace() : sk_srgb_singleton();
749
750 // 2) Alpha type: We already tweaked our idea of "inputPremul" above -- if we interpolated in a
751 // non-RGB space, then we had to unpremul the colors to get proper conversion back to RGB.
752 // Our final goal is to emit premul colors, but under certain conditions we don't need to do
753 // anything to achieve that: i.e. its interpolating already premul colors (inputPremul) or
754 // all the colors have a = 1, in which case premul is a no op. Note that this allOpaque check
755 // is more permissive than SkGradientBaseShader's isOpaque(), since we can optimize away the
756 // make-premul op for two point conical gradients (which report false for isOpaque).
757 SkAlphaType intermediateAlphaType = inputPremul ? kPremul_SkAlphaType : kUnpremul_SkAlphaType;
758 SkAlphaType dstAlphaType = kPremul_SkAlphaType;
759
760 // If all the colors were opaque, then we don't need to do any premultiplication. We describe
761 // all the colors as *unpremul*, though. That will eliminate any extra unpremul/premul pair
762 // that would be injected if we have to do a color-space conversion here.
763 if (allOpaque) {
764 intermediateAlphaType = dstAlphaType = kUnpremul_SkAlphaType;
765 }
766
767 return GrColorSpaceXformEffect::Make(std::move(gradient),
768 intermediateColorSpace, intermediateAlphaType,
769 dstColorSpace, dstAlphaType);
770}
771
773
774/**
775 * Produces an FP that muls its input coords by the inverse of the pending matrix and then
776 * samples the passed FP with those coordinates. 'postInv' is an additional matrix to
777 * post-apply to the inverted pending matrix. If the pending matrix is not invertible the
778 * GrFPResult's bool will be false and the passed FP will be returned to the caller in the
779 * GrFPResult.
780 */
781static GrFPResult apply_matrix(std::unique_ptr<GrFragmentProcessor> fp,
782 const SkShaders::MatrixRec& rec,
783 const SkMatrix& postInv) {
784 auto [total, ok] = rec.applyForFragmentProcessor(postInv);
785 if (!ok) {
786 return {false, std::move(fp)};
787 }
788 // GrMatrixEffect returns 'fp' if total worked out to identity.
789 return {true, GrMatrixEffect::Make(total, std::move(fp))};
790}
791
792// Combines the colorizer and layout with an appropriately configured top-level effect based on the
793// gradient's tile mode
794std::unique_ptr<GrFragmentProcessor> MakeGradientFP(const SkGradientBaseShader& shader,
795 const GrFPArgs& args,
796 const SkShaders::MatrixRec& mRec,
797 std::unique_ptr<GrFragmentProcessor> layout,
798 const SkMatrix* overrideMatrix) {
799 // No shader is possible if a layout couldn't be created, e.g. a layout-specific Make() returned
800 // null.
801 if (layout == nullptr) {
802 return nullptr;
803 }
804
805 // Some two-point conical gradients use a custom matrix here. Otherwise, use
806 // SkGradientBaseShader's matrix;
807 if (!overrideMatrix) {
808 overrideMatrix = &shader.getGradientMatrix();
809 }
810 bool success;
811 std::tie(success, layout) = apply_matrix(std::move(layout), mRec, *overrideMatrix);
812 if (!success) {
813 return nullptr;
814 }
815
816 // Convert all colors into destination space and into SkPMColor4fs, and handle
817 // premul issues depending on the interpolation mode.
818 //
819 // SkGradientShader stores positions implicitly when they are evenly spaced, but the getPos()
820 // implementation performs a branch for every position index. Since the shader conversion
821 // requires lots of position tests, instruct the xformer to calculate all of the positions up
822 // front if needed.
823 SkColor4fXformer xformedColors(
824 &shader, args.fDstColorInfo->colorSpace(), /*forceExplicitPositions=*/true);
825 const SkPMColor4f* colors = xformedColors.fColors.begin();
826 const SkScalar* positions = xformedColors.fPositions;
827 const int colorCount = xformedColors.fColors.size();
828
829 bool allOpaque = true;
830 for (int i = 0; i < colorCount; i++) {
831 if (allOpaque && !SkScalarNearlyEqual(colors[i].fA, 1.0)) {
832 allOpaque = false;
833 }
834 }
835
836 // All gradients are colorized the same way, regardless of layout
837 std::unique_ptr<GrFragmentProcessor> colorizer = make_uniform_colorizer(
838 colors, positions, colorCount, shader.interpolateInPremul(), args);
839
840 if (colorizer) {
841 // If we made a uniform colorizer, wrap it in a conversion from interpolated space to
842 // destination. This also applies any final premultiplication.
843 colorizer = make_interpolated_to_dst(std::move(colorizer),
844 shader.fInterpolation,
845 xformedColors.fIntermediateColorSpace.get(),
846 *args.fDstColorInfo,
847 allOpaque);
848 } else {
849 // If we failed to make a uniform colorizer, we need to rasterize the gradient to a
850 // texture (which can handle arbitrarily complex gradients). This method directly encodes
851 // the result of the interpolated-to-dst into the texture, so we skip the wrapper FP above.
852 //
853 // Also, note that the texture technique has limited sampling resolution, and always blurs
854 // hard-stops.)
855 colorizer = make_textured_colorizer(colors,
856 positions,
857 colorCount,
858 allOpaque,
859 shader.fInterpolation,
860 xformedColors.fIntermediateColorSpace.get(),
861 args.fDstColorInfo->colorSpace(),
862 args);
863 }
864
865 if (colorizer == nullptr) {
866 return nullptr;
867 }
868
869 // If interpolation space is different than destination, wrap the colorizer in a conversion.
870 // This also handles any final premultiplication, etc.
871
872 // All tile modes are supported (unless something was added to SkShader)
873 std::unique_ptr<GrFragmentProcessor> gradient;
874 switch(shader.getTileMode()) {
876 gradient = make_tiled_gradient(args, std::move(colorizer), std::move(layout),
877 /* mirror */ false, allOpaque);
878 break;
880 gradient = make_tiled_gradient(args, std::move(colorizer), std::move(layout),
881 /* mirror */ true, allOpaque);
882 break;
883 case SkTileMode::kClamp: {
884 // For the clamped mode, the border colors are the first and last colors, corresponding
885 // to t=0 and t=1, because SkGradientBaseShader enforces that by adding color stops as
886 // appropriate. If there is a hard stop, this grabs the expected outer colors for the
887 // border.
888
889 // However, we need to finish converting to destination color space. (These are still
890 // in the interpolated color space).
891 SkPMColor4f borderColors[2] = { colors[0], colors[colorCount - 1] };
892 SkArenaAlloc alloc(/*firstHeapAllocation=*/0);
893 SkRasterPipeline p(&alloc);
894 SkRasterPipeline_MemoryCtx ctx = { borderColors, 0 };
895
896 p.append(SkRasterPipelineOp::load_f32, &ctx);
898 &p,
899 &alloc,
900 allOpaque,
901 shader.fInterpolation,
902 xformedColors.fIntermediateColorSpace.get(),
903 args.fDstColorInfo->colorSpace());
904 p.append(SkRasterPipelineOp::store_f32, &ctx);
905 p.run(0, 0, 2, 1);
906
907 gradient = make_clamped_gradient(std::move(colorizer), std::move(layout),
908 borderColors[0], borderColors[1], allOpaque);
909 break;
910 }
912 // Even if the gradient colors are opaque, the decal borders are transparent so
913 // disable that optimization
914 gradient = make_clamped_gradient(std::move(colorizer), std::move(layout),
916 /* colorsAreOpaque */ false);
917 break;
918 }
919
920 return gradient;
921}
922
923std::unique_ptr<GrFragmentProcessor> MakeLinear(const SkLinearGradient& shader,
924 const GrFPArgs& args,
925 const SkShaders::MatrixRec& mRec) {
926 // We add a tiny delta to t. When gradient stops are set up so that a hard stop in a vertically
927 // or horizontally oriented gradient falls exactly at a column or row of pixel centers we can
928 // get slightly different interpolated t values along the column/row. By adding the delta
929 // we will consistently get the color to the "right" of the stop. Of course if the hard stop
930 // falls at X.5 - delta then we still could get inconsistent results, but that is much less
931 // likely. crbug.com/938592
932 // If/when we add filtering of the gradient this can be removed.
934 "half4 main(float2 coord) {"
935 "return half4(half(coord.x) + 0.00001, 1, 0, 0);" // y = 1 for always valid
936 "}"
937 );
938 // The linear gradient never rejects a pixel so it doesn't change opacity
939 auto fp = GrSkSLFP::Make(effect, "LinearLayout", /*inputFP=*/nullptr,
941 return MakeGradientFP(shader, args, mRec, std::move(fp));
942}
943
944#if defined(GR_TEST_UTILS)
945RandomParams::RandomParams(SkRandom* random) {
946 // Set color count to min of 2 so that we don't trigger the const color optimization and make
947 // a non-gradient processor.
948 fColorCount = random->nextRangeU(2, kMaxRandomGradientColors);
949 fUseColors4f = random->nextBool();
950
951 // if one color, omit stops, otherwise randomly decide whether or not to
952 if (fColorCount == 1 || (fColorCount >= 2 && random->nextBool())) {
953 fStops = nullptr;
954 } else {
955 fStops = fStopStorage;
956 }
957
958 // if using SkColor4f, attach a random (possibly null) color space (with linear gamma)
959 if (fUseColors4f) {
960 fColorSpace = GrTest::TestColorSpace(random);
961 }
962
963 SkScalar stop = 0.f;
964 for (int i = 0; i < fColorCount; ++i) {
965 if (fUseColors4f) {
966 fColors4f[i].fR = random->nextUScalar1();
967 fColors4f[i].fG = random->nextUScalar1();
968 fColors4f[i].fB = random->nextUScalar1();
969 fColors4f[i].fA = random->nextUScalar1();
970 } else {
971 fColors[i] = random->nextU();
972 }
973 if (fStops) {
974 fStops[i] = stop;
975 stop = i < fColorCount - 1 ? stop + random->nextUScalar1() * (1.f - stop) : 1.f;
976 }
977 }
978 fTileMode = static_cast<SkTileMode>(random->nextULessThan(kSkTileModeCount));
979}
980#endif
981
982} // namespace GrGradientShader
int count
std::tuple< bool, std::unique_ptr< GrFragmentProcessor > > GrFPResult
static std::unique_ptr< GrFragmentProcessor > make_clamped_gradient(std::unique_ptr< GrFragmentProcessor > colorizer, std::unique_ptr< GrFragmentProcessor > gradLayout, SkPMColor4f leftBorderColor, SkPMColor4f rightBorderColor, bool colorsAreOpaque)
static std::unique_ptr< GrFragmentProcessor > make_looping_binary_colorizer(const SkPMColor4f *colors, const SkScalar *positions, int count)
static std::unique_ptr< GrFragmentProcessor > make_uniform_colorizer(const SkPMColor4f *colors, const SkScalar *positions, int count, bool premul, const GrFPArgs &args)
static std::unique_ptr< GrFragmentProcessor > make_tiled_gradient(const GrFPArgs &args, std::unique_ptr< GrFragmentProcessor > colorizer, std::unique_ptr< GrFragmentProcessor > gradLayout, bool mirror, bool colorsAreOpaque)
static std::unique_ptr< GrFragmentProcessor > make_dual_interval_colorizer(const SkPMColor4f &c0, const SkPMColor4f &c1, const SkPMColor4f &c2, const SkPMColor4f &c3, float threshold)
static std::unique_ptr< GrFragmentProcessor > make_interpolated_to_dst(std::unique_ptr< GrFragmentProcessor > gradient, const SkGradientShader::Interpolation &interpolation, SkColorSpace *intermediateColorSpace, const GrColorInfo &dstInfo, bool allOpaque)
static std::unique_ptr< GrFragmentProcessor > make_textured_colorizer(const SkPMColor4f *colors, const SkScalar *positions, int count, bool colorsAreOpaque, const SkGradientShader::Interpolation &interpolation, const SkColorSpace *intermediateColorSpace, const SkColorSpace *dstColorSpace, const GrFPArgs &args)
static const int kMaxNumCachedGradientBitmaps
int build_intervals(int inputLength, const SkPMColor4f *inColors, const SkScalar *inPositions, int outputLength, SkPMColor4f *outScales, SkPMColor4f *outBiases, SkScalar *outThresholds)
static constexpr int kMaxLoopingIntervalCount
static constexpr int kMaxLoopingColorCount
static std::unique_ptr< GrFragmentProcessor > make_unrolled_colorizer(int intervalCount, const SkPMColor4f *scale, const SkPMColor4f *bias, SkRect thresholds1_7, SkRect thresholds9_13)
static constexpr int kMaxUnrolledColorCount
static constexpr int kMaxUnrolledIntervalCount
static const SkScalar kLowPrecisionIntervalLimit
static std::unique_ptr< GrFragmentProcessor > make_unrolled_binary_colorizer(const SkPMColor4f *colors, const SkScalar *positions, int count)
static const int kGradientTextureSize
static std::unique_ptr< GrFragmentProcessor > make_looping_colorizer(int intervalCount, const SkPMColor4f *scale, const SkPMColor4f *bias, const SkScalar *thresholds)
static std::unique_ptr< GrFragmentProcessor > make_single_interval_colorizer(const SkPMColor4f &start, const SkPMColor4f &end)
static constexpr bool GrColorTypeIsWiderThan(GrColorType colorType, int n)
kUnpremul_SkAlphaType
SkAlphaType
Definition SkAlphaType.h:26
@ kPremul_SkAlphaType
pixel components are premultiplied by alpha
Definition SkAlphaType.h:29
#define SkASSERT(cond)
Definition SkAssert.h:116
#define SkASSERTF(cond, fmt,...)
Definition SkAssert.h:117
static unsigned mirror(SkFixed fx, int max)
constexpr SkPMColor4f SK_PMColor4fTRANSPARENT
SkColorSpace * sk_srgb_singleton()
SkColorType
Definition SkColorType.h:19
@ kRGBA_F16_SkColorType
pixel with half floats for red, green, blue, alpha;
Definition SkColorType.h:38
@ kRGBA_8888_SkColorType
pixel with 8 bits for red, green, blue, alpha; in 32-bit word
Definition SkColorType.h:24
void SK_SPI SkDebugf(const char format[],...) SK_PRINTF_LIKE(1
std::tuple< GrSurfaceProxyView, GrColorType > GrMakeCachedBitmapProxyView(GrRecordingContext *rContext, const SkBitmap &bitmap, std::string_view label, skgpu::Mipmapped mipmapped)
Definition SkGr.cpp:188
static SkColorType colorType(AImageDecoder *decoder, const AImageDecoderHeaderInfo *headerInfo)
static bool ok(int result)
static int SkNextPow2(int value)
Definition SkMathPriv.h:272
static int SkNextLog2(uint32_t value)
Definition SkMathPriv.h:238
constexpr bool SkIsPow2(T value)
Definition SkMath.h:51
SkRuntimeEffect * SkMakeRuntimeEffect(SkRuntimeEffect::Result(*make)(SkString, const SkRuntimeEffect::Options &), const char *sksl, SkRuntimeEffect::Options options=SkRuntimeEffect::Options{})
static bool SkScalarNearlyZero(SkScalar x, SkScalar tolerance=SK_ScalarNearlyZero)
Definition SkScalar.h:101
static bool SkScalarNearlyEqual(SkScalar x, SkScalar y, SkScalar tolerance=SK_ScalarNearlyZero)
Definition SkScalar.h:107
#define SK_ScalarNearlyZero
Definition SkScalar.h:99
#define SkScalarAbs(x)
Definition SkScalar.h:39
SkTileMode
Definition SkTileMode.h:13
static constexpr int kSkTileModeCount
Definition SkTileMode.h:39
static uint32_t premul(uint32_t color)
SkColorSpace * colorSpace() const
static std::unique_ptr< GrFragmentProcessor > Make(std::unique_ptr< GrFragmentProcessor > child, SkColorSpace *src, SkAlphaType srcAT, SkColorSpace *dst, SkAlphaType dstAT)
void getGradient(const SkPMColor4f *colors, const SkScalar *positions, int count, bool colorsAreOpaque, const SkGradientShader::Interpolation &interpolation, const SkColorSpace *intermediateColorSpace, const SkColorSpace *dstColorSpace, SkColorType colorType, SkAlphaType alphaType, SkBitmap *bitmap)
static std::unique_ptr< GrFragmentProcessor > Make(const SkMatrix &matrix, std::unique_ptr< GrFragmentProcessor > child)
static GrIgnoreOptFlags IgnoreOptFlags(std::unique_ptr< GrFragmentProcessor > child)
Definition GrSkSLFP.h:96
static std::unique_ptr< GrSkSLFP > Make(const SkRuntimeEffect *effect, const char *name, std::unique_ptr< GrFragmentProcessor > inputFP, OptFlags optFlags, Args &&... args)
Definition GrSkSLFP.h:158
static std::unique_ptr< GrFragmentProcessor > Make(GrSurfaceProxyView, SkAlphaType, const SkMatrix &=SkMatrix::I(), GrSamplerState::Filter=GrSamplerState::Filter::kNearest, GrSamplerState::MipmapMode mipmapMode=GrSamplerState::MipmapMode::kNone)
static void AppendInterpolatedToDstStages(SkRasterPipeline *p, SkArenaAlloc *alloc, bool colorsAreOpaque, const Interpolation &interpolation, const SkColorSpace *intermediateColorSpace, const SkColorSpace *dstColorSpace)
const SkMatrix & getGradientMatrix() const
SkTileMode getTileMode() const
static SkMatrix Scale(SkScalar sx, SkScalar sy)
Definition SkMatrix.h:75
uint32_t nextU()
Definition SkRandom.h:42
SkScalar nextUScalar1()
Definition SkRandom.h:101
bool nextBool()
Definition SkRandom.h:117
uint32_t nextULessThan(uint32_t count)
Definition SkRandom.h:93
uint32_t nextRangeU(uint32_t min, uint32_t max)
Definition SkRandom.h:80
static Result MakeForColorFilter(SkString sksl, const Options &)
static Result MakeForShader(SkString sksl, const Options &)
std::tuple< SkMatrix, bool > applyForFragmentProcessor(const SkMatrix &postInv) const
void append(const char text[])
Definition SkString.h:203
void void void appendf(const char format[],...) SK_PRINTF_LIKE(2
Definition SkString.cpp:550
T * get() const
Definition SkRefCnt.h:303
int size() const
Definition SkTArray.h:416
static sk_sp< SkShader > MakeLinear()
float SkScalar
Definition extension.cpp:12
glong glong end
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
GAsyncResult * result
std::unique_ptr< GrFragmentProcessor > MakeGradientFP(const SkGradientBaseShader &shader, const GrFPArgs &args, const SkShaders::MatrixRec &mRec, std::unique_ptr< GrFragmentProcessor > layout, const SkMatrix *overrideMatrix)
static GrFPResult apply_matrix(std::unique_ptr< GrFragmentProcessor > fp, const SkShaders::MatrixRec &rec, const SkMatrix &postInv)
const Scalar scale
bool fNonconstantArrayIndexSupport
sk_sp< SkColorSpace > fIntermediateColorSpace
const float * vec() const
Definition SkColor.h:308
Definition SkM44.h:98
static SKVX_ALWAYS_INLINE Vec Load(const void *ptr)
Definition SkVx.h:109
SKVX_ALWAYS_INLINE void store(void *ptr) const
Definition SkVx.h:112