blob: 18db1ee8b5e6c32ce004c8c8ad82a663de58e54e [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"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050011#include "src/gpu/gradients/generated/GrUnrolledBinaryGradientColorizer.h"
Michael Ludwig4f94ef62018-09-12 15:22:16 -040012
Robert Phillipsb7bfbc22020-07-01 12:55:01 -040013#include "include/gpu/GrRecordingContext.h"
Brian Osman65b45972021-06-25 15:58:43 +000014#include "src/core/SkRuntimeEffectPriv.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050015#include "src/gpu/GrCaps.h"
Greg Danielf91aeb22019-06-18 09:58:02 -040016#include "src/gpu/GrColor.h"
Brian Salomon4bc0c1f2019-09-30 15:12:27 -040017#include "src/gpu/GrColorInfo.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050018#include "src/gpu/GrRecordingContextPriv.h"
19#include "src/gpu/SkGr.h"
Brian Osman65b45972021-06-25 15:58:43 +000020#include "src/gpu/effects/GrMatrixEffect.h"
21#include "src/gpu/effects/GrSkSLFP.h"
Brian Salomon17473752020-06-19 09:53:50 -040022#include "src/gpu/effects/GrTextureEffect.h"
Michael Ludwiga7914d32018-09-14 09:47:21 -040023
Michael Ludwig72535fb2018-09-28 11:53:32 -040024// Intervals smaller than this (that aren't hard stops) on low-precision-only devices force us to
25// use the textured gradient
26static const SkScalar kLowPrecisionIntervalLimit = 0.01f;
27
Michael Ludwiga7914d32018-09-14 09:47:21 -040028// Each cache entry costs 1K or 2K of RAM. Each bitmap will be 1x256 at either 32bpp or 64bpp.
29static const int kMaxNumCachedGradientBitmaps = 32;
30static const int kGradientTextureSize = 256;
31
32// NOTE: signature takes raw pointers to the color/pos arrays and a count to make it easy for
33// MakeColorizer to transparently take care of hard stops at the end points of the gradient.
Brian Osman021ed512018-10-16 15:19:44 -040034static std::unique_ptr<GrFragmentProcessor> make_textured_colorizer(const SkPMColor4f* colors,
Michael Ludwiga7914d32018-09-14 09:47:21 -040035 const SkScalar* positions, int count, bool premul, const GrFPArgs& args) {
36 static GrGradientBitmapCache gCache(kMaxNumCachedGradientBitmaps, kGradientTextureSize);
37
38 // Use 8888 or F16, depending on the destination config.
39 // TODO: Use 1010102 for opaque gradients, at least if destination is 1010102?
40 SkColorType colorType = kRGBA_8888_SkColorType;
Brian Salomon4bc0c1f2019-09-30 15:12:27 -040041 if (GrColorTypeIsWiderThan(args.fDstColorInfo->colorType(), 8)) {
Greg Daniel7bfc9132019-08-14 14:23:53 -040042 auto f16Format = args.fContext->priv().caps()->getDefaultBackendFormat(
43 GrColorType::kRGBA_F16, GrRenderable::kNo);
44 if (f16Format.isValid()) {
45 colorType = kRGBA_F16_SkColorType;
46 }
Michael Ludwiga7914d32018-09-14 09:47:21 -040047 }
48 SkAlphaType alphaType = premul ? kPremul_SkAlphaType : kUnpremul_SkAlphaType;
49
50 SkBitmap bitmap;
51 gCache.getGradient(colors, positions, count, colorType, alphaType, &bitmap);
52 SkASSERT(1 == bitmap.height() && SkIsPow2(bitmap.width()));
53 SkASSERT(bitmap.isImmutable());
54
Brian Salomon27c42022021-04-28 12:39:21 -040055 auto view = std::get<0>(GrMakeCachedBitmapProxyView(args.fContext, bitmap, GrMipmapped::kNo));
56 if (!view) {
Michael Ludwiga7914d32018-09-14 09:47:21 -040057 SkDebugf("Gradient won't draw. Could not create texture.");
58 return nullptr;
59 }
John Stiles1cf15932020-07-31 16:08:24 -040060
Brian Salomon17473752020-06-19 09:53:50 -040061 auto m = SkMatrix::Scale(view.width(), 1.f);
John Stiles1cf15932020-07-31 16:08:24 -040062 return GrTextureEffect::Make(std::move(view), alphaType, m, GrSamplerState::Filter::kLinear);
Michael Ludwiga7914d32018-09-14 09:47:21 -040063}
Michael Ludwig4f94ef62018-09-12 15:22:16 -040064
Brian Osman65b45972021-06-25 15:58:43 +000065
66static std::unique_ptr<GrFragmentProcessor> make_single_interval_colorizer(const SkPMColor4f& start,
67 const SkPMColor4f& end) {
68 static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
69 uniform half4 start;
70 uniform half4 end;
71 half4 main(float2 coord) {
72 // Clamping and/or wrapping was already handled by the parent shader so the output
73 // color is a simple lerp.
74 return mix(start, end, half(coord.x));
75 }
76 )");
77 return GrSkSLFP::Make(effect, "SingleIntervalColorizer", /*inputFP=*/nullptr,
78 GrSkSLFP::OptFlags::kNone,
79 "start", start,
80 "end", end);
81}
82
83static std::unique_ptr<GrFragmentProcessor> make_dual_interval_colorizer(const SkPMColor4f& c0,
84 const SkPMColor4f& c1,
85 const SkPMColor4f& c2,
86 const SkPMColor4f& c3,
87 float threshold) {
88 static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
89 uniform float4 scale01;
90 uniform float4 bias01;
91 uniform float4 scale23;
92 uniform float4 bias23;
93 uniform half threshold;
94
95 half4 main(float2 coord) {
96 half t = half(coord.x);
97
98 float4 scale, bias;
99 if (t < threshold) {
100 scale = scale01;
101 bias = bias01;
102 } else {
103 scale = scale23;
104 bias = bias23;
105 }
106
107 return half4(t * scale + bias);
108 }
109 )");
110
111 using sk4f = skvx::Vec<4, float>;
112
113 // Derive scale and biases from the 4 colors and threshold
114 auto vc0 = sk4f::Load(c0.vec());
115 auto vc1 = sk4f::Load(c1.vec());
116 auto scale01 = (vc1 - vc0) / threshold;
117 // bias01 = c0
118
119 auto vc2 = sk4f::Load(c2.vec());
120 auto vc3 = sk4f::Load(c3.vec());
121 auto scale23 = (vc3 - vc2) / (1 - threshold);
122 auto bias23 = vc2 - threshold * scale23;
123
124 return GrSkSLFP::Make(effect, "DualIntervalColorizer", /*inputFP=*/nullptr,
125 GrSkSLFP::OptFlags::kNone,
126 "scale01", scale01,
127 "bias01", c0,
128 "scale23", scale23,
129 "bias23", bias23,
130 "threshold", threshold);
131}
132
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400133// Analyze the shader's color stops and positions and chooses an appropriate colorizer to represent
134// the gradient.
Brian Osman021ed512018-10-16 15:19:44 -0400135static std::unique_ptr<GrFragmentProcessor> make_colorizer(const SkPMColor4f* colors,
Michael Ludwiga7914d32018-09-14 09:47:21 -0400136 const SkScalar* positions, int count, bool premul, const GrFPArgs& args) {
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400137 // If there are hard stops at the beginning or end, the first and/or last color should be
138 // ignored by the colorizer since it should only be used in a clamped border color. By detecting
139 // and removing these stops at the beginning, it makes optimizing the remaining color stops
140 // simpler.
141
Kevin Lubickbe03ef12021-06-16 15:28:00 -0400142 // SkGradientShaderBase guarantees that pos[0] == 0 by adding a default value.
Michael Ludwig0495f7a2018-09-12 15:23:33 -0400143 bool bottomHardStop = SkScalarNearlyEqual(positions[0], positions[1]);
144 // The same is true for pos[end] == 1
Michael Ludwiga7914d32018-09-14 09:47:21 -0400145 bool topHardStop = SkScalarNearlyEqual(positions[count - 2], positions[count - 1]);
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400146
147 int offset = 0;
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400148 if (bottomHardStop) {
149 offset += 1;
150 count--;
151 }
152 if (topHardStop) {
153 count--;
154 }
155
Michael Ludwig0495f7a2018-09-12 15:23:33 -0400156 // Two remaining colors means a single interval from 0 to 1
157 // (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 -0400158 if (count == 2) {
Brian Osman65b45972021-06-25 15:58:43 +0000159 return make_single_interval_colorizer(colors[offset], colors[offset + 1]);
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400160 }
161
Michael Ludwig72535fb2018-09-28 11:53:32 -0400162 // Do an early test for the texture fallback to skip all of the other tests for specific
163 // analytic support of the gradient (and compatibility with the hardware), when it's definitely
164 // impossible to use an analytic solution.
165 bool tryAnalyticColorizer = count <= GrUnrolledBinaryGradientColorizer::kMaxColorCount;
166
167 // The remaining analytic colorizers use scale*t+bias, and the scale/bias values can become
168 // quite large when thresholds are close (but still outside the hardstop limit). If float isn't
169 // 32-bit, output can be incorrect if the thresholds are too close together. However, the
170 // analytic shaders are higher quality, so they can be used with lower precision hardware when
171 // the thresholds are not ill-conditioned.
Robert Phillips9da87e02019-02-04 13:26:26 -0500172 const GrShaderCaps* caps = args.fContext->priv().caps()->shaderCaps();
Michael Ludwig72535fb2018-09-28 11:53:32 -0400173 if (!caps->floatIs32Bits() && tryAnalyticColorizer) {
174 // Could run into problems, check if thresholds are close together (with a limit of .01, so
175 // that scales will be less than 100, which leaves 4 decimals of precision on 16-bit).
176 for (int i = offset; i < count - 1; i++) {
177 SkScalar dt = SkScalarAbs(positions[i] - positions[i + 1]);
178 if (dt <= kLowPrecisionIntervalLimit && dt > SK_ScalarNearlyZero) {
179 tryAnalyticColorizer = false;
180 break;
181 }
182 }
183 }
184
185 if (tryAnalyticColorizer) {
186 if (count == 3) {
187 // Must be a dual interval gradient, where the middle point is at offset+1 and the two
188 // intervals share the middle color stop.
Brian Osman65b45972021-06-25 15:58:43 +0000189 return make_dual_interval_colorizer(colors[offset], colors[offset + 1],
190 colors[offset + 1], colors[offset + 2],
191 positions[offset + 1]);
Michael Ludwig72535fb2018-09-28 11:53:32 -0400192 } else if (count == 4 && SkScalarNearlyEqual(positions[offset + 1],
193 positions[offset + 2])) {
194 // Two separate intervals that join at the same threshold position
Brian Osman65b45972021-06-25 15:58:43 +0000195 return make_dual_interval_colorizer(colors[offset], colors[offset + 1],
196 colors[offset + 2], colors[offset + 3],
197 positions[offset + 1]);
Michael Ludwig72535fb2018-09-28 11:53:32 -0400198 }
199
200 // The single and dual intervals are a specialized case of the unrolled binary search
201 // colorizer which can analytically render gradients of up to 8 intervals (up to 9 or 16
202 // colors depending on how many hard stops are inserted).
203 std::unique_ptr<GrFragmentProcessor> unrolled = GrUnrolledBinaryGradientColorizer::Make(
204 colors + offset, positions + offset, count);
205 if (unrolled) {
206 return unrolled;
207 }
208 }
209
210 // Otherwise fall back to a rasterized gradient sampled by a texture, which can handle
211 // arbitrary gradients (the only downside being sampling resolution).
Michael Ludwiga7914d32018-09-14 09:47:21 -0400212 return make_textured_colorizer(colors + offset, positions + offset, count, premul, args);
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400213}
214
Brian Osmanc6804ed2021-06-29 11:11:49 -0400215// This top-level effect implements clamping on the layout coordinate and requires specifying the
216// border colors that are used when outside the clamped boundary. Gradients with the
217// SkTileMode::kClamp should use the colors at their first and last stop (after adding default stops
218// for t=0,t=1) as the border color. This will automatically replicate the edge color, even when
219// there is a hard stop.
220//
221// The SkTileMode::kDecal can be produced by specifying transparent black as the border colors,
222// regardless of the gradient's stop colors.
223static std::unique_ptr<GrFragmentProcessor> make_clamped_gradient(
224 std::unique_ptr<GrFragmentProcessor> colorizer,
225 std::unique_ptr<GrFragmentProcessor> gradLayout,
226 SkPMColor4f leftBorderColor,
227 SkPMColor4f rightBorderColor,
228 bool makePremul,
229 bool colorsAreOpaque) {
230 static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
231 uniform shader colorizer;
232 uniform shader gradLayout;
233
234 uniform half4 leftBorderColor; // t < 0.0
235 uniform half4 rightBorderColor; // t > 1.0
236
237 uniform int makePremul; // specialized
238 uniform int layoutPreservesOpacity; // specialized
239
240 half4 main(float2 coord) {
241 half4 t = sample(gradLayout, coord);
242 half4 outColor;
243
244 // If t.x is below 0, use the left border color without invoking the child processor.
245 // If any t.x is above 1, use the right border color. Otherwise, t is in the [0, 1]
246 // range assumed by the colorizer FP, so delegate to the child processor.
247 if (!bool(layoutPreservesOpacity) && t.y < 0) {
248 // layout has rejected this fragment (rely on sksl to remove this branch if the
249 // layout FP preserves opacity is false)
250 outColor = half4(0);
251 } else if (t.x < 0) {
252 outColor = leftBorderColor;
253 } else if (t.x > 1.0) {
254 outColor = rightBorderColor;
255 } else {
256 // Always sample from (x, 0), discarding y, since the layout FP can use y as a
257 // side-channel.
258 outColor = sample(colorizer, t.x0);
259 }
260 if (bool(makePremul)) {
261 outColor.rgb *= outColor.a;
262 }
263 return outColor;
264 }
265 )");
266
267 // If the layout does not preserve opacity, remove the opaque optimization,
268 // but otherwise respect the provided color opacity state (which should take
269 // into account the opacity of the border colors).
270 bool layoutPreservesOpacity = gradLayout->preservesOpaqueInput();
271 GrSkSLFP::OptFlags optFlags = GrSkSLFP::OptFlags::kCompatibleWithCoverageAsAlpha;
272 if (colorsAreOpaque && layoutPreservesOpacity) {
273 optFlags |= GrSkSLFP::OptFlags::kPreservesOpaqueInput;
274 }
275
276 return GrSkSLFP::Make(effect, "ClampedGradient", /*inputFP=*/nullptr, optFlags,
277 "colorizer", GrSkSLFP::IgnoreOptFlags(std::move(colorizer)),
278 "gradLayout", GrSkSLFP::IgnoreOptFlags(std::move(gradLayout)),
279 "leftBorderColor", leftBorderColor,
280 "rightBorderColor", rightBorderColor,
281 "makePremul", GrSkSLFP::Specialize<int>(makePremul),
282 "layoutPreservesOpacity",
283 GrSkSLFP::Specialize<int>(layoutPreservesOpacity));
284}
285
286static std::unique_ptr<GrFragmentProcessor> make_tiled_gradient(
287 const GrFPArgs& args,
288 std::unique_ptr<GrFragmentProcessor> colorizer,
289 std::unique_ptr<GrFragmentProcessor> gradLayout,
290 bool mirror,
291 bool makePremul,
292 bool colorsAreOpaque) {
293 static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
294 uniform shader colorizer;
295 uniform shader gradLayout;
296
297 uniform int mirror; // specialized
298 uniform int makePremul; // specialized
299 uniform int layoutPreservesOpacity; // specialized
300 uniform int useFloorAbsWorkaround; // specialized
301
302 half4 main(float2 coord) {
303 half4 t = sample(gradLayout, coord);
304
305 if (!bool(layoutPreservesOpacity) && t.y < 0) {
306 // layout has rejected this fragment (rely on sksl to remove this branch if the
307 // layout FP preserves opacity is false)
308 return half4(0);
309 } else {
310 if (bool(mirror)) {
311 half t_1 = t.x - 1;
312 half tiled_t = t_1 - 2 * floor(t_1 * 0.5) - 1;
313 if (bool(useFloorAbsWorkaround)) {
314 // At this point the expected value of tiled_t should between -1 and 1, so
315 // this clamp has no effect other than to break up the floor and abs calls
316 // and make sure the compiler doesn't merge them back together.
317 tiled_t = clamp(tiled_t, -1, 1);
318 }
319 t.x = abs(tiled_t);
320 } else {
321 // Simple repeat mode
322 t.x = fract(t.x);
323 }
324
325 // Always sample from (x, 0), discarding y, since the layout FP can use y as a
326 // side-channel.
327 half4 outColor = sample(colorizer, t.x0);
328 if (bool(makePremul)) {
329 outColor.rgb *= outColor.a;
330 }
331 return outColor;
332 }
333 }
334 )");
335
336 // If the layout does not preserve opacity, remove the opaque optimization,
337 // but otherwise respect the provided color opacity state (which should take
338 // into account the opacity of the border colors).
339 bool layoutPreservesOpacity = gradLayout->preservesOpaqueInput();
340 GrSkSLFP::OptFlags optFlags = GrSkSLFP::OptFlags::kCompatibleWithCoverageAsAlpha;
341 if (colorsAreOpaque && layoutPreservesOpacity) {
342 optFlags |= GrSkSLFP::OptFlags::kPreservesOpaqueInput;
343 }
344 const bool useFloorAbsWorkaround =
345 args.fContext->priv().caps()->shaderCaps()->mustDoOpBetweenFloorAndAbs();
346
347 return GrSkSLFP::Make(effect, "TiledGradient", /*inputFP=*/nullptr, optFlags,
348 "colorizer", GrSkSLFP::IgnoreOptFlags(std::move(colorizer)),
349 "gradLayout", GrSkSLFP::IgnoreOptFlags(std::move(gradLayout)),
350 "mirror", GrSkSLFP::Specialize<int>(mirror),
351 "makePremul", GrSkSLFP::Specialize<int>(makePremul),
352 "layoutPreservesOpacity",
353 GrSkSLFP::Specialize<int>(layoutPreservesOpacity),
354 "useFloorAbsWorkaround",
355 GrSkSLFP::Specialize<int>(useFloorAbsWorkaround));
356}
357
Michael Ludwig672c4762020-08-05 17:28:42 -0400358// Combines the colorizer and layout with an appropriately configured top-level effect based on the
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400359// gradient's tile mode
Brian Osman65b45972021-06-25 15:58:43 +0000360static std::unique_ptr<GrFragmentProcessor> make_gradient(
361 const SkGradientShaderBase& shader,
362 const GrFPArgs& args,
363 std::unique_ptr<GrFragmentProcessor> layout,
364 const SkMatrix* overrideMatrix = nullptr) {
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400365 // No shader is possible if a layout couldn't be created, e.g. a layout-specific Make() returned
366 // null.
367 if (layout == nullptr) {
368 return nullptr;
369 }
370
Brian Osman65b45972021-06-25 15:58:43 +0000371 // Wrap the layout in a matrix effect to apply the gradient's matrix:
372 SkMatrix matrix;
373 if (!shader.totalLocalMatrix(args.fPreLocalMatrix)->invert(&matrix)) {
374 return nullptr;
375 }
376 // Some two-point conical gradients use a custom matrix here
377 matrix.postConcat(overrideMatrix ? *overrideMatrix : shader.getGradientMatrix());
378 layout = GrMatrixEffect::Make(matrix, std::move(layout));
379
Brian Osman021ed512018-10-16 15:19:44 -0400380 // Convert all colors into destination space and into SkPMColor4fs, and handle
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400381 // premul issues depending on the interpolation mode
382 bool inputPremul = shader.getGradFlags() & SkGradientShader::kInterpolateColorsInPremul_Flag;
Michael Ludwigb96cba32018-09-14 13:59:24 -0400383 bool allOpaque = true;
Brian Osman021ed512018-10-16 15:19:44 -0400384 SkAutoSTMalloc<4, SkPMColor4f> colors(shader.fColorCount);
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400385 SkColor4fXformer xformedColors(shader.fOrigColors4f, shader.fColorCount,
Brian Salomon4bc0c1f2019-09-30 15:12:27 -0400386 shader.fColorSpace.get(), args.fDstColorInfo->colorSpace());
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400387 for (int i = 0; i < shader.fColorCount; i++) {
Brian Osman021ed512018-10-16 15:19:44 -0400388 const SkColor4f& upmColor = xformedColors.fColors[i];
389 colors[i] = inputPremul ? upmColor.premul()
390 : SkPMColor4f{ upmColor.fR, upmColor.fG, upmColor.fB, upmColor.fA };
391 if (allOpaque && !SkScalarNearlyEqual(colors[i].fA, 1.0)) {
Michael Ludwigb96cba32018-09-14 13:59:24 -0400392 allOpaque = false;
393 }
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400394 }
395
Michael Ludwig0495f7a2018-09-12 15:23:33 -0400396 // SkGradientShader stores positions implicitly when they are evenly spaced, but the getPos()
397 // implementation performs a branch for every position index. Since the shader conversion
398 // requires lots of position tests, calculate all of the positions up front if needed.
399 SkTArray<SkScalar, true> implicitPos;
400 SkScalar* positions;
401 if (shader.fOrigPos) {
402 positions = shader.fOrigPos;
403 } else {
John Stilesf4bda742020-10-14 16:57:41 -0400404 implicitPos.reserve_back(shader.fColorCount);
Mike Kleind3ed3012018-11-06 19:23:08 -0500405 SkScalar posScale = SK_Scalar1 / (shader.fColorCount - 1);
Michael Ludwig0495f7a2018-09-12 15:23:33 -0400406 for (int i = 0 ; i < shader.fColorCount; i++) {
407 implicitPos.push_back(SkIntToScalar(i) * posScale);
408 }
409 positions = implicitPos.begin();
410 }
411
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400412 // All gradients are colorized the same way, regardless of layout
Michael Ludwiga7914d32018-09-14 09:47:21 -0400413 std::unique_ptr<GrFragmentProcessor> colorizer = make_colorizer(
414 colors.get(), positions, shader.fColorCount, inputPremul, args);
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400415 if (colorizer == nullptr) {
416 return nullptr;
417 }
418
Michael Ludwig672c4762020-08-05 17:28:42 -0400419 // The top-level effect has to export premul colors, but under certain conditions it doesn't
420 // need to do anything to achieve that: i.e. its interpolating already premul colors
421 // (inputPremul) or all the colors have a = 1, in which case premul is a no op. Note that this
422 // allOpaque check is more permissive than SkGradientShaderBase's isOpaque(), since we can
423 // optimize away the make-premul op for two point conical gradients (which report false for
424 // isOpaque).
Michael Ludwigb96cba32018-09-14 13:59:24 -0400425 bool makePremul = !inputPremul && !allOpaque;
426
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400427 // All tile modes are supported (unless something was added to SkShader)
Michael Ludwig672c4762020-08-05 17:28:42 -0400428 std::unique_ptr<GrFragmentProcessor> gradient;
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400429 switch(shader.getTileMode()) {
Mike Reedfae8fce2019-04-03 10:27:45 -0400430 case SkTileMode::kRepeat:
Brian Osmanc6804ed2021-06-29 11:11:49 -0400431 gradient = make_tiled_gradient(args, std::move(colorizer), std::move(layout),
432 /* mirror */ false, makePremul, allOpaque);
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400433 break;
Mike Reedfae8fce2019-04-03 10:27:45 -0400434 case SkTileMode::kMirror:
Brian Osmanc6804ed2021-06-29 11:11:49 -0400435 gradient = make_tiled_gradient(args, std::move(colorizer), std::move(layout),
436 /* mirror */ true, makePremul, allOpaque);
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400437 break;
Mike Reedfae8fce2019-04-03 10:27:45 -0400438 case SkTileMode::kClamp:
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400439 // For the clamped mode, the border colors are the first and last colors, corresponding
440 // to t=0 and t=1, because SkGradientShaderBase enforces that by adding color stops as
441 // appropriate. If there is a hard stop, this grabs the expected outer colors for the
442 // border.
Brian Osmanc6804ed2021-06-29 11:11:49 -0400443 gradient = make_clamped_gradient(std::move(colorizer), std::move(layout),
444 colors[0], colors[shader.fColorCount - 1],
445 makePremul, allOpaque);
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400446 break;
Mike Reedfae8fce2019-04-03 10:27:45 -0400447 case SkTileMode::kDecal:
Michael Ludwigb96cba32018-09-14 13:59:24 -0400448 // Even if the gradient colors are opaque, the decal borders are transparent so
449 // disable that optimization
Brian Osmanc6804ed2021-06-29 11:11:49 -0400450 gradient = make_clamped_gradient(std::move(colorizer), std::move(layout),
451 SK_PMColor4fTRANSPARENT, SK_PMColor4fTRANSPARENT,
452 makePremul, /* colorsAreOpaque */ false);
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400453 break;
454 }
455
Michael Ludwig672c4762020-08-05 17:28:42 -0400456 if (gradient == nullptr) {
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400457 // Unexpected tile mode
458 return nullptr;
459 }
Brian Salomonc0d79e52019-04-10 15:02:11 -0400460 if (args.fInputColorIsOpaque) {
Brian Osman55cbc752021-05-27 11:57:13 -0400461 // If the input alpha is known to be 1, we don't need to take the kSrcIn path. This is
462 // just an optimization. However, we can't just return 'gradient' here. We need to actually
463 // inhibit the coverage-as-alpha optimization, or we'll fail to incorporate AA correctly.
464 // The OverrideInput FP happens to do that, so wrap our fp in one of those. The gradient FP
465 // doesn't actually use the input color at all, so the overridden input is irrelevant.
Michael Ludwig672c4762020-08-05 17:28:42 -0400466 return GrFragmentProcessor::OverrideInput(std::move(gradient), SK_PMColor4fWHITE, false);
Brian Salomonc0d79e52019-04-10 15:02:11 -0400467 }
Michael Ludwig672c4762020-08-05 17:28:42 -0400468 return GrFragmentProcessor::MulChildByInputAlpha(std::move(gradient));
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400469}
470
471namespace GrGradientShader {
472
473std::unique_ptr<GrFragmentProcessor> MakeLinear(const SkLinearGradient& shader,
474 const GrFPArgs& args) {
Brian Osman65b45972021-06-25 15:58:43 +0000475 // We add a tiny delta to t. When gradient stops are set up so that a hard stop in a vertically
476 // or horizontally oriented gradient falls exactly at a column or row of pixel centers we can
477 // we can get slightly different interpolated t values along the column/row. By adding the delta
478 // we will consistently get the color to the "right" of the stop. Of course if the hard stop
479 // falls at X.5 - delta then we still could get inconsistent results, but that is much less
480 // likely. crbug.com/938592
481 // If/when we add filtering of the gradient this can be removed.
482 static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
483 half4 main(float2 coord) {
484 return half4(half(coord.x) + 0.00001, 1, 0, 0); // y = 1 for always valid
485 }
486 )");
487 // The linear gradient never rejects a pixel so it doesn't change opacity
488 auto fp = GrSkSLFP::Make(effect, "LinearLayout", /*inputFP=*/nullptr,
489 GrSkSLFP::OptFlags::kPreservesOpaqueInput);
490 return make_gradient(shader, args, std::move(fp));
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400491}
492
Michael Ludwig4089df82018-09-12 15:22:37 -0400493std::unique_ptr<GrFragmentProcessor> MakeRadial(const SkRadialGradient& shader,
494 const GrFPArgs& args) {
Brian Osman65b45972021-06-25 15:58:43 +0000495 static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
496 half4 main(float2 coord) {
497 return half4(half(length(coord)), 1, 0, 0); // y = 1 for always valid
498 }
499 )");
500 // The radial gradient never rejects a pixel so it doesn't change opacity
501 auto fp = GrSkSLFP::Make(effect, "RadialLayout", /*inputFP=*/nullptr,
502 GrSkSLFP::OptFlags::kPreservesOpaqueInput);
503 return make_gradient(shader, args, std::move(fp));
Michael Ludwig4089df82018-09-12 15:22:37 -0400504}
505
Michael Ludwig24d438b2018-09-12 15:22:50 -0400506std::unique_ptr<GrFragmentProcessor> MakeSweep(const SkSweepGradient& shader,
507 const GrFPArgs& args) {
Brian Osman65b45972021-06-25 15:58:43 +0000508 // On some devices they incorrectly implement atan2(y,x) as atan(y/x). In actuality it is
509 // atan2(y,x) = 2 * atan(y / (sqrt(x^2 + y^2) + x)). So to work around this we pass in (sqrt(x^2
510 // + y^2) + x) as the second parameter to atan2 in these cases. We let the device handle the
511 // undefined behavior of the second paramenter being 0 instead of doing the divide ourselves and
512 // using atan instead.
513 int useAtanWorkaround =
514 args.fContext->priv().caps()->shaderCaps()->atan2ImplementedAsAtanYOverX();
515 static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
516 uniform half bias;
517 uniform half scale;
518 uniform int useAtanWorkaround; // specialized
519
520 half4 main(float2 coord) {
521 half angle = bool(useAtanWorkaround)
522 ? half(2 * atan(-coord.y, length(coord) - coord.x))
523 : half(atan(-coord.y, -coord.x));
524
525 // 0.1591549430918 is 1/(2*pi), used since atan returns values [-pi, pi]
526 half t = (angle * 0.1591549430918 + 0.5 + bias) * scale;
527 return half4(t, 1, 0, 0); // y = 1 for always valid
528 }
529 )");
530 // The sweep gradient never rejects a pixel so it doesn't change opacity
531 auto fp = GrSkSLFP::Make(effect, "SweepLayout", /*inputFP=*/nullptr,
532 GrSkSLFP::OptFlags::kPreservesOpaqueInput,
533 "bias", shader.getTBias(),
534 "scale", shader.getTScale(),
535 "useAtanWorkaround", GrSkSLFP::Specialize(useAtanWorkaround));
536 return make_gradient(shader, args, std::move(fp));
Michael Ludwig24d438b2018-09-12 15:22:50 -0400537}
538
Michael Ludwig8f685082018-09-12 15:23:01 -0400539std::unique_ptr<GrFragmentProcessor> MakeConical(const SkTwoPointConicalGradient& shader,
540 const GrFPArgs& args) {
Brian Osman65b45972021-06-25 15:58:43 +0000541 // The 2 point conical gradient can reject a pixel so it does change opacity even if the input
542 // was opaque. Thus, all of these layout FPs disable that optimization.
543 std::unique_ptr<GrFragmentProcessor> fp;
544 SkTLazy<SkMatrix> matrix;
545 switch (shader.getType()) {
546 case SkTwoPointConicalGradient::Type::kStrip: {
547 static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
548 uniform half r0_2;
549 half4 main(float2 p) {
550 half v = 1; // validation flag, set to negative to discard fragment later
551 float t = r0_2 - p.y * p.y;
552 if (t >= 0) {
553 t = p.x + sqrt(t);
554 } else {
555 v = -1;
556 }
557 return half4(half(t), v, 0, 0);
558 }
559 )");
560 float r0 = shader.getStartRadius() / shader.getCenterX1();
561 fp = GrSkSLFP::Make(effect, "TwoPointConicalStripLayout", /*inputFP=*/nullptr,
562 GrSkSLFP::OptFlags::kNone,
563 "r0_2", r0 * r0);
564 } break;
565
566 case SkTwoPointConicalGradient::Type::kRadial: {
567 static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
568 uniform half r0;
569 uniform half lengthScale;
570 half4 main(float2 p) {
571 half v = 1; // validation flag, set to negative to discard fragment later
572 float t = length(p) * lengthScale - r0;
573 return half4(half(t), v, 0, 0);
574 }
575 )");
576 float dr = shader.getDiffRadius();
577 float r0 = shader.getStartRadius() / dr;
578 bool isRadiusIncreasing = dr >= 0;
579 fp = GrSkSLFP::Make(effect, "TwoPointConicalRadialLayout", /*inputFP=*/nullptr,
580 GrSkSLFP::OptFlags::kNone,
581 "r0", r0,
582 "lengthScale", isRadiusIncreasing ? 1.0f : -1.0f);
583
584 // GPU radial matrix is different from the original matrix, since we map the diff radius
585 // to have |dr| = 1, so manually compute the final gradient matrix here.
586
587 // Map center to (0, 0)
588 matrix.set(SkMatrix::Translate(-shader.getStartCenter().fX,
589 -shader.getStartCenter().fY));
590 // scale |diffRadius| to 1
591 matrix->postScale(1 / dr, 1 / dr);
592 } break;
593
594 case SkTwoPointConicalGradient::Type::kFocal: {
595 static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
596 // Optimization flags, all specialized:
597 uniform int isRadiusIncreasing;
598 uniform int isFocalOnCircle;
599 uniform int isWellBehaved;
600 uniform int isSwapped;
601 uniform int isNativelyFocal;
602
603 uniform half invR1; // 1/r1
604 uniform half fx; // focalX = r0/(r0-r1)
605
606 half4 main(float2 p) {
607 float t = -1;
608 half v = 1; // validation flag, set to negative to discard fragment later
609
610 float x_t = -1;
611 if (bool(isFocalOnCircle)) {
612 x_t = dot(p, p) / p.x;
613 } else if (bool(isWellBehaved)) {
614 x_t = length(p) - p.x * invR1;
615 } else {
616 float temp = p.x * p.x - p.y * p.y;
617
618 // Only do sqrt if temp >= 0; this is significantly slower than checking
619 // temp >= 0 in the if statement that checks r(t) >= 0. But GPU may break if
620 // we sqrt a negative float. (Although I havevn't observed that on any
621 // devices so far, and the old approach also does sqrt negative value
622 // without a check.) If the performance is really critical, maybe we should
623 // just compute the area where temp and x_t are always valid and drop all
624 // these ifs.
625 if (temp >= 0) {
626 if (bool(isSwapped) || !bool(isRadiusIncreasing)) {
627 x_t = -sqrt(temp) - p.x * invR1;
628 } else {
629 x_t = sqrt(temp) - p.x * invR1;
630 }
631 }
632 }
633
634 // The final calculation of t from x_t has lots of static optimizations but only
635 // do them when x_t is positive (which can be assumed true if isWellBehaved is
636 // true)
637 if (!bool(isWellBehaved)) {
638 // This will still calculate t even though it will be ignored later in the
639 // pipeline to avoid a branch
640 if (x_t <= 0.0) {
641 v = -1;
642 }
643 }
644 if (bool(isRadiusIncreasing)) {
645 if (bool(isNativelyFocal)) {
646 t = x_t;
647 } else {
648 t = x_t + fx;
649 }
650 } else {
651 if (bool(isNativelyFocal)) {
652 t = -x_t;
653 } else {
654 t = -x_t + fx;
655 }
656 }
657
658 if (bool(isSwapped)) {
659 t = 1 - t;
660 }
661
662 return half4(half(t), v, 0, 0);
663 }
664 )");
665
666 const SkTwoPointConicalGradient::FocalData& focalData = shader.getFocalData();
667 bool isRadiusIncreasing = (1 - focalData.fFocalX) > 0,
668 isFocalOnCircle = focalData.isFocalOnCircle(),
669 isWellBehaved = focalData.isWellBehaved(),
670 isSwapped = focalData.isSwapped(),
671 isNativelyFocal = focalData.isNativelyFocal();
672
673 fp = GrSkSLFP::Make(effect, "TwoPointConicalFocalLayout", /*inputFP=*/nullptr,
674 GrSkSLFP::OptFlags::kNone,
675 "isRadiusIncreasing", GrSkSLFP::Specialize<int>(isRadiusIncreasing),
676 "isFocalOnCircle", GrSkSLFP::Specialize<int>(isFocalOnCircle),
677 "isWellBehaved", GrSkSLFP::Specialize<int>(isWellBehaved),
678 "isSwapped", GrSkSLFP::Specialize<int>(isSwapped),
679 "isNativelyFocal", GrSkSLFP::Specialize<int>(isNativelyFocal),
680 "invR1", 1.0f / focalData.fR1,
681 "fx", focalData.fFocalX);
682 } break;
683 }
684 return make_gradient(shader, args, std::move(fp), matrix.getMaybeNull());
Michael Ludwig8f685082018-09-12 15:23:01 -0400685}
686
Michael Ludwig7f8c5242018-09-14 15:07:55 -0400687#if GR_TEST_UTILS
688RandomParams::RandomParams(SkRandom* random) {
689 // Set color count to min of 2 so that we don't trigger the const color optimization and make
690 // a non-gradient processor.
691 fColorCount = random->nextRangeU(2, kMaxRandomGradientColors);
692 fUseColors4f = random->nextBool();
693
694 // if one color, omit stops, otherwise randomly decide whether or not to
695 if (fColorCount == 1 || (fColorCount >= 2 && random->nextBool())) {
696 fStops = nullptr;
697 } else {
698 fStops = fStopStorage;
699 }
700
701 // if using SkColor4f, attach a random (possibly null) color space (with linear gamma)
702 if (fUseColors4f) {
703 fColorSpace = GrTest::TestColorSpace(random);
704 }
705
706 SkScalar stop = 0.f;
707 for (int i = 0; i < fColorCount; ++i) {
708 if (fUseColors4f) {
709 fColors4f[i].fR = random->nextUScalar1();
710 fColors4f[i].fG = random->nextUScalar1();
711 fColors4f[i].fB = random->nextUScalar1();
712 fColors4f[i].fA = random->nextUScalar1();
713 } else {
714 fColors[i] = random->nextU();
715 }
716 if (fStops) {
717 fStops[i] = stop;
718 stop = i < fColorCount - 1 ? stop + random->nextUScalar1() * (1.f - stop) : 1.f;
719 }
720 }
Mike Reedfae8fce2019-04-03 10:27:45 -0400721 fTileMode = static_cast<SkTileMode>(random->nextULessThan(kSkTileModeCount));
Michael Ludwig7f8c5242018-09-14 15:07:55 -0400722}
723#endif
724
John Stilesa6841be2020-08-06 14:11:56 -0400725} // namespace GrGradientShader