Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add missing information about Runtime shaders in the documentation #1737

Merged
merged 11 commits into from Aug 7, 2023
92 changes: 87 additions & 5 deletions docs/docs/image-filters/runtime-shader.md
Expand Up @@ -6,7 +6,13 @@ slug: /image-filters/runtime-shader
---

The `RuntimeShader` image filter allows you to write your own [Skia Shader](/docs/shaders/overview) as an image filter.
This component receive the currently filtered image as a shader uniform (or the implicit source image if no children are provided).
This component receives the currently filtered image as a shader uniform (or the implicit source image if no children are provided).

:::info

Because RuntimeShader relies on texture sampling of the Skia drawing, we recommend applying a technique known as supersampling. [See below](#supersampling).

:::


| Name | Type | Description |
Expand All @@ -18,7 +24,7 @@ This component receive the currently filtered image as a shader uniform (or the
## Example

The example below generates a circle with a green mint color.
The circle is first draw with the lightblue color `#add8e6` and the runtime shader switches the blue and green channel: we get mint green `#ade6d8`.
The circle is first drawn with the light blue color `#add8e6`, and the runtime shader switches the blue with the green channel: we get mint green `#ade6d8`.

```tsx twoslash
import {Canvas, Text, RuntimeShader, Skia, Group, Circle} from "@shopify/react-native-skia";
Expand All @@ -36,12 +42,88 @@ export const RuntimeShaderDemo = () => {
return (
<Canvas style={{ flex: 1 }}>
<Group>
<RuntimeShader source={source} />
<Circle cx={r} cy={r} r={r} color="lightblue" />
<RuntimeShader source={source} />
<Circle cx={r} cy={r} r={r} color="lightblue" />
</Group>
</Canvas>
);
};
```

<img alt="Runtime Shader" src={require("/static/img/image-filters/runtime-shader.png").default} width="256" height="256" />

## Supersampling

To keep the image filter output crisp, We upscale the filtered drawing to the [pixel density of the app](https://reactnative.dev/docs/pixelratio). Once the drawing is filtered, we scale it back to the original size. This can be seen in the example below. These operations must be performed on a Skia layer via the `layer` property.

```tsx twoslash
import {Canvas, Text, RuntimeShader, Skia, Group, Circle, Paint, Fill, useFont} from "@shopify/react-native-skia";
import {PixelRatio} from "react-native";

const pd = PixelRatio.get();
const source = Skia.RuntimeEffect.Make(`
uniform shader image;

half4 main(float2 xy) {
if (xy.x < 256 * ${pd}/2) {
return color;
}
return image.eval(xy).rbga;
}
`)!;

export const RuntimeShaderDemo = () => {
const r = 128;
const font = useFont(require("./SF-Pro.ttf"), 24);
return (
<Canvas style={{ flex: 1 }}>
<Group transform={[{ scale: 1 / pd }]}>
<Group
layer={
<Paint>
<RuntimeShader source={source} />
</Paint>
}
transform={[{ scale: pd }]}
>
<Fill color="#b7c9e2" />
<Text
text="Hello World"
x={16}
y={32}
color="#e38ede"
font={font}
/>
</Group>
</Group>
</Canvas>
);
};
```

<img alt="Runtime Shader" src={require("/static/img/image-filters/runtime-shader.png").default} width="256" height="256" />
<table style={{ width: '100%' }}>
<tr>
<td><b>With supersampling</b></td>
<td><b>Without supersampling</b></td>
</tr>
<tr>
<td style={{ textAlign: 'left', width: '50%' }}>
<div style={{ overflow: 'hidden', height: 100 }}>
<img
alt="Runtime Shader"
src={require("/static/img/runtime-shader/with-supersampling.png").default}
style={{ width: 512, height: 512 }}
/>
</div>
</td>
<td style={{ textAlign: 'right', width: '50%' }}>
<div style={{ overflow: 'hidden', height: 100 }}>
<img
alt="Runtime Shader"
src={require("/static/img/runtime-shader/without-supersampling.png").default}
style={{ width: 512, height: 512 }}
/>
</div>
</td>
</tr>
</table>
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
86 changes: 83 additions & 3 deletions package/src/renderer/__tests__/e2e/RuntimeShader.spec.tsx
@@ -1,9 +1,16 @@
/* eslint-disable max-len */
import React from "react";

import { surface, importSkia } from "../setup";
import { Circle, Fill, Group, Paint, RuntimeShader } from "../../components";
import { checkImage, itRunsE2eOnly } from "../../../__tests__/setup";
import { surface, importSkia, fonts } from "../setup";
import {
Circle,
Fill,
Group,
Paint,
RuntimeShader,
Text,
} from "../../components";
import { checkImage, docPath, itRunsE2eOnly } from "../../../__tests__/setup";

const spiral = `
uniform float scale;
Expand Down Expand Up @@ -241,4 +248,77 @@ half4 main(float2 xy) {
checkImage(img, "snapshots/runtime-shader/scaled-circle2.png");
}
);
itRunsE2eOnly("should use supersampling", async () => {
const font = fonts.RobotoMedium;
const { Skia } = importSkia();
const source = Skia.RuntimeEffect.Make(`
uniform shader image;

half4 main(float2 xy) {
vec4 color = image.eval(xy);
if (xy.x < 256 * 3/2) {
return color;
}
return color.rbga;
}
`)!;
const img = await surface.draw(
<>
<Group transform={[{ scale: 1 / 3 }]}>
<Group
layer={
<Paint>
<RuntimeShader source={source} />
</Paint>
}
transform={[{ scale: 3 }]}
>
<Fill color="#B7C9E2" />
<Text
text="Hello World"
x={16}
y={32}
color="#e38ede"
font={font}
/>
</Group>
</Group>
</>
);
checkImage(img, docPath("runtime-shader/with-supersampling.png"), {
maxPixelDiff: 1200,
});
});
itRunsE2eOnly("shouldn't use supersampling", async () => {
const font = fonts.RobotoMedium;
const { Skia } = importSkia();
const source = Skia.RuntimeEffect.Make(`
uniform shader image;

half4 main(float2 xy) {
vec4 color = image.eval(xy);
if (xy.x < 256 / 2) {
return color;
}
return color.rbga;
}
`)!;
const img = await surface.draw(
<>
<Group
layer={
<Paint>
<RuntimeShader source={source} />
</Paint>
}
>
<Fill color="#B7C9E2" />
<Text text="Hello World" x={16} y={32} color="#e38ede" font={font} />
</Group>
</>
);
checkImage(img, docPath("runtime-shader/without-supersampling.png"), {
maxPixelDiff: 500,
});
});
});