blob: 201aa7cd64182b159de658d2f113ed98b57c4d61 [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"(
88 uniform float4 scale01;
89 uniform float4 bias01;
90 uniform float4 scale23;
91 uniform float4 bias23;
92 uniform half threshold;
93
94 half4 main(float2 coord) {
95 half t = half(coord.x);
96
97 float4 scale, bias;
98 if (t < threshold) {
99 scale = scale01;
100 bias = bias01;
101 } else {
102 scale = scale23;
103 bias = bias23;
104 }
105
106 return half4(t * scale + bias);
107 }
108 )");
109
110 using sk4f = skvx::Vec<4, float>;
111
112 // Derive scale and biases from the 4 colors and threshold
113 auto vc0 = sk4f::Load(c0.vec());
114 auto vc1 = sk4f::Load(c1.vec());
115 auto scale01 = (vc1 - vc0) / threshold;
116 // bias01 = c0
117
118 auto vc2 = sk4f::Load(c2.vec());
119 auto vc3 = sk4f::Load(c3.vec());
120 auto scale23 = (vc3 - vc2) / (1 - threshold);
121 auto bias23 = vc2 - threshold * scale23;
122
123 return GrSkSLFP::Make(effect, "DualIntervalColorizer", /*inputFP=*/nullptr,
124 GrSkSLFP::OptFlags::kNone,
125 "scale01", scale01,
126 "bias01", c0,
127 "scale23", scale23,
128 "bias23", bias23,
129 "threshold", threshold);
130}
131
Brian Osman07bf3882021-07-01 12:56:23 -0400132static constexpr int kMaxUnrolledColorCount = 16;
133static constexpr int kMaxUnrolledIntervalCount = 8;
134
135static std::unique_ptr<GrFragmentProcessor> make_unrolled_colorizer(int intervalCount,
136 const SkPMColor4f* scale,
137 const SkPMColor4f* bias,
138 SkRect thresholds1_7,
139 SkRect thresholds9_13) {
140 SkASSERT(intervalCount >= 1 && intervalCount <= 8);
141
142 static SkOnce once[kMaxUnrolledIntervalCount];
143 static sk_sp<SkRuntimeEffect> effects[kMaxUnrolledIntervalCount];
144
145 once[intervalCount - 1]([intervalCount] {
146 SkString sksl;
147
148 // The 7 threshold positions that define the boundaries of the 8 intervals (excluding t = 0,
149 // and t = 1) are packed into two half4s instead of having up to 7 separate scalar uniforms.
150 // For low interval counts, the extra components are ignored in the shader, but the uniform
151 // simplification is worth it. It is assumed thresholds are provided in increasing value,
152 // mapped as:
153 // - thresholds1_7.x = boundary between (0,1) and (2,3) -> 1_2
154 // - .y = boundary between (2,3) and (4,5) -> 3_4
155 // - .z = boundary between (4,5) and (6,7) -> 5_6
156 // - .w = boundary between (6,7) and (8,9) -> 7_8
157 // - thresholds9_13.x = boundary between (8,9) and (10,11) -> 9_10
158 // - .y = boundary between (10,11) and (12,13) -> 11_12
159 // - .z = boundary between (12,13) and (14,15) -> 13_14
160 // - .w = unused
161 sksl.append("uniform half4 thresholds1_7, thresholds9_13;");
162
163 // With the current hardstop detection threshold of 0.00024, the maximum scale and bias
164 // values will be on the order of 4k (since they divide by dt). That is well outside the
165 // precision capabilities of half floats, which can lead to inaccurate gradient calculations
John Stiles63727522021-10-07 11:20:50 -0400166 sksl.appendf("uniform float4 scale[%d];", intervalCount);
167 sksl.appendf("uniform float4 bias[%d];", intervalCount);
Brian Osman07bf3882021-07-01 12:56:23 -0400168
John Stiles63727522021-10-07 11:20:50 -0400169 // Explicit binary search for the proper interval that t falls within. The interval
170 // count checks are constant expressions, which are then optimized to the minimal number
171 // of branches for the specific interval count.
Brian Osman07bf3882021-07-01 12:56:23 -0400172 sksl.appendf(R"(
John Stiles63727522021-10-07 11:20:50 -0400173 half4 main(float2 coord) {
174 half t = half(coord.x);
175 float4 s, b;
Brian Osman07bf3882021-07-01 12:56:23 -0400176 // thresholds1_7.w is mid point for intervals (0,7) and (8,15)
177 if (%d <= 4 || t < thresholds1_7.w) {
178 // thresholds1_7.y is mid point for intervals (0,3) and (4,7)
179 if (%d <= 2 || t < thresholds1_7.y) {
180 // thresholds1_7.x is mid point for intervals (0,1) and (2,3)
181 if (%d <= 1 || t < thresholds1_7.x) {
John Stiles63727522021-10-07 11:20:50 -0400182 %s s = scale[0]; b = bias[0];
Brian Osman07bf3882021-07-01 12:56:23 -0400183 } else {
John Stiles63727522021-10-07 11:20:50 -0400184 %s s = scale[1]; b = bias[1];
Brian Osman07bf3882021-07-01 12:56:23 -0400185 }
186 } else {
187 // thresholds1_7.z is mid point for intervals (4,5) and (6,7)
188 if (%d <= 3 || t < thresholds1_7.z) {
John Stiles63727522021-10-07 11:20:50 -0400189 %s s = scale[2]; b = bias[2];
Brian Osman07bf3882021-07-01 12:56:23 -0400190 } else {
John Stiles63727522021-10-07 11:20:50 -0400191 %s s = scale[3]; b = bias[3];
Brian Osman07bf3882021-07-01 12:56:23 -0400192 }
193 }
194 } else {
195 // thresholds9_13.y is mid point for intervals (8,11) and (12,15)
196 if (%d <= 6 || t < thresholds9_13.y) {
197 // thresholds9_13.x is mid point for intervals (8,9) and (10,11)
198 if (%d <= 5 || t < thresholds9_13.x) {
John Stiles63727522021-10-07 11:20:50 -0400199 %s s = scale[4]; b = bias[4];
Brian Osman07bf3882021-07-01 12:56:23 -0400200 } else {
John Stiles63727522021-10-07 11:20:50 -0400201 %s s = scale[5]; b = bias[5];
Brian Osman07bf3882021-07-01 12:56:23 -0400202 }
203 } else {
204 // thresholds9_13.z is mid point for intervals (12,13) and (14,15)
205 if (%d <= 7 || t < thresholds9_13.z) {
John Stiles63727522021-10-07 11:20:50 -0400206 %s s = scale[6]; b = bias[6];
Brian Osman07bf3882021-07-01 12:56:23 -0400207 } else {
John Stiles63727522021-10-07 11:20:50 -0400208 %s s = scale[7]; b = bias[7];
Brian Osman07bf3882021-07-01 12:56:23 -0400209 }
210 }
211 }
John Stiles63727522021-10-07 11:20:50 -0400212 return t * s + b;
213 }
214 )", intervalCount,
215 intervalCount,
216 intervalCount,
217 (intervalCount <= 0) ? "//" : "",
218 (intervalCount <= 1) ? "//" : "",
219 intervalCount,
220 (intervalCount <= 2) ? "//" : "",
221 (intervalCount <= 3) ? "//" : "",
222 intervalCount,
223 intervalCount,
224 (intervalCount <= 4) ? "//" : "",
225 (intervalCount <= 5) ? "//" : "",
226 intervalCount,
227 (intervalCount <= 6) ? "//" : "",
228 (intervalCount <= 7) ? "//" : "");
Brian Osman07bf3882021-07-01 12:56:23 -0400229
230 auto result = SkRuntimeEffect::MakeForShader(std::move(sksl));
231 SkASSERTF(result.effect, "%s", result.errorText.c_str());
232 effects[intervalCount - 1] = std::move(result.effect);
233 });
234
Brian Osman07bf3882021-07-01 12:56:23 -0400235 return GrSkSLFP::Make(effects[intervalCount - 1], "UnrolledBinaryColorizer",
236 /*inputFP=*/nullptr, GrSkSLFP::OptFlags::kNone,
237 "thresholds1_7", thresholds1_7,
238 "thresholds9_13", thresholds9_13,
John Stiles63727522021-10-07 11:20:50 -0400239 "scale", SkMakeSpan(scale, intervalCount),
240 "bias", SkMakeSpan(bias, intervalCount));
Brian Osman07bf3882021-07-01 12:56:23 -0400241}
242
243static std::unique_ptr<GrFragmentProcessor> make_unrolled_binary_colorizer(
244 const SkPMColor4f* colors, const SkScalar* positions, int count) {
245 // Depending on how the positions resolve into hard stops or regular stops, the number of
246 // intervals specified by the number of colors/positions can change. For instance, a plain
247 // 3 color gradient is two intervals, but a 4 color gradient with a hard stop is also
248 // two intervals. At the most extreme end, an 8 interval gradient made entirely of hard
249 // stops has 16 colors.
250
251 if (count > kMaxUnrolledColorCount) {
252 // Definitely cannot represent this gradient configuration
253 return nullptr;
254 }
255
256 // The raster implementation also uses scales and biases, but since they must be calculated
257 // after the dst color space is applied, it limits our ability to cache their values.
258 SkPMColor4f scales[kMaxUnrolledIntervalCount];
259 SkPMColor4f biases[kMaxUnrolledIntervalCount];
260 SkScalar thresholds[kMaxUnrolledIntervalCount] = { 0 };
261
262 int intervalCount = 0;
263
264 for (int i = 0; i < count - 1; i++) {
265 if (intervalCount >= kMaxUnrolledIntervalCount) {
266 // Already reached kMaxUnrolledIntervalCount, and haven't run out of color stops so this
267 // gradient cannot be represented by this shader.
268 return nullptr;
269 }
270
271 SkScalar t0 = positions[i];
272 SkScalar t1 = positions[i + 1];
273 SkScalar dt = t1 - t0;
274 // If the interval is empty, skip to the next interval. This will automatically create
275 // distinct hard stop intervals as needed. It also protects against malformed gradients
276 // that have repeated hard stops at the very beginning that are effectively unreachable.
277 if (SkScalarNearlyZero(dt)) {
278 continue;
279 }
280
281 auto c0 = Sk4f::Load(colors[i].vec());
282 auto c1 = Sk4f::Load(colors[i + 1].vec());
283
284 auto scale = (c1 - c0) / dt;
285 auto bias = c0 - t0 * scale;
286
287 scale.store(scales + intervalCount);
288 bias.store(biases + intervalCount);
289 thresholds[intervalCount] = t1;
290 intervalCount++;
291 }
292
293 SkRect thresholds1_7 = {thresholds[0], thresholds[1], thresholds[2], thresholds[3]},
294 thresholds9_13 = {thresholds[4], thresholds[5], thresholds[6], 0.0};
295
296 return make_unrolled_colorizer(intervalCount, scales, biases, thresholds1_7, thresholds9_13);
297}
298
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400299// Analyze the shader's color stops and positions and chooses an appropriate colorizer to represent
300// the gradient.
Brian Osman021ed512018-10-16 15:19:44 -0400301static std::unique_ptr<GrFragmentProcessor> make_colorizer(const SkPMColor4f* colors,
Michael Ludwiga7914d32018-09-14 09:47:21 -0400302 const SkScalar* positions, int count, bool premul, const GrFPArgs& args) {
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400303 // If there are hard stops at the beginning or end, the first and/or last color should be
304 // ignored by the colorizer since it should only be used in a clamped border color. By detecting
305 // and removing these stops at the beginning, it makes optimizing the remaining color stops
306 // simpler.
307
Kevin Lubickbe03ef12021-06-16 15:28:00 -0400308 // SkGradientShaderBase guarantees that pos[0] == 0 by adding a default value.
Michael Ludwig0495f7a2018-09-12 15:23:33 -0400309 bool bottomHardStop = SkScalarNearlyEqual(positions[0], positions[1]);
310 // The same is true for pos[end] == 1
Michael Ludwiga7914d32018-09-14 09:47:21 -0400311 bool topHardStop = SkScalarNearlyEqual(positions[count - 2], positions[count - 1]);
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400312
313 int offset = 0;
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400314 if (bottomHardStop) {
315 offset += 1;
316 count--;
317 }
318 if (topHardStop) {
319 count--;
320 }
321
Michael Ludwig0495f7a2018-09-12 15:23:33 -0400322 // Two remaining colors means a single interval from 0 to 1
323 // (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 -0400324 if (count == 2) {
Brian Osman65b45972021-06-25 15:58:43 +0000325 return make_single_interval_colorizer(colors[offset], colors[offset + 1]);
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400326 }
327
Michael Ludwig72535fb2018-09-28 11:53:32 -0400328 // Do an early test for the texture fallback to skip all of the other tests for specific
329 // analytic support of the gradient (and compatibility with the hardware), when it's definitely
330 // impossible to use an analytic solution.
Brian Osman07bf3882021-07-01 12:56:23 -0400331 bool tryAnalyticColorizer = count <= kMaxUnrolledColorCount;
Michael Ludwig72535fb2018-09-28 11:53:32 -0400332
333 // The remaining analytic colorizers use scale*t+bias, and the scale/bias values can become
334 // quite large when thresholds are close (but still outside the hardstop limit). If float isn't
335 // 32-bit, output can be incorrect if the thresholds are too close together. However, the
336 // analytic shaders are higher quality, so they can be used with lower precision hardware when
337 // the thresholds are not ill-conditioned.
Robert Phillips9da87e02019-02-04 13:26:26 -0500338 const GrShaderCaps* caps = args.fContext->priv().caps()->shaderCaps();
Michael Ludwig72535fb2018-09-28 11:53:32 -0400339 if (!caps->floatIs32Bits() && tryAnalyticColorizer) {
340 // Could run into problems, check if thresholds are close together (with a limit of .01, so
341 // that scales will be less than 100, which leaves 4 decimals of precision on 16-bit).
342 for (int i = offset; i < count - 1; i++) {
343 SkScalar dt = SkScalarAbs(positions[i] - positions[i + 1]);
344 if (dt <= kLowPrecisionIntervalLimit && dt > SK_ScalarNearlyZero) {
345 tryAnalyticColorizer = false;
346 break;
347 }
348 }
349 }
350
351 if (tryAnalyticColorizer) {
352 if (count == 3) {
353 // Must be a dual interval gradient, where the middle point is at offset+1 and the two
354 // intervals share the middle color stop.
Brian Osman65b45972021-06-25 15:58:43 +0000355 return make_dual_interval_colorizer(colors[offset], colors[offset + 1],
356 colors[offset + 1], colors[offset + 2],
357 positions[offset + 1]);
Michael Ludwig72535fb2018-09-28 11:53:32 -0400358 } else if (count == 4 && SkScalarNearlyEqual(positions[offset + 1],
359 positions[offset + 2])) {
360 // Two separate intervals that join at the same threshold position
Brian Osman65b45972021-06-25 15:58:43 +0000361 return make_dual_interval_colorizer(colors[offset], colors[offset + 1],
362 colors[offset + 2], colors[offset + 3],
363 positions[offset + 1]);
Michael Ludwig72535fb2018-09-28 11:53:32 -0400364 }
365
366 // The single and dual intervals are a specialized case of the unrolled binary search
367 // colorizer which can analytically render gradients of up to 8 intervals (up to 9 or 16
368 // colors depending on how many hard stops are inserted).
Brian Osman07bf3882021-07-01 12:56:23 -0400369 std::unique_ptr<GrFragmentProcessor> unrolled =
370 make_unrolled_binary_colorizer(colors + offset, positions + offset, count);
Michael Ludwig72535fb2018-09-28 11:53:32 -0400371 if (unrolled) {
372 return unrolled;
373 }
374 }
375
376 // Otherwise fall back to a rasterized gradient sampled by a texture, which can handle
377 // arbitrary gradients (the only downside being sampling resolution).
Michael Ludwiga7914d32018-09-14 09:47:21 -0400378 return make_textured_colorizer(colors + offset, positions + offset, count, premul, args);
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400379}
380
Brian Osmanc6804ed2021-06-29 11:11:49 -0400381// This top-level effect implements clamping on the layout coordinate and requires specifying the
382// border colors that are used when outside the clamped boundary. Gradients with the
383// SkTileMode::kClamp should use the colors at their first and last stop (after adding default stops
384// for t=0,t=1) as the border color. This will automatically replicate the edge color, even when
385// there is a hard stop.
386//
387// The SkTileMode::kDecal can be produced by specifying transparent black as the border colors,
388// regardless of the gradient's stop colors.
389static std::unique_ptr<GrFragmentProcessor> make_clamped_gradient(
390 std::unique_ptr<GrFragmentProcessor> colorizer,
391 std::unique_ptr<GrFragmentProcessor> gradLayout,
392 SkPMColor4f leftBorderColor,
393 SkPMColor4f rightBorderColor,
394 bool makePremul,
395 bool colorsAreOpaque) {
396 static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
397 uniform shader colorizer;
398 uniform shader gradLayout;
399
400 uniform half4 leftBorderColor; // t < 0.0
401 uniform half4 rightBorderColor; // t > 1.0
402
403 uniform int makePremul; // specialized
404 uniform int layoutPreservesOpacity; // specialized
405
406 half4 main(float2 coord) {
Brian Osmancbfa34a2021-09-02 09:26:27 -0400407 half4 t = gradLayout.eval(coord);
Brian Osmanc6804ed2021-06-29 11:11:49 -0400408 half4 outColor;
409
410 // If t.x is below 0, use the left border color without invoking the child processor.
411 // If any t.x is above 1, use the right border color. Otherwise, t is in the [0, 1]
412 // range assumed by the colorizer FP, so delegate to the child processor.
413 if (!bool(layoutPreservesOpacity) && t.y < 0) {
414 // layout has rejected this fragment (rely on sksl to remove this branch if the
415 // layout FP preserves opacity is false)
416 outColor = half4(0);
417 } else if (t.x < 0) {
418 outColor = leftBorderColor;
419 } else if (t.x > 1.0) {
420 outColor = rightBorderColor;
421 } else {
422 // Always sample from (x, 0), discarding y, since the layout FP can use y as a
423 // side-channel.
Brian Osmancbfa34a2021-09-02 09:26:27 -0400424 outColor = colorizer.eval(t.x0);
Brian Osmanc6804ed2021-06-29 11:11:49 -0400425 }
426 if (bool(makePremul)) {
427 outColor.rgb *= outColor.a;
428 }
429 return outColor;
430 }
431 )");
432
433 // If the layout does not preserve opacity, remove the opaque optimization,
434 // but otherwise respect the provided color opacity state (which should take
435 // into account the opacity of the border colors).
436 bool layoutPreservesOpacity = gradLayout->preservesOpaqueInput();
437 GrSkSLFP::OptFlags optFlags = GrSkSLFP::OptFlags::kCompatibleWithCoverageAsAlpha;
438 if (colorsAreOpaque && layoutPreservesOpacity) {
439 optFlags |= GrSkSLFP::OptFlags::kPreservesOpaqueInput;
440 }
441
442 return GrSkSLFP::Make(effect, "ClampedGradient", /*inputFP=*/nullptr, optFlags,
443 "colorizer", GrSkSLFP::IgnoreOptFlags(std::move(colorizer)),
444 "gradLayout", GrSkSLFP::IgnoreOptFlags(std::move(gradLayout)),
445 "leftBorderColor", leftBorderColor,
446 "rightBorderColor", rightBorderColor,
447 "makePremul", GrSkSLFP::Specialize<int>(makePremul),
448 "layoutPreservesOpacity",
449 GrSkSLFP::Specialize<int>(layoutPreservesOpacity));
450}
451
452static std::unique_ptr<GrFragmentProcessor> make_tiled_gradient(
453 const GrFPArgs& args,
454 std::unique_ptr<GrFragmentProcessor> colorizer,
455 std::unique_ptr<GrFragmentProcessor> gradLayout,
456 bool mirror,
457 bool makePremul,
458 bool colorsAreOpaque) {
459 static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
460 uniform shader colorizer;
461 uniform shader gradLayout;
462
463 uniform int mirror; // specialized
464 uniform int makePremul; // specialized
465 uniform int layoutPreservesOpacity; // specialized
466 uniform int useFloorAbsWorkaround; // specialized
467
468 half4 main(float2 coord) {
Brian Osmancbfa34a2021-09-02 09:26:27 -0400469 half4 t = gradLayout.eval(coord);
Brian Osmanc6804ed2021-06-29 11:11:49 -0400470
471 if (!bool(layoutPreservesOpacity) && t.y < 0) {
472 // layout has rejected this fragment (rely on sksl to remove this branch if the
473 // layout FP preserves opacity is false)
474 return half4(0);
475 } else {
476 if (bool(mirror)) {
477 half t_1 = t.x - 1;
478 half tiled_t = t_1 - 2 * floor(t_1 * 0.5) - 1;
479 if (bool(useFloorAbsWorkaround)) {
480 // At this point the expected value of tiled_t should between -1 and 1, so
481 // this clamp has no effect other than to break up the floor and abs calls
482 // and make sure the compiler doesn't merge them back together.
483 tiled_t = clamp(tiled_t, -1, 1);
484 }
485 t.x = abs(tiled_t);
486 } else {
487 // Simple repeat mode
488 t.x = fract(t.x);
489 }
490
491 // Always sample from (x, 0), discarding y, since the layout FP can use y as a
492 // side-channel.
Brian Osmancbfa34a2021-09-02 09:26:27 -0400493 half4 outColor = colorizer.eval(t.x0);
Brian Osmanc6804ed2021-06-29 11:11:49 -0400494 if (bool(makePremul)) {
495 outColor.rgb *= outColor.a;
496 }
497 return outColor;
498 }
499 }
500 )");
501
502 // If the layout does not preserve opacity, remove the opaque optimization,
503 // but otherwise respect the provided color opacity state (which should take
504 // into account the opacity of the border colors).
505 bool layoutPreservesOpacity = gradLayout->preservesOpaqueInput();
506 GrSkSLFP::OptFlags optFlags = GrSkSLFP::OptFlags::kCompatibleWithCoverageAsAlpha;
507 if (colorsAreOpaque && layoutPreservesOpacity) {
508 optFlags |= GrSkSLFP::OptFlags::kPreservesOpaqueInput;
509 }
510 const bool useFloorAbsWorkaround =
511 args.fContext->priv().caps()->shaderCaps()->mustDoOpBetweenFloorAndAbs();
512
513 return GrSkSLFP::Make(effect, "TiledGradient", /*inputFP=*/nullptr, optFlags,
514 "colorizer", GrSkSLFP::IgnoreOptFlags(std::move(colorizer)),
515 "gradLayout", GrSkSLFP::IgnoreOptFlags(std::move(gradLayout)),
516 "mirror", GrSkSLFP::Specialize<int>(mirror),
517 "makePremul", GrSkSLFP::Specialize<int>(makePremul),
518 "layoutPreservesOpacity",
519 GrSkSLFP::Specialize<int>(layoutPreservesOpacity),
520 "useFloorAbsWorkaround",
521 GrSkSLFP::Specialize<int>(useFloorAbsWorkaround));
522}
523
Michael Ludwig672c4762020-08-05 17:28:42 -0400524// Combines the colorizer and layout with an appropriately configured top-level effect based on the
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400525// gradient's tile mode
Brian Osman65b45972021-06-25 15:58:43 +0000526static std::unique_ptr<GrFragmentProcessor> make_gradient(
527 const SkGradientShaderBase& shader,
528 const GrFPArgs& args,
529 std::unique_ptr<GrFragmentProcessor> layout,
530 const SkMatrix* overrideMatrix = nullptr) {
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400531 // No shader is possible if a layout couldn't be created, e.g. a layout-specific Make() returned
532 // null.
533 if (layout == nullptr) {
534 return nullptr;
535 }
536
Brian Osman65b45972021-06-25 15:58:43 +0000537 // Wrap the layout in a matrix effect to apply the gradient's matrix:
538 SkMatrix matrix;
539 if (!shader.totalLocalMatrix(args.fPreLocalMatrix)->invert(&matrix)) {
540 return nullptr;
541 }
542 // Some two-point conical gradients use a custom matrix here
543 matrix.postConcat(overrideMatrix ? *overrideMatrix : shader.getGradientMatrix());
544 layout = GrMatrixEffect::Make(matrix, std::move(layout));
545
Brian Osman021ed512018-10-16 15:19:44 -0400546 // Convert all colors into destination space and into SkPMColor4fs, and handle
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400547 // premul issues depending on the interpolation mode
548 bool inputPremul = shader.getGradFlags() & SkGradientShader::kInterpolateColorsInPremul_Flag;
Michael Ludwigb96cba32018-09-14 13:59:24 -0400549 bool allOpaque = true;
Brian Osman021ed512018-10-16 15:19:44 -0400550 SkAutoSTMalloc<4, SkPMColor4f> colors(shader.fColorCount);
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400551 SkColor4fXformer xformedColors(shader.fOrigColors4f, shader.fColorCount,
Brian Salomon4bc0c1f2019-09-30 15:12:27 -0400552 shader.fColorSpace.get(), args.fDstColorInfo->colorSpace());
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400553 for (int i = 0; i < shader.fColorCount; i++) {
Brian Osman021ed512018-10-16 15:19:44 -0400554 const SkColor4f& upmColor = xformedColors.fColors[i];
555 colors[i] = inputPremul ? upmColor.premul()
556 : SkPMColor4f{ upmColor.fR, upmColor.fG, upmColor.fB, upmColor.fA };
557 if (allOpaque && !SkScalarNearlyEqual(colors[i].fA, 1.0)) {
Michael Ludwigb96cba32018-09-14 13:59:24 -0400558 allOpaque = false;
559 }
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400560 }
561
Michael Ludwig0495f7a2018-09-12 15:23:33 -0400562 // SkGradientShader stores positions implicitly when they are evenly spaced, but the getPos()
563 // implementation performs a branch for every position index. Since the shader conversion
564 // requires lots of position tests, calculate all of the positions up front if needed.
565 SkTArray<SkScalar, true> implicitPos;
566 SkScalar* positions;
567 if (shader.fOrigPos) {
568 positions = shader.fOrigPos;
569 } else {
John Stilesf4bda742020-10-14 16:57:41 -0400570 implicitPos.reserve_back(shader.fColorCount);
Mike Kleind3ed3012018-11-06 19:23:08 -0500571 SkScalar posScale = SK_Scalar1 / (shader.fColorCount - 1);
Michael Ludwig0495f7a2018-09-12 15:23:33 -0400572 for (int i = 0 ; i < shader.fColorCount; i++) {
573 implicitPos.push_back(SkIntToScalar(i) * posScale);
574 }
575 positions = implicitPos.begin();
576 }
577
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400578 // All gradients are colorized the same way, regardless of layout
Michael Ludwiga7914d32018-09-14 09:47:21 -0400579 std::unique_ptr<GrFragmentProcessor> colorizer = make_colorizer(
580 colors.get(), positions, shader.fColorCount, inputPremul, args);
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400581 if (colorizer == nullptr) {
582 return nullptr;
583 }
584
Michael Ludwig672c4762020-08-05 17:28:42 -0400585 // The top-level effect has to export premul colors, but under certain conditions it doesn't
586 // need to do anything to achieve that: i.e. its interpolating already premul colors
587 // (inputPremul) or all the colors have a = 1, in which case premul is a no op. Note that this
588 // allOpaque check is more permissive than SkGradientShaderBase's isOpaque(), since we can
589 // optimize away the make-premul op for two point conical gradients (which report false for
590 // isOpaque).
Michael Ludwigb96cba32018-09-14 13:59:24 -0400591 bool makePremul = !inputPremul && !allOpaque;
592
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400593 // All tile modes are supported (unless something was added to SkShader)
Michael Ludwig672c4762020-08-05 17:28:42 -0400594 std::unique_ptr<GrFragmentProcessor> gradient;
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400595 switch(shader.getTileMode()) {
Mike Reedfae8fce2019-04-03 10:27:45 -0400596 case SkTileMode::kRepeat:
Brian Osmanc6804ed2021-06-29 11:11:49 -0400597 gradient = make_tiled_gradient(args, std::move(colorizer), std::move(layout),
598 /* mirror */ false, makePremul, allOpaque);
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400599 break;
Mike Reedfae8fce2019-04-03 10:27:45 -0400600 case SkTileMode::kMirror:
Brian Osmanc6804ed2021-06-29 11:11:49 -0400601 gradient = make_tiled_gradient(args, std::move(colorizer), std::move(layout),
602 /* mirror */ true, makePremul, allOpaque);
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400603 break;
Mike Reedfae8fce2019-04-03 10:27:45 -0400604 case SkTileMode::kClamp:
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400605 // For the clamped mode, the border colors are the first and last colors, corresponding
606 // to t=0 and t=1, because SkGradientShaderBase enforces that by adding color stops as
607 // appropriate. If there is a hard stop, this grabs the expected outer colors for the
608 // border.
Brian Osmanc6804ed2021-06-29 11:11:49 -0400609 gradient = make_clamped_gradient(std::move(colorizer), std::move(layout),
610 colors[0], colors[shader.fColorCount - 1],
611 makePremul, allOpaque);
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400612 break;
Mike Reedfae8fce2019-04-03 10:27:45 -0400613 case SkTileMode::kDecal:
Michael Ludwigb96cba32018-09-14 13:59:24 -0400614 // Even if the gradient colors are opaque, the decal borders are transparent so
615 // disable that optimization
Brian Osmanc6804ed2021-06-29 11:11:49 -0400616 gradient = make_clamped_gradient(std::move(colorizer), std::move(layout),
617 SK_PMColor4fTRANSPARENT, SK_PMColor4fTRANSPARENT,
618 makePremul, /* colorsAreOpaque */ false);
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400619 break;
620 }
621
Michael Ludwig672c4762020-08-05 17:28:42 -0400622 if (gradient == nullptr) {
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400623 // Unexpected tile mode
624 return nullptr;
625 }
Brian Salomonc0d79e52019-04-10 15:02:11 -0400626 if (args.fInputColorIsOpaque) {
Brian Osman55cbc752021-05-27 11:57:13 -0400627 // If the input alpha is known to be 1, we don't need to take the kSrcIn path. This is
628 // just an optimization. However, we can't just return 'gradient' here. We need to actually
629 // inhibit the coverage-as-alpha optimization, or we'll fail to incorporate AA correctly.
630 // The OverrideInput FP happens to do that, so wrap our fp in one of those. The gradient FP
631 // doesn't actually use the input color at all, so the overridden input is irrelevant.
Michael Ludwig672c4762020-08-05 17:28:42 -0400632 return GrFragmentProcessor::OverrideInput(std::move(gradient), SK_PMColor4fWHITE, false);
Brian Salomonc0d79e52019-04-10 15:02:11 -0400633 }
Michael Ludwig672c4762020-08-05 17:28:42 -0400634 return GrFragmentProcessor::MulChildByInputAlpha(std::move(gradient));
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400635}
636
637namespace GrGradientShader {
638
639std::unique_ptr<GrFragmentProcessor> MakeLinear(const SkLinearGradient& shader,
640 const GrFPArgs& args) {
Brian Osman65b45972021-06-25 15:58:43 +0000641 // We add a tiny delta to t. When gradient stops are set up so that a hard stop in a vertically
642 // or horizontally oriented gradient falls exactly at a column or row of pixel centers we can
643 // we can get slightly different interpolated t values along the column/row. By adding the delta
644 // we will consistently get the color to the "right" of the stop. Of course if the hard stop
645 // falls at X.5 - delta then we still could get inconsistent results, but that is much less
646 // likely. crbug.com/938592
647 // If/when we add filtering of the gradient this can be removed.
648 static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
649 half4 main(float2 coord) {
650 return half4(half(coord.x) + 0.00001, 1, 0, 0); // y = 1 for always valid
651 }
652 )");
653 // The linear gradient never rejects a pixel so it doesn't change opacity
654 auto fp = GrSkSLFP::Make(effect, "LinearLayout", /*inputFP=*/nullptr,
655 GrSkSLFP::OptFlags::kPreservesOpaqueInput);
656 return make_gradient(shader, args, std::move(fp));
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400657}
658
Michael Ludwig4089df82018-09-12 15:22:37 -0400659std::unique_ptr<GrFragmentProcessor> MakeRadial(const SkRadialGradient& shader,
660 const GrFPArgs& args) {
Brian Osman65b45972021-06-25 15:58:43 +0000661 static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
662 half4 main(float2 coord) {
663 return half4(half(length(coord)), 1, 0, 0); // y = 1 for always valid
664 }
665 )");
666 // The radial gradient never rejects a pixel so it doesn't change opacity
667 auto fp = GrSkSLFP::Make(effect, "RadialLayout", /*inputFP=*/nullptr,
668 GrSkSLFP::OptFlags::kPreservesOpaqueInput);
669 return make_gradient(shader, args, std::move(fp));
Michael Ludwig4089df82018-09-12 15:22:37 -0400670}
671
Michael Ludwig24d438b2018-09-12 15:22:50 -0400672std::unique_ptr<GrFragmentProcessor> MakeSweep(const SkSweepGradient& shader,
673 const GrFPArgs& args) {
Brian Osman65b45972021-06-25 15:58:43 +0000674 // On some devices they incorrectly implement atan2(y,x) as atan(y/x). In actuality it is
675 // atan2(y,x) = 2 * atan(y / (sqrt(x^2 + y^2) + x)). So to work around this we pass in (sqrt(x^2
676 // + y^2) + x) as the second parameter to atan2 in these cases. We let the device handle the
677 // undefined behavior of the second paramenter being 0 instead of doing the divide ourselves and
678 // using atan instead.
679 int useAtanWorkaround =
680 args.fContext->priv().caps()->shaderCaps()->atan2ImplementedAsAtanYOverX();
681 static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
682 uniform half bias;
683 uniform half scale;
684 uniform int useAtanWorkaround; // specialized
685
686 half4 main(float2 coord) {
687 half angle = bool(useAtanWorkaround)
688 ? half(2 * atan(-coord.y, length(coord) - coord.x))
689 : half(atan(-coord.y, -coord.x));
690
691 // 0.1591549430918 is 1/(2*pi), used since atan returns values [-pi, pi]
692 half t = (angle * 0.1591549430918 + 0.5 + bias) * scale;
693 return half4(t, 1, 0, 0); // y = 1 for always valid
694 }
695 )");
696 // The sweep gradient never rejects a pixel so it doesn't change opacity
697 auto fp = GrSkSLFP::Make(effect, "SweepLayout", /*inputFP=*/nullptr,
698 GrSkSLFP::OptFlags::kPreservesOpaqueInput,
699 "bias", shader.getTBias(),
700 "scale", shader.getTScale(),
701 "useAtanWorkaround", GrSkSLFP::Specialize(useAtanWorkaround));
702 return make_gradient(shader, args, std::move(fp));
Michael Ludwig24d438b2018-09-12 15:22:50 -0400703}
704
Michael Ludwig8f685082018-09-12 15:23:01 -0400705std::unique_ptr<GrFragmentProcessor> MakeConical(const SkTwoPointConicalGradient& shader,
706 const GrFPArgs& args) {
Brian Osman65b45972021-06-25 15:58:43 +0000707 // The 2 point conical gradient can reject a pixel so it does change opacity even if the input
708 // was opaque. Thus, all of these layout FPs disable that optimization.
709 std::unique_ptr<GrFragmentProcessor> fp;
710 SkTLazy<SkMatrix> matrix;
711 switch (shader.getType()) {
712 case SkTwoPointConicalGradient::Type::kStrip: {
713 static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
714 uniform half r0_2;
715 half4 main(float2 p) {
716 half v = 1; // validation flag, set to negative to discard fragment later
717 float t = r0_2 - p.y * p.y;
718 if (t >= 0) {
719 t = p.x + sqrt(t);
720 } else {
721 v = -1;
722 }
723 return half4(half(t), v, 0, 0);
724 }
725 )");
726 float r0 = shader.getStartRadius() / shader.getCenterX1();
727 fp = GrSkSLFP::Make(effect, "TwoPointConicalStripLayout", /*inputFP=*/nullptr,
728 GrSkSLFP::OptFlags::kNone,
729 "r0_2", r0 * r0);
730 } break;
731
732 case SkTwoPointConicalGradient::Type::kRadial: {
733 static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
734 uniform half r0;
735 uniform half lengthScale;
736 half4 main(float2 p) {
737 half v = 1; // validation flag, set to negative to discard fragment later
738 float t = length(p) * lengthScale - r0;
739 return half4(half(t), v, 0, 0);
740 }
741 )");
742 float dr = shader.getDiffRadius();
743 float r0 = shader.getStartRadius() / dr;
744 bool isRadiusIncreasing = dr >= 0;
745 fp = GrSkSLFP::Make(effect, "TwoPointConicalRadialLayout", /*inputFP=*/nullptr,
746 GrSkSLFP::OptFlags::kNone,
747 "r0", r0,
748 "lengthScale", isRadiusIncreasing ? 1.0f : -1.0f);
749
750 // GPU radial matrix is different from the original matrix, since we map the diff radius
751 // to have |dr| = 1, so manually compute the final gradient matrix here.
752
753 // Map center to (0, 0)
754 matrix.set(SkMatrix::Translate(-shader.getStartCenter().fX,
755 -shader.getStartCenter().fY));
756 // scale |diffRadius| to 1
757 matrix->postScale(1 / dr, 1 / dr);
758 } break;
759
760 case SkTwoPointConicalGradient::Type::kFocal: {
761 static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
762 // Optimization flags, all specialized:
763 uniform int isRadiusIncreasing;
764 uniform int isFocalOnCircle;
765 uniform int isWellBehaved;
766 uniform int isSwapped;
767 uniform int isNativelyFocal;
768
769 uniform half invR1; // 1/r1
770 uniform half fx; // focalX = r0/(r0-r1)
771
772 half4 main(float2 p) {
773 float t = -1;
774 half v = 1; // validation flag, set to negative to discard fragment later
775
776 float x_t = -1;
777 if (bool(isFocalOnCircle)) {
778 x_t = dot(p, p) / p.x;
779 } else if (bool(isWellBehaved)) {
780 x_t = length(p) - p.x * invR1;
781 } else {
782 float temp = p.x * p.x - p.y * p.y;
783
784 // Only do sqrt if temp >= 0; this is significantly slower than checking
785 // temp >= 0 in the if statement that checks r(t) >= 0. But GPU may break if
786 // we sqrt a negative float. (Although I havevn't observed that on any
787 // devices so far, and the old approach also does sqrt negative value
788 // without a check.) If the performance is really critical, maybe we should
789 // just compute the area where temp and x_t are always valid and drop all
790 // these ifs.
791 if (temp >= 0) {
792 if (bool(isSwapped) || !bool(isRadiusIncreasing)) {
793 x_t = -sqrt(temp) - p.x * invR1;
794 } else {
795 x_t = sqrt(temp) - p.x * invR1;
796 }
797 }
798 }
799
800 // The final calculation of t from x_t has lots of static optimizations but only
801 // do them when x_t is positive (which can be assumed true if isWellBehaved is
802 // true)
803 if (!bool(isWellBehaved)) {
804 // This will still calculate t even though it will be ignored later in the
805 // pipeline to avoid a branch
806 if (x_t <= 0.0) {
807 v = -1;
808 }
809 }
810 if (bool(isRadiusIncreasing)) {
811 if (bool(isNativelyFocal)) {
812 t = x_t;
813 } else {
814 t = x_t + fx;
815 }
816 } else {
817 if (bool(isNativelyFocal)) {
818 t = -x_t;
819 } else {
820 t = -x_t + fx;
821 }
822 }
823
824 if (bool(isSwapped)) {
825 t = 1 - t;
826 }
827
828 return half4(half(t), v, 0, 0);
829 }
830 )");
831
832 const SkTwoPointConicalGradient::FocalData& focalData = shader.getFocalData();
833 bool isRadiusIncreasing = (1 - focalData.fFocalX) > 0,
834 isFocalOnCircle = focalData.isFocalOnCircle(),
835 isWellBehaved = focalData.isWellBehaved(),
836 isSwapped = focalData.isSwapped(),
837 isNativelyFocal = focalData.isNativelyFocal();
838
839 fp = GrSkSLFP::Make(effect, "TwoPointConicalFocalLayout", /*inputFP=*/nullptr,
840 GrSkSLFP::OptFlags::kNone,
841 "isRadiusIncreasing", GrSkSLFP::Specialize<int>(isRadiusIncreasing),
842 "isFocalOnCircle", GrSkSLFP::Specialize<int>(isFocalOnCircle),
843 "isWellBehaved", GrSkSLFP::Specialize<int>(isWellBehaved),
844 "isSwapped", GrSkSLFP::Specialize<int>(isSwapped),
845 "isNativelyFocal", GrSkSLFP::Specialize<int>(isNativelyFocal),
846 "invR1", 1.0f / focalData.fR1,
847 "fx", focalData.fFocalX);
848 } break;
849 }
850 return make_gradient(shader, args, std::move(fp), matrix.getMaybeNull());
Michael Ludwig8f685082018-09-12 15:23:01 -0400851}
852
Michael Ludwig7f8c5242018-09-14 15:07:55 -0400853#if GR_TEST_UTILS
854RandomParams::RandomParams(SkRandom* random) {
855 // Set color count to min of 2 so that we don't trigger the const color optimization and make
856 // a non-gradient processor.
857 fColorCount = random->nextRangeU(2, kMaxRandomGradientColors);
858 fUseColors4f = random->nextBool();
859
860 // if one color, omit stops, otherwise randomly decide whether or not to
861 if (fColorCount == 1 || (fColorCount >= 2 && random->nextBool())) {
862 fStops = nullptr;
863 } else {
864 fStops = fStopStorage;
865 }
866
867 // if using SkColor4f, attach a random (possibly null) color space (with linear gamma)
868 if (fUseColors4f) {
869 fColorSpace = GrTest::TestColorSpace(random);
870 }
871
872 SkScalar stop = 0.f;
873 for (int i = 0; i < fColorCount; ++i) {
874 if (fUseColors4f) {
875 fColors4f[i].fR = random->nextUScalar1();
876 fColors4f[i].fG = random->nextUScalar1();
877 fColors4f[i].fB = random->nextUScalar1();
878 fColors4f[i].fA = random->nextUScalar1();
879 } else {
880 fColors[i] = random->nextU();
881 }
882 if (fStops) {
883 fStops[i] = stop;
884 stop = i < fColorCount - 1 ? stop + random->nextUScalar1() * (1.f - stop) : 1.f;
885 }
886 }
Mike Reedfae8fce2019-04-03 10:27:45 -0400887 fTileMode = static_cast<SkTileMode>(random->nextULessThan(kSkTileModeCount));
Michael Ludwig7f8c5242018-09-14 15:07:55 -0400888}
889#endif
890
John Stilesa6841be2020-08-06 14:11:56 -0400891} // namespace GrGradientShader