1 /*
2 * Copyright 2019 Google LLC
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
8 #include "gm/gm.h"
9 #include "include/core/SkCanvas.h"
10 #include "include/core/SkData.h"
11 #include "include/core/SkPaint.h"
12 #include "include/core/SkRRect.h"
13 #include "include/core/SkSize.h"
14 #include "include/core/SkString.h"
15 #include "include/core/SkSurface.h"
16 #include "include/effects/SkGradientShader.h"
17 #include "include/effects/SkImageFilters.h"
18 #include "include/effects/SkRuntimeEffect.h"
19 #include "include/utils/SkRandom.h"
20 #include "src/core/SkColorSpacePriv.h"
21 #include "tools/Resources.h"
22
23 enum RT_Flags {
24 kAnimate_RTFlag = 0x1,
25 kBench_RTFlag = 0x2,
26 kColorFilter_RTFlag = 0x4,
27 };
28
29 class RuntimeShaderGM : public skiagm::GM {
30 public:
RuntimeShaderGM(const char * name,SkISize size,const char * sksl,uint32_t flags=0)31 RuntimeShaderGM(const char* name, SkISize size, const char* sksl, uint32_t flags = 0)
32 : fName(name), fSize(size), fFlags(flags), fSkSL(sksl) {}
33
onOnceBeforeDraw()34 void onOnceBeforeDraw() override {
35 auto [effect, error] = (fFlags & kColorFilter_RTFlag)
36 ? SkRuntimeEffect::MakeForColorFilter(fSkSL)
37 : SkRuntimeEffect::MakeForShader(fSkSL);
38 if (!effect) {
39 SkDebugf("RuntimeShader error: %s\n", error.c_str());
40 }
41 fEffect = std::move(effect);
42 }
43
runAsBench() const44 bool runAsBench() const override { return SkToBool(fFlags & kBench_RTFlag); }
onShortName()45 SkString onShortName() override { return fName; }
onISize()46 SkISize onISize() override { return fSize; }
47
onAnimate(double nanos)48 bool onAnimate(double nanos) override {
49 fSecs = nanos / (1000 * 1000 * 1000);
50 return SkToBool(fFlags & kAnimate_RTFlag);
51 }
52
53 protected:
54 SkString fName;
55 SkISize fSize;
56 uint32_t fFlags;
57 float fSecs = 0.0f;
58
59 SkString fSkSL;
60 sk_sp<SkRuntimeEffect> fEffect;
61 };
62
63 class SimpleRT : public RuntimeShaderGM {
64 public:
SimpleRT()65 SimpleRT() : RuntimeShaderGM("runtime_shader", {512, 256}, R"(
66 uniform half4 gColor;
67
68 half4 main(float2 p) {
69 return half4(p*(1.0/255), gColor.b, 1);
70 }
71 )", kBench_RTFlag) {}
72
onDraw(SkCanvas * canvas)73 void onDraw(SkCanvas* canvas) override {
74 SkRuntimeShaderBuilder builder(fEffect);
75
76 SkMatrix localM;
77 localM.setRotate(90, 128, 128);
78 builder.uniform("gColor") = SkColor4f{1, 0, 0, 1};
79
80 SkPaint p;
81 p.setShader(builder.makeShader(&localM));
82 canvas->drawRect({0, 0, 256, 256}, p);
83 }
84 };
DEF_GM(return new SimpleRT;)85 DEF_GM(return new SimpleRT;)
86
87 static sk_sp<SkShader> make_shader(sk_sp<SkImage> img, SkISize size) {
88 SkMatrix scale = SkMatrix::Scale(size.width() / (float)img->width(),
89 size.height() / (float)img->height());
90 return img->makeShader(SkSamplingOptions(), scale);
91 }
92
make_threshold(SkISize size)93 static sk_sp<SkShader> make_threshold(SkISize size) {
94 auto info = SkImageInfo::Make(size.width(), size.height(), kAlpha_8_SkColorType,
95 kPremul_SkAlphaType);
96 auto surf = SkSurface::MakeRaster(info);
97 auto canvas = surf->getCanvas();
98
99 const SkScalar rad = 50;
100 SkColor colors[] = {SK_ColorBLACK, 0};
101 SkPaint paint;
102 paint.setAntiAlias(true);
103 paint.setShader(SkGradientShader::MakeRadial({0,0}, rad, colors, nullptr, 2, SkTileMode::kClamp));
104
105 SkPaint layerPaint;
106 const SkScalar sigma = 16.0f;
107 layerPaint.setImageFilter(SkImageFilters::Blur(sigma, sigma, nullptr));
108 canvas->saveLayer(nullptr, &layerPaint);
109
110 SkRandom rand;
111 for (int i = 0; i < 25; ++i) {
112 SkScalar x = rand.nextF() * size.width();
113 SkScalar y = rand.nextF() * size.height();
114 canvas->save();
115 canvas->translate(x, y);
116 canvas->drawCircle(0, 0, rad, paint);
117 canvas->restore();
118 }
119
120 canvas->restore(); // apply the blur
121
122 return surf->makeImageSnapshot()->makeShader(SkSamplingOptions());
123 }
124
125 class ThresholdRT : public RuntimeShaderGM {
126 public:
ThresholdRT()127 ThresholdRT() : RuntimeShaderGM("threshold_rt", {256, 256}, R"(
128 uniform shader before_map;
129 uniform shader after_map;
130 uniform shader threshold_map;
131
132 uniform float cutoff;
133 uniform float slope;
134
135 float smooth_cutoff(float x) {
136 x = x * slope + (0.5 - slope * cutoff);
137 return clamp(x, 0, 1);
138 }
139
140 half4 main(float2 xy) {
141 half4 before = before_map.eval(xy);
142 half4 after = after_map.eval(xy);
143
144 float m = smooth_cutoff(threshold_map.eval(xy).a);
145 return mix(before, after, m);
146 }
147 )", kAnimate_RTFlag | kBench_RTFlag) {}
148
149 sk_sp<SkShader> fBefore, fAfter, fThreshold;
150
onOnceBeforeDraw()151 void onOnceBeforeDraw() override {
152 const SkISize size = {256, 256};
153 fThreshold = make_threshold(size);
154 fBefore = make_shader(GetResourceAsImage("images/mandrill_256.png"), size);
155 fAfter = make_shader(GetResourceAsImage("images/dog.jpg"), size);
156
157 this->RuntimeShaderGM::onOnceBeforeDraw();
158 }
159
onDraw(SkCanvas * canvas)160 void onDraw(SkCanvas* canvas) override {
161 SkRuntimeShaderBuilder builder(fEffect);
162
163 builder.uniform("cutoff") = sin(fSecs) * 0.55f + 0.5f;
164 builder.uniform("slope") = 10.0f;
165
166 builder.child("before_map") = fBefore;
167 builder.child("after_map") = fAfter;
168 builder.child("threshold_map") = fThreshold;
169
170 SkPaint paint;
171 paint.setShader(builder.makeShader());
172 canvas->drawRect({0, 0, 256, 256}, paint);
173
174 auto draw = [&](SkScalar x, SkScalar y, sk_sp<SkShader> shader) {
175 paint.setShader(shader);
176 canvas->save();
177 canvas->translate(x, y);
178 canvas->drawRect({0, 0, 256, 256}, paint);
179 canvas->restore();
180 };
181 draw(256, 0, fThreshold);
182 draw( 0, 256, fBefore);
183 draw(256, 256, fAfter);
184 }
185 };
186 DEF_GM(return new ThresholdRT;)
187
188 class SpiralRT : public RuntimeShaderGM {
189 public:
SpiralRT()190 SpiralRT() : RuntimeShaderGM("spiral_rt", {512, 512}, R"(
191 uniform float rad_scale;
192 uniform float2 in_center;
193 layout(color) uniform float4 in_colors0;
194 layout(color) uniform float4 in_colors1;
195
196 half4 main(float2 p) {
197 float2 pp = p - in_center;
198 float radius = length(pp);
199 radius = sqrt(radius);
200 float angle = atan(pp.y / pp.x);
201 float t = (angle + 3.1415926/2) / (3.1415926);
202 t += radius * rad_scale;
203 t = fract(t);
204 return in_colors0 * (1-t) + in_colors1 * t;
205 }
206 )", kAnimate_RTFlag | kBench_RTFlag) {}
207
onDraw(SkCanvas * canvas)208 void onDraw(SkCanvas* canvas) override {
209 SkRuntimeShaderBuilder builder(fEffect);
210
211 builder.uniform("rad_scale") = std::sin(fSecs * 0.5f + 2.0f) / 5;
212 builder.uniform("in_center") = SkV2{256, 256};
213 builder.uniform("in_colors0") = SkColors::kRed;
214 builder.uniform("in_colors1") = SkColors::kGreen;
215
216 SkPaint paint;
217 paint.setShader(builder.makeShader());
218 canvas->drawRect({0, 0, 512, 512}, paint);
219 }
220 };
221 DEF_GM(return new SpiralRT;)
222
223 // Test case for sampling with both unmodified input coordinates, and explicit coordinates.
224 // The first version of skbug.com/11869 suffered a bug where all samples of a child were treated
225 // as pass-through if *at least one* used the unmodified coordinates. This was detected & tracked
226 // in b/181092919. This GM is similar, and demonstrates the bug before the fix was applied.
227 class UnsharpRT : public RuntimeShaderGM {
228 public:
UnsharpRT()229 UnsharpRT() : RuntimeShaderGM("unsharp_rt", {512, 256}, R"(
230 uniform shader child;
231 half4 main(float2 xy) {
232 half4 c = child.eval(xy) * 5;
233 c -= child.eval(xy + float2( 1, 0));
234 c -= child.eval(xy + float2(-1, 0));
235 c -= child.eval(xy + float2( 0, 1));
236 c -= child.eval(xy + float2( 0, -1));
237 return c;
238 }
239 )") {}
240
241 sk_sp<SkImage> fMandrill;
242
onOnceBeforeDraw()243 void onOnceBeforeDraw() override {
244 fMandrill = GetResourceAsImage("images/mandrill_256.png");
245 this->RuntimeShaderGM::onOnceBeforeDraw();
246 }
247
onDraw(SkCanvas * canvas)248 void onDraw(SkCanvas* canvas) override {
249 // First we draw the unmodified image
250 canvas->drawImage(fMandrill, 0, 0);
251
252 // Now draw the image with our unsharp mask applied
253 SkRuntimeShaderBuilder builder(fEffect);
254 const SkSamplingOptions sampling(SkFilterMode::kNearest);
255 builder.child("child") = fMandrill->makeShader(sampling);
256
257 SkPaint paint;
258 paint.setShader(builder.makeShader());
259 canvas->translate(256, 0);
260 canvas->drawRect({ 0, 0, 256, 256 }, paint);
261 }
262 };
263 DEF_GM(return new UnsharpRT;)
264
265 class ColorCubeRT : public RuntimeShaderGM {
266 public:
ColorCubeRT()267 ColorCubeRT() : RuntimeShaderGM("color_cube_rt", {512, 512}, R"(
268 uniform shader child;
269 uniform shader color_cube;
270
271 uniform float rg_scale;
272 uniform float rg_bias;
273 uniform float b_scale;
274 uniform float inv_size;
275
276 half4 main(float2 xy) {
277 float4 c = unpremul(child.eval(xy));
278
279 // Map to cube coords:
280 float3 cubeCoords = float3(c.rg * rg_scale + rg_bias, c.b * b_scale);
281
282 // Compute slice coordinate
283 float2 coords1 = float2((floor(cubeCoords.b) + cubeCoords.r) * inv_size, cubeCoords.g);
284 float2 coords2 = float2(( ceil(cubeCoords.b) + cubeCoords.r) * inv_size, cubeCoords.g);
285
286 // Two bilinear fetches, plus a manual lerp for the third axis:
287 half4 color = mix(color_cube.eval(coords1), color_cube.eval(coords2),
288 fract(cubeCoords.b));
289
290 // Premul again
291 color.rgb *= color.a;
292
293 return color;
294 }
295 )") {}
296
297 sk_sp<SkImage> fMandrill, fMandrillSepia, fIdentityCube, fSepiaCube;
298
onOnceBeforeDraw()299 void onOnceBeforeDraw() override {
300 fMandrill = GetResourceAsImage("images/mandrill_256.png");
301 fMandrillSepia = GetResourceAsImage("images/mandrill_sepia.png");
302 fIdentityCube = GetResourceAsImage("images/lut_identity.png");
303 fSepiaCube = GetResourceAsImage("images/lut_sepia.png");
304
305 this->RuntimeShaderGM::onOnceBeforeDraw();
306 }
307
onDraw(SkCanvas * canvas)308 void onDraw(SkCanvas* canvas) override {
309 SkRuntimeShaderBuilder builder(fEffect);
310
311 // First we draw the unmodified image, and a copy that was sepia-toned in Photoshop:
312 canvas->drawImage(fMandrill, 0, 0);
313 canvas->drawImage(fMandrillSepia, 0, 256);
314
315 // LUT dimensions should be (kSize^2, kSize)
316 constexpr float kSize = 16.0f;
317
318 const SkSamplingOptions sampling(SkFilterMode::kLinear);
319
320 builder.uniform("rg_scale") = (kSize - 1) / kSize;
321 builder.uniform("rg_bias") = 0.5f / kSize;
322 builder.uniform("b_scale") = kSize - 1;
323 builder.uniform("inv_size") = 1.0f / kSize;
324
325 builder.child("child") = fMandrill->makeShader(sampling);
326
327 SkPaint paint;
328
329 // TODO: Should we add SkImage::makeNormalizedShader() to handle this automatically?
330 SkMatrix normalize = SkMatrix::Scale(1.0f / (kSize * kSize), 1.0f / kSize);
331
332 // Now draw the image with an identity color cube - it should look like the original
333 builder.child("color_cube") = fIdentityCube->makeShader(sampling, normalize);
334 paint.setShader(builder.makeShader());
335 canvas->translate(256, 0);
336 canvas->drawRect({ 0, 0, 256, 256 }, paint);
337
338 // ... and with a sepia-tone color cube. This should match the sepia-toned image.
339 builder.child("color_cube") = fSepiaCube->makeShader(sampling, normalize);
340 paint.setShader(builder.makeShader());
341 canvas->translate(0, 256);
342 canvas->drawRect({ 0, 0, 256, 256 }, paint);
343 }
344 };
345 DEF_GM(return new ColorCubeRT;)
346
347 // Same as above, but demonstrating how to implement this as a runtime color filter (that samples
348 // a shader child for the LUT).
349 class ColorCubeColorFilterRT : public RuntimeShaderGM {
350 public:
ColorCubeColorFilterRT()351 ColorCubeColorFilterRT() : RuntimeShaderGM("color_cube_cf_rt", {512, 512}, R"(
352 uniform shader color_cube;
353
354 uniform float rg_scale;
355 uniform float rg_bias;
356 uniform float b_scale;
357 uniform float inv_size;
358
359 half4 main(half4 inColor) {
360 float4 c = unpremul(inColor);
361
362 // Map to cube coords:
363 float3 cubeCoords = float3(c.rg * rg_scale + rg_bias, c.b * b_scale);
364
365 // Compute slice coordinate
366 float2 coords1 = float2((floor(cubeCoords.b) + cubeCoords.r) * inv_size, cubeCoords.g);
367 float2 coords2 = float2(( ceil(cubeCoords.b) + cubeCoords.r) * inv_size, cubeCoords.g);
368
369 // Two bilinear fetches, plus a manual lerp for the third axis:
370 half4 color = mix(color_cube.eval(coords1), color_cube.eval(coords2),
371 fract(cubeCoords.b));
372
373 // Premul again
374 color.rgb *= color.a;
375
376 return color;
377 }
378 )", kColorFilter_RTFlag) {}
379
380 sk_sp<SkImage> fMandrill, fMandrillSepia, fIdentityCube, fSepiaCube;
381
onOnceBeforeDraw()382 void onOnceBeforeDraw() override {
383 fMandrill = GetResourceAsImage("images/mandrill_256.png");
384 fMandrillSepia = GetResourceAsImage("images/mandrill_sepia.png");
385 fIdentityCube = GetResourceAsImage("images/lut_identity.png");
386 fSepiaCube = GetResourceAsImage("images/lut_sepia.png");
387
388 this->RuntimeShaderGM::onOnceBeforeDraw();
389 }
390
onDraw(SkCanvas * canvas)391 void onDraw(SkCanvas* canvas) override {
392 // First we draw the unmodified image, and a copy that was sepia-toned in Photoshop:
393 canvas->drawImage(fMandrill, 0, 0);
394 canvas->drawImage(fMandrillSepia, 0, 256);
395
396 // LUT dimensions should be (kSize^2, kSize)
397 constexpr float kSize = 16.0f;
398
399 const SkSamplingOptions sampling(SkFilterMode::kLinear);
400
401 float uniforms[] = {
402 (kSize - 1) / kSize, // rg_scale
403 0.5f / kSize, // rg_bias
404 kSize - 1, // b_scale
405 1.0f / kSize, // inv_size
406 };
407
408 SkPaint paint;
409
410 // TODO: Should we add SkImage::makeNormalizedShader() to handle this automatically?
411 SkMatrix normalize = SkMatrix::Scale(1.0f / (kSize * kSize), 1.0f / kSize);
412
413 // Now draw the image with an identity color cube - it should look like the original
414 SkRuntimeEffect::ChildPtr children[] = {fIdentityCube->makeShader(sampling, normalize)};
415 paint.setColorFilter(fEffect->makeColorFilter(
416 SkData::MakeWithCopy(uniforms, sizeof(uniforms)), SkMakeSpan(children)));
417 canvas->drawImage(fMandrill, 256, 0, sampling, &paint);
418
419 // ... and with a sepia-tone color cube. This should match the sepia-toned image.
420 children[0] = fSepiaCube->makeShader(sampling, normalize);
421 paint.setColorFilter(fEffect->makeColorFilter(
422 SkData::MakeWithCopy(uniforms, sizeof(uniforms)), SkMakeSpan(children)));
423 canvas->drawImage(fMandrill, 256, 256, sampling, &paint);
424 }
425 };
426 DEF_GM(return new ColorCubeColorFilterRT;)
427
428 class DefaultColorRT : public RuntimeShaderGM {
429 public:
DefaultColorRT()430 DefaultColorRT() : RuntimeShaderGM("default_color_rt", {512, 256}, R"(
431 uniform shader child;
432 half4 main(float2 xy) {
433 return child.eval(xy);
434 }
435 )") {}
436
437 sk_sp<SkImage> fMandrill;
438
onOnceBeforeDraw()439 void onOnceBeforeDraw() override {
440 fMandrill = GetResourceAsImage("images/mandrill_256.png");
441 this->RuntimeShaderGM::onOnceBeforeDraw();
442 }
443
onDraw(SkCanvas * canvas)444 void onDraw(SkCanvas* canvas) override {
445 SkRuntimeShaderBuilder builder(fEffect);
446
447 // First, we leave the child as null, so sampling it returns the default (paint) color
448 SkPaint paint;
449 paint.setColor4f({ 0.25f, 0.75f, 0.75f, 1.0f });
450 paint.setShader(builder.makeShader());
451 canvas->drawRect({ 0, 0, 256, 256 }, paint);
452
453 // Now we bind an image shader as the child. This (by convention) scales by the paint alpha
454 builder.child("child") = fMandrill->makeShader(SkSamplingOptions());
455 paint.setColor4f({ 1.0f, 1.0f, 1.0f, 0.5f });
456 paint.setShader(builder.makeShader());
457 canvas->translate(256, 0);
458 canvas->drawRect({ 0, 0, 256, 256 }, paint);
459
460 }
461 };
462 DEF_GM(return new DefaultColorRT;)
463
464 // Emits coverage for a rounded rectangle whose corners are superellipses defined by the boundary:
465 //
466 // x^n + y^n == 1
467 //
468 // Where x and y are normalized, clamped coordinates ranging from 0..1 inside the nearest corner's
469 // bounding box.
470 //
471 // See: https://en.wikipedia.org/wiki/Superellipse
472 class ClipSuperRRect : public RuntimeShaderGM {
473 public:
ClipSuperRRect(const char * name,float power)474 ClipSuperRRect(const char* name, float power) : RuntimeShaderGM(name, {500, 500}, R"(
475 uniform float power_minus1;
476 uniform float2 stretch_factor;
477 uniform float2x2 derivatives;
478 half4 main(float2 xy) {
479 xy = max(abs(xy) + stretch_factor, 0);
480 float2 exp_minus1 = pow(xy, power_minus1.xx); // If power == 3.5: xy * xy * sqrt(xy)
481 float f = dot(exp_minus1, xy) - 1; // f = x^n + y^n - 1
482 float2 grad = exp_minus1 * derivatives;
483 float fwidth = abs(grad.x) + abs(grad.y) + 1e-12; // 1e-12 to avoid a divide by zero.
484 return half4(saturate(.5 - f/fwidth)); // Approx coverage by riding the gradient to f=0.
485 }
486 )"), fPower(power) {}
487
drawSuperRRect(SkCanvas * canvas,const SkRect & superRRect,float radX,float radY,SkColor color)488 void drawSuperRRect(SkCanvas* canvas, const SkRect& superRRect, float radX, float radY,
489 SkColor color) {
490 SkPaint paint;
491 paint.setColor(color);
492
493 if (fPower == 2) {
494 // Draw a normal round rect for the sake of testing.
495 SkRRect rrect = SkRRect::MakeRectXY(superRRect, radX, radY);
496 paint.setAntiAlias(true);
497 canvas->drawRRect(rrect, paint);
498 return;
499 }
500
501 SkRuntimeShaderBuilder builder(fEffect);
502 builder.uniform("power_minus1") = fPower - 1;
503
504 // Size the corners such that the "apex" of our "super" rounded corner is in the same
505 // location that the apex of a circular rounded corner would be with the given radii. We
506 // define the apex as the point on the rounded corner that is 45 degrees between the
507 // horizontal and vertical edges.
508 float scale = (1 - SK_ScalarRoot2Over2) / (1 - exp2f(-1/fPower));
509 float cornerWidth = radX * scale;
510 float cornerHeight = radY * scale;
511 cornerWidth = std::min(cornerWidth, superRRect.width() * .5f);
512 cornerHeight = std::min(cornerHeight, superRRect.height() * .5f);
513 // The stretch factor controls how long the flat edge should be between rounded corners.
514 builder.uniform("stretch_factor") = SkV2{1 - superRRect.width()*.5f / cornerWidth,
515 1 - superRRect.height()*.5f / cornerHeight};
516
517 // Calculate a 2x2 "derivatives" matrix that the shader will use to find the gradient.
518 //
519 // f = s^n + t^n - 1 [s,t are "super" rounded corner coords in normalized 0..1 space]
520 //
521 // gradient = [df/dx df/dy] = [ns^(n-1) nt^(n-1)] * |ds/dx ds/dy|
522 // |dt/dx dt/dy|
523 //
524 // = [s^(n-1) t^(n-1)] * |n 0| * |ds/dx ds/dy|
525 // |0 n| |dt/dx dt/dy|
526 //
527 // = [s^(n-1) t^(n-1)] * |2n/cornerWidth 0| * mat2x2(canvasMatrix)^-1
528 // |0 2n/cornerHeight|
529 //
530 // = [s^(n-1) t^(n-1)] * "derivatives"
531 //
532 const SkMatrix& M = canvas->getTotalMatrix();
533 float a=M.getScaleX(), b=M.getSkewX(), c=M.getSkewY(), d=M.getScaleY();
534 float determinant = a*d - b*c;
535 float dx = fPower / (cornerWidth * determinant);
536 float dy = fPower / (cornerHeight * determinant);
537 builder.uniform("derivatives") = SkV4{d*dx, -c*dy, -b*dx, a*dy};
538
539 // This matrix will be inverted by the effect system, giving a matrix that converts local
540 // coordinates to (almost) coner coordinates. To get the rest of the way to the nearest
541 // corner's space, the shader will have to take the absolute value, add the stretch_factor,
542 // then clamp above zero.
543 SkMatrix cornerToLocal;
544 cornerToLocal.setScaleTranslate(cornerWidth, cornerHeight, superRRect.centerX(),
545 superRRect.centerY());
546 canvas->clipShader(builder.makeShader(&cornerToLocal));
547
548 // Bloat the outer edges of the rect we will draw so it contains all the antialiased pixels.
549 // Bloat by a full pixel instead of half in case Skia is in a mode that draws this rect with
550 // unexpected AA of its own.
551 float inverseDet = 1 / fabsf(determinant);
552 float bloatX = (fabsf(d) + fabsf(c)) * inverseDet;
553 float bloatY = (fabsf(b) + fabsf(a)) * inverseDet;
554 canvas->drawRect(superRRect.makeOutset(bloatX, bloatY), paint);
555 }
556
onDraw(SkCanvas * canvas)557 void onDraw(SkCanvas* canvas) override {
558 SkRandom rand(2);
559
560 canvas->save();
561 canvas->translate(canvas->imageInfo().width() / 2.f, canvas->imageInfo().height() / 2.f);
562
563 canvas->save();
564 canvas->rotate(21);
565 this->drawSuperRRect(canvas, SkRect::MakeXYWH(-5, 25, 175, 100), 50, 30,
566 rand.nextU() | 0xff808080);
567 canvas->restore();
568
569 canvas->save();
570 canvas->rotate(94);
571 this->drawSuperRRect(canvas, SkRect::MakeXYWH(95, 75, 125, 100), 30, 30,
572 rand.nextU() | 0xff808080);
573 canvas->restore();
574
575 canvas->save();
576 canvas->rotate(132);
577 this->drawSuperRRect(canvas, SkRect::MakeXYWH(0, 75, 150, 100), 40, 30,
578 rand.nextU() | 0xff808080);
579 canvas->restore();
580
581 canvas->save();
582 canvas->rotate(282);
583 this->drawSuperRRect(canvas, SkRect::MakeXYWH(15, -20, 100, 100), 20, 20,
584 rand.nextU() | 0xff808080);
585 canvas->restore();
586
587 canvas->save();
588 canvas->rotate(0);
589 this->drawSuperRRect(canvas, SkRect::MakeXYWH(140, -50, 90, 110), 25, 25,
590 rand.nextU() | 0xff808080);
591 canvas->restore();
592
593 canvas->save();
594 canvas->rotate(-35);
595 this->drawSuperRRect(canvas, SkRect::MakeXYWH(160, -60, 60, 90), 18, 18,
596 rand.nextU() | 0xff808080);
597 canvas->restore();
598
599 canvas->save();
600 canvas->rotate(65);
601 this->drawSuperRRect(canvas, SkRect::MakeXYWH(220, -120, 60, 90), 18, 18,
602 rand.nextU() | 0xff808080);
603 canvas->restore();
604
605 canvas->save();
606 canvas->rotate(265);
607 this->drawSuperRRect(canvas, SkRect::MakeXYWH(150, -129, 80, 160), 24, 39,
608 rand.nextU() | 0xff808080);
609 canvas->restore();
610
611 canvas->restore();
612 }
613
614 private:
615 const float fPower;
616 };
617 DEF_GM(return new ClipSuperRRect("clip_super_rrect_pow2", 2);)
618 // DEF_GM(return new ClipSuperRRect("clip_super_rrect_pow3", 3);)
619 DEF_GM(return new ClipSuperRRect("clip_super_rrect_pow3.5", 3.5);)
620 // DEF_GM(return new ClipSuperRRect("clip_super_rrect_pow4", 4);)
621 // DEF_GM(return new ClipSuperRRect("clip_super_rrect_pow4.5", 4.5);)
622 // DEF_GM(return new ClipSuperRRect("clip_super_rrect_pow5", 5);)
623
624 class LinearGradientRT : public RuntimeShaderGM {
625 public:
LinearGradientRT()626 LinearGradientRT() : RuntimeShaderGM("linear_gradient_rt", {256 + 10, 128 + 15}, R"(
627 layout(color) uniform vec4 in_colors0;
628 layout(color) uniform vec4 in_colors1;
629
630 vec4 main(vec2 p) {
631 float t = p.x / 256;
632 if (p.y < 32) {
633 return mix(in_colors0, in_colors1, t);
634 } else {
635 vec3 linColor0 = toLinearSrgb(in_colors0.rgb);
636 vec3 linColor1 = toLinearSrgb(in_colors1.rgb);
637 vec3 linColor = mix(linColor0, linColor1, t);
638 return fromLinearSrgb(linColor).rgb1;
639 }
640 }
641 )") {}
642
onDraw(SkCanvas * canvas)643 void onDraw(SkCanvas* canvas) override {
644 // Colors chosen to use values other than 0 and 1 - so that it's obvious if the conversion
645 // intrinsics are doing anything. (Most transfer functions map 0 -> 0 and 1 -> 1).
646 SkRuntimeShaderBuilder builder(fEffect);
647 builder.uniform("in_colors0") = SkColor4f{0.75f, 0.25f, 0.0f, 1.0f};
648 builder.uniform("in_colors1") = SkColor4f{0.0f, 0.75f, 0.25f, 1.0f};
649 SkPaint paint;
650 paint.setShader(builder.makeShader());
651
652 canvas->save();
653 canvas->clear(SK_ColorWHITE);
654 canvas->translate(5, 5);
655
656 // We draw everything twice. First to a surface with no color management, where the
657 // intrinsics should do nothing (eg, the top bar should look the same in the top and bottom
658 // halves). Then to an sRGB surface, where they should produce linearly interpolated
659 // gradients (the bottom half of the second bar should be brighter than the top half).
660 for (auto cs : {static_cast<SkColorSpace*>(nullptr), sk_srgb_singleton()}) {
661 SkImageInfo info = SkImageInfo::Make(
662 256, 64, kN32_SkColorType, kPremul_SkAlphaType, sk_ref_sp(cs));
663 auto surface = canvas->makeSurface(info);
664 if (!surface) {
665 surface = SkSurface::MakeRaster(info);
666 }
667
668 surface->getCanvas()->drawRect({0, 0, 256, 64}, paint);
669 canvas->drawImage(surface->makeImageSnapshot(), 0, 0);
670 canvas->translate(0, 64 + 5);
671 }
672
673 canvas->restore();
674 }
675 };
676 DEF_GM(return new LinearGradientRT;)
677
678 DEF_SIMPLE_GM(child_sampling_rt, canvas, 256,256) {
679 static constexpr char scale[] =
680 "uniform shader child;"
681 "half4 main(float2 xy) {"
682 " return child.eval(xy*0.1);"
683 "}";
684
685 SkPaint p;
686 p.setColor(SK_ColorRED);
687 p.setAntiAlias(true);
688 p.setStyle(SkPaint::kStroke_Style);
689 p.setStrokeWidth(1);
690
691 auto surf = SkSurface::MakeRasterN32Premul(100,100);
692 surf->getCanvas()->drawLine(0, 0, 100, 100, p);
693 auto shader = surf->makeImageSnapshot()->makeShader(SkSamplingOptions(SkFilterMode::kLinear));
694
695 SkRuntimeShaderBuilder builder(SkRuntimeEffect::MakeForShader(SkString(scale)).effect);
696 builder.child("child") = shader;
697 p.setShader(builder.makeShader());
698
699 canvas->drawPaint(p);
700 }
701
normal_map_shader()702 static sk_sp<SkShader> normal_map_shader() {
703 // Produces a hemispherical normal:
704 static const char* kSrc = R"(
705 half4 main(vec2 p) {
706 p = (p / 256) * 2 - 1;
707 float p2 = dot(p, p);
708 vec3 v = (p2 > 1) ? vec3(0, 0, 1) : vec3(p, sqrt(1 - p2));
709 return (v * 0.5 + 0.5).xyz1;
710 }
711 )";
712 auto effect = SkRuntimeEffect::MakeForShader(SkString(kSrc)).effect;
713 return effect->makeShader(nullptr, {});
714 }
715
normal_map_image()716 static sk_sp<SkImage> normal_map_image() {
717 // Above, baked into an image:
718 auto info = SkImageInfo::Make(256, 256, kN32_SkColorType, kPremul_SkAlphaType);
719 auto surface = SkSurface::MakeRaster(info);
720 SkPaint p;
721 p.setShader(normal_map_shader());
722 surface->getCanvas()->drawPaint(p);
723 return surface->makeImageSnapshot();
724 }
725
normal_map_image_shader()726 static sk_sp<SkShader> normal_map_image_shader() {
727 return normal_map_image()->makeShader(SkSamplingOptions{});
728 }
729
normal_map_raw_image_shader()730 static sk_sp<SkShader> normal_map_raw_image_shader() {
731 return normal_map_image()->makeRawShader(SkSamplingOptions{});
732 }
733
normal_map_unpremul_image()734 static sk_sp<SkImage> normal_map_unpremul_image() {
735 auto image = normal_map_image();
736 SkPixmap pm;
737 SkAssertResult(image->peekPixels(&pm));
738 SkBitmap bmp;
739 bmp.allocPixels(image->imageInfo().makeAlphaType(kUnpremul_SkAlphaType));
740 // Copy all pixels over, but set alpha to 0
741 for (int y = 0; y < pm.height(); y++) {
742 for (int x = 0; x < pm.width(); x++) {
743 *bmp.getAddr32(x, y) = *pm.addr32(x, y) & 0x00FFFFFF;
744 }
745 }
746 return bmp.asImage();
747 }
748
normal_map_unpremul_image_shader()749 static sk_sp<SkShader> normal_map_unpremul_image_shader() {
750 return normal_map_unpremul_image()->makeShader(SkSamplingOptions{});
751 }
752
normal_map_raw_unpremul_image_shader()753 static sk_sp<SkShader> normal_map_raw_unpremul_image_shader() {
754 return normal_map_unpremul_image()->makeRawShader(SkSamplingOptions{});
755 }
756
lit_shader(sk_sp<SkShader> normals)757 static sk_sp<SkShader> lit_shader(sk_sp<SkShader> normals) {
758 // Simple N-dot-L against a fixed, directional light:
759 static const char* kSrc = R"(
760 uniform shader normals;
761 half4 main(vec2 p) {
762 vec3 n = normalize(normals.eval(p).xyz * 2 - 1);
763 vec3 l = normalize(vec3(1, -1, 1));
764 return saturate(dot(n, l)).xxx1;
765 }
766 )";
767 auto effect = SkRuntimeEffect::MakeForShader(SkString(kSrc)).effect;
768 return effect->makeShader(nullptr, &normals, 1);
769 }
770
lit_shader_linear(sk_sp<SkShader> normals)771 static sk_sp<SkShader> lit_shader_linear(sk_sp<SkShader> normals) {
772 // Simple N-dot-L against a fixed, directional light, done in linear space:
773 static const char* kSrc = R"(
774 uniform shader normals;
775 half4 main(vec2 p) {
776 vec3 n = normalize(normals.eval(p).xyz * 2 - 1);
777 vec3 l = normalize(vec3(1, -1, 1));
778 return fromLinearSrgb(saturate(dot(n, l)).xxx).xxx1;
779 }
780 )";
781 auto effect = SkRuntimeEffect::MakeForShader(SkString(kSrc)).effect;
782 return effect->makeShader(nullptr, &normals, 1);
783 }
784
785 DEF_SIMPLE_GM(paint_alpha_normals_rt, canvas, 512,512) {
786 // Various draws, with non-opaque paint alpha. This demonstrates several issues around how
787 // paint alpha is applied differently on CPU (globally, after all shaders) and GPU (per shader,
788 // inconsistently). See: skbug.com/11942
789 //
790 // When this works, it will be a demo of applying paint alpha to fade out a complex effect.
__anon0f1ba0830202(int x, int y, sk_sp<SkShader> shader) 791 auto draw_shader = [=](int x, int y, sk_sp<SkShader> shader) {
792 SkPaint p;
793 p.setAlpha(164);
794 p.setShader(shader);
795
796 canvas->save();
797 canvas->translate(x, y);
798 canvas->clipRect({0, 0, 256, 256});
799 canvas->drawPaint(p);
800 canvas->restore();
801 };
802
803 draw_shader(0, 0, normal_map_shader());
804 draw_shader(0, 256, normal_map_image_shader());
805
806 draw_shader(256, 0, lit_shader(normal_map_shader()));
807 draw_shader(256, 256, lit_shader(normal_map_image_shader()));
808 }
809
810 DEF_SIMPLE_GM(raw_image_shader_normals_rt, canvas, 768, 512) {
811 // Demonstrates the utility of SkImage::makeRawShader, for non-color child shaders.
812
813 // First, make an offscreen surface, so we can control the destination color space:
814 auto surfInfo = SkImageInfo::Make(512, 512,
815 kN32_SkColorType,
816 kPremul_SkAlphaType,
817 SkColorSpace::MakeSRGB()->makeColorSpin());
818 auto surface = canvas->makeSurface(surfInfo);
819 if (!surface) {
820 surface = SkSurface::MakeRaster(surfInfo);
821 }
822
__anon0f1ba0830302(int x, int y, sk_sp<SkShader> shader, SkCanvas* canvas) 823 auto draw_shader = [](int x, int y, sk_sp<SkShader> shader, SkCanvas* canvas) {
824 SkPaint p;
825 p.setShader(shader);
826
827 canvas->save();
828 canvas->translate(x, y);
829 canvas->clipRect({0, 0, 256, 256});
830 canvas->drawPaint(p);
831 canvas->restore();
832 };
833
834 sk_sp<SkShader> colorNormals = normal_map_image_shader(),
835 rawNormals = normal_map_raw_image_shader();
836
837 // Draw our normal map as colors (will be color-rotated), and raw (untransformed)
838 draw_shader(0, 0, colorNormals, surface->getCanvas());
839 draw_shader(0, 256, rawNormals, surface->getCanvas());
840
841 // Now draw our lighting shader using the normal and raw versions of the normals as children.
842 // The top image will have the normals rotated (incorrectly), so the lighting is very dark.
843 draw_shader(256, 0, lit_shader(colorNormals), surface->getCanvas());
844 draw_shader(256, 256, lit_shader(rawNormals), surface->getCanvas());
845
846 // Now draw the offscreen surface back to our original canvas. If we do this naively, the image
847 // will be un-transformed back to the canvas' color space. That will have the effect of undoing
848 // the color spin on the upper-left, and APPLYING a color-spin on the bottom left. To preserve
849 // the intent of this GM (and make it draw consistently whether or not the original surface has
850 // a color space attached), we reinterpret the offscreen image as being in sRGB:
851 canvas->drawImage(
852 surface->makeImageSnapshot()->reinterpretColorSpace(SkColorSpace::MakeSRGB()), 0, 0);
853
854 // Finally, to demonstrate that raw unpremul image shaders don't premul, draw lighting two more
855 // times, with an unpremul normal map (containing ZERO in the alpha channel). THe top will
856 // premultiply the normals, resulting in totally dark lighting. The bottom will retain the RGB
857 // encoded normals, even with zero alpha:
858 draw_shader(512, 0, lit_shader(normal_map_unpremul_image_shader()), canvas);
859 draw_shader(512, 256, lit_shader(normal_map_raw_unpremul_image_shader()), canvas);
860 }
861
862 DEF_SIMPLE_GM(lit_shader_linear_rt, canvas, 512, 256) {
863 // First, make an offscreen surface, so we can control the destination color space:
864 auto surfInfo = SkImageInfo::Make(512, 256,
865 kN32_SkColorType,
866 kPremul_SkAlphaType,
867 SkColorSpace::MakeSRGB());
868 auto surface = canvas->makeSurface(surfInfo);
869 if (!surface) {
870 surface = SkSurface::MakeRaster(surfInfo);
871 }
872
__anon0f1ba0830402(int x, int y, sk_sp<SkShader> shader, SkCanvas* canvas) 873 auto draw_shader = [](int x, int y, sk_sp<SkShader> shader, SkCanvas* canvas) {
874 SkPaint p;
875 p.setShader(shader);
876
877 canvas->save();
878 canvas->translate(x, y);
879 canvas->clipRect({0, 0, 256, 256});
880 canvas->drawPaint(p);
881 canvas->restore();
882 };
883
884 // We draw two lit spheres - one does math in the working space (so gamma-encoded). The second
885 // works in linear space, then converts to sRGB. This produces (more accurate) sharp falloff:
886 draw_shader(0, 0, lit_shader(normal_map_shader()), surface->getCanvas());
887 draw_shader(256, 0, lit_shader_linear(normal_map_shader()), surface->getCanvas());
888
889 // Now draw the offscreen surface back to our original canvas:
890 canvas->drawImage(surface->makeImageSnapshot(), 0, 0);
891 }
892
893 // skbug.com/13598 GPU was double applying the local matrix.
894 DEF_SIMPLE_GM(local_matrix_shader_rt, canvas, 256, 256) {
895 SkString passthrough(R"(
896 uniform shader s;
897 half4 main(float2 p) { return s.eval(p); }
898 )");
899 auto [rte, error] = SkRuntimeEffect::MakeForShader(passthrough, {});
900 if (!rte) {
901 SkDebugf("%s\n", error.c_str());
902 return;
903 }
904
905 auto image = GetResourceAsImage("images/mandrill_128.png");
906 auto imgShader = image->makeShader(SkSamplingOptions{});
907
908 auto r = SkRect::MakeWH(image->width(), image->height());
909
910 auto lm = SkMatrix::RotateDeg(90.f, {image->width()/2.f, image->height()/2.f});
911
912 SkPaint paint;
913
914 // image
915 paint.setShader(imgShader);
916 canvas->drawRect(r, paint);
917
918 // passthrough(image)
919 canvas->save();
920 canvas->translate(image->width(), 0);
921 paint.setShader(rte->makeShader(nullptr, &imgShader, 1));
922 canvas->drawRect(r, paint);
923 canvas->restore();
924
925 // localmatrix(image)
926 canvas->save();
927 canvas->translate(0, image->height());
928 paint.setShader(imgShader->makeWithLocalMatrix(lm));
929 canvas->drawRect(r, paint);
930 canvas->restore();
931
932 // localmatrix(passthrough(image)) This was the bug.
933 canvas->save();
934 canvas->translate(image->width(), image->height());
935 paint.setShader(rte->makeShader(nullptr, &imgShader, 1)->makeWithLocalMatrix(lm));
936 canvas->drawRect(r, paint);
937 canvas->restore();
938 }
939