# Anti-aliasing in PixiJS v8

> Compare six ways to get smooth graphics in PixiJS v8: MSAA, higher resolution, curve smoothness, supersampling, FXAA and analytic SDF anti-aliasing.

Source: https://pixi-silk.schmooky.dev/docs/antialiasing-in-pixijs/

Anti-aliasing removes the jagged "staircase" edges of shapes on a pixel grid. It gives each edge
pixel a partial colour, proportional to how much of it the shape covers. PixiJS v8 has five
practical options: MSAA (`antialias: true`), a higher resolution, finer curve tessellation,
supersampling and post-process FXAA. pixi-silk adds a sixth, **analytic anti-aliasing**. It
computes coverage from each shape's exact signed distance field, so it is exact at any zoom,
rotation and device pixel ratio.

Live demo `versus` (source):

```ts
import { Container, Graphics, Text } from 'pixi.js';
import { SilkGraphics } from 'pixi-silk';
import { defineDemo } from './runtime';

// Left: Pixi v8 Graphics (tessellated, no MSAA). Right: SilkGraphics (distance fields).
export default defineDemo({
    size: [480, 240],
    controls: {
        zoom: { type: 'range', label: 'zoom', min: 1, max: 6, step: 0.01, value: 1 },
        spin: { type: 'toggle', label: 'rotate', value: true },
    },
    setup({ stage, params, tick }) {
        const sides = [new Container(), new Container()];
        const pixi = new Graphics();
        const silk = new SilkGraphics();

        sides[0].addChild(pixi);
        sides[1].addChild(silk);
        sides.forEach((side, i) => {
            const mask = new Graphics().rect(i * 240, 0, 240, 240).fill(0xffffff);
            const label = new Text({
                text: i ? 'SilkGraphics' : 'Graphics',
                style: { fill: 0x8e8e93, fontSize: 12, fontFamily: 'system-ui' },
            });

            label.position.set(i * 240 + 12, 10);
            side.mask = mask;
            stage.addChild(side, mask, label);
        });
        let angle = 0;

        tick((_t, dt) => {
            if (params.spin) angle += dt * 0.15;
            for (const [i, side] of sides.entries()) {
                side.pivot.set(120, 130);
                side.position.set(i * 240 + 120, 130);
                side.scale.set(params.zoom);
                side.rotation = angle;
            }
            for (const g of [pixi, silk]) {
                g.clear();
                g.circle(120, 130, 62).stroke({ width: 3, color: 0x64d2ff });
                g.roundRect(70, 90, 100, 80, 24).stroke({ width: 1, color: 0xffffff });
                g.circle(120, 130, 18).fill(0xff375f);
                for (let k = 0; k < 6; k++)
                    g.moveTo(40, 60 + k * 3)
                        .lineTo(200, 64 + k * 5)
                        .stroke({ width: 0.6, color: 0xffd60a });
            }
        });
    },
});
```

## The options at a glance

| Technique | How to enable in PixiJS v8 | Edge quality | Cost | Applies to |
|---|---|---|---|---|
| MSAA | `app.init({ antialias: true })` | 4 samples, so 5 coverage levels per edge pixel | memory bandwidth, resolve pass | main canvas (render textures and filters opt in) |
| Higher resolution | `resolution: devicePixelRatio, autoDensity: true` | smaller stairs, still aliased | fill rate grows with DPR squared | everything |
| Curve smoothness | `app.init({ bezierSmoothness: 0.9 })` | fewer facets on curves, edges still aliased | more triangles | Graphics curves |
| Supersampling (SSAA) | render into a 2x render texture, then draw it scaled down | high | 4x pixels, extra texture | what you render into it |
| FXAA | a post-process filter | softened edges, blurred detail and text | one full-screen pass | the filtered object |
| **Analytic SDF (pixi-silk)** | `new SilkGraphics()` | exact coverage, continuous | one instanced draw call | everywhere, including filters and render textures |

## How to enable MSAA in PixiJS v8

```ts
import { Application, Graphics } from 'pixi.js';

const app = new Application();
await app.init({
    antialias: true,                        // MSAA on the main canvas (WebGL context attribute)
    resolution: window.devicePixelRatio,    // render at device resolution
    autoDensity: true,                      // keep the CSS size
});
```

MSAA belongs to each render target, so it does not follow your objects everywhere:

- **Render textures** are not multisampled unless you create them with `antialias: true`.
- **Filters** render into their own targets with `antialias: 'off'` by default (`'on'` or
  `'inherit'` changes it). They also render at `resolution: 1` unless you raise it.
- **Thin lines** below one pixel still break up. Four samples cannot represent a line that covers a
  tenth of a pixel.

## Why MSAA is not enough for 2D UI

MSAA samples coverage at four fixed points per pixel. That is fine for large shapes at rest. The
things UI and charts are made of show its limits:

- Slowly moving or rotating edges jump between the five coverage levels, which reads as shimmer.
- Hairlines and small text-like strokes fall between samples and look dotted.
- Zooming reveals the tessellation of curves, because Graphics fixed its triangles when you drew.
- Fractional DPRs (1.25, 1.5, 2.625) add resampling blur, unless the canvas backing store matches
  the device pixels exactly.

## Analytic anti-aliasing with signed distance fields

A signed distance field (SDF) gives the distance from any point to the shape's edge. pixi-silk
evaluates each primitive's exact SDF per pixel. It turns the distance into coverage over exactly
one device pixel:

```glsl
float px = sqrt(abs(dFdx(p).x * dFdy(p).y - dFdx(p).y * dFdy(p).x)); // one device pixel, in shape units
float coverage = clamp(0.5 - d / px, 0.0, 1.0);                        // d: signed distance to the edge
```

The result is right at any scale, rotation, resolution and render target, because the pixel size
comes from screen-space derivatives. There is nothing to tessellate and no samples to miss. See
[How it works](/docs/how-it-works/) for details.

```ts
import { createSilkApp, SilkGraphics } from 'pixi-silk';

const { app } = await createSilkApp({ parent: document.body });   // antialias: false, DPR-exact canvas
const g = new SilkGraphics();

g.circle(200, 150, 80).stroke({ width: 2, color: 0x64d2ff });     // smooth at any zoom
g.roundRect(100, 60, 200, 180, 40, 0.6).stroke({ width: 0.5, color: 0xffffff }); // a clean hairline
app.stage.addChild(g);
```

## Which one should you use?

- For sprites and textures only, skip MSAA. The GPU filters textures.
- For occasional large shapes and no zoom, use `antialias: true` plus `resolution: devicePixelRatio`.
- For UI, charts, icons, thin lines and zoomable or rotating vector content, use analytic
  anti-aliasing with `SilkGraphics`. Keep `antialias: false` to save multisample bandwidth.
- For content behind filters or in render textures, SilkGraphics keeps its quality. With Graphics,
  opt those targets into MSAA and raise the filter resolution.

## References

- PixiJS v8 [Graphics API](https://pixijs.download/release/docs/scene.Graphics.html) and the
  [issue on smoother curves in v8](https://github.com/pixijs/pixijs/issues/9831).
- Inigo Quilez, [2D distance functions](https://iquilezles.org/articles/distfunctions2d/).
- web.dev, [Pixel-perfect rendering with devicePixelContentBox](https://web.dev/articles/device-pixel-content-box).
- Glossary: [MSAA](/docs/glossary/#msaa), [SSAA](/docs/glossary/#ssaa), [FXAA](/docs/glossary/#fxaa),
  [signed distance field](/docs/glossary/#signed-distance-field).
