blob: 3746baa83b6d54a6c8ed0af9d8de5e33513f4cb6 [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/generated/GrClampedGradientEffect.h"
11#include "src/gpu/gradients/generated/GrTiledGradientEffect.h"
Michael Ludwig4f94ef62018-09-12 15:22:16 -040012
Mike Kleinc0bd9f92019-04-23 12:05:21 -050013#include "src/gpu/gradients/GrGradientBitmapCache.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050014#include "src/gpu/gradients/generated/GrUnrolledBinaryGradientColorizer.h"
Michael Ludwig4f94ef62018-09-12 15:22:16 -040015
Robert Phillipsb7bfbc22020-07-01 12:55:01 -040016#include "include/gpu/GrRecordingContext.h"
Brian Osman65b45972021-06-25 15:58:43 +000017#include "src/core/SkRuntimeEffectPriv.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050018#include "src/gpu/GrCaps.h"
Greg Danielf91aeb22019-06-18 09:58:02 -040019#include "src/gpu/GrColor.h"
Brian Salomon4bc0c1f2019-09-30 15:12:27 -040020#include "src/gpu/GrColorInfo.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050021#include "src/gpu/GrRecordingContextPriv.h"
22#include "src/gpu/SkGr.h"
Brian Osman65b45972021-06-25 15:58:43 +000023#include "src/gpu/effects/GrMatrixEffect.h"
24#include "src/gpu/effects/GrSkSLFP.h"
Brian Salomon17473752020-06-19 09:53:50 -040025#include "src/gpu/effects/GrTextureEffect.h"
Michael Ludwiga7914d32018-09-14 09:47:21 -040026
Michael Ludwig72535fb2018-09-28 11:53:32 -040027// Intervals smaller than this (that aren't hard stops) on low-precision-only devices force us to
28// use the textured gradient
29static const SkScalar kLowPrecisionIntervalLimit = 0.01f;
30
Michael Ludwiga7914d32018-09-14 09:47:21 -040031// Each cache entry costs 1K or 2K of RAM. Each bitmap will be 1x256 at either 32bpp or 64bpp.
32static const int kMaxNumCachedGradientBitmaps = 32;
33static const int kGradientTextureSize = 256;
34
35// NOTE: signature takes raw pointers to the color/pos arrays and a count to make it easy for
36// MakeColorizer to transparently take care of hard stops at the end points of the gradient.
Brian Osman021ed512018-10-16 15:19:44 -040037static std::unique_ptr<GrFragmentProcessor> make_textured_colorizer(const SkPMColor4f* colors,
Michael Ludwiga7914d32018-09-14 09:47:21 -040038 const SkScalar* positions, int count, bool premul, const GrFPArgs& args) {
39 static GrGradientBitmapCache gCache(kMaxNumCachedGradientBitmaps, kGradientTextureSize);
40
41 // Use 8888 or F16, depending on the destination config.
42 // TODO: Use 1010102 for opaque gradients, at least if destination is 1010102?
43 SkColorType colorType = kRGBA_8888_SkColorType;
Brian Salomon4bc0c1f2019-09-30 15:12:27 -040044 if (GrColorTypeIsWiderThan(args.fDstColorInfo->colorType(), 8)) {
Greg Daniel7bfc9132019-08-14 14:23:53 -040045 auto f16Format = args.fContext->priv().caps()->getDefaultBackendFormat(
46 GrColorType::kRGBA_F16, GrRenderable::kNo);
47 if (f16Format.isValid()) {
48 colorType = kRGBA_F16_SkColorType;
49 }
Michael Ludwiga7914d32018-09-14 09:47:21 -040050 }
51 SkAlphaType alphaType = premul ? kPremul_SkAlphaType : kUnpremul_SkAlphaType;
52
53 SkBitmap bitmap;
54 gCache.getGradient(colors, positions, count, colorType, alphaType, &bitmap);
55 SkASSERT(1 == bitmap.height() && SkIsPow2(bitmap.width()));
56 SkASSERT(bitmap.isImmutable());
57
Brian Salomon27c42022021-04-28 12:39:21 -040058 auto view = std::get<0>(GrMakeCachedBitmapProxyView(args.fContext, bitmap, GrMipmapped::kNo));
59 if (!view) {
Michael Ludwiga7914d32018-09-14 09:47:21 -040060 SkDebugf("Gradient won't draw. Could not create texture.");
61 return nullptr;
62 }
John Stiles1cf15932020-07-31 16:08:24 -040063
Brian Salomon17473752020-06-19 09:53:50 -040064 auto m = SkMatrix::Scale(view.width(), 1.f);
John Stiles1cf15932020-07-31 16:08:24 -040065 return GrTextureEffect::Make(std::move(view), alphaType, m, GrSamplerState::Filter::kLinear);
Michael Ludwiga7914d32018-09-14 09:47:21 -040066}
Michael Ludwig4f94ef62018-09-12 15:22:16 -040067
Brian Osman65b45972021-06-25 15:58:43 +000068
69static std::unique_ptr<GrFragmentProcessor> make_single_interval_colorizer(const SkPMColor4f& start,
70 const SkPMColor4f& end) {
71 static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
72 uniform half4 start;
73 uniform half4 end;
74 half4 main(float2 coord) {
75 // Clamping and/or wrapping was already handled by the parent shader so the output
76 // color is a simple lerp.
77 return mix(start, end, half(coord.x));
78 }
79 )");
80 return GrSkSLFP::Make(effect, "SingleIntervalColorizer", /*inputFP=*/nullptr,
81 GrSkSLFP::OptFlags::kNone,
82 "start", start,
83 "end", end);
84}
85
86static std::unique_ptr<GrFragmentProcessor> make_dual_interval_colorizer(const SkPMColor4f& c0,
87 const SkPMColor4f& c1,
88 const SkPMColor4f& c2,
89 const SkPMColor4f& c3,
90 float threshold) {
91 static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
92 uniform float4 scale01;
93 uniform float4 bias01;
94 uniform float4 scale23;
95 uniform float4 bias23;
96 uniform half threshold;
97
98 half4 main(float2 coord) {
99 half t = half(coord.x);
100
101 float4 scale, bias;
102 if (t < threshold) {
103 scale = scale01;
104 bias = bias01;
105 } else {
106 scale = scale23;
107 bias = bias23;
108 }
109
110 return half4(t * scale + bias);
111 }
112 )");
113
114 using sk4f = skvx::Vec<4, float>;
115
116 // Derive scale and biases from the 4 colors and threshold
117 auto vc0 = sk4f::Load(c0.vec());
118 auto vc1 = sk4f::Load(c1.vec());
119 auto scale01 = (vc1 - vc0) / threshold;
120 // bias01 = c0
121
122 auto vc2 = sk4f::Load(c2.vec());
123 auto vc3 = sk4f::Load(c3.vec());
124 auto scale23 = (vc3 - vc2) / (1 - threshold);
125 auto bias23 = vc2 - threshold * scale23;
126
127 return GrSkSLFP::Make(effect, "DualIntervalColorizer", /*inputFP=*/nullptr,
128 GrSkSLFP::OptFlags::kNone,
129 "scale01", scale01,
130 "bias01", c0,
131 "scale23", scale23,
132 "bias23", bias23,
133 "threshold", threshold);
134}
135
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400136// Analyze the shader's color stops and positions and chooses an appropriate colorizer to represent
137// the gradient.
Brian Osman021ed512018-10-16 15:19:44 -0400138static std::unique_ptr<GrFragmentProcessor> make_colorizer(const SkPMColor4f* colors,
Michael Ludwiga7914d32018-09-14 09:47:21 -0400139 const SkScalar* positions, int count, bool premul, const GrFPArgs& args) {
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400140 // If there are hard stops at the beginning or end, the first and/or last color should be
141 // ignored by the colorizer since it should only be used in a clamped border color. By detecting
142 // and removing these stops at the beginning, it makes optimizing the remaining color stops
143 // simpler.
144
Kevin Lubickbe03ef12021-06-16 15:28:00 -0400145 // SkGradientShaderBase guarantees that pos[0] == 0 by adding a default value.
Michael Ludwig0495f7a2018-09-12 15:23:33 -0400146 bool bottomHardStop = SkScalarNearlyEqual(positions[0], positions[1]);
147 // The same is true for pos[end] == 1
Michael Ludwiga7914d32018-09-14 09:47:21 -0400148 bool topHardStop = SkScalarNearlyEqual(positions[count - 2], positions[count - 1]);
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400149
150 int offset = 0;
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400151 if (bottomHardStop) {
152 offset += 1;
153 count--;
154 }
155 if (topHardStop) {
156 count--;
157 }
158
Michael Ludwig0495f7a2018-09-12 15:23:33 -0400159 // Two remaining colors means a single interval from 0 to 1
160 // (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 -0400161 if (count == 2) {
Brian Osman65b45972021-06-25 15:58:43 +0000162 return make_single_interval_colorizer(colors[offset], colors[offset + 1]);
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400163 }
164
Michael Ludwig72535fb2018-09-28 11:53:32 -0400165 // Do an early test for the texture fallback to skip all of the other tests for specific
166 // analytic support of the gradient (and compatibility with the hardware), when it's definitely
167 // impossible to use an analytic solution.
168 bool tryAnalyticColorizer = count <= GrUnrolledBinaryGradientColorizer::kMaxColorCount;
169
170 // The remaining analytic colorizers use scale*t+bias, and the scale/bias values can become
171 // quite large when thresholds are close (but still outside the hardstop limit). If float isn't
172 // 32-bit, output can be incorrect if the thresholds are too close together. However, the
173 // analytic shaders are higher quality, so they can be used with lower precision hardware when
174 // the thresholds are not ill-conditioned.
Robert Phillips9da87e02019-02-04 13:26:26 -0500175 const GrShaderCaps* caps = args.fContext->priv().caps()->shaderCaps();
Michael Ludwig72535fb2018-09-28 11:53:32 -0400176 if (!caps->floatIs32Bits() && tryAnalyticColorizer) {
177 // Could run into problems, check if thresholds are close together (with a limit of .01, so
178 // that scales will be less than 100, which leaves 4 decimals of precision on 16-bit).
179 for (int i = offset; i < count - 1; i++) {
180 SkScalar dt = SkScalarAbs(positions[i] - positions[i + 1]);
181 if (dt <= kLowPrecisionIntervalLimit && dt > SK_ScalarNearlyZero) {
182 tryAnalyticColorizer = false;
183 break;
184 }
185 }
186 }
187
188 if (tryAnalyticColorizer) {
189 if (count == 3) {
190 // Must be a dual interval gradient, where the middle point is at offset+1 and the two
191 // intervals share the middle color stop.
Brian Osman65b45972021-06-25 15:58:43 +0000192 return make_dual_interval_colorizer(colors[offset], colors[offset + 1],
193 colors[offset + 1], colors[offset + 2],
194 positions[offset + 1]);
Michael Ludwig72535fb2018-09-28 11:53:32 -0400195 } else if (count == 4 && SkScalarNearlyEqual(positions[offset + 1],
196 positions[offset + 2])) {
197 // Two separate intervals that join at the same threshold position
Brian Osman65b45972021-06-25 15:58:43 +0000198 return make_dual_interval_colorizer(colors[offset], colors[offset + 1],
199 colors[offset + 2], colors[offset + 3],
200 positions[offset + 1]);
Michael Ludwig72535fb2018-09-28 11:53:32 -0400201 }
202
203 // The single and dual intervals are a specialized case of the unrolled binary search
204 // colorizer which can analytically render gradients of up to 8 intervals (up to 9 or 16
205 // colors depending on how many hard stops are inserted).
206 std::unique_ptr<GrFragmentProcessor> unrolled = GrUnrolledBinaryGradientColorizer::Make(
207 colors + offset, positions + offset, count);
208 if (unrolled) {
209 return unrolled;
210 }
211 }
212
213 // Otherwise fall back to a rasterized gradient sampled by a texture, which can handle
214 // arbitrary gradients (the only downside being sampling resolution).
Michael Ludwiga7914d32018-09-14 09:47:21 -0400215 return make_textured_colorizer(colors + offset, positions + offset, count, premul, args);
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400216}
217
Michael Ludwig672c4762020-08-05 17:28:42 -0400218// Combines the colorizer and layout with an appropriately configured top-level effect based on the
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400219// gradient's tile mode
Brian Osman65b45972021-06-25 15:58:43 +0000220static std::unique_ptr<GrFragmentProcessor> make_gradient(
221 const SkGradientShaderBase& shader,
222 const GrFPArgs& args,
223 std::unique_ptr<GrFragmentProcessor> layout,
224 const SkMatrix* overrideMatrix = nullptr) {
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400225 // No shader is possible if a layout couldn't be created, e.g. a layout-specific Make() returned
226 // null.
227 if (layout == nullptr) {
228 return nullptr;
229 }
230
Brian Osman65b45972021-06-25 15:58:43 +0000231 // Wrap the layout in a matrix effect to apply the gradient's matrix:
232 SkMatrix matrix;
233 if (!shader.totalLocalMatrix(args.fPreLocalMatrix)->invert(&matrix)) {
234 return nullptr;
235 }
236 // Some two-point conical gradients use a custom matrix here
237 matrix.postConcat(overrideMatrix ? *overrideMatrix : shader.getGradientMatrix());
238 layout = GrMatrixEffect::Make(matrix, std::move(layout));
239
Brian Osman021ed512018-10-16 15:19:44 -0400240 // Convert all colors into destination space and into SkPMColor4fs, and handle
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400241 // premul issues depending on the interpolation mode
242 bool inputPremul = shader.getGradFlags() & SkGradientShader::kInterpolateColorsInPremul_Flag;
Michael Ludwigb96cba32018-09-14 13:59:24 -0400243 bool allOpaque = true;
Brian Osman021ed512018-10-16 15:19:44 -0400244 SkAutoSTMalloc<4, SkPMColor4f> colors(shader.fColorCount);
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400245 SkColor4fXformer xformedColors(shader.fOrigColors4f, shader.fColorCount,
Brian Salomon4bc0c1f2019-09-30 15:12:27 -0400246 shader.fColorSpace.get(), args.fDstColorInfo->colorSpace());
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400247 for (int i = 0; i < shader.fColorCount; i++) {
Brian Osman021ed512018-10-16 15:19:44 -0400248 const SkColor4f& upmColor = xformedColors.fColors[i];
249 colors[i] = inputPremul ? upmColor.premul()
250 : SkPMColor4f{ upmColor.fR, upmColor.fG, upmColor.fB, upmColor.fA };
251 if (allOpaque && !SkScalarNearlyEqual(colors[i].fA, 1.0)) {
Michael Ludwigb96cba32018-09-14 13:59:24 -0400252 allOpaque = false;
253 }
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400254 }
255
Michael Ludwig0495f7a2018-09-12 15:23:33 -0400256 // SkGradientShader stores positions implicitly when they are evenly spaced, but the getPos()
257 // implementation performs a branch for every position index. Since the shader conversion
258 // requires lots of position tests, calculate all of the positions up front if needed.
259 SkTArray<SkScalar, true> implicitPos;
260 SkScalar* positions;
261 if (shader.fOrigPos) {
262 positions = shader.fOrigPos;
263 } else {
John Stilesf4bda742020-10-14 16:57:41 -0400264 implicitPos.reserve_back(shader.fColorCount);
Mike Kleind3ed3012018-11-06 19:23:08 -0500265 SkScalar posScale = SK_Scalar1 / (shader.fColorCount - 1);
Michael Ludwig0495f7a2018-09-12 15:23:33 -0400266 for (int i = 0 ; i < shader.fColorCount; i++) {
267 implicitPos.push_back(SkIntToScalar(i) * posScale);
268 }
269 positions = implicitPos.begin();
270 }
271
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400272 // All gradients are colorized the same way, regardless of layout
Michael Ludwiga7914d32018-09-14 09:47:21 -0400273 std::unique_ptr<GrFragmentProcessor> colorizer = make_colorizer(
274 colors.get(), positions, shader.fColorCount, inputPremul, args);
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400275 if (colorizer == nullptr) {
276 return nullptr;
277 }
278
Michael Ludwig672c4762020-08-05 17:28:42 -0400279 // The top-level effect has to export premul colors, but under certain conditions it doesn't
280 // need to do anything to achieve that: i.e. its interpolating already premul colors
281 // (inputPremul) or all the colors have a = 1, in which case premul is a no op. Note that this
282 // allOpaque check is more permissive than SkGradientShaderBase's isOpaque(), since we can
283 // optimize away the make-premul op for two point conical gradients (which report false for
284 // isOpaque).
Michael Ludwigb96cba32018-09-14 13:59:24 -0400285 bool makePremul = !inputPremul && !allOpaque;
286
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400287 // All tile modes are supported (unless something was added to SkShader)
Michael Ludwig672c4762020-08-05 17:28:42 -0400288 std::unique_ptr<GrFragmentProcessor> gradient;
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400289 switch(shader.getTileMode()) {
Mike Reedfae8fce2019-04-03 10:27:45 -0400290 case SkTileMode::kRepeat:
Michael Ludwig672c4762020-08-05 17:28:42 -0400291 gradient = GrTiledGradientEffect::Make(std::move(colorizer), std::move(layout),
292 /* mirror */ false, makePremul, allOpaque);
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400293 break;
Mike Reedfae8fce2019-04-03 10:27:45 -0400294 case SkTileMode::kMirror:
Michael Ludwig672c4762020-08-05 17:28:42 -0400295 gradient = GrTiledGradientEffect::Make(std::move(colorizer), std::move(layout),
296 /* mirror */ true, makePremul, allOpaque);
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400297 break;
Mike Reedfae8fce2019-04-03 10:27:45 -0400298 case SkTileMode::kClamp:
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400299 // For the clamped mode, the border colors are the first and last colors, corresponding
300 // to t=0 and t=1, because SkGradientShaderBase enforces that by adding color stops as
301 // appropriate. If there is a hard stop, this grabs the expected outer colors for the
302 // border.
Michael Ludwig672c4762020-08-05 17:28:42 -0400303 gradient = GrClampedGradientEffect::Make(std::move(colorizer), std::move(layout),
304 colors[0], colors[shader.fColorCount - 1],
305 makePremul, allOpaque);
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400306 break;
Mike Reedfae8fce2019-04-03 10:27:45 -0400307 case SkTileMode::kDecal:
Michael Ludwigb96cba32018-09-14 13:59:24 -0400308 // Even if the gradient colors are opaque, the decal borders are transparent so
309 // disable that optimization
Michael Ludwig672c4762020-08-05 17:28:42 -0400310 gradient = GrClampedGradientEffect::Make(std::move(colorizer), std::move(layout),
311 SK_PMColor4fTRANSPARENT,
312 SK_PMColor4fTRANSPARENT,
313 makePremul, /* colorsAreOpaque */ false);
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400314 break;
315 }
316
Michael Ludwig672c4762020-08-05 17:28:42 -0400317 if (gradient == nullptr) {
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400318 // Unexpected tile mode
319 return nullptr;
320 }
Brian Salomonc0d79e52019-04-10 15:02:11 -0400321 if (args.fInputColorIsOpaque) {
Brian Osman55cbc752021-05-27 11:57:13 -0400322 // If the input alpha is known to be 1, we don't need to take the kSrcIn path. This is
323 // just an optimization. However, we can't just return 'gradient' here. We need to actually
324 // inhibit the coverage-as-alpha optimization, or we'll fail to incorporate AA correctly.
325 // The OverrideInput FP happens to do that, so wrap our fp in one of those. The gradient FP
326 // doesn't actually use the input color at all, so the overridden input is irrelevant.
Michael Ludwig672c4762020-08-05 17:28:42 -0400327 return GrFragmentProcessor::OverrideInput(std::move(gradient), SK_PMColor4fWHITE, false);
Brian Salomonc0d79e52019-04-10 15:02:11 -0400328 }
Michael Ludwig672c4762020-08-05 17:28:42 -0400329 return GrFragmentProcessor::MulChildByInputAlpha(std::move(gradient));
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400330}
331
332namespace GrGradientShader {
333
334std::unique_ptr<GrFragmentProcessor> MakeLinear(const SkLinearGradient& shader,
335 const GrFPArgs& args) {
Brian Osman65b45972021-06-25 15:58:43 +0000336 // We add a tiny delta to t. When gradient stops are set up so that a hard stop in a vertically
337 // or horizontally oriented gradient falls exactly at a column or row of pixel centers we can
338 // we can get slightly different interpolated t values along the column/row. By adding the delta
339 // we will consistently get the color to the "right" of the stop. Of course if the hard stop
340 // falls at X.5 - delta then we still could get inconsistent results, but that is much less
341 // likely. crbug.com/938592
342 // If/when we add filtering of the gradient this can be removed.
343 static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
344 half4 main(float2 coord) {
345 return half4(half(coord.x) + 0.00001, 1, 0, 0); // y = 1 for always valid
346 }
347 )");
348 // The linear gradient never rejects a pixel so it doesn't change opacity
349 auto fp = GrSkSLFP::Make(effect, "LinearLayout", /*inputFP=*/nullptr,
350 GrSkSLFP::OptFlags::kPreservesOpaqueInput);
351 return make_gradient(shader, args, std::move(fp));
Michael Ludwig4f94ef62018-09-12 15:22:16 -0400352}
353
Michael Ludwig4089df82018-09-12 15:22:37 -0400354std::unique_ptr<GrFragmentProcessor> MakeRadial(const SkRadialGradient& shader,
355 const GrFPArgs& args) {
Brian Osman65b45972021-06-25 15:58:43 +0000356 static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
357 half4 main(float2 coord) {
358 return half4(half(length(coord)), 1, 0, 0); // y = 1 for always valid
359 }
360 )");
361 // The radial gradient never rejects a pixel so it doesn't change opacity
362 auto fp = GrSkSLFP::Make(effect, "RadialLayout", /*inputFP=*/nullptr,
363 GrSkSLFP::OptFlags::kPreservesOpaqueInput);
364 return make_gradient(shader, args, std::move(fp));
Michael Ludwig4089df82018-09-12 15:22:37 -0400365}
366
Michael Ludwig24d438b2018-09-12 15:22:50 -0400367std::unique_ptr<GrFragmentProcessor> MakeSweep(const SkSweepGradient& shader,
368 const GrFPArgs& args) {
Brian Osman65b45972021-06-25 15:58:43 +0000369 // On some devices they incorrectly implement atan2(y,x) as atan(y/x). In actuality it is
370 // atan2(y,x) = 2 * atan(y / (sqrt(x^2 + y^2) + x)). So to work around this we pass in (sqrt(x^2
371 // + y^2) + x) as the second parameter to atan2 in these cases. We let the device handle the
372 // undefined behavior of the second paramenter being 0 instead of doing the divide ourselves and
373 // using atan instead.
374 int useAtanWorkaround =
375 args.fContext->priv().caps()->shaderCaps()->atan2ImplementedAsAtanYOverX();
376 static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
377 uniform half bias;
378 uniform half scale;
379 uniform int useAtanWorkaround; // specialized
380
381 half4 main(float2 coord) {
382 half angle = bool(useAtanWorkaround)
383 ? half(2 * atan(-coord.y, length(coord) - coord.x))
384 : half(atan(-coord.y, -coord.x));
385
386 // 0.1591549430918 is 1/(2*pi), used since atan returns values [-pi, pi]
387 half t = (angle * 0.1591549430918 + 0.5 + bias) * scale;
388 return half4(t, 1, 0, 0); // y = 1 for always valid
389 }
390 )");
391 // The sweep gradient never rejects a pixel so it doesn't change opacity
392 auto fp = GrSkSLFP::Make(effect, "SweepLayout", /*inputFP=*/nullptr,
393 GrSkSLFP::OptFlags::kPreservesOpaqueInput,
394 "bias", shader.getTBias(),
395 "scale", shader.getTScale(),
396 "useAtanWorkaround", GrSkSLFP::Specialize(useAtanWorkaround));
397 return make_gradient(shader, args, std::move(fp));
Michael Ludwig24d438b2018-09-12 15:22:50 -0400398}
399
Michael Ludwig8f685082018-09-12 15:23:01 -0400400std::unique_ptr<GrFragmentProcessor> MakeConical(const SkTwoPointConicalGradient& shader,
401 const GrFPArgs& args) {
Brian Osman65b45972021-06-25 15:58:43 +0000402 // The 2 point conical gradient can reject a pixel so it does change opacity even if the input
403 // was opaque. Thus, all of these layout FPs disable that optimization.
404 std::unique_ptr<GrFragmentProcessor> fp;
405 SkTLazy<SkMatrix> matrix;
406 switch (shader.getType()) {
407 case SkTwoPointConicalGradient::Type::kStrip: {
408 static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
409 uniform half r0_2;
410 half4 main(float2 p) {
411 half v = 1; // validation flag, set to negative to discard fragment later
412 float t = r0_2 - p.y * p.y;
413 if (t >= 0) {
414 t = p.x + sqrt(t);
415 } else {
416 v = -1;
417 }
418 return half4(half(t), v, 0, 0);
419 }
420 )");
421 float r0 = shader.getStartRadius() / shader.getCenterX1();
422 fp = GrSkSLFP::Make(effect, "TwoPointConicalStripLayout", /*inputFP=*/nullptr,
423 GrSkSLFP::OptFlags::kNone,
424 "r0_2", r0 * r0);
425 } break;
426
427 case SkTwoPointConicalGradient::Type::kRadial: {
428 static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
429 uniform half r0;
430 uniform half lengthScale;
431 half4 main(float2 p) {
432 half v = 1; // validation flag, set to negative to discard fragment later
433 float t = length(p) * lengthScale - r0;
434 return half4(half(t), v, 0, 0);
435 }
436 )");
437 float dr = shader.getDiffRadius();
438 float r0 = shader.getStartRadius() / dr;
439 bool isRadiusIncreasing = dr >= 0;
440 fp = GrSkSLFP::Make(effect, "TwoPointConicalRadialLayout", /*inputFP=*/nullptr,
441 GrSkSLFP::OptFlags::kNone,
442 "r0", r0,
443 "lengthScale", isRadiusIncreasing ? 1.0f : -1.0f);
444
445 // GPU radial matrix is different from the original matrix, since we map the diff radius
446 // to have |dr| = 1, so manually compute the final gradient matrix here.
447
448 // Map center to (0, 0)
449 matrix.set(SkMatrix::Translate(-shader.getStartCenter().fX,
450 -shader.getStartCenter().fY));
451 // scale |diffRadius| to 1
452 matrix->postScale(1 / dr, 1 / dr);
453 } break;
454
455 case SkTwoPointConicalGradient::Type::kFocal: {
456 static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
457 // Optimization flags, all specialized:
458 uniform int isRadiusIncreasing;
459 uniform int isFocalOnCircle;
460 uniform int isWellBehaved;
461 uniform int isSwapped;
462 uniform int isNativelyFocal;
463
464 uniform half invR1; // 1/r1
465 uniform half fx; // focalX = r0/(r0-r1)
466
467 half4 main(float2 p) {
468 float t = -1;
469 half v = 1; // validation flag, set to negative to discard fragment later
470
471 float x_t = -1;
472 if (bool(isFocalOnCircle)) {
473 x_t = dot(p, p) / p.x;
474 } else if (bool(isWellBehaved)) {
475 x_t = length(p) - p.x * invR1;
476 } else {
477 float temp = p.x * p.x - p.y * p.y;
478
479 // Only do sqrt if temp >= 0; this is significantly slower than checking
480 // temp >= 0 in the if statement that checks r(t) >= 0. But GPU may break if
481 // we sqrt a negative float. (Although I havevn't observed that on any
482 // devices so far, and the old approach also does sqrt negative value
483 // without a check.) If the performance is really critical, maybe we should
484 // just compute the area where temp and x_t are always valid and drop all
485 // these ifs.
486 if (temp >= 0) {
487 if (bool(isSwapped) || !bool(isRadiusIncreasing)) {
488 x_t = -sqrt(temp) - p.x * invR1;
489 } else {
490 x_t = sqrt(temp) - p.x * invR1;
491 }
492 }
493 }
494
495 // The final calculation of t from x_t has lots of static optimizations but only
496 // do them when x_t is positive (which can be assumed true if isWellBehaved is
497 // true)
498 if (!bool(isWellBehaved)) {
499 // This will still calculate t even though it will be ignored later in the
500 // pipeline to avoid a branch
501 if (x_t <= 0.0) {
502 v = -1;
503 }
504 }
505 if (bool(isRadiusIncreasing)) {
506 if (bool(isNativelyFocal)) {
507 t = x_t;
508 } else {
509 t = x_t + fx;
510 }
511 } else {
512 if (bool(isNativelyFocal)) {
513 t = -x_t;
514 } else {
515 t = -x_t + fx;
516 }
517 }
518
519 if (bool(isSwapped)) {
520 t = 1 - t;
521 }
522
523 return half4(half(t), v, 0, 0);
524 }
525 )");
526
527 const SkTwoPointConicalGradient::FocalData& focalData = shader.getFocalData();
528 bool isRadiusIncreasing = (1 - focalData.fFocalX) > 0,
529 isFocalOnCircle = focalData.isFocalOnCircle(),
530 isWellBehaved = focalData.isWellBehaved(),
531 isSwapped = focalData.isSwapped(),
532 isNativelyFocal = focalData.isNativelyFocal();
533
534 fp = GrSkSLFP::Make(effect, "TwoPointConicalFocalLayout", /*inputFP=*/nullptr,
535 GrSkSLFP::OptFlags::kNone,
536 "isRadiusIncreasing", GrSkSLFP::Specialize<int>(isRadiusIncreasing),
537 "isFocalOnCircle", GrSkSLFP::Specialize<int>(isFocalOnCircle),
538 "isWellBehaved", GrSkSLFP::Specialize<int>(isWellBehaved),
539 "isSwapped", GrSkSLFP::Specialize<int>(isSwapped),
540 "isNativelyFocal", GrSkSLFP::Specialize<int>(isNativelyFocal),
541 "invR1", 1.0f / focalData.fR1,
542 "fx", focalData.fFocalX);
543 } break;
544 }
545 return make_gradient(shader, args, std::move(fp), matrix.getMaybeNull());
Michael Ludwig8f685082018-09-12 15:23:01 -0400546}
547
Michael Ludwig7f8c5242018-09-14 15:07:55 -0400548#if GR_TEST_UTILS
549RandomParams::RandomParams(SkRandom* random) {
550 // Set color count to min of 2 so that we don't trigger the const color optimization and make
551 // a non-gradient processor.
552 fColorCount = random->nextRangeU(2, kMaxRandomGradientColors);
553 fUseColors4f = random->nextBool();
554
555 // if one color, omit stops, otherwise randomly decide whether or not to
556 if (fColorCount == 1 || (fColorCount >= 2 && random->nextBool())) {
557 fStops = nullptr;
558 } else {
559 fStops = fStopStorage;
560 }
561
562 // if using SkColor4f, attach a random (possibly null) color space (with linear gamma)
563 if (fUseColors4f) {
564 fColorSpace = GrTest::TestColorSpace(random);
565 }
566
567 SkScalar stop = 0.f;
568 for (int i = 0; i < fColorCount; ++i) {
569 if (fUseColors4f) {
570 fColors4f[i].fR = random->nextUScalar1();
571 fColors4f[i].fG = random->nextUScalar1();
572 fColors4f[i].fB = random->nextUScalar1();
573 fColors4f[i].fA = random->nextUScalar1();
574 } else {
575 fColors[i] = random->nextU();
576 }
577 if (fStops) {
578 fStops[i] = stop;
579 stop = i < fColorCount - 1 ? stop + random->nextUScalar1() * (1.f - stop) : 1.f;
580 }
581 }
Mike Reedfae8fce2019-04-03 10:27:45 -0400582 fTileMode = static_cast<SkTileMode>(random->nextULessThan(kSkTileModeCount));
Michael Ludwig7f8c5242018-09-14 15:07:55 -0400583}
584#endif
585
John Stilesa6841be2020-08-06 14:11:56 -0400586} // namespace GrGradientShader