blob: 94b3e0d4c2278a7bf9028a9bd257c3b2003321c6 [file] [log] [blame]
Mike Reed3fd3cc92019-06-20 12:40:30 -04001/*
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"
Mike Reed3fd3cc92019-06-20 12:40:30 -040010#include "include/core/SkData.h"
Mike Reed3fd3cc92019-06-20 12:40:30 -040011#include "include/core/SkPaint.h"
Chris Dalton224e5e42021-04-16 11:43:39 -060012#include "include/core/SkRRect.h"
Mike Reed3fd3cc92019-06-20 12:40:30 -040013#include "include/core/SkSize.h"
14#include "include/core/SkString.h"
Brian Osman44fafa62020-07-15 10:52:37 -040015#include "include/core/SkSurface.h"
16#include "include/effects/SkGradientShader.h"
Mike Reed146722e2020-02-13 12:36:28 -050017#include "include/effects/SkImageFilters.h"
Brian Osmanee426f22020-01-02 11:55:24 -050018#include "include/effects/SkRuntimeEffect.h"
Brian Osman44fafa62020-07-15 10:52:37 -040019#include "include/utils/SkRandom.h"
Brian Osman2f2977e2021-12-07 16:05:39 -050020#include "src/core/SkColorSpacePriv.h"
Mike Reed146722e2020-02-13 12:36:28 -050021#include "tools/Resources.h"
Mike Reed3fd3cc92019-06-20 12:40:30 -040022
Brian Osman44fafa62020-07-15 10:52:37 -040023enum RT_Flags {
Brian Osman6285d642021-05-05 08:24:03 -040024 kAnimate_RTFlag = 0x1,
25 kBench_RTFlag = 0x2,
26 kColorFilter_RTFlag = 0x4,
Brian Osman44fafa62020-07-15 10:52:37 -040027};
Mike Reed3fd3cc92019-06-20 12:40:30 -040028
Brian Osman44fafa62020-07-15 10:52:37 -040029class RuntimeShaderGM : public skiagm::GM {
30public:
31 RuntimeShaderGM(const char* name, SkISize size, const char* sksl, uint32_t flags = 0)
32 : fName(name), fSize(size), fFlags(flags), fSkSL(sksl) {}
33
34 void onOnceBeforeDraw() override {
Brian Osman6285d642021-05-05 08:24:03 -040035 auto [effect, error] = (fFlags & kColorFilter_RTFlag)
36 ? SkRuntimeEffect::MakeForColorFilter(fSkSL)
37 : SkRuntimeEffect::MakeForShader(fSkSL);
Brian Osman44fafa62020-07-15 10:52:37 -040038 if (!effect) {
39 SkDebugf("RuntimeShader error: %s\n", error.c_str());
40 }
41 fEffect = std::move(effect);
Mike Reed3fd3cc92019-06-20 12:40:30 -040042 }
Mike Reed3fd3cc92019-06-20 12:40:30 -040043
Brian Osman44fafa62020-07-15 10:52:37 -040044 bool runAsBench() const override { return SkToBool(fFlags & kBench_RTFlag); }
45 SkString onShortName() override { return fName; }
46 SkISize onISize() override { return fSize; }
Mike Reed3fd3cc92019-06-20 12:40:30 -040047
Brian Osman44fafa62020-07-15 10:52:37 -040048 bool onAnimate(double nanos) override {
49 fSecs = nanos / (1000 * 1000 * 1000);
50 return SkToBool(fFlags & kAnimate_RTFlag);
51 }
Mike Reed3fd3cc92019-06-20 12:40:30 -040052
Brian Osman44fafa62020-07-15 10:52:37 -040053protected:
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
63class SimpleRT : public RuntimeShaderGM {
64public:
65 SimpleRT() : RuntimeShaderGM("runtime_shader", {512, 256}, R"(
66 uniform half4 gColor;
67
Brian Osman767f4442020-08-13 16:59:48 -040068 half4 main(float2 p) {
Brian Osmanb25e6e12020-09-14 14:44:42 -040069 return half4(p*(1.0/255), gColor.b, 1);
Brian Osman44fafa62020-07-15 10:52:37 -040070 }
71 )", kBench_RTFlag) {}
Mike Reed3fd3cc92019-06-20 12:40:30 -040072
Mike Reed3fd3cc92019-06-20 12:40:30 -040073 void onDraw(SkCanvas* canvas) override {
Brian Osman44fafa62020-07-15 10:52:37 -040074 SkRuntimeShaderBuilder builder(fEffect);
Brian Osman93de1622019-12-26 08:43:05 -050075
76 SkMatrix localM;
77 localM.setRotate(90, 128, 128);
Brian Osmana4b91692020-08-10 14:26:16 -040078 builder.uniform("gColor") = SkColor4f{1, 0, 0, 1};
Brian Osman93de1622019-12-26 08:43:05 -050079
Mike Reed3fd3cc92019-06-20 12:40:30 -040080 SkPaint p;
Brian Osmancd189e82022-02-09 11:56:45 -050081 p.setShader(builder.makeShader(&localM));
Mike Reed3fd3cc92019-06-20 12:40:30 -040082 canvas->drawRect({0, 0, 256, 256}, p);
83 }
Mike Reed3fd3cc92019-06-20 12:40:30 -040084};
Brian Osman44fafa62020-07-15 10:52:37 -040085DEF_GM(return new SimpleRT;)
Mike Reed146722e2020-02-13 12:36:28 -050086
87static sk_sp<SkShader> make_shader(sk_sp<SkImage> img, SkISize size) {
Mike Reed1f607332020-05-21 12:11:27 -040088 SkMatrix scale = SkMatrix::Scale(size.width() / (float)img->width(),
89 size.height() / (float)img->height());
Mike Reed41068192020-12-09 21:48:52 -050090 return img->makeShader(SkSamplingOptions(), scale);
Mike Reed146722e2020-02-13 12:36:28 -050091}
92
Mike Reed146722e2020-02-13 12:36:28 -050093static 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
Mike Reed41068192020-12-09 21:48:52 -0500122 return surf->makeImageSnapshot()->makeShader(SkSamplingOptions());
Mike Reed146722e2020-02-13 12:36:28 -0500123}
124
Brian Osman44fafa62020-07-15 10:52:37 -0400125class ThresholdRT : public RuntimeShaderGM {
126public:
127 ThresholdRT() : RuntimeShaderGM("threshold_rt", {256, 256}, R"(
Brian Osman91292e92020-11-04 15:40:50 -0500128 uniform shader before_map;
129 uniform shader after_map;
130 uniform shader threshold_map;
Mike Reed146722e2020-02-13 12:36:28 -0500131
Brian Osman44fafa62020-07-15 10:52:37 -0400132 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
Brian Osman767f4442020-08-13 16:59:48 -0400140 half4 main(float2 xy) {
Brian Osmancbfa34a2021-09-02 09:26:27 -0400141 half4 before = before_map.eval(xy);
142 half4 after = after_map.eval(xy);
Brian Osman44fafa62020-07-15 10:52:37 -0400143
Brian Osmancbfa34a2021-09-02 09:26:27 -0400144 float m = smooth_cutoff(threshold_map.eval(xy).a);
Brian Osmanb25e6e12020-09-14 14:44:42 -0400145 return mix(before, after, m);
Brian Osman44fafa62020-07-15 10:52:37 -0400146 }
147 )", kAnimate_RTFlag | kBench_RTFlag) {}
148
149 sk_sp<SkShader> fBefore, fAfter, fThreshold;
Mike Reed146722e2020-02-13 12:36:28 -0500150
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
Brian Osman44fafa62020-07-15 10:52:37 -0400157 this->RuntimeShaderGM::onOnceBeforeDraw();
Mike Reed146722e2020-02-13 12:36:28 -0500158 }
159
Mike Kleinf8d68fe2020-06-18 10:01:31 -0500160 void onDraw(SkCanvas* canvas) override {
Brian Osman44fafa62020-07-15 10:52:37 -0400161 SkRuntimeShaderBuilder builder(fEffect);
162
Brian Osmana4b91692020-08-10 14:26:16 -0400163 builder.uniform("cutoff") = sin(fSecs) * 0.55f + 0.5f;
164 builder.uniform("slope") = 10.0f;
Brian Osman44fafa62020-07-15 10:52:37 -0400165
166 builder.child("before_map") = fBefore;
167 builder.child("after_map") = fAfter;
168 builder.child("threshold_map") = fThreshold;
Mike Reed146722e2020-02-13 12:36:28 -0500169
170 SkPaint paint;
Brian Osmancd189e82022-02-09 11:56:45 -0500171 paint.setShader(builder.makeShader());
Mike Reed146722e2020-02-13 12:36:28 -0500172 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);
Mike Reed146722e2020-02-13 12:36:28 -0500184 }
Mike Reed146722e2020-02-13 12:36:28 -0500185};
186DEF_GM(return new ThresholdRT;)
Mike Reed8520e762020-04-30 12:06:23 -0400187
Brian Osman44fafa62020-07-15 10:52:37 -0400188class SpiralRT : public RuntimeShaderGM {
189public:
190 SpiralRT() : RuntimeShaderGM("spiral_rt", {512, 512}, R"(
191 uniform float rad_scale;
192 uniform float2 in_center;
Brian Osman8c3d1832021-12-06 10:30:51 -0500193 layout(color) uniform float4 in_colors0;
194 layout(color) uniform float4 in_colors1;
Mike Reed8520e762020-04-30 12:06:23 -0400195
Brian Osman767f4442020-08-13 16:59:48 -0400196 half4 main(float2 p) {
Brian Osman44fafa62020-07-15 10:52:37 -0400197 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);
Brian Osmanb25e6e12020-09-14 14:44:42 -0400204 return in_colors0 * (1-t) + in_colors1 * t;
Mike Reed8520e762020-04-30 12:06:23 -0400205 }
Brian Osman44fafa62020-07-15 10:52:37 -0400206 )", kAnimate_RTFlag | kBench_RTFlag) {}
Mike Reed8520e762020-04-30 12:06:23 -0400207
208 void onDraw(SkCanvas* canvas) override {
Brian Osman44fafa62020-07-15 10:52:37 -0400209 SkRuntimeShaderBuilder builder(fEffect);
210
Brian Osmana4b91692020-08-10 14:26:16 -0400211 builder.uniform("rad_scale") = std::sin(fSecs * 0.5f + 2.0f) / 5;
212 builder.uniform("in_center") = SkV2{256, 256};
Brian Osman8c3d1832021-12-06 10:30:51 -0500213 builder.uniform("in_colors0") = SkColors::kRed;
214 builder.uniform("in_colors1") = SkColors::kGreen;
Mike Reed8520e762020-04-30 12:06:23 -0400215
216 SkPaint paint;
Brian Osmancd189e82022-02-09 11:56:45 -0500217 paint.setShader(builder.makeShader());
Mike Reed8520e762020-04-30 12:06:23 -0400218 canvas->drawRect({0, 0, 512, 512}, paint);
219 }
Mike Reed8520e762020-04-30 12:06:23 -0400220};
221DEF_GM(return new SpiralRT;)
Brian Osman9c3eccd2020-05-21 16:41:43 -0400222
Brian Osman827dab42021-04-26 17:02:57 -0400223// 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.
227class UnsharpRT : public RuntimeShaderGM {
228public:
229 UnsharpRT() : RuntimeShaderGM("unsharp_rt", {512, 256}, R"(
John Stiles67f443b2021-09-30 12:45:36 -0400230 uniform shader child;
Brian Osman827dab42021-04-26 17:02:57 -0400231 half4 main(float2 xy) {
John Stiles67f443b2021-09-30 12:45:36 -0400232 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));
Brian Osman827dab42021-04-26 17:02:57 -0400237 return c;
238 }
239 )") {}
240
241 sk_sp<SkImage> fMandrill;
242
243 void onOnceBeforeDraw() override {
244 fMandrill = GetResourceAsImage("images/mandrill_256.png");
245 this->RuntimeShaderGM::onOnceBeforeDraw();
246 }
247
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);
John Stiles67f443b2021-09-30 12:45:36 -0400255 builder.child("child") = fMandrill->makeShader(sampling);
Brian Osman827dab42021-04-26 17:02:57 -0400256
257 SkPaint paint;
Brian Osmancd189e82022-02-09 11:56:45 -0500258 paint.setShader(builder.makeShader());
Brian Osman827dab42021-04-26 17:02:57 -0400259 canvas->translate(256, 0);
260 canvas->drawRect({ 0, 0, 256, 256 }, paint);
261 }
262};
263DEF_GM(return new UnsharpRT;)
264
Brian Osman44fafa62020-07-15 10:52:37 -0400265class ColorCubeRT : public RuntimeShaderGM {
266public:
267 ColorCubeRT() : RuntimeShaderGM("color_cube_rt", {512, 512}, R"(
John Stiles67f443b2021-09-30 12:45:36 -0400268 uniform shader child;
Brian Osman91292e92020-11-04 15:40:50 -0500269 uniform shader color_cube;
Brian Osman44fafa62020-07-15 10:52:37 -0400270
271 uniform float rg_scale;
272 uniform float rg_bias;
273 uniform float b_scale;
274 uniform float inv_size;
275
Brian Osman767f4442020-08-13 16:59:48 -0400276 half4 main(float2 xy) {
John Stiles67f443b2021-09-30 12:45:36 -0400277 float4 c = unpremul(child.eval(xy));
Brian Osman44fafa62020-07-15 10:52:37 -0400278
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:
Brian Osmancbfa34a2021-09-02 09:26:27 -0400287 half4 color = mix(color_cube.eval(coords1), color_cube.eval(coords2),
Brian Osmanb25e6e12020-09-14 14:44:42 -0400288 fract(cubeCoords.b));
Brian Osman44fafa62020-07-15 10:52:37 -0400289
290 // Premul again
291 color.rgb *= color.a;
Brian Osman767f4442020-08-13 16:59:48 -0400292
293 return color;
Brian Osman44fafa62020-07-15 10:52:37 -0400294 }
295 )") {}
296
Brian Osman9c3eccd2020-05-21 16:41:43 -0400297 sk_sp<SkImage> fMandrill, fMandrillSepia, fIdentityCube, fSepiaCube;
Brian Osman9c3eccd2020-05-21 16:41:43 -0400298
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
Brian Osman44fafa62020-07-15 10:52:37 -0400305 this->RuntimeShaderGM::onOnceBeforeDraw();
Brian Osman9c3eccd2020-05-21 16:41:43 -0400306 }
307
Brian Osman89bf7342020-06-18 17:11:16 -0400308 void onDraw(SkCanvas* canvas) override {
Brian Osman44fafa62020-07-15 10:52:37 -0400309 SkRuntimeShaderBuilder builder(fEffect);
310
Brian Osman9c3eccd2020-05-21 16:41:43 -0400311 // 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
Mike Reed5ec22382021-01-14 21:59:01 -0500318 const SkSamplingOptions sampling(SkFilterMode::kLinear);
Mike Reed41068192020-12-09 21:48:52 -0500319
Brian Osmana4b91692020-08-10 14:26:16 -0400320 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;
Brian Osman2c28bf92020-05-28 13:38:24 -0400324
John Stiles67f443b2021-09-30 12:45:36 -0400325 builder.child("child") = fMandrill->makeShader(sampling);
Brian Osman9c3eccd2020-05-21 16:41:43 -0400326
Brian Osman9c3eccd2020-05-21 16:41:43 -0400327 SkPaint paint;
Brian Osman9c3eccd2020-05-21 16:41:43 -0400328
Brian Osman2c28bf92020-05-28 13:38:24 -0400329 // TODO: Should we add SkImage::makeNormalizedShader() to handle this automatically?
330 SkMatrix normalize = SkMatrix::Scale(1.0f / (kSize * kSize), 1.0f / kSize);
331
Brian Osman9c3eccd2020-05-21 16:41:43 -0400332 // Now draw the image with an identity color cube - it should look like the original
Mike Reed41068192020-12-09 21:48:52 -0500333 builder.child("color_cube") = fIdentityCube->makeShader(sampling, normalize);
Brian Osmancd189e82022-02-09 11:56:45 -0500334 paint.setShader(builder.makeShader());
Brian Osman9c3eccd2020-05-21 16:41:43 -0400335 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.
Mike Reed41068192020-12-09 21:48:52 -0500339 builder.child("color_cube") = fSepiaCube->makeShader(sampling, normalize);
Brian Osmancd189e82022-02-09 11:56:45 -0500340 paint.setShader(builder.makeShader());
Brian Osman9c3eccd2020-05-21 16:41:43 -0400341 canvas->translate(0, 256);
342 canvas->drawRect({ 0, 0, 256, 256 }, paint);
Brian Osman9c3eccd2020-05-21 16:41:43 -0400343 }
344};
345DEF_GM(return new ColorCubeRT;)
Brian Osmana7685b22020-07-10 14:08:56 -0400346
Brian Osman6285d642021-05-05 08:24:03 -0400347// Same as above, but demonstrating how to implement this as a runtime color filter (that samples
348// a shader child for the LUT).
349class ColorCubeColorFilterRT : public RuntimeShaderGM {
350public:
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:
Brian Osmancbfa34a2021-09-02 09:26:27 -0400370 half4 color = mix(color_cube.eval(coords1), color_cube.eval(coords2),
Brian Osman6285d642021-05-05 08:24:03 -0400371 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
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
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};
426DEF_GM(return new ColorCubeColorFilterRT;)
427
Brian Osman44fafa62020-07-15 10:52:37 -0400428class DefaultColorRT : public RuntimeShaderGM {
429public:
430 DefaultColorRT() : RuntimeShaderGM("default_color_rt", {512, 256}, R"(
John Stiles67f443b2021-09-30 12:45:36 -0400431 uniform shader child;
Brian Osmanf6123f12021-04-15 13:52:43 -0400432 half4 main(float2 xy) {
John Stiles67f443b2021-09-30 12:45:36 -0400433 return child.eval(xy);
Brian Osman44fafa62020-07-15 10:52:37 -0400434 }
435 )") {}
436
Brian Osmana7685b22020-07-10 14:08:56 -0400437 sk_sp<SkImage> fMandrill;
Brian Osmana7685b22020-07-10 14:08:56 -0400438
439 void onOnceBeforeDraw() override {
440 fMandrill = GetResourceAsImage("images/mandrill_256.png");
Brian Osman44fafa62020-07-15 10:52:37 -0400441 this->RuntimeShaderGM::onOnceBeforeDraw();
Brian Osmana7685b22020-07-10 14:08:56 -0400442 }
443
Brian Osmana7685b22020-07-10 14:08:56 -0400444 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 });
Brian Osmancd189e82022-02-09 11:56:45 -0500450 paint.setShader(builder.makeShader());
Brian Osmana7685b22020-07-10 14:08:56 -0400451 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
John Stiles67f443b2021-09-30 12:45:36 -0400454 builder.child("child") = fMandrill->makeShader(SkSamplingOptions());
Brian Osmana7685b22020-07-10 14:08:56 -0400455 paint.setColor4f({ 1.0f, 1.0f, 1.0f, 0.5f });
Brian Osmancd189e82022-02-09 11:56:45 -0500456 paint.setShader(builder.makeShader());
Brian Osmana7685b22020-07-10 14:08:56 -0400457 canvas->translate(256, 0);
458 canvas->drawRect({ 0, 0, 256, 256 }, paint);
459
460 }
461};
462DEF_GM(return new DefaultColorRT;)
Mike Klein71d420d2021-02-03 09:54:07 -0600463
Chris Dalton224e5e42021-04-16 11:43:39 -0600464// Emits coverage for a rounded rectangle whose corners are superellipses defined by the boundary:
Chris Dalton4e94fd12021-03-15 10:16:09 -0600465//
Chris Dalton224e5e42021-04-16 11:43:39 -0600466// x^n + y^n == 1
Chris Dalton4e94fd12021-03-15 10:16:09 -0600467//
Chris Dalton224e5e42021-04-16 11:43:39 -0600468// Where x and y are normalized, clamped coordinates ranging from 0..1 inside the nearest corner's
469// bounding box.
Chris Dalton4e94fd12021-03-15 10:16:09 -0600470//
Chris Dalton224e5e42021-04-16 11:43:39 -0600471// See: https://en.wikipedia.org/wiki/Superellipse
472class ClipSuperRRect : public RuntimeShaderGM {
Chris Dalton4e94fd12021-03-15 10:16:09 -0600473public:
Chris Dalton224e5e42021-04-16 11:43:39 -0600474 ClipSuperRRect(const char* name, float power) : RuntimeShaderGM(name, {500, 500}, R"(
475 uniform float power_minus1;
476 uniform float2 stretch_factor;
Chris Dalton4e94fd12021-03-15 10:16:09 -0600477 uniform float2x2 derivatives;
478 half4 main(float2 xy) {
Chris Dalton224e5e42021-04-16 11:43:39 -0600479 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.
Chris Dalton4e94fd12021-03-15 10:16:09 -0600484 return half4(saturate(.5 - f/fwidth)); // Approx coverage by riding the gradient to f=0.
485 }
Chris Dalton224e5e42021-04-16 11:43:39 -0600486 )"), fPower(power) {}
Chris Dalton4e94fd12021-03-15 10:16:09 -0600487
Chris Dalton224e5e42021-04-16 11:43:39 -0600488 void drawSuperRRect(SkCanvas* canvas, const SkRect& superRRect, float radX, float radY,
489 SkColor color) {
490 SkPaint paint;
491 paint.setColor(color);
Chris Dalton4e94fd12021-03-15 10:16:09 -0600492
Chris Dalton224e5e42021-04-16 11:43:39 -0600493 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 }
Chris Dalton4e94fd12021-03-15 10:16:09 -0600500
501 SkRuntimeShaderBuilder builder(fEffect);
Chris Dalton224e5e42021-04-16 11:43:39 -0600502 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};
Chris Dalton4e94fd12021-03-15 10:16:09 -0600516
517 // Calculate a 2x2 "derivatives" matrix that the shader will use to find the gradient.
518 //
Chris Dalton224e5e42021-04-16 11:43:39 -0600519 // f = s^n + t^n - 1 [s,t are "super" rounded corner coords in normalized 0..1 space]
Chris Dalton4e94fd12021-03-15 10:16:09 -0600520 //
Chris Dalton224e5e42021-04-16 11:43:39 -0600521 // gradient = [df/dx df/dy] = [ns^(n-1) nt^(n-1)] * |ds/dx ds/dy|
Chris Dalton4e94fd12021-03-15 10:16:09 -0600522 // |dt/dx dt/dy|
523 //
Chris Dalton224e5e42021-04-16 11:43:39 -0600524 // = [s^(n-1) t^(n-1)] * |n 0| * |ds/dx ds/dy|
Chris Dalton4e94fd12021-03-15 10:16:09 -0600525 // |0 n| |dt/dx dt/dy|
526 //
Chris Dalton224e5e42021-04-16 11:43:39 -0600527 // = [s^(n-1) t^(n-1)] * |2n/cornerWidth 0| * mat2x2(canvasMatrix)^-1
528 // |0 2n/cornerHeight|
Chris Dalton4e94fd12021-03-15 10:16:09 -0600529 //
Chris Dalton224e5e42021-04-16 11:43:39 -0600530 // = [s^(n-1) t^(n-1)] * "derivatives"
Chris Dalton4e94fd12021-03-15 10:16:09 -0600531 //
532 const SkMatrix& M = canvas->getTotalMatrix();
533 float a=M.getScaleX(), b=M.getSkewX(), c=M.getSkewY(), d=M.getScaleY();
Chris Dalton224e5e42021-04-16 11:43:39 -0600534 float determinant = a*d - b*c;
535 float dx = fPower / (cornerWidth * determinant);
536 float dy = fPower / (cornerHeight * determinant);
Chris Dalton4e94fd12021-03-15 10:16:09 -0600537 builder.uniform("derivatives") = SkV4{d*dx, -c*dy, -b*dx, a*dy};
538
Chris Dalton224e5e42021-04-16 11:43:39 -0600539 // 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());
Brian Osmancd189e82022-02-09 11:56:45 -0500546 canvas->clipShader(builder.makeShader(&cornerToLocal));
Chris Dalton224e5e42021-04-16 11:43:39 -0600547
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
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();
Chris Dalton4e94fd12021-03-15 10:16:09 -0600610
611 canvas->restore();
612 }
Chris Dalton224e5e42021-04-16 11:43:39 -0600613
614private:
615 const float fPower;
Chris Dalton4e94fd12021-03-15 10:16:09 -0600616};
Chris Dalton224e5e42021-04-16 11:43:39 -0600617DEF_GM(return new ClipSuperRRect("clip_super_rrect_pow2", 2);)
618// DEF_GM(return new ClipSuperRRect("clip_super_rrect_pow3", 3);)
619DEF_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);)
Mike Klein71d420d2021-02-03 09:54:07 -0600623
Brian Osman2f2977e2021-12-07 16:05:39 -0500624class LinearGradientRT : public RuntimeShaderGM {
625public:
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
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;
Brian Osmancd189e82022-02-09 11:56:45 -0500650 paint.setShader(builder.makeShader());
Brian Osman2f2977e2021-12-07 16:05:39 -0500651
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};
676DEF_GM(return new LinearGradientRT;)
677
Mike Klein71d420d2021-02-03 09:54:07 -0600678DEF_SIMPLE_GM(child_sampling_rt, canvas, 256,256) {
679 static constexpr char scale[] =
680 "uniform shader child;"
681 "half4 main(float2 xy) {"
Brian Osmancbfa34a2021-09-02 09:26:27 -0400682 " return child.eval(xy*0.1);"
Mike Klein71d420d2021-02-03 09:54:07 -0600683 "}";
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
Brian Osmanf6123f12021-04-15 13:52:43 -0400695 SkRuntimeShaderBuilder builder(SkRuntimeEffect::MakeForShader(SkString(scale)).effect);
Mike Klein71d420d2021-02-03 09:54:07 -0600696 builder.child("child") = shader;
Brian Osmancd189e82022-02-09 11:56:45 -0500697 p.setShader(builder.makeShader());
Mike Klein71d420d2021-02-03 09:54:07 -0600698
699 canvas->drawPaint(p);
700}
Brian Osmand34b49f2021-10-18 14:07:43 -0400701
702static 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;
Brian Osmancd189e82022-02-09 11:56:45 -0500713 return effect->makeShader(nullptr, {});
Brian Osmand34b49f2021-10-18 14:07:43 -0400714}
715
Brian Osman20c6a942021-11-29 12:27:00 -0500716static sk_sp<SkImage> normal_map_image() {
Brian Osmand34b49f2021-10-18 14:07:43 -0400717 // Above, baked into an image:
Brian Osman20c6a942021-11-29 12:27:00 -0500718 auto info = SkImageInfo::Make(256, 256, kN32_SkColorType, kPremul_SkAlphaType);
719 auto surface = SkSurface::MakeRaster(info);
Brian Osmand34b49f2021-10-18 14:07:43 -0400720 SkPaint p;
721 p.setShader(normal_map_shader());
722 surface->getCanvas()->drawPaint(p);
Brian Osman20c6a942021-11-29 12:27:00 -0500723 return surface->makeImageSnapshot();
724}
725
726static sk_sp<SkShader> normal_map_image_shader() {
727 return normal_map_image()->makeShader(SkSamplingOptions{});
728}
729
730static sk_sp<SkShader> normal_map_raw_image_shader() {
731 return normal_map_image()->makeRawShader(SkSamplingOptions{});
732}
733
734static 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
749static sk_sp<SkShader> normal_map_unpremul_image_shader() {
750 return normal_map_unpremul_image()->makeShader(SkSamplingOptions{});
751}
752
753static sk_sp<SkShader> normal_map_raw_unpremul_image_shader() {
754 return normal_map_unpremul_image()->makeRawShader(SkSamplingOptions{});
Brian Osmand34b49f2021-10-18 14:07:43 -0400755}
756
757static sk_sp<SkShader> lit_shader(sk_sp<SkShader> normals) {
Brian Osman2f2977e2021-12-07 16:05:39 -0500758 // Simple N-dot-L against a fixed, directional light:
Brian Osmand34b49f2021-10-18 14:07:43 -0400759 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));
Brian Osmanf893df92021-11-09 10:10:45 -0500764 return saturate(dot(n, l)).xxx1;
Brian Osmand34b49f2021-10-18 14:07:43 -0400765 }
766 )";
767 auto effect = SkRuntimeEffect::MakeForShader(SkString(kSrc)).effect;
Brian Osmancd189e82022-02-09 11:56:45 -0500768 return effect->makeShader(nullptr, &normals, 1);
Brian Osmand34b49f2021-10-18 14:07:43 -0400769}
770
Brian Osman2f2977e2021-12-07 16:05:39 -0500771static 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;
Brian Osmancd189e82022-02-09 11:56:45 -0500782 return effect->makeShader(nullptr, &normals, 1);
Brian Osman2f2977e2021-12-07 16:05:39 -0500783}
784
Brian Osmand34b49f2021-10-18 14:07:43 -0400785DEF_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.
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}
Brian Osman20c6a942021-11-29 12:27:00 -0500809
810DEF_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
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}
Brian Osman2f2977e2021-12-07 16:05:39 -0500861
862DEF_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
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}