# Renderers

> pixi-silk renders on WebGL2 by default and on WebGPU when you ask for it. How to pick one, set up your own Pixi app and turn on batching.

Source: https://pixi-silk.schmooky.dev/docs/renderers/

pixi-silk renders on WebGL2 by default and on WebGPU when you ask for it. Both run the same
shader, a GLSL program and its WGSL port, so a scene looks the same on either.

Demo sources run in the docs demo runner, `defineDemo({ setup({ stage, params, tick }) })`. In your own app, `stage` is `app.stage` (or a container on it), `params` holds the values of the demo controls, and `tick(fn)` is `app.ticker.add`, with `t` and `dt` in seconds.

Live demo `webgpu` (source):

```ts
import { along, conic, linear, mesh, radial, SilkGraphics, vertical } from 'pixi-silk';
import { defineDemo } from './runtime';

// The same scene on both renderers: WebGL2 runs the GLSL shader, WebGPU its WGSL port.
// Each cell uses a different part of the shader: every shape, dashes, caps, alignment,
// hairlines, blur, spread, progressive blur, inner shadows, every gradient kind and plus-lighter.
const CELL = 72;

export default defineDemo({
    title: 'WebGL2 and WebGPU',
    size: [460, 310],
    renderers: ['webgl', 'webgpu'],
    controls: {
        rotate: { type: 'toggle', label: 'rotate', value: false },
    },
    setup({ stage, params, tick }) {
        const g = new SilkGraphics();

        stage.addChild(g);
        tick((t) => {
            const a = params.rotate ? t * 0.5 : 0;
            const cells: ((g: SilkGraphics) => void)[] = [
                (g) => g.roundRect(-28, -22, 56, 44, 14, 0.6).fill(linear([0x0a84ff, 0xbf5af2])),
                (g) => g.circle(0, 0, 26).fill(radial([0xffffff, 0xff9f0a, [1, 0xff375f, 0]])),
                (g) => g.ellipse(0, 0, 30, 20).stroke({ width: 4, color: 0x64d2ff, dash: [0, 9], cap: 'round' }),
                (g) =>
                    g.arcSweep(0, 0, 22, -Math.PI / 2, Math.PI * 1.5).stroke({
                        width: 9,
                        cap: 'round',
                        gradient: conic([0x30d158, 0xffd60a, 0xff453a]),
                    }),
                (g) => g.sector(0, 0, 28, -2.2, 1.4, 12, 4).fill(0x30d158),
                (g) => g.heart(0, 2, 54, 0.12).fill({ color: 0xff375f, blur: 6, blend: 'add' }),
                (g) =>
                    g
                        .star(0, 0, 5, 28, undefined, 0, 3)
                        .fill(0xffd60a)
                        .stroke({ width: 3, color: 0xffffff, alignment: 'outside' }),
                (g) =>
                    g.triangle(-26, 20, 26, 20, 0, -26, 5).stroke({ width: 4, color: 0xac8e68, alignment: 'inside' }),
                (g) =>
                    g
                        .beginPath()
                        .moveTo(-26, 18)
                        .bezierCurveTo(-30, -30, 10, -10, 4, -2)
                        .bezierCurveTo(30, -30, 30, 20, -26, 18)
                        .closePath()
                        .fill(vertical([0xff9f0a, 0xff375f])),
                (g) =>
                    g.polyline([-30, 16, -12, -14, 8, 10, 30, -18], { smooth: 'catmull' }).stroke({
                        width: [2, 9],
                        cap: 'round',
                        gradient: along([0x5e5ce6, 0x64d2ff]),
                    }),
                (g) =>
                    g.area([-30, 10, -15, -6, 0, 2, 15, -16, 30, -8], 20, { smooth: 'monotone' }).fill(
                        vertical([
                            [0, 0x30d158, 0.9],
                            [1, 0x30d158, 0.05],
                        ]),
                    ),
                (g) => {
                    for (let i = 0; i < 6; i++)
                        g.line(-28, -18 + i * 7, 28, -18 + i * 7).stroke({ width: 0.25 + i * 0.25, color: 0xffffff });
                },
                (g) =>
                    g
                        .roundRect(-22, -16, 44, 32, 8)
                        .fill({ color: 0x0a84ff, alpha: 0.9, blur: 7, spread: 4 })
                        .roundRect(-22, -16, 44, 32, 8)
                        .fill(0x1c1c1e),
                (g) =>
                    g.roundRect(-26, -22, 52, 44, 12).fill({
                        color: 0x1c1c1e,
                        innerShadow: [
                            { color: 0x6694ff, x: 0, y: -10, blur: 8, spread: -2 },
                            { color: 0xffffff, alpha: 0.5, x: 0, y: 4, blur: 2, blend: 'add' },
                        ],
                    }),
                (g) => g.roundRect(-26, -22, 52, 44, 10).fill({ color: 0xff9f0a, blur: { from: 0, to: 9 } }),
                (g) => g.roundRect(-28, -22, 56, 44, 6).fill(mesh(2, 2, [0xff375f, 0x0a84ff, 0xffd60a, 0x30d158])),
                (g) => g.circle(0, 0, 24).stroke({ width: 6, color: 0xbf5af2, dash: [10, 6], cap: 'butt' }),
                (g) => g.regularPoly(0, 0, 26, 6, 0, 5).fill({ gradient: linear([0x64d2ff, 0x0a84ff]), alpha: 0.85 }),
            ];

            g.clear();
            cells.forEach((draw, i) => {
                g.save()
                    // the top row leaves room for the renderer label
                    .translateTransform(50 + (i % 6) * CELL, 76 + Math.floor(i / 6) * 90)
                    .rotateTransform(a);
                draw(g);
                g.restore();
            });
        });

        return undefined;
    },
});
```

## Pick a renderer

`createSilkApp` creates a WebGL2 app. Pass `preference: 'webgpu'` to use WebGPU where the browser
has it:

```ts

const silk = await createSilkApp({ parent: document.body, preference: 'webgpu' });

silk.app.renderer.name; // 'webgpu', or 'webgl' where the browser has no WebGPU
silk.app.stage.addChild(new SilkGraphics().circle(60, 60, 40).fill(0x30d158));
```

Pixi tries WebGPU first and falls back to WebGL2. When it gets WebGPU, `createSilkApp` loads the
WGSL shader before it resolves. A browser can offer a WebGPU adapter and still fail to start the
canvas context, for example after a GPU process reset. `createSilkApp` then logs a warning and
starts on WebGL2 instead.

WebGL2 stays the default because every current browser has it and it starts faster. Choose WebGPU
when the rest of your app needs it.

## Use WebGPU in your own Pixi app

The WGSL shader lives in its own entry point, `pixi-silk/webgpu`, so WebGL2 apps never download
it. If you create the application yourself, call `enableWebGPU()` once, before the first
`SilkGraphics`:

```ts

const app = new Application();

await app.init({ preference: 'webgpu' });
enableWebGPU();
app.stage.addChild(new SilkGraphics().circle(60, 60, 40).fill(0x30d158));
```

Objects created before the call have no WebGPU program. Pixi skips them and logs "Mesh shader has
no gpuProgram". Calling `enableWebGPU()` in a WebGL2 app does nothing harmful.

## Batch objects into one draw

Batching is off by default. Call `enableSilkBatching()` before you create the objects:

```ts

enableSilkBatching();
const dot = new SilkGraphics().circle(0, 0, 4).fill(0x0a84ff);
```

It works on WebGL2 and on WebGPU. On WebGPU it also needs `enableWebGPU()`, which `createSilkApp`
calls for you. [Performance](/docs/performance/#batch-many-objects) says what breaks a batch.

## Glass on both renderers

`Glass` from `pixi-silk/glass` reads the pixels behind it. On WebGL2 it reads the back buffer,
which it turns on the first time it draws. On WebGPU it copies the render target, so it needs no
back buffer.

## What differs

The two shaders are line-for-line ports. Three details differ because of the APIs:

| Detail | WebGL2 | WebGPU |
|---|---|---|
| Dither grain | rows counted from the bottom | rows counted from the top, so the grain is mirrored. Its strength is the same |
| Gradient ramp reads | the GPU's linear filter, which can round the blend weight to 8 bits | two exact texel loads, blended in the shader |
| Derivatives | `dFdx`, precision up to the GPU | `dpdx`, precision up to the GPU |

Edges can differ by a rounding step. Add `?renderer=webgpu` to a docs URL to run every demo on that
page on WebGPU.
