Skip to content
Experimental · canvas · WebGL

Canvas pixels from the GPU you claim

A GPU string is easy to change; the pixels that GPU would draw are not. The canvas bridge forwards canvas and WebGL operations to a machine that really has the GPU your persona presents and returns its pixels, so a page that draws and reads back gets the answer that GPU would give.

Experimental and opt-in. Most setups don't need it.

A render server, a tunnel, one option

# on the machine with the real GPU (a Windows GPU for a Windows persona), next to a regular Chrome
# started with --remote-debugging-port=9222 --user-data-dir=<a separate folder>
# (Chrome ignores the port on its default profile)
pip install playwright
python tools/canvas-bridge-server/server.py \
    --backend cdp \
    --cdp-url ws://127.0.0.1:9222/devtools/browser/... \
    --port 8443
# it prints the render GPU's renderer string: use it in step 3

Highlights

Operations, not recordings

It forwards the drawing calls, not a library of pre-recorded images, so most canvases generated on the fly work, not only known probes.

The readbacks that matter

getImageData, toDataURL, readPixels and measureText return the bridge host's real output. Local noise is skipped on that path; the bridge pixels are the ground truth.

Per-site policy

Bridge every site, only an allow-list of sites where canvas coherence matters, or every site except a deny-list.

Wait or keep moving

On a cold cache miss, block waits for the bridge and local serves the fast local render instead of stalling.

Off unless you set it

Without a bridge URL, rendering stays fully local. If the bridge is unreachable, Clearcote logs a warning and falls back to local rendering.

Why a bridge, and what it costs

Clearcote renders canvas and WebGL on whatever GPU the host actually has. When a persona claims a different GPU, checks that compare rendered pixels with the claimed hardware can see the difference, and no amount of software makes one GPU emit another's exact pixels. The bridge removes the mismatch instead of hiding it: the drawing happens on the GPU being claimed.

The render server is a small Python coordinator on the GPU machine that drives a browser there (a regular Chrome over CDP, or a headless Clearcote it launches itself) and replays the forwarded operations on its real canvas. The client opens the bridge socket from the renderer process, so it needs --no-sandbox, which the SDK adds when you set a bridge URL.

It has real costs. A pixel readback that misses the bridge's cache waits on a network round trip (with a five-second timeout before the local fallback), and every measureText call makes one, so keep the render host on the same LAN or datacenter. And the canvas identity becomes the render host's, not the seed's: every profile that shares one bridge host shares its canvas hash, so run one GPU host per group of identities you want kept apart.

What people use it for

Personas the host can't render

Present a GPU your automation machine doesn't have, with pixels to match.

GPU-less servers

Headless servers that would otherwise draw through a software rasterizer.

Mobile personas

An Android persona on a desktop engine, with rendering from matching hardware.

Clearcote is built for privacy, testing, research and lawful automation. You're responsible for how you use it and for following the terms of the sites you visit.

Which plan includes it

Experimental, and in every tier. You provide the render host: a machine with the GPU you want to present (a Windows GPU for a Windows persona), reachable over a private network.

Compare plans

FAQ

Why can't the GPU just be spoofed?

The GPU string can be, and Clearcote does. But a page can also draw something and read the pixels back, and those pixels come from the GPU that did the drawing. Changing the string without changing the pixels is exactly the kind of contradiction checks look for.

What do I need to run it?

A machine with a real GPU to act as the render host (a Windows GPU for a Windows persona; its GPU becomes the canvas identity your profiles present) and a private network path to it, such as Tailscale, WireGuard or an SSH tunnel.

Is it secure?

The bridge speaks plaintext WebSocket, so run it only over a private network or an encrypted tunnel, and never expose the bridge port on the public internet. The reference server doesn't check --canvas-bridge-auth, so the network is your access control.

Which canvases are bridged?

Canvas 2D and WebGL, including procedurally generated WebGL textures. Image, 2D-canvas, video, ImageBitmap and 3D texture sources fall back to local rendering for that canvas, so they stay correct but aren't bridged.

Go deeper in the docs

Related features

Try Clearcote

A drop-in for Playwright & Puppeteer, coherent down to the TLS handshake, with an open-source build to start from.

Free for one browser with GitHub. No card.