Skip to content

Commit

Permalink
[canvaskit] Support children shaders into runtime shaders
Browse files Browse the repository at this point in the history
Change-Id: I88106babe35f6e5b3ee764e7fbaf82c7f43d136d
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/272646
Reviewed-by: Nathaniel Nifong <nifong@google.com>
  • Loading branch information
kjlubick committed Feb 22, 2020
1 parent 1a85d58 commit ecd8762
Show file tree
Hide file tree
Showing 6 changed files with 210 additions and 35 deletions.
5 changes: 3 additions & 2 deletions modules/canvaskit/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
`SkSurface.requestAnimationFrame` for animation logic).
- `CanvasKit.parseColorString` which processes color strings like "#2288FF"
- Particles module now exposes effect uniforms, which can be modified for live-updating.
- Experimental 4x4 matrices added in `SkM44`
- Vector math functions added in `SkVector`
- Experimental 4x4 matrices added in `SkM44`.
- Vector math functions added in `SkVector`.
- `SkRuntimeEffect.makeShaderWithChildren`, which can take in other shaders as fragmentProcessors.

### Changed
- We now compile/ship with Emscripten v1.39.6.
Expand Down
Binary file removed modules/canvaskit/canvaskit/Roboto-Regular.woff
Binary file not shown.
172 changes: 145 additions & 27 deletions modules/canvaskit/canvaskit/extra.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,10 @@ <h2> Skottie </h2>
<canvas id=sk_onboarding width=500 height=500></canvas>
<canvas id=sk_animated_gif width=500 height=500
title='This is an animated gif being animated in Skottie'></canvas>
<canvas id=sk_webfont width=500 height=500
title='This shows loading of a custom font (e.g. WebFont)'></canvas>

<h2> RT Shader </h2>
<canvas id=rtshader width=300 height=300></canvas>

<canvas id=rtshader2 width=300 height=300></canvas>

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

var robotoData = null;
Expand All @@ -57,9 +54,10 @@ <h2> 3D perspective transformations </h2>
var bonesImageData = null;
var flightAnimGif = null;
var skpData = null;
CanvasKitInit({
const ckLoaded = CanvasKitInit({
locateFile: (file) => '/node_modules/canvaskit/bin/'+file,
}).ready().then((CK) => {
}).ready();
ckLoaded.then((CK) => {
CanvasKit = CK;
// Set bounds to fix the 4:3 resolution of the legos
SkottieExample(CanvasKit, 'sk_legos', legoJSON,
Expand All @@ -71,10 +69,6 @@ <h2> 3D perspective transformations </h2>
SkottieExample(CanvasKit, 'sk_animated_gif', multiFrameJSON, fullBounds, {
'image_0.png': flightAnimGif,
});
SkottieExample(CanvasKit, 'sk_webfont', webfontJSON, fullBounds, {
'Roboto-Regular': robotoData,
});

ParticlesAPI1(CanvasKit);

ParagraphAPI1(CanvasKit, robotoData);
Expand Down Expand Up @@ -132,12 +126,9 @@ <h2> 3D perspective transformations </h2>
});
});

fetch('./Roboto-Regular.woff').then((resp) => {
fetch('./Roboto-Regular.ttf').then((resp) => {
resp.arrayBuffer().then((buffer) => {
robotoData = buffer;
SkottieExample(CanvasKit, 'sk_webfont', webfontJSON, fullBounds, {
'Roboto-Regular': robotoData,
});
ParagraphAPI1(CanvasKit, robotoData);
});
});
Expand All @@ -147,6 +138,9 @@ <h2> 3D perspective transformations </h2>
SkpExample(CanvasKit, skpData);
});

const loadDog = fetch('https://storage.googleapis.com/skia-cdn/misc/dog.jpg').then((response) => response.arrayBuffer());
const loadMandrill = fetch('https://storage.googleapis.com/skia-cdn/misc/mandrill_256.png').then((response) => response.arrayBuffer());

function SkottieExample(CanvasKit, id, jsonStr, bounds, assets) {
if (!CanvasKit || !jsonStr) {
return;
Expand Down Expand Up @@ -370,19 +364,28 @@ <h2> 3D perspective transformations </h2>
return surface;
}

const spiralSkSL = `
uniform float rad_scale;
uniform float2 in_center;
uniform float4 in_colors0;
uniform float4 in_colors1;
void main(float2 p, inout half4 color) {
float2 pp = p - in_center;
float radius = sqrt(dot(pp, pp));
radius = sqrt(radius);
float angle = atan(pp.y / pp.x);
float t = (angle + 3.1415926/2) / (3.1415926);
t += radius * rad_scale;
t = fract(t);
color = half4(mix(in_colors0, in_colors1, t));
}`;

function RTShaderAPI1(CanvasKit) {
return; // TODO(kjlubick): Fix this example.
if (!CanvasKit) {
return;
}
const prog = `

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

const canvas = surface.getCanvas();

const effect = CanvasKit._SkRuntimeEffect.Make(prog);
const rot = CanvasKit.SkMatrix.rotated(90, 128, 128);
const shader = effect.makeShader([1, 0, 0, 1], true, rot);

const effect = CanvasKit.SkRuntimeEffect.Make(spiralSkSL);
const shader = effect.makeShader([
0.5,
150, 150,
0, 1, 0, 1,
1, 0, 0, 1], true);
const paint = new CanvasKit.SkPaint();
paint.setShader(shader);
canvas.drawRect(CanvasKit.LTRBRect(0, 0, 256, 256), paint);
canvas.drawRect(CanvasKit.LTRBRect(0, 0, 300, 300), paint);

surface.flush();
shader.delete();
paint.delete();
effect.delete();
}

// RTShader2 demo
Promise.all([ckLoaded, loadDog, loadMandrill]).then((values) => {
const [CanvasKit, dogData, mandrillData] = values;
const dogImg = CanvasKit.MakeImageFromEncoded(dogData);
if (!dogImg) {
console.error('could not decode dog');
return;
}
const mandrillImg = CanvasKit.MakeImageFromEncoded(mandrillData);
if (!mandrillImg) {
console.error('could not decode mandrill');
return;
}
const quadrantSize = 150;

const dogShader = dogImg.makeShader(CanvasKit.TileMode.Clamp, CanvasKit.TileMode.Clamp,
CanvasKit.SkMatrix.scaled(quadrantSize/dogImg.width(),
quadrantSize/dogImg.height()));
const mandrillShader = mandrillImg.makeShader(CanvasKit.TileMode.Clamp, CanvasKit.TileMode.Clamp,
CanvasKit.SkMatrix.scaled(
quadrantSize/mandrillImg.width(),
quadrantSize/mandrillImg.height()));

const surface = CanvasKit.MakeCanvasSurface('rtshader2');
if (!surface) {
console.error('Could not make surface');
return;
}

const prog = `
in fragmentProcessor before_map;
in fragmentProcessor after_map;
in fragmentProcessor threshold_map;
uniform float cutoff;
uniform float slope;
float smooth_cutoff(float x) {
x = x * slope + (0.5 - slope * cutoff);
return clamp(x, 0, 1);
}
void main(float2 xy, inout half4 color) {
half4 before = sample(before_map, xy);
half4 after = sample(after_map, xy);
float m = smooth_cutoff(sample(threshold_map, xy).r);
color = mix(before, after, half(m));
}`;

const canvas = surface.getCanvas();

const thresholdEffect = CanvasKit.SkRuntimeEffect.Make(prog);
const spiralEffect = CanvasKit.SkRuntimeEffect.Make(spiralSkSL);

const draw = (x, y, shader) => {
const paint = new CanvasKit.SkPaint();
paint.setShader(shader);
canvas.save();
canvas.translate(x, y);
canvas.drawRect(CanvasKit.LTRBRect(0, 0, quadrantSize, quadrantSize), paint);
canvas.restore();
paint.delete();
};

const offscreenSurface = CanvasKit.MakeSurface(quadrantSize, quadrantSize);
const getBlurrySpiralShader = (rad_scale) => {
const oCanvas = offscreenSurface.getCanvas();

const spiralShader = spiralEffect.makeShader([
rad_scale,
quadrantSize/2, quadrantSize/2,
1, 1, 1, 1,
0, 0, 0, 1], true);

return spiralShader;
// TODO(kjlubick): The raster backend does not like atan or fract, so we can't
// draw the shader into the offscreen canvas and mess with it. When we can, that
// would be cool to show off.

const blur = CanvasKit.SkImageFilter.MakeBlur(0.1, 0.1, CanvasKit.TileMode.Clamp, null);

const paint = new CanvasKit.SkPaint();
paint.setShader(spiralShader);
paint.setImageFilter(blur);
oCanvas.drawRect(CanvasKit.LTRBRect(0, 0, quadrantSize, quadrantSize), paint);

paint.delete();
blur.delete();
spiralShader.delete();
return offscreenSurface.makeImageSnapshot()
.makeShader(CanvasKit.TileMode.Clamp, CanvasKit.TileMode.Clamp);

};

const drawFrame = () => {
surface.requestAnimationFrame(drawFrame);
const thresholdShader = getBlurrySpiralShader(Math.sin(Date.now() / 5000) / 2);

const blendShader = thresholdEffect.makeShaderWithChildren(
[0.5, 10],
true, [dogShader, mandrillShader, thresholdShader]);
draw(0, 0, blendShader);
draw(quadrantSize, 0, thresholdShader);
draw(0, quadrantSize, dogShader);
draw(quadrantSize, quadrantSize, mandrillShader);

blendShader.delete();
};

surface.requestAnimationFrame(drawFrame);
});

function SkpExample(CanvasKit, skpData) {
if (!skpData || !CanvasKit) {
return;
Expand Down
48 changes: 42 additions & 6 deletions modules/canvaskit/canvaskit_bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1447,21 +1447,57 @@ EMSCRIPTEN_BINDINGS(Skia) {
auto [effect, errorText] = SkRuntimeEffect::Make(s);
if (!effect) {
SkDebugf("Runtime effect failed to compile:\n%s\n", errorText.c_str());
return nullptr;
}
return effect;
}))
.function("_makeShader", optional_override([](SkRuntimeEffect& self, uintptr_t fptr, size_t len, bool isOpaque)->sk_sp<SkShader> {
.function("_makeShader", optional_override([](SkRuntimeEffect& self, uintptr_t fPtr, size_t fLen, bool isOpaque)->sk_sp<SkShader> {
// See comment above for uintptr_t explanation
void* inputData = reinterpret_cast<void*>(fptr);
sk_sp<SkData> inputs = SkData::MakeFromMalloc(inputData, len);
void* inputData = reinterpret_cast<void*>(fPtr);
sk_sp<SkData> inputs = SkData::MakeFromMalloc(inputData, fLen);
return self.makeShader(inputs, nullptr, 0, nullptr, isOpaque);
}))
.function("_makeShader", optional_override([](SkRuntimeEffect& self, uintptr_t fptr, size_t len, bool isOpaque, SimpleMatrix sm)->sk_sp<SkShader> {
.function("_makeShader", optional_override([](SkRuntimeEffect& self, uintptr_t fPtr, size_t fLen, bool isOpaque, SimpleMatrix sm)->sk_sp<SkShader> {
// See comment above for uintptr_t explanation
void* inputData = reinterpret_cast<void*>(fptr);
sk_sp<SkData> inputs = SkData::MakeFromMalloc(inputData, len);
void* inputData = reinterpret_cast<void*>(fPtr);
sk_sp<SkData> inputs = SkData::MakeFromMalloc(inputData, fLen);
auto m = toSkMatrix(sm);
return self.makeShader(inputs, nullptr, 0, &m, isOpaque);
}))
.function("_makeShaderWithChildren", optional_override([](SkRuntimeEffect& self, uintptr_t fPtr, size_t fLen, bool isOpaque,
uintptr_t /** SkShader*[] */cPtrs, size_t cLen)->sk_sp<SkShader> {
// See comment above for uintptr_t explanation
void* inputData = reinterpret_cast<void*>(fPtr);
sk_sp<SkData> inputs = SkData::MakeFromMalloc(inputData, fLen);

sk_sp<SkShader>* children = new sk_sp<SkShader>[cLen];
SkShader** childrenPtrs = reinterpret_cast<SkShader**>(cPtrs);
for (size_t i = 0; i < cLen; i++) {
// This bare pointer was already part of an sk_sp (owned outside of here),
// so we want to ref the new sk_sp so makeShader doesn't clean it up.
children[i] = sk_ref_sp<SkShader>(childrenPtrs[i]);
}
auto s = self.makeShader(inputs, children, cLen, nullptr, isOpaque);
delete[] children;
return s;
}))
.function("_makeShaderWithChildren", optional_override([](SkRuntimeEffect& self, uintptr_t fPtr, size_t fLen, bool isOpaque,
uintptr_t /** SkShader*[] */cPtrs, size_t cLen, SimpleMatrix sm)->sk_sp<SkShader> {
// See comment above for uintptr_t explanation
void* inputData = reinterpret_cast<void*>(fPtr);
sk_sp<SkData> inputs = SkData::MakeFromMalloc(inputData, fLen);

sk_sp<SkShader>* children = new sk_sp<SkShader>[cLen];
SkShader** childrenPtrs = reinterpret_cast<SkShader**>(cPtrs);
for (size_t i = 0; i < cLen; i++) {
// This bare pointer was already part of an sk_sp (owned outside of here),
// so we want to ref the new sk_sp so makeShader doesn't clean it up.
children[i] = sk_ref_sp<SkShader>(childrenPtrs[i]);
}
auto m = toSkMatrix(sm);
auto s = self.makeShader(inputs, children, cLen, &m, isOpaque);
delete[] children;
return s;
}));
#endif

Expand Down
2 changes: 2 additions & 0 deletions modules/canvaskit/externs.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ var CanvasKit = {

// private API
_makeShader: function() {},
_makeShaderWithChildren: function() {},
},

ParagraphStyle: function() {},
Expand Down Expand Up @@ -855,6 +856,7 @@ CanvasKit.SkColorBuilder.prototype.push = function() {};
CanvasKit.SkColorBuilder.prototype.set = function() {};

CanvasKit.SkRuntimeEffect.prototype.makeShader = function() {};
CanvasKit.SkRuntimeEffect.prototype.makeShaderWithChildren = function() {};

CanvasKit.SkParticleEffect.prototype.effectUniforms = function() {};
CanvasKit.SkParticleEffect.prototype.particleUniforms = function() {};
Expand Down
18 changes: 18 additions & 0 deletions modules/canvaskit/rt_shader.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,22 @@ CanvasKit._extraInitializations.push(function() {
}
return this._makeShader(fptr, floats.length * 4, !!isOpaque, matrix);
}

// childrenWithShaders is an array of other shaders (e.g. SkImage.makeShader())
CanvasKit.SkRuntimeEffect.prototype.makeShaderWithChildren = function(floats, isOpaque, childrenShaders, matrix) {
var fptr = copy1dArray(floats, CanvasKit.HEAPF32);
var barePointers = [];
for (var i = 0; i<childrenShaders.length;i++) {
// childrenShaders are emscriptens smart pointer type. We want to get the bare pointer
// and send that over the wire, so it can be re-wrapped as an sk_sp.
barePointers.push(childrenShaders[i].$$.ptr);
}
var childrenPointers = copy1dArray(barePointers, CanvasKit.HEAPU32);
// Our array has 4 bytes per float, so be sure to account for that before
// sending it over the wire.
if (!matrix) {
return this._makeShaderWithChildren(fptr, floats.length * 4, !!isOpaque, childrenPointers, barePointers.length);
}
return this._makeShaderWithChildren(fptr, floats.length * 4, !!isOpaque, childrenPointers, barePointers.length, matrix);
}
});

0 comments on commit ecd8762

Please sign in to comment.