Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit ecd8762

Browse files
committed
[canvaskit] Support children shaders into runtime shaders
Change-Id: I88106babe35f6e5b3ee764e7fbaf82c7f43d136d Reviewed-on: https://skia-review.googlesource.com/c/skia/+/272646 Reviewed-by: Nathaniel Nifong <nifong@google.com>
1 parent 1a85d58 commit ecd8762

File tree

6 files changed

+210
-35
lines changed

6 files changed

+210
-35
lines changed

modules/canvaskit/CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1717
`SkSurface.requestAnimationFrame` for animation logic).
1818
- `CanvasKit.parseColorString` which processes color strings like "#2288FF"
1919
- Particles module now exposes effect uniforms, which can be modified for live-updating.
20-
- Experimental 4x4 matrices added in `SkM44`
21-
- Vector math functions added in `SkVector`
20+
- Experimental 4x4 matrices added in `SkM44`.
21+
- Vector math functions added in `SkVector`.
22+
- `SkRuntimeEffect.makeShaderWithChildren`, which can take in other shaders as fragmentProcessors.
2223

2324
### Changed
2425
- We now compile/ship with Emscripten v1.39.6.
-60.3 KB
Binary file not shown.

modules/canvaskit/canvaskit/extra.html

Lines changed: 145 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,10 @@ <h2> Skottie </h2>
1818
<canvas id=sk_onboarding width=500 height=500></canvas>
1919
<canvas id=sk_animated_gif width=500 height=500
2020
title='This is an animated gif being animated in Skottie'></canvas>
21-
<canvas id=sk_webfont width=500 height=500
22-
title='This shows loading of a custom font (e.g. WebFont)'></canvas>
2321

2422
<h2> RT Shader </h2>
2523
<canvas id=rtshader width=300 height=300></canvas>
26-
24+
<canvas id=rtshader2 width=300 height=300></canvas>
2725

2826
<h2> Particles </h2>
2927
<canvas id=particles width=500 height=500></canvas>
@@ -48,7 +46,6 @@ <h2> 3D perspective transformations </h2>
4846
var confettiJSON = null;
4947
var onboardingJSON = null;
5048
var multiFrameJSON = null;
51-
var webfontJSON = null;
5249
var fullBounds = {fLeft: 0, fTop: 0, fRight: 500, fBottom: 500};
5350

5451
var robotoData = null;
@@ -57,9 +54,10 @@ <h2> 3D perspective transformations </h2>
5754
var bonesImageData = null;
5855
var flightAnimGif = null;
5956
var skpData = null;
60-
CanvasKitInit({
57+
const ckLoaded = CanvasKitInit({
6158
locateFile: (file) => '/node_modules/canvaskit/bin/'+file,
62-
}).ready().then((CK) => {
59+
}).ready();
60+
ckLoaded.then((CK) => {
6361
CanvasKit = CK;
6462
// Set bounds to fix the 4:3 resolution of the legos
6563
SkottieExample(CanvasKit, 'sk_legos', legoJSON,
@@ -71,10 +69,6 @@ <h2> 3D perspective transformations </h2>
7169
SkottieExample(CanvasKit, 'sk_animated_gif', multiFrameJSON, fullBounds, {
7270
'image_0.png': flightAnimGif,
7371
});
74-
SkottieExample(CanvasKit, 'sk_webfont', webfontJSON, fullBounds, {
75-
'Roboto-Regular': robotoData,
76-
});
77-
7872
ParticlesAPI1(CanvasKit);
7973

8074
ParagraphAPI1(CanvasKit, robotoData);
@@ -132,12 +126,9 @@ <h2> 3D perspective transformations </h2>
132126
});
133127
});
134128

135-
fetch('./Roboto-Regular.woff').then((resp) => {
129+
fetch('./Roboto-Regular.ttf').then((resp) => {
136130
resp.arrayBuffer().then((buffer) => {
137131
robotoData = buffer;
138-
SkottieExample(CanvasKit, 'sk_webfont', webfontJSON, fullBounds, {
139-
'Roboto-Regular': robotoData,
140-
});
141132
ParagraphAPI1(CanvasKit, robotoData);
142133
});
143134
});
@@ -147,6 +138,9 @@ <h2> 3D perspective transformations </h2>
147138
SkpExample(CanvasKit, skpData);
148139
});
149140

141+
const loadDog = fetch('https://storage.googleapis.com/skia-cdn/misc/dog.jpg').then((response) => response.arrayBuffer());
142+
const loadMandrill = fetch('https://storage.googleapis.com/skia-cdn/misc/mandrill_256.png').then((response) => response.arrayBuffer());
143+
150144
function SkottieExample(CanvasKit, id, jsonStr, bounds, assets) {
151145
if (!CanvasKit || !jsonStr) {
152146
return;
@@ -370,19 +364,28 @@ <h2> 3D perspective transformations </h2>
370364
return surface;
371365
}
372366

367+
const spiralSkSL = `
368+
uniform float rad_scale;
369+
uniform float2 in_center;
370+
uniform float4 in_colors0;
371+
uniform float4 in_colors1;
372+
373+
void main(float2 p, inout half4 color) {
374+
float2 pp = p - in_center;
375+
float radius = sqrt(dot(pp, pp));
376+
radius = sqrt(radius);
377+
float angle = atan(pp.y / pp.x);
378+
float t = (angle + 3.1415926/2) / (3.1415926);
379+
t += radius * rad_scale;
380+
t = fract(t);
381+
color = half4(mix(in_colors0, in_colors1, t));
382+
}`;
383+
373384
function RTShaderAPI1(CanvasKit) {
374-
return; // TODO(kjlubick): Fix this example.
375385
if (!CanvasKit) {
376386
return;
377387
}
378-
const prog = `
379388

380-
uniform half4 gColor;
381-
382-
void main(float2 p, inout half4 color) {
383-
color = half4(half2(p)*(1.0/255), gColor.b, 1);
384-
}
385-
`;
386389
const surface = CanvasKit.MakeCanvasSurface('rtshader');
387390
if (!surface) {
388391
console.error('Could not make surface');
@@ -391,20 +394,135 @@ <h2> 3D perspective transformations </h2>
391394

392395
const canvas = surface.getCanvas();
393396

394-
const effect = CanvasKit._SkRuntimeEffect.Make(prog);
395-
const rot = CanvasKit.SkMatrix.rotated(90, 128, 128);
396-
const shader = effect.makeShader([1, 0, 0, 1], true, rot);
397-
397+
const effect = CanvasKit.SkRuntimeEffect.Make(spiralSkSL);
398+
const shader = effect.makeShader([
399+
0.5,
400+
150, 150,
401+
0, 1, 0, 1,
402+
1, 0, 0, 1], true);
398403
const paint = new CanvasKit.SkPaint();
399404
paint.setShader(shader);
400-
canvas.drawRect(CanvasKit.LTRBRect(0, 0, 256, 256), paint);
405+
canvas.drawRect(CanvasKit.LTRBRect(0, 0, 300, 300), paint);
401406

402407
surface.flush();
403408
shader.delete();
404409
paint.delete();
405410
effect.delete();
406411
}
407412

413+
// RTShader2 demo
414+
Promise.all([ckLoaded, loadDog, loadMandrill]).then((values) => {
415+
const [CanvasKit, dogData, mandrillData] = values;
416+
const dogImg = CanvasKit.MakeImageFromEncoded(dogData);
417+
if (!dogImg) {
418+
console.error('could not decode dog');
419+
return;
420+
}
421+
const mandrillImg = CanvasKit.MakeImageFromEncoded(mandrillData);
422+
if (!mandrillImg) {
423+
console.error('could not decode mandrill');
424+
return;
425+
}
426+
const quadrantSize = 150;
427+
428+
const dogShader = dogImg.makeShader(CanvasKit.TileMode.Clamp, CanvasKit.TileMode.Clamp,
429+
CanvasKit.SkMatrix.scaled(quadrantSize/dogImg.width(),
430+
quadrantSize/dogImg.height()));
431+
const mandrillShader = mandrillImg.makeShader(CanvasKit.TileMode.Clamp, CanvasKit.TileMode.Clamp,
432+
CanvasKit.SkMatrix.scaled(
433+
quadrantSize/mandrillImg.width(),
434+
quadrantSize/mandrillImg.height()));
435+
436+
const surface = CanvasKit.MakeCanvasSurface('rtshader2');
437+
if (!surface) {
438+
console.error('Could not make surface');
439+
return;
440+
}
441+
442+
const prog = `
443+
in fragmentProcessor before_map;
444+
in fragmentProcessor after_map;
445+
in fragmentProcessor threshold_map;
446+
447+
uniform float cutoff;
448+
uniform float slope;
449+
450+
float smooth_cutoff(float x) {
451+
x = x * slope + (0.5 - slope * cutoff);
452+
return clamp(x, 0, 1);
453+
}
454+
455+
void main(float2 xy, inout half4 color) {
456+
half4 before = sample(before_map, xy);
457+
half4 after = sample(after_map, xy);
458+
459+
float m = smooth_cutoff(sample(threshold_map, xy).r);
460+
color = mix(before, after, half(m));
461+
}`;
462+
463+
const canvas = surface.getCanvas();
464+
465+
const thresholdEffect = CanvasKit.SkRuntimeEffect.Make(prog);
466+
const spiralEffect = CanvasKit.SkRuntimeEffect.Make(spiralSkSL);
467+
468+
const draw = (x, y, shader) => {
469+
const paint = new CanvasKit.SkPaint();
470+
paint.setShader(shader);
471+
canvas.save();
472+
canvas.translate(x, y);
473+
canvas.drawRect(CanvasKit.LTRBRect(0, 0, quadrantSize, quadrantSize), paint);
474+
canvas.restore();
475+
paint.delete();
476+
};
477+
478+
const offscreenSurface = CanvasKit.MakeSurface(quadrantSize, quadrantSize);
479+
const getBlurrySpiralShader = (rad_scale) => {
480+
const oCanvas = offscreenSurface.getCanvas();
481+
482+
const spiralShader = spiralEffect.makeShader([
483+
rad_scale,
484+
quadrantSize/2, quadrantSize/2,
485+
1, 1, 1, 1,
486+
0, 0, 0, 1], true);
487+
488+
return spiralShader;
489+
// TODO(kjlubick): The raster backend does not like atan or fract, so we can't
490+
// draw the shader into the offscreen canvas and mess with it. When we can, that
491+
// would be cool to show off.
492+
493+
const blur = CanvasKit.SkImageFilter.MakeBlur(0.1, 0.1, CanvasKit.TileMode.Clamp, null);
494+
495+
const paint = new CanvasKit.SkPaint();
496+
paint.setShader(spiralShader);
497+
paint.setImageFilter(blur);
498+
oCanvas.drawRect(CanvasKit.LTRBRect(0, 0, quadrantSize, quadrantSize), paint);
499+
500+
paint.delete();
501+
blur.delete();
502+
spiralShader.delete();
503+
return offscreenSurface.makeImageSnapshot()
504+
.makeShader(CanvasKit.TileMode.Clamp, CanvasKit.TileMode.Clamp);
505+
506+
};
507+
508+
const drawFrame = () => {
509+
surface.requestAnimationFrame(drawFrame);
510+
const thresholdShader = getBlurrySpiralShader(Math.sin(Date.now() / 5000) / 2);
511+
512+
const blendShader = thresholdEffect.makeShaderWithChildren(
513+
[0.5, 10],
514+
true, [dogShader, mandrillShader, thresholdShader]);
515+
draw(0, 0, blendShader);
516+
draw(quadrantSize, 0, thresholdShader);
517+
draw(0, quadrantSize, dogShader);
518+
draw(quadrantSize, quadrantSize, mandrillShader);
519+
520+
blendShader.delete();
521+
};
522+
523+
surface.requestAnimationFrame(drawFrame);
524+
});
525+
408526
function SkpExample(CanvasKit, skpData) {
409527
if (!skpData || !CanvasKit) {
410528
return;

modules/canvaskit/canvaskit_bindings.cpp

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1447,21 +1447,57 @@ EMSCRIPTEN_BINDINGS(Skia) {
14471447
auto [effect, errorText] = SkRuntimeEffect::Make(s);
14481448
if (!effect) {
14491449
SkDebugf("Runtime effect failed to compile:\n%s\n", errorText.c_str());
1450+
return nullptr;
14501451
}
14511452
return effect;
14521453
}))
1453-
.function("_makeShader", optional_override([](SkRuntimeEffect& self, uintptr_t fptr, size_t len, bool isOpaque)->sk_sp<SkShader> {
1454+
.function("_makeShader", optional_override([](SkRuntimeEffect& self, uintptr_t fPtr, size_t fLen, bool isOpaque)->sk_sp<SkShader> {
14541455
// See comment above for uintptr_t explanation
1455-
void* inputData = reinterpret_cast<void*>(fptr);
1456-
sk_sp<SkData> inputs = SkData::MakeFromMalloc(inputData, len);
1456+
void* inputData = reinterpret_cast<void*>(fPtr);
1457+
sk_sp<SkData> inputs = SkData::MakeFromMalloc(inputData, fLen);
14571458
return self.makeShader(inputs, nullptr, 0, nullptr, isOpaque);
14581459
}))
1459-
.function("_makeShader", optional_override([](SkRuntimeEffect& self, uintptr_t fptr, size_t len, bool isOpaque, SimpleMatrix sm)->sk_sp<SkShader> {
1460+
.function("_makeShader", optional_override([](SkRuntimeEffect& self, uintptr_t fPtr, size_t fLen, bool isOpaque, SimpleMatrix sm)->sk_sp<SkShader> {
14601461
// See comment above for uintptr_t explanation
1461-
void* inputData = reinterpret_cast<void*>(fptr);
1462-
sk_sp<SkData> inputs = SkData::MakeFromMalloc(inputData, len);
1462+
void* inputData = reinterpret_cast<void*>(fPtr);
1463+
sk_sp<SkData> inputs = SkData::MakeFromMalloc(inputData, fLen);
14631464
auto m = toSkMatrix(sm);
14641465
return self.makeShader(inputs, nullptr, 0, &m, isOpaque);
1466+
}))
1467+
.function("_makeShaderWithChildren", optional_override([](SkRuntimeEffect& self, uintptr_t fPtr, size_t fLen, bool isOpaque,
1468+
uintptr_t /** SkShader*[] */cPtrs, size_t cLen)->sk_sp<SkShader> {
1469+
// See comment above for uintptr_t explanation
1470+
void* inputData = reinterpret_cast<void*>(fPtr);
1471+
sk_sp<SkData> inputs = SkData::MakeFromMalloc(inputData, fLen);
1472+
1473+
sk_sp<SkShader>* children = new sk_sp<SkShader>[cLen];
1474+
SkShader** childrenPtrs = reinterpret_cast<SkShader**>(cPtrs);
1475+
for (size_t i = 0; i < cLen; i++) {
1476+
// This bare pointer was already part of an sk_sp (owned outside of here),
1477+
// so we want to ref the new sk_sp so makeShader doesn't clean it up.
1478+
children[i] = sk_ref_sp<SkShader>(childrenPtrs[i]);
1479+
}
1480+
auto s = self.makeShader(inputs, children, cLen, nullptr, isOpaque);
1481+
delete[] children;
1482+
return s;
1483+
}))
1484+
.function("_makeShaderWithChildren", optional_override([](SkRuntimeEffect& self, uintptr_t fPtr, size_t fLen, bool isOpaque,
1485+
uintptr_t /** SkShader*[] */cPtrs, size_t cLen, SimpleMatrix sm)->sk_sp<SkShader> {
1486+
// See comment above for uintptr_t explanation
1487+
void* inputData = reinterpret_cast<void*>(fPtr);
1488+
sk_sp<SkData> inputs = SkData::MakeFromMalloc(inputData, fLen);
1489+
1490+
sk_sp<SkShader>* children = new sk_sp<SkShader>[cLen];
1491+
SkShader** childrenPtrs = reinterpret_cast<SkShader**>(cPtrs);
1492+
for (size_t i = 0; i < cLen; i++) {
1493+
// This bare pointer was already part of an sk_sp (owned outside of here),
1494+
// so we want to ref the new sk_sp so makeShader doesn't clean it up.
1495+
children[i] = sk_ref_sp<SkShader>(childrenPtrs[i]);
1496+
}
1497+
auto m = toSkMatrix(sm);
1498+
auto s = self.makeShader(inputs, children, cLen, &m, isOpaque);
1499+
delete[] children;
1500+
return s;
14651501
}));
14661502
#endif
14671503

modules/canvaskit/externs.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ var CanvasKit = {
133133

134134
// private API
135135
_makeShader: function() {},
136+
_makeShaderWithChildren: function() {},
136137
},
137138

138139
ParagraphStyle: function() {},
@@ -855,6 +856,7 @@ CanvasKit.SkColorBuilder.prototype.push = function() {};
855856
CanvasKit.SkColorBuilder.prototype.set = function() {};
856857

857858
CanvasKit.SkRuntimeEffect.prototype.makeShader = function() {};
859+
CanvasKit.SkRuntimeEffect.prototype.makeShaderWithChildren = function() {};
858860

859861
CanvasKit.SkParticleEffect.prototype.effectUniforms = function() {};
860862
CanvasKit.SkParticleEffect.prototype.particleUniforms = function() {};

modules/canvaskit/rt_shader.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,22 @@ CanvasKit._extraInitializations.push(function() {
99
}
1010
return this._makeShader(fptr, floats.length * 4, !!isOpaque, matrix);
1111
}
12+
13+
// childrenWithShaders is an array of other shaders (e.g. SkImage.makeShader())
14+
CanvasKit.SkRuntimeEffect.prototype.makeShaderWithChildren = function(floats, isOpaque, childrenShaders, matrix) {
15+
var fptr = copy1dArray(floats, CanvasKit.HEAPF32);
16+
var barePointers = [];
17+
for (var i = 0; i<childrenShaders.length;i++) {
18+
// childrenShaders are emscriptens smart pointer type. We want to get the bare pointer
19+
// and send that over the wire, so it can be re-wrapped as an sk_sp.
20+
barePointers.push(childrenShaders[i].$$.ptr);
21+
}
22+
var childrenPointers = copy1dArray(barePointers, CanvasKit.HEAPU32);
23+
// Our array has 4 bytes per float, so be sure to account for that before
24+
// sending it over the wire.
25+
if (!matrix) {
26+
return this._makeShaderWithChildren(fptr, floats.length * 4, !!isOpaque, childrenPointers, barePointers.length);
27+
}
28+
return this._makeShaderWithChildren(fptr, floats.length * 4, !!isOpaque, childrenPointers, barePointers.length, matrix);
29+
}
1230
});

0 commit comments

Comments
 (0)