blob: ab24d21342582fdcb94bc9689a3f02f4cb3098cc [file] [log] [blame]
Michael Ludwig4f94ef62018-09-12 15:22:16 -04001/*
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
Mike Kleinc0bd9f92019-04-23 12:05:21 -05008#include "src/gpu/gradients/GrGradientShader.h"
Michael Ludwig4f94ef62018-09-12 15:22:16 -04009
Mike Kleinc0bd9f92019-04-23 12:05:21 -050010#include "src/gpu/gradients/GrGradientBitmapCache.h"
Michael Ludwig4f94ef62018-09-12 15:22:16 -040011
Robert Phillipsb7bfbc22020-07-01 12:55:01 -040012#include "include/gpu/GrRecordingContext.h"
Brian Osman65b45972021-06-25 15:58:43 +000013#include "src/core/SkRuntimeEffectPriv.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050014#include "src/gpu/GrCaps.h"
Greg Danielf91aeb22019-06-18 09:58:02 -040015#include "src/gpu/GrColor.h"
Brian Salomon4bc0c1f2019-09-30 15:12:27 -040016#include "src/gpu/GrColorInfo.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050017#include "src/gpu/GrRecordingContextPriv.h"
18#include "src/gpu/SkGr.h"
Brian Osman65b45972021-06-25 15:58:43 +000019#include "src/gpu/effects/GrMatrixEffect.h"
20#include "src/gpu/effects/GrSkSLFP.h"
Brian Salomon17473752020-06-19 09:53:50 -040021#include "src/gpu/effects/GrTextureEffect.h"
Michael Ludwiga7914d32018-09-14 09:47:21 -040022
Michael Ludwig72535fb2018-09-28 11:53:32 -040023// Intervals smaller than this (that aren't hard stops) on low-precision-only devices force us to
24// use the textured gradient
25static const SkScalar kLowPrecisionIntervalLimit = 0.01f;
26
Michael Ludwiga7914d32018-09-14 09:47:21 -040027// Each cache entry costs 1K or 2K of RAM. Each bitmap will be 1x256 at either 32bpp or 64bpp.
28static const int kMaxNumCachedGradientBitmaps = 32;
29static const int kGradientTextureSize = 256;
30
31// NOTE: signature takes raw pointers to the color/pos arrays and a count to make it easy for
32// MakeColorizer to transparently take care of hard stops at the end points of the gradient.
Brian Osman021ed512018-10-16 15:19:44 -040033static std::unique_ptr<GrFragmentProcessor> make_textured_colorizer(const SkPMColor4f* colors,
Michael Ludwiga7914d32018-09-14 09:47:21 -040034 const SkScalar* positions, int count, bool premul, const GrFPArgs& args) {
35 static GrGradientBitmapCache gCache(kMaxNumCachedGradientBitmaps, kGradientTextureSize);
36
37 // Use 8888 or F16, depending on the destination config.
38 // TODO: Use 1010102 for opaque gradients, at least if destination is 1010102?
39 SkColorType colorType = kRGBA_8888_SkColorType;
Brian Salomon4bc0c1f2019-09-30 15:12:27 -040040 if (GrColorTypeIsWiderThan(args.fDstColorInfo->colorType(), 8)) {
Greg Daniel7bfc9132019-08-14 14:23:53 -040041 auto f16Format = args.fContext->priv().caps()->getDefaultBackendFormat(
42 GrColorType::kRGBA_F16, GrRenderable::kNo);
43 if (f16Format.isValid()) {
44 colorType = kRGBA_F16_SkColorType;
45 }
Michael Ludwiga7914d32018-09-14 09:47:21 -040046 }
47 SkAlphaType alphaType = premul ? kPremul_SkAlphaType : kUnpremul_SkAlphaType;
48
49 SkBitmap bitmap;
50 gCache.getGradient(colors, positions, count, colorType, alphaType, &bitmap);
51 SkASSERT(1 == bitmap.height() && SkIsPow2(bitmap.width()));
52 SkASSERT(bitmap.isImmutable());
53
Brian Salomon27c42022021-04-28 12:39:21 -040054 auto view = std::get<0>(GrMakeCachedBitmapProxyView(args.fContext, bitmap, GrMipmapped::kNo));
55 if (!view) {
Michael Ludwiga7914d32018-09-14 09:47:21 -040056 SkDebugf("Gradient won't draw. Could not create texture.");
57 return nullptr;
58 }
John Stiles1cf15932020-07-31 16:08:24 -040059
Brian Salomon17473752020-06-19 09:53:50 -040060 auto m = SkMatrix::Scale(view.width(), 1.f);
John Stiles1cf15932020-07-31 16:08:24 -040061 return GrTextureEffect::Make(std::move(view), alphaType, m, GrSamplerState::Filter::kLinear);
Michael Ludwiga7914d32018-09-14 09:47:21 -040062}
Michael Ludwig4f94ef62018-09-12 15:22:16 -040063
Brian Osman65b45972021-06-25 15:58:43 +000064
65static std::unique_ptr<GrFragmentProcessor> make_single_interval_colorizer(const SkPMColor4f& start,
66 const SkPMColor4f& end) {
67 static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
68 uniform half4 start;
69 uniform half4 end;
70 half4 main(float2 coord) {
71 // Clamping and/or wrapping was already handled by the parent shader so the output
72 // color is a simple lerp.
73 return mix(start, end, half(coord.x));
74 }
75 )");
76 return GrSkSLFP::Make(effect, "SingleIntervalColorizer", /*inputFP=*/nullptr,
77 GrSkSLFP::OptFlags::kNone,
78 "start", start,
79 "end", end);
80}
81
82static std::unique_ptr<GrFragmentProcessor> make_dual_interval_colorizer(const SkPMColor4f& c0,
83 const SkPMColor4f& c1,
84 const SkPMColor4f& c2,
85 const SkPMColor4f& c3,
86 float threshold) {
87 static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
John Stiles59b5f522021-10-07 15:27:37 -040088 uniform float4 scale[2];
89 uniform float4 bias[2];
Brian Osman65b45972021-06-25 15:58:43 +000090 uniform half threshold;
91
92 half4 main(float2 coord) {
93 half t = half(coord.x);
94
John Stiles59b5f522021-10-07 15:27:37 -040095 float4 s, b;
Brian Osman65b45972021-06-25 15:58:43 +000096 if (t < threshold) {
John Stiles59b5f522021-10-07 15:27:37 -040097 s = scale[0];
98 b = bias[0];
Brian Osman65b45972021-06-25 15:58:43 +000099 } else {
John Stiles59b5f522021-10-07 15:27:37 -0400100 s = scale[1];
101 b = bias[1];
Brian Osman65b45972021-06-25 15:58:43 +0000102 }
103
John Stiles59b5f522021-10-07 15:27:37 -0400104 return half4(t * s + b);
Brian Osman65b45972021-06-25 15:58:43 +0000105 }
106 )");
107
108 using sk4f = skvx::Vec<4, float>;
109
110 // Derive scale and biases from the 4 colors and threshold
John Stiles59b5f522021-10-07 15:27:37 -0400111 sk4f vc0 = sk4f::Load(c0.vec());
112 sk4f vc1 = sk4f::Load(c1.vec());
113 sk4f vc2 = sk4f::Load(c2.vec());
114 sk4f vc3 = sk4f::Load(c3.vec());
Brian Osman65b45972021-06-25 15:58:43 +0000115
John Stiles59b5f522021-10-07 15:27:37 -0400116 const sk4f scale[2] = {(vc1 - vc0) / threshold,
117 (vc3 - vc2) / (1 - threshold)};
118 const sk4f bias[2] = {vc0,
119 vc2 - threshold * scale[1]};
Brian Osman65b45972021-06-25 15:58:43 +0000120 return GrSkSLFP::Make(effect, "DualIntervalColorizer", /*inputFP=*/nullptr,
121 GrSkSLFP::OptFlags::kNone,
John Stiles59b5f522021-10-07 15:27:37 -0400122 "scale", SkMakeSpan(scale),
123 "bias", SkMakeSpan(bias),
Brian Osman65b45972021-06-25 15:58:43 +0000124 "threshold", threshold);
125}
126
Brian Osman07bf3882021-07-01 12:56:23 -0400127static constexpr int kMaxUnrolledColorCount = 16;
128static constexpr int kMaxUnrolledIntervalCount = 8;
129
130static std::unique_ptr<GrFragmentProcessor> make_unrolled_colorizer(int intervalCount,
131 const SkPMColor4f* scale,
132 const SkPMColor4f* bias,
133 SkRect thresholds1_7,
134 SkRect thresholds9_13) {
135 SkASSERT(intervalCount >= 1 && intervalCount <= 8);
136
137 static SkOnce once[kMaxUnrolledIntervalCount];
138 static sk_sp<SkRuntimeEffect> effects[kMaxUnrolledIntervalCount];
139
140 once[intervalCount - 1]([intervalCount] {
141 SkString sksl;
142
143 // The 7 threshold positions that define the boundaries of the 8 intervals (excluding t = 0,
144 // and t = 1) are packed into two half4s instead of having up to 7 separate scalar uniforms.
145 // For low interval counts, the extra components are ignored in the shader, but the uniform
146 // simplification is worth it. It is assumed thresholds are provided in increasing value,
147 // mapped as:
148 // - thresholds1_7.x = boundary between (0,1) and (2,3) -> 1_2
149 // - .y = boundary between (2,3) and (4,5) -> 3_4
150 // - .z = boundary between (4,5) and (6,7) -> 5_6
151 // - .w = boundary between (6,7) and (8,9) -> 7_8
152 // - thresholds9_13.x = boundary between (8,9) and (10,11) -> 9_10
153 // - .y = boundary between (10,11) and (12,13) -> 11_12
154 // - .z = boundary between (12,13) and (14,15) -> 13_14
155 // - .w = unused
156 sksl.append("uniform half4 thresholds1_7, thresholds9_13;");
157
158 // With the current hardstop detection threshold of 0.00024, the maximum scale and bias
159 // values will be on the order of 4k (since they divide by dt). That is well outside the
160 // precision capabilities of half floats, which can lead to inaccurate gradient calculations
John Stiles63727522021-10-07 11:20:50 -0400161 sksl.appendf("uniform float4 scale[%d];", intervalCount);
162 sksl.appendf("uniform float4 bias[%d];", intervalCount);
Brian Osman07bf3882021-07-01 12:56:23 -0400163
John Stiles63727522021-10-07 11:20:50 -0400164 // Explicit binary search for the proper interval that t falls within. The interval
165 // count checks are constant expressions, which are then optimized to the minimal number
166 // of branches for the specific interval count.
Brian Osman07bf3882021-07-01 12:56:23 -0400167 sksl.appendf(R"(
John Stiles63727522021-10-07 11:20:50 -0400168 half4 main(float2 coord) {
169 half t = half(coord.x);
170 float4 s, b;
Brian Osman07bf3882021-07-01 12:56:23 -0400171 // thresholds1_7.w is mid point for intervals (0,7) and (8,15)
172 if (%d <= 4 || t < thresholds1_7.w) {
173 // thresholds1_7.y is mid point for intervals (0,3) and (4,7)
174 if (%d <= 2 || t < thresholds1_7.y) {
175 // thresholds1_7.x is mid point for intervals (0,1) and (2,3)
176 if (%d <= 1 || t < thresholds1_7.x) {
John Stiles63727522021-10-07 11:20:50 -0400177 %s s = scale[0]; b = bias[0];
Brian Osman07bf3882021-07-01 12:56:23 -0400178 } else {
John Stiles63727522021-10-07 11:20:50 -0400179 %s s = scale[1]; b = bias[1];
Brian Osman07bf3882021-07-01 12:56:23 -0400180 }
181 } else {
182 // thresholds1_7.z is mid point for intervals (4,5) and (6,7)
183 if (%d <= 3 || t < thresholds1_7.z) {
John Stiles63727522021-10-07 11:20:50 -0400184 %s s = scale[2]; b = bias[2];
Brian Osman07bf3882021-07-01 12:56:23 -0400185 } else {
John Stiles63727522021-10-07 11:20:50 -0400186 %s s = scale[3]; b = bias[3];
Brian Osman07bf3882021-07-01 12:56:23 -0400187 }
188 }
189 } else {
190 // thresholds9_13.y is mid point for intervals (8,11) and (12,15)
191 if (%d <= 6 || t < thresholds9_13.y) {
192 // thresholds9_13.x is mid point for intervals (8,9) and (10,11)
193 if (%d <= 5 || t < thresholds9_13.x) {
John Stiles63727522021-10-07 11:20:50 -0400194 %s s = scale[4]; b = bias[4];
Brian Osman07bf3882021-07-01 12:56:23 -0400195 } else {
John Stiles63727522021-10-07 11:20:50 -0400196 %s s = scale[5]; b = bias[5];
Brian Osman07bf3882021-07-01 12:56:23 -0400197 }
198 } else {
199 // thresholds9_13.z is mid point for intervals (12,13) and (14,15)
200 if (%d <= 7 || t < thresholds9_13.z) {
John Stiles63727522021-10-07 11:20:50 -0400201 %s s = scale[6]; b = bias[6];
Brian Osman07bf3882021-07-01 12:56:23 -0400202 } else {
John Stiles63727522021-10-07 11:20:50 -0400203 %s s = scale[7]; b = bias[7];
Brian Osman07bf3882021-07-01 12:56:23 -0400204 }
205 }
206 }
John Stiles63727522021-10-07 11:20:50 -0400207 return t * s + b;
208 }
209 )", intervalCount,
210 intervalCount,
211 intervalCount,
212 (intervalCount <= 0) ? "//" : "",
213 (intervalCount <= 1) ? "//" : "",
214 intervalCount,
215 (intervalCount <= 2) ? "//" : "",
216 (intervalCount <= 3) ? "//" : "",
217 intervalCount,
218 intervalCount,
219 (intervalCount <= 4) ? "//" : "",
220 (intervalCount <= 5) ? "//" : "",
221 intervalCount,
222 (intervalCount <= 6) ? "//" : "",
223 (intervalCount <= 7) ? "//" : "");
Brian Osman07bf3882021-07-01 12:56:23 -0400224
225 auto result = SkRuntimeEffect::MakeForShader(std::move(sksl));
226 SkASSERTF(result.effect, "%s", result.errorText.c_str());
227 effects[intervalCount - 1] = std::move(result.effect);
228 });
229
Brian Osman07bf3882021-07-01 12:56:23 -0400230 return GrSkSLFP::Make(effects[intervalCount - 1], "UnrolledBinaryColorizer",
231 /*inputFP=*/nullptr, GrSkSLFP::OptFlags::kNone,
232 "thresholds1_7", thresholds1_7,
233 "thresholds9_13", thresholds9_13,
John Stiles63727522021-10-07 11:20:50 -0400234 "scale", SkMakeSpan(scale, intervalCount),
235 "bias", SkMakeSpan(bias, intervalCount));
Brian Osman07bf3882021-07-01 12:56:23 -0400236}
237
238static std::unique_ptr<GrFragmentProcessor> make_unrolled_binary_colorizer(
239 const SkPMColor4f* colors, const SkScalar* positions, int count) {
240 // Depending on how the positions resolve into hard stops or regular stops, the number of
241 // intervals specified by the number of colors/positions can change. For instance, a plain
242 // 3 color gradient is two intervals, but a 4 color gradient with a hard stop is also
243 // two intervals. At the most extreme end, an 8 interval gradient made entirely of hard
244 // stops has 16 colors.
245
246 if (count > kMaxUnrolledColorCount) {
247 // Definitely cannot represent this gradient configuration
248 return nullptr;
249 }
250
251 // The raster implementation also uses scales and biases, but since they must be calculated
252 // after the dst color space is applied, it limits our ability to cache their values.
253 SkPMColor4f scales[kMaxUnrolledIntervalCount];
254 SkPMColor4f biases[kMaxUnrolledIntervalCount];
255 SkScalar thresholds[kMaxUnrolledIntervalCount] = { 0 };
256
257 int intervalCount = 0;
258
259 for (int i = 0; i < count - 1; i++) {
260 if (intervalCount >= kMaxUnrolledIntervalCount) {
261 // Already reached kMaxUnrolledIntervalCount, and haven't run out of color stops so this
262 // gradient cannot be represented by this shader.
263 return nullptr;
264 }
265
266 SkScalar t0 = positions[i];
267 SkScalar t1 = positions[i + 1];
268 SkScalar dt = t1 - t0;
269 // If the interval is empty, skip to the next interval. This will automatically create
270 // distinct hard stop intervals as needed. It also protects against malformed gradients
271 // that have repeated hard stops at the very beginning that are effectively unreachable.
272 if (SkScalarNearlyZero(dt)) {
273 continue;
274 }
275
276 auto c0 = Sk4f::Load(colors[i].vec());
277 auto c1 = Sk4f::Load(colors[i + 1].vec());
278
279 auto scale = (c1 - c0) / dt;
280 auto bias = c0 - t0 * scale;
281
282 scale.store(scales + intervalCount);
283 bias.store(biases + intervalCount);
284 thresholds[intervalCount] = t1;
285 intervalCount++;
286 }
287
288 SkRect thresholds1_7 = {thresholds[0], thresholds[1], thresholds[2], thresholds[3]},
289 thresholds9_13 = {thresholds[4], thresholds[5], thresholds[6], 0.0};
290
291 return make_unrolled_colorizer(intervalCount, scales, biases, thresholds1_7, thresholds9_13);
292}
293
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400294// Analyze the shader's color stops and positions and chooses an appropriate colorizer to represent
295// the gradient.
Brian Osman021ed512018-10-16 15:19:44 -0400296static std::unique_ptr<GrFragmentProcessor> make_colorizer(const SkPMColor4f* colors,
Michael Ludwiga7914d32018-09-14 09:47:21 -0400297 const SkScalar* positions, int count, bool premul, const GrFPArgs& args) {
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400298 // If there are hard stops at the beginning or end, the first and/or last color should be
299 // ignored by the colorizer since it should only be used in a clamped border color. By detecting
300 // and removing these stops at the beginning, it makes optimizing the remaining color stops
301 // simpler.
302
Kevin Lubickbe03ef12021-06-16 15:28:00 -0400303 // SkGradientShaderBase guarantees that pos[0] == 0 by adding a default value.
Michael Ludwig0495f7a2018-09-12 15:23:33 -0400304 bool bottomHardStop = SkScalarNearlyEqual(positions[0], positions[1]);
305 // The same is true for pos[end] == 1
Michael Ludwiga7914d32018-09-14 09:47:21 -0400306 bool topHardStop = SkScalarNearlyEqual(positions[count - 2], positions[count - 1]);
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400307
308 int offset = 0;
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400309 if (bottomHardStop) {
310 offset += 1;
311 count--;
312 }
313 if (topHardStop) {
314 count--;
315 }
316
Michael Ludwig0495f7a2018-09-12 15:23:33 -0400317 // Two remaining colors means a single interval from 0 to 1
318 // (but it may have originally been a 3 or 4 color gradient with 1-2 hard stops at the ends)
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400319 if (count == 2) {
Brian Osman65b45972021-06-25 15:58:43 +0000320 return make_single_interval_colorizer(colors[offset], colors[offset + 1]);
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400321 }
322
Michael Ludwig72535fb2018-09-28 11:53:32 -0400323 // Do an early test for the texture fallback to skip all of the other tests for specific
324 // analytic support of the gradient (and compatibility with the hardware), when it's definitely
325 // impossible to use an analytic solution.
Brian Osman07bf3882021-07-01 12:56:23 -0400326 bool tryAnalyticColorizer = count <= kMaxUnrolledColorCount;
Michael Ludwig72535fb2018-09-28 11:53:32 -0400327
328 // The remaining analytic colorizers use scale*t+bias, and the scale/bias values can become
329 // quite large when thresholds are close (but still outside the hardstop limit). If float isn't
330 // 32-bit, output can be incorrect if the thresholds are too close together. However, the
331 // analytic shaders are higher quality, so they can be used with lower precision hardware when
332 // the thresholds are not ill-conditioned.
Robert Phillips9da87e02019-02-04 13:26:26 -0500333 const GrShaderCaps* caps = args.fContext->priv().caps()->shaderCaps();
Michael Ludwig72535fb2018-09-28 11:53:32 -0400334 if (!caps->floatIs32Bits() && tryAnalyticColorizer) {
335 // Could run into problems, check if thresholds are close together (with a limit of .01, so
336 // that scales will be less than 100, which leaves 4 decimals of precision on 16-bit).
337 for (int i = offset; i < count - 1; i++) {
338 SkScalar dt = SkScalarAbs(positions[i] - positions[i + 1]);
339 if (dt <= kLowPrecisionIntervalLimit && dt > SK_ScalarNearlyZero) {
340 tryAnalyticColorizer = false;
341 break;
342 }
343 }
344 }
345
346 if (tryAnalyticColorizer) {
347 if (count == 3) {
348 // Must be a dual interval gradient, where the middle point is at offset+1 and the two
349 // intervals share the middle color stop.
Brian Osman65b45972021-06-25 15:58:43 +0000350 return make_dual_interval_colorizer(colors[offset], colors[offset + 1],
351 colors[offset + 1], colors[offset + 2],
352 positions[offset + 1]);
Michael Ludwig72535fb2018-09-28 11:53:32 -0400353 } else if (count == 4 && SkScalarNearlyEqual(positions[offset + 1],
354 positions[offset + 2])) {
355 // Two separate intervals that join at the same threshold position
Brian Osman65b45972021-06-25 15:58:43 +0000356 return make_dual_interval_colorizer(colors[offset], colors[offset + 1],
357 colors[offset + 2], colors[offset + 3],
358 positions[offset + 1]);
Michael Ludwig72535fb2018-09-28 11:53:32 -0400359 }
360
361 // The single and dual intervals are a specialized case of the unrolled binary search
362 // colorizer which can analytically render gradients of up to 8 intervals (up to 9 or 16
363 // colors depending on how many hard stops are inserted).
Brian Osman07bf3882021-07-01 12:56:23 -0400364 std::unique_ptr<GrFragmentProcessor> unrolled =
365 make_unrolled_binary_colorizer(colors + offset, positions + offset, count);
Michael Ludwig72535fb2018-09-28 11:53:32 -0400366 if (unrolled) {
367 return unrolled;
368 }
369 }
370
371 // Otherwise fall back to a rasterized gradient sampled by a texture, which can handle
372 // arbitrary gradients (the only downside being sampling resolution).
Michael Ludwiga7914d32018-09-14 09:47:21 -0400373 return make_textured_colorizer(colors + offset, positions + offset, count, premul, args);
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400374}
375
Brian Osmanc6804ed2021-06-29 11:11:49 -0400376// This top-level effect implements clamping on the layout coordinate and requires specifying the
377// border colors that are used when outside the clamped boundary. Gradients with the
378// SkTileMode::kClamp should use the colors at their first and last stop (after adding default stops
379// for t=0,t=1) as the border color. This will automatically replicate the edge color, even when
380// there is a hard stop.
381//
382// The SkTileMode::kDecal can be produced by specifying transparent black as the border colors,
383// regardless of the gradient's stop colors.
384static std::unique_ptr<GrFragmentProcessor> make_clamped_gradient(
385 std::unique_ptr<GrFragmentProcessor> colorizer,
386 std::unique_ptr<GrFragmentProcessor> gradLayout,
387 SkPMColor4f leftBorderColor,
388 SkPMColor4f rightBorderColor,
389 bool makePremul,
390 bool colorsAreOpaque) {
391 static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
392 uniform shader colorizer;
393 uniform shader gradLayout;
394
395 uniform half4 leftBorderColor; // t < 0.0
396 uniform half4 rightBorderColor; // t > 1.0
397
398 uniform int makePremul; // specialized
399 uniform int layoutPreservesOpacity; // specialized
400
401 half4 main(float2 coord) {
Brian Osmancbfa34a2021-09-02 09:26:27 -0400402 half4 t = gradLayout.eval(coord);
Brian Osmanc6804ed2021-06-29 11:11:49 -0400403 half4 outColor;
404
405 // If t.x is below 0, use the left border color without invoking the child processor.
406 // If any t.x is above 1, use the right border color. Otherwise, t is in the [0, 1]
407 // range assumed by the colorizer FP, so delegate to the child processor.
408 if (!bool(layoutPreservesOpacity) && t.y < 0) {
409 // layout has rejected this fragment (rely on sksl to remove this branch if the
410 // layout FP preserves opacity is false)
411 outColor = half4(0);
412 } else if (t.x < 0) {
413 outColor = leftBorderColor;
414 } else if (t.x > 1.0) {
415 outColor = rightBorderColor;
416 } else {
417 // Always sample from (x, 0), discarding y, since the layout FP can use y as a
418 // side-channel.
Brian Osmancbfa34a2021-09-02 09:26:27 -0400419 outColor = colorizer.eval(t.x0);
Brian Osmanc6804ed2021-06-29 11:11:49 -0400420 }
421 if (bool(makePremul)) {
422 outColor.rgb *= outColor.a;
423 }
424 return outColor;
425 }
426 )");
427
428 // If the layout does not preserve opacity, remove the opaque optimization,
429 // but otherwise respect the provided color opacity state (which should take
430 // into account the opacity of the border colors).
431 bool layoutPreservesOpacity = gradLayout->preservesOpaqueInput();
432 GrSkSLFP::OptFlags optFlags = GrSkSLFP::OptFlags::kCompatibleWithCoverageAsAlpha;
433 if (colorsAreOpaque && layoutPreservesOpacity) {
434 optFlags |= GrSkSLFP::OptFlags::kPreservesOpaqueInput;
435 }
436
437 return GrSkSLFP::Make(effect, "ClampedGradient", /*inputFP=*/nullptr, optFlags,
438 "colorizer", GrSkSLFP::IgnoreOptFlags(std::move(colorizer)),
439 "gradLayout", GrSkSLFP::IgnoreOptFlags(std::move(gradLayout)),
440 "leftBorderColor", leftBorderColor,
441 "rightBorderColor", rightBorderColor,
442 "makePremul", GrSkSLFP::Specialize<int>(makePremul),
443 "layoutPreservesOpacity",
444 GrSkSLFP::Specialize<int>(layoutPreservesOpacity));
445}
446
447static std::unique_ptr<GrFragmentProcessor> make_tiled_gradient(
448 const GrFPArgs& args,
449 std::unique_ptr<GrFragmentProcessor> colorizer,
450 std::unique_ptr<GrFragmentProcessor> gradLayout,
451 bool mirror,
452 bool makePremul,
453 bool colorsAreOpaque) {
454 static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
455 uniform shader colorizer;
456 uniform shader gradLayout;
457
458 uniform int mirror; // specialized
459 uniform int makePremul; // specialized
460 uniform int layoutPreservesOpacity; // specialized
461 uniform int useFloorAbsWorkaround; // specialized
462
463 half4 main(float2 coord) {
Brian Osmancbfa34a2021-09-02 09:26:27 -0400464 half4 t = gradLayout.eval(coord);
Brian Osmanc6804ed2021-06-29 11:11:49 -0400465
466 if (!bool(layoutPreservesOpacity) && t.y < 0) {
467 // layout has rejected this fragment (rely on sksl to remove this branch if the
468 // layout FP preserves opacity is false)
469 return half4(0);
470 } else {
471 if (bool(mirror)) {
472 half t_1 = t.x - 1;
473 half tiled_t = t_1 - 2 * floor(t_1 * 0.5) - 1;
474 if (bool(useFloorAbsWorkaround)) {
475 // At this point the expected value of tiled_t should between -1 and 1, so
476 // this clamp has no effect other than to break up the floor and abs calls
477 // and make sure the compiler doesn't merge them back together.
478 tiled_t = clamp(tiled_t, -1, 1);
479 }
480 t.x = abs(tiled_t);
481 } else {
482 // Simple repeat mode
483 t.x = fract(t.x);
484 }
485
486 // Always sample from (x, 0), discarding y, since the layout FP can use y as a
487 // side-channel.
Brian Osmancbfa34a2021-09-02 09:26:27 -0400488 half4 outColor = colorizer.eval(t.x0);
Brian Osmanc6804ed2021-06-29 11:11:49 -0400489 if (bool(makePremul)) {
490 outColor.rgb *= outColor.a;
491 }
492 return outColor;
493 }
494 }
495 )");
496
497 // If the layout does not preserve opacity, remove the opaque optimization,
498 // but otherwise respect the provided color opacity state (which should take
499 // into account the opacity of the border colors).
500 bool layoutPreservesOpacity = gradLayout->preservesOpaqueInput();
501 GrSkSLFP::OptFlags optFlags = GrSkSLFP::OptFlags::kCompatibleWithCoverageAsAlpha;
502 if (colorsAreOpaque && layoutPreservesOpacity) {
503 optFlags |= GrSkSLFP::OptFlags::kPreservesOpaqueInput;
504 }
505 const bool useFloorAbsWorkaround =
506 args.fContext->priv().caps()->shaderCaps()->mustDoOpBetweenFloorAndAbs();
507
508 return GrSkSLFP::Make(effect, "TiledGradient", /*inputFP=*/nullptr, optFlags,
509 "colorizer", GrSkSLFP::IgnoreOptFlags(std::move(colorizer)),
510 "gradLayout", GrSkSLFP::IgnoreOptFlags(std::move(gradLayout)),
511 "mirror", GrSkSLFP::Specialize<int>(mirror),
512 "makePremul", GrSkSLFP::Specialize<int>(makePremul),
513 "layoutPreservesOpacity",
514 GrSkSLFP::Specialize<int>(layoutPreservesOpacity),
515 "useFloorAbsWorkaround",
516 GrSkSLFP::Specialize<int>(useFloorAbsWorkaround));
517}
518
Michael Ludwig672c4762020-08-05 17:28:42 -0400519// Combines the colorizer and layout with an appropriately configured top-level effect based on the
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400520// gradient's tile mode
Brian Osman65b45972021-06-25 15:58:43 +0000521static std::unique_ptr<GrFragmentProcessor> make_gradient(
522 const SkGradientShaderBase& shader,
523 const GrFPArgs& args,
524 std::unique_ptr<GrFragmentProcessor> layout,
525 const SkMatrix* overrideMatrix = nullptr) {
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400526 // No shader is possible if a layout couldn't be created, e.g. a layout-specific Make() returned
527 // null.
528 if (layout == nullptr) {
529 return nullptr;
530 }
531
Brian Osman65b45972021-06-25 15:58:43 +0000532 // Wrap the layout in a matrix effect to apply the gradient's matrix:
533 SkMatrix matrix;
534 if (!shader.totalLocalMatrix(args.fPreLocalMatrix)->invert(&matrix)) {
535 return nullptr;
536 }
537 // Some two-point conical gradients use a custom matrix here
538 matrix.postConcat(overrideMatrix ? *overrideMatrix : shader.getGradientMatrix());
539 layout = GrMatrixEffect::Make(matrix, std::move(layout));
540
Brian Osman021ed512018-10-16 15:19:44 -0400541 // Convert all colors into destination space and into SkPMColor4fs, and handle
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400542 // premul issues depending on the interpolation mode
543 bool inputPremul = shader.getGradFlags() & SkGradientShader::kInterpolateColorsInPremul_Flag;
Michael Ludwigb96cba32018-09-14 13:59:24 -0400544 bool allOpaque = true;
Brian Osman021ed512018-10-16 15:19:44 -0400545 SkAutoSTMalloc<4, SkPMColor4f> colors(shader.fColorCount);
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400546 SkColor4fXformer xformedColors(shader.fOrigColors4f, shader.fColorCount,
Brian Salomon4bc0c1f2019-09-30 15:12:27 -0400547 shader.fColorSpace.get(), args.fDstColorInfo->colorSpace());
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400548 for (int i = 0; i < shader.fColorCount; i++) {
Brian Osman021ed512018-10-16 15:19:44 -0400549 const SkColor4f& upmColor = xformedColors.fColors[i];
550 colors[i] = inputPremul ? upmColor.premul()
551 : SkPMColor4f{ upmColor.fR, upmColor.fG, upmColor.fB, upmColor.fA };
552 if (allOpaque && !SkScalarNearlyEqual(colors[i].fA, 1.0)) {
Michael Ludwigb96cba32018-09-14 13:59:24 -0400553 allOpaque = false;
554 }
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400555 }
556
Michael Ludwig0495f7a2018-09-12 15:23:33 -0400557 // SkGradientShader stores positions implicitly when they are evenly spaced, but the getPos()
558 // implementation performs a branch for every position index. Since the shader conversion
559 // requires lots of position tests, calculate all of the positions up front if needed.
560 SkTArray<SkScalar, true> implicitPos;
561 SkScalar* positions;
562 if (shader.fOrigPos) {
563 positions = shader.fOrigPos;
564 } else {
John Stilesf4bda742020-10-14 16:57:41 -0400565 implicitPos.reserve_back(shader.fColorCount);
Mike Kleind3ed3012018-11-06 19:23:08 -0500566 SkScalar posScale = SK_Scalar1 / (shader.fColorCount - 1);
Michael Ludwig0495f7a2018-09-12 15:23:33 -0400567 for (int i = 0 ; i < shader.fColorCount; i++) {
568 implicitPos.push_back(SkIntToScalar(i) * posScale);
569 }
570 positions = implicitPos.begin();
571 }
572
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400573 // All gradients are colorized the same way, regardless of layout
Michael Ludwiga7914d32018-09-14 09:47:21 -0400574 std::unique_ptr<GrFragmentProcessor> colorizer = make_colorizer(
575 colors.get(), positions, shader.fColorCount, inputPremul, args);
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400576 if (colorizer == nullptr) {
577 return nullptr;
578 }
579
Michael Ludwig672c4762020-08-05 17:28:42 -0400580 // The top-level effect has to export premul colors, but under certain conditions it doesn't
581 // need to do anything to achieve that: i.e. its interpolating already premul colors
582 // (inputPremul) or all the colors have a = 1, in which case premul is a no op. Note that this
583 // allOpaque check is more permissive than SkGradientShaderBase's isOpaque(), since we can
584 // optimize away the make-premul op for two point conical gradients (which report false for
585 // isOpaque).
Michael Ludwigb96cba32018-09-14 13:59:24 -0400586 bool makePremul = !inputPremul && !allOpaque;
587
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400588 // All tile modes are supported (unless something was added to SkShader)
Michael Ludwig672c4762020-08-05 17:28:42 -0400589 std::unique_ptr<GrFragmentProcessor> gradient;
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400590 switch(shader.getTileMode()) {
Mike Reedfae8fce2019-04-03 10:27:45 -0400591 case SkTileMode::kRepeat:
Brian Osmanc6804ed2021-06-29 11:11:49 -0400592 gradient = make_tiled_gradient(args, std::move(colorizer), std::move(layout),
593 /* mirror */ false, makePremul, allOpaque);
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400594 break;
Mike Reedfae8fce2019-04-03 10:27:45 -0400595 case SkTileMode::kMirror:
Brian Osmanc6804ed2021-06-29 11:11:49 -0400596 gradient = make_tiled_gradient(args, std::move(colorizer), std::move(layout),
597 /* mirror */ true, makePremul, allOpaque);
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400598 break;
Mike Reedfae8fce2019-04-03 10:27:45 -0400599 case SkTileMode::kClamp:
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400600 // For the clamped mode, the border colors are the first and last colors, corresponding
601 // to t=0 and t=1, because SkGradientShaderBase enforces that by adding color stops as
602 // appropriate. If there is a hard stop, this grabs the expected outer colors for the
603 // border.
Brian Osmanc6804ed2021-06-29 11:11:49 -0400604 gradient = make_clamped_gradient(std::move(colorizer), std::move(layout),
605 colors[0], colors[shader.fColorCount - 1],
606 makePremul, allOpaque);
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400607 break;
Mike Reedfae8fce2019-04-03 10:27:45 -0400608 case SkTileMode::kDecal:
Michael Ludwigb96cba32018-09-14 13:59:24 -0400609 // Even if the gradient colors are opaque, the decal borders are transparent so
610 // disable that optimization
Brian Osmanc6804ed2021-06-29 11:11:49 -0400611 gradient = make_clamped_gradient(std::move(colorizer), std::move(layout),
612 SK_PMColor4fTRANSPARENT, SK_PMColor4fTRANSPARENT,
613 makePremul, /* colorsAreOpaque */ false);
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400614 break;
615 }
616
Michael Ludwig672c4762020-08-05 17:28:42 -0400617 if (gradient == nullptr) {
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400618 // Unexpected tile mode
619 return nullptr;
620 }
Brian Salomonc0d79e52019-04-10 15:02:11 -0400621 if (args.fInputColorIsOpaque) {
Brian Osman55cbc752021-05-27 11:57:13 -0400622 // If the input alpha is known to be 1, we don't need to take the kSrcIn path. This is
623 // just an optimization. However, we can't just return 'gradient' here. We need to actually
624 // inhibit the coverage-as-alpha optimization, or we'll fail to incorporate AA correctly.
625 // The OverrideInput FP happens to do that, so wrap our fp in one of those. The gradient FP
626 // doesn't actually use the input color at all, so the overridden input is irrelevant.
Michael Ludwig672c4762020-08-05 17:28:42 -0400627 return GrFragmentProcessor::OverrideInput(std::move(gradient), SK_PMColor4fWHITE, false);
Brian Salomonc0d79e52019-04-10 15:02:11 -0400628 }
Michael Ludwig672c4762020-08-05 17:28:42 -0400629 return GrFragmentProcessor::MulChildByInputAlpha(std::move(gradient));
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400630}
631
632namespace GrGradientShader {
633
634std::unique_ptr<GrFragmentProcessor> MakeLinear(const SkLinearGradient& shader,
635 const GrFPArgs& args) {
Brian Osman65b45972021-06-25 15:58:43 +0000636 // We add a tiny delta to t. When gradient stops are set up so that a hard stop in a vertically
637 // or horizontally oriented gradient falls exactly at a column or row of pixel centers we can
638 // we can get slightly different interpolated t values along the column/row. By adding the delta
639 // we will consistently get the color to the "right" of the stop. Of course if the hard stop
640 // falls at X.5 - delta then we still could get inconsistent results, but that is much less
641 // likely. crbug.com/938592
642 // If/when we add filtering of the gradient this can be removed.
643 static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
644 half4 main(float2 coord) {
645 return half4(half(coord.x) + 0.00001, 1, 0, 0); // y = 1 for always valid
646 }
647 )");
648 // The linear gradient never rejects a pixel so it doesn't change opacity
649 auto fp = GrSkSLFP::Make(effect, "LinearLayout", /*inputFP=*/nullptr,
650 GrSkSLFP::OptFlags::kPreservesOpaqueInput);
651 return make_gradient(shader, args, std::move(fp));
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400652}
653
Michael Ludwig4089df82018-09-12 15:22:37 -0400654std::unique_ptr<GrFragmentProcessor> MakeRadial(const SkRadialGradient& shader,
655 const GrFPArgs& args) {
Brian Osman65b45972021-06-25 15:58:43 +0000656 static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
657 half4 main(float2 coord) {
658 return half4(half(length(coord)), 1, 0, 0); // y = 1 for always valid
659 }
660 )");
661 // The radial gradient never rejects a pixel so it doesn't change opacity
662 auto fp = GrSkSLFP::Make(effect, "RadialLayout", /*inputFP=*/nullptr,
663 GrSkSLFP::OptFlags::kPreservesOpaqueInput);
664 return make_gradient(shader, args, std::move(fp));
Michael Ludwig4089df82018-09-12 15:22:37 -0400665}
666
Michael Ludwig24d438b2018-09-12 15:22:50 -0400667std::unique_ptr<GrFragmentProcessor> MakeSweep(const SkSweepGradient& shader,
668 const GrFPArgs& args) {
Brian Osman65b45972021-06-25 15:58:43 +0000669 // On some devices they incorrectly implement atan2(y,x) as atan(y/x). In actuality it is
670 // atan2(y,x) = 2 * atan(y / (sqrt(x^2 + y^2) + x)). So to work around this we pass in (sqrt(x^2
671 // + y^2) + x) as the second parameter to atan2 in these cases. We let the device handle the
672 // undefined behavior of the second paramenter being 0 instead of doing the divide ourselves and
673 // using atan instead.
674 int useAtanWorkaround =
675 args.fContext->priv().caps()->shaderCaps()->atan2ImplementedAsAtanYOverX();
676 static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
677 uniform half bias;
678 uniform half scale;
679 uniform int useAtanWorkaround; // specialized
680
681 half4 main(float2 coord) {
682 half angle = bool(useAtanWorkaround)
683 ? half(2 * atan(-coord.y, length(coord) - coord.x))
684 : half(atan(-coord.y, -coord.x));
685
686 // 0.1591549430918 is 1/(2*pi), used since atan returns values [-pi, pi]
687 half t = (angle * 0.1591549430918 + 0.5 + bias) * scale;
688 return half4(t, 1, 0, 0); // y = 1 for always valid
689 }
690 )");
691 // The sweep gradient never rejects a pixel so it doesn't change opacity
692 auto fp = GrSkSLFP::Make(effect, "SweepLayout", /*inputFP=*/nullptr,
693 GrSkSLFP::OptFlags::kPreservesOpaqueInput,
694 "bias", shader.getTBias(),
695 "scale", shader.getTScale(),
696 "useAtanWorkaround", GrSkSLFP::Specialize(useAtanWorkaround));
697 return make_gradient(shader, args, std::move(fp));
Michael Ludwig24d438b2018-09-12 15:22:50 -0400698}
699
Michael Ludwig8f685082018-09-12 15:23:01 -0400700std::unique_ptr<GrFragmentProcessor> MakeConical(const SkTwoPointConicalGradient& shader,
701 const GrFPArgs& args) {
Brian Osman65b45972021-06-25 15:58:43 +0000702 // The 2 point conical gradient can reject a pixel so it does change opacity even if the input
703 // was opaque. Thus, all of these layout FPs disable that optimization.
704 std::unique_ptr<GrFragmentProcessor> fp;
705 SkTLazy<SkMatrix> matrix;
706 switch (shader.getType()) {
707 case SkTwoPointConicalGradient::Type::kStrip: {
708 static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
709 uniform half r0_2;
710 half4 main(float2 p) {
711 half v = 1; // validation flag, set to negative to discard fragment later
712 float t = r0_2 - p.y * p.y;
713 if (t >= 0) {
714 t = p.x + sqrt(t);
715 } else {
716 v = -1;
717 }
718 return half4(half(t), v, 0, 0);
719 }
720 )");
721 float r0 = shader.getStartRadius() / shader.getCenterX1();
722 fp = GrSkSLFP::Make(effect, "TwoPointConicalStripLayout", /*inputFP=*/nullptr,
723 GrSkSLFP::OptFlags::kNone,
724 "r0_2", r0 * r0);
725 } break;
726
727 case SkTwoPointConicalGradient::Type::kRadial: {
728 static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
729 uniform half r0;
730 uniform half lengthScale;
731 half4 main(float2 p) {
732 half v = 1; // validation flag, set to negative to discard fragment later
733 float t = length(p) * lengthScale - r0;
734 return half4(half(t), v, 0, 0);
735 }
736 )");
737 float dr = shader.getDiffRadius();
738 float r0 = shader.getStartRadius() / dr;
739 bool isRadiusIncreasing = dr >= 0;
740 fp = GrSkSLFP::Make(effect, "TwoPointConicalRadialLayout", /*inputFP=*/nullptr,
741 GrSkSLFP::OptFlags::kNone,
742 "r0", r0,
743 "lengthScale", isRadiusIncreasing ? 1.0f : -1.0f);
744
745 // GPU radial matrix is different from the original matrix, since we map the diff radius
746 // to have |dr| = 1, so manually compute the final gradient matrix here.
747
748 // Map center to (0, 0)
749 matrix.set(SkMatrix::Translate(-shader.getStartCenter().fX,
750 -shader.getStartCenter().fY));
751 // scale |diffRadius| to 1
752 matrix->postScale(1 / dr, 1 / dr);
753 } break;
754
755 case SkTwoPointConicalGradient::Type::kFocal: {
756 static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
757 // Optimization flags, all specialized:
758 uniform int isRadiusIncreasing;
759 uniform int isFocalOnCircle;
760 uniform int isWellBehaved;
761 uniform int isSwapped;
762 uniform int isNativelyFocal;
763
764 uniform half invR1; // 1/r1
765 uniform half fx; // focalX = r0/(r0-r1)
766
767 half4 main(float2 p) {
768 float t = -1;
769 half v = 1; // validation flag, set to negative to discard fragment later
770
771 float x_t = -1;
772 if (bool(isFocalOnCircle)) {
773 x_t = dot(p, p) / p.x;
774 } else if (bool(isWellBehaved)) {
775 x_t = length(p) - p.x * invR1;
776 } else {
777 float temp = p.x * p.x - p.y * p.y;
778
779 // Only do sqrt if temp >= 0; this is significantly slower than checking
780 // temp >= 0 in the if statement that checks r(t) >= 0. But GPU may break if
781 // we sqrt a negative float. (Although I havevn't observed that on any
782 // devices so far, and the old approach also does sqrt negative value
783 // without a check.) If the performance is really critical, maybe we should
784 // just compute the area where temp and x_t are always valid and drop all
785 // these ifs.
786 if (temp >= 0) {
787 if (bool(isSwapped) || !bool(isRadiusIncreasing)) {
788 x_t = -sqrt(temp) - p.x * invR1;
789 } else {
790 x_t = sqrt(temp) - p.x * invR1;
791 }
792 }
793 }
794
795 // The final calculation of t from x_t has lots of static optimizations but only
796 // do them when x_t is positive (which can be assumed true if isWellBehaved is
797 // true)
798 if (!bool(isWellBehaved)) {
799 // This will still calculate t even though it will be ignored later in the
800 // pipeline to avoid a branch
801 if (x_t <= 0.0) {
802 v = -1;
803 }
804 }
805 if (bool(isRadiusIncreasing)) {
806 if (bool(isNativelyFocal)) {
807 t = x_t;
808 } else {
809 t = x_t + fx;
810 }
811 } else {
812 if (bool(isNativelyFocal)) {
813 t = -x_t;
814 } else {
815 t = -x_t + fx;
816 }
817 }
818
819 if (bool(isSwapped)) {
820 t = 1 - t;
821 }
822
823 return half4(half(t), v, 0, 0);
824 }
825 )");
826
827 const SkTwoPointConicalGradient::FocalData& focalData = shader.getFocalData();
828 bool isRadiusIncreasing = (1 - focalData.fFocalX) > 0,
829 isFocalOnCircle = focalData.isFocalOnCircle(),
830 isWellBehaved = focalData.isWellBehaved(),
831 isSwapped = focalData.isSwapped(),
832 isNativelyFocal = focalData.isNativelyFocal();
833
834 fp = GrSkSLFP::Make(effect, "TwoPointConicalFocalLayout", /*inputFP=*/nullptr,
835 GrSkSLFP::OptFlags::kNone,
836 "isRadiusIncreasing", GrSkSLFP::Specialize<int>(isRadiusIncreasing),
837 "isFocalOnCircle", GrSkSLFP::Specialize<int>(isFocalOnCircle),
838 "isWellBehaved", GrSkSLFP::Specialize<int>(isWellBehaved),
839 "isSwapped", GrSkSLFP::Specialize<int>(isSwapped),
840 "isNativelyFocal", GrSkSLFP::Specialize<int>(isNativelyFocal),
841 "invR1", 1.0f / focalData.fR1,
842 "fx", focalData.fFocalX);
843 } break;
844 }
845 return make_gradient(shader, args, std::move(fp), matrix.getMaybeNull());
Michael Ludwig8f685082018-09-12 15:23:01 -0400846}
847
Michael Ludwig7f8c5242018-09-14 15:07:55 -0400848#if GR_TEST_UTILS
849RandomParams::RandomParams(SkRandom* random) {
850 // Set color count to min of 2 so that we don't trigger the const color optimization and make
851 // a non-gradient processor.
852 fColorCount = random->nextRangeU(2, kMaxRandomGradientColors);
853 fUseColors4f = random->nextBool();
854
855 // if one color, omit stops, otherwise randomly decide whether or not to
856 if (fColorCount == 1 || (fColorCount >= 2 && random->nextBool())) {
857 fStops = nullptr;
858 } else {
859 fStops = fStopStorage;
860 }
861
862 // if using SkColor4f, attach a random (possibly null) color space (with linear gamma)
863 if (fUseColors4f) {
864 fColorSpace = GrTest::TestColorSpace(random);
865 }
866
867 SkScalar stop = 0.f;
868 for (int i = 0; i < fColorCount; ++i) {
869 if (fUseColors4f) {
870 fColors4f[i].fR = random->nextUScalar1();
871 fColors4f[i].fG = random->nextUScalar1();
872 fColors4f[i].fB = random->nextUScalar1();
873 fColors4f[i].fA = random->nextUScalar1();
874 } else {
875 fColors[i] = random->nextU();
876 }
877 if (fStops) {
878 fStops[i] = stop;
879 stop = i < fColorCount - 1 ? stop + random->nextUScalar1() * (1.f - stop) : 1.f;
880 }
881 }
Mike Reedfae8fce2019-04-03 10:27:45 -0400882 fTileMode = static_cast<SkTileMode>(random->nextULessThan(kSkTileModeCount));
Michael Ludwig7f8c5242018-09-14 15:07:55 -0400883}
884#endif
885
John Stilesa6841be2020-08-06 14:11:56 -0400886} // namespace GrGradientShader