A palette-first WebGPU retro engine for TypeScript, inspired by RetroBlit. Draw with palette indices, animate with palette cycling and fades, and ship authentic VGA-era effects on modern GPUs.
Blit-Tech draws heavy inspiration from RetroBlit by Martin Cietwierkowski (@daafu) - a retro pixel demo framework for Unity that replaces the editor with a clean, low-level demo loop. Blit-Tech brings the same philosophy to the web using WebGPU: no scene graphs, no complex frameworks, just sprites, primitives, and fonts.
- True indexed rendering: primitives and sprites write palette indices, not RGBA pixels
- Palette effects built-in: cycling, fade, flash, and swap run per frame with no per-sprite rewrites
- Built-in retro palettes: VGA, CGA, C64, Game Boy, PICO-8, and NES preset factories
- Palette offset variants: recolor one sprite sheet into team colors, states, or themes without duplicate textures
- Performance-first data model: tiny palette uploads (4 KB), smaller sprite textures, and compact primitive vertices
- WebGPU rendering with dual-pipeline architecture (primitives + sprites); automatic Canvas 2D software fallback
- Post-process effects: two-tier system - pixel tier on the
r8uintindex framebuffer; display tier on upscaled RGBA; bundled CRT presets - Primitive drawing: pixels, lines, rectangles (outline and filled)
- Sprite system: palette-indexed textures, palette offset, automatic texture batching
- Bitmap fonts: variable-width rendering from
.btfontfiles with palette offset support - Camera system: scrolling with offset/reset and world-bounds clamping via
BT.cameraClamp - Asset loading: sprite sheets and bitmap fonts with automatic caching
- Pointer input: mouse, touch, and pen unified under four slots; scroll delta; cursor control
- Keyboard input: raw keys via
KeyboardEvent.code, virtual face buttons, remapping, text accumulation - Gamepad input: up to four players via standard Gamepad API, stick dead zone, face buttons
- Fixed timestep: deterministic update loop with tick counter,
Timer, and timing helpers - Frame capture:
BT.captureFrame()andBT.downloadFrame()for PNG export - Overlay: engine-drawn FPS, backend, resolution, and demo title (toggle with
~or bottom-left corner; disable viaisOverlayEnabled: falseinconfigure())
| Feature | Blit-Tech | Typical 2D WebGPU engines |
|---|---|---|
| Rendering model | Native indexed palette pipeline | RGBA textures and framebuffers |
| Color animation | Palette cycling/fade/flash built-in | Manual sprite or shader rewrites |
| Global recolor/fade cost | One palette update | Scene redraw and blend passes |
| Color variants | Palette offsets | Duplicate assets or tint logic |
| Retro palette presets | C64, NES, Game Boy, CGA, VGA, etc. | Usually custom/manual only |
Runtime (browser)
- A WebGPU-compatible browser (the engine falls back to Canvas 2D software rendering when WebGPU is unavailable):
- Chrome/Edge 113+ (Windows, macOS, Linux, Android)
- Firefox 141+ on Windows; 145+/147+ on macOS; Nightly on Linux and Android
- Safari 26+ (macOS Tahoe / iOS 26); or Safari 18-25 with WebGPU enabled via Feature Flags
App toolchain
- Node.js >=22.18.0 (LTS)
- An ESM bundler (Vite, webpack, esbuild, and similar) to load the published package in the browser
The fastest way to start is the scaffolder. It writes a ready-to-run Vite project, installs the engine, and includes a starter game and local docs:
npm create blit-tech@latest my-game
cd my-game
npm run devWorks with npm, pnpm, yarn, or bun (it uses whichever you ran it with). See create-blit-tech for options and what the project contains.
Install blit-tech from npm (npmjs.com/package/blit-tech):
pnpm add blit-techbootstrap() expects a canvas inside #canvas-container (defaults: canvas id blit-tech-canvas, container id
canvas-container):
<div id="canvas-container"><canvas id="blit-tech-canvas"></canvas></div>
<script type="module" src="/src/main.ts"></script>import { bootstrap, BT, Color32, Palette, Rect2i, type IBlitTechDemo } from 'blit-tech';
const BG = 1;
const WATER_A = 9;
const WATER_B = 12;
class MyDemo implements IBlitTechDemo {
async init(): Promise<boolean> {
const palette = Palette.c64();
palette.set(BG, new Color32(20, 30, 40, 255));
BT.paletteSet(palette);
// Animate every pixel that uses slots 9..12 (water/lava style cycling).
BT.paletteCycle(WATER_A, WATER_B, 6);
return true;
}
update(): void {
// fixed-step logic here
}
render(): void {
BT.clear(BG);
BT.drawRectFill(new Rect2i(100, 100, 50, 50), WATER_A);
}
}
bootstrap(MyDemo);See API: Core for full bootstrap() options.
For interactive examples and demos, visit the Blit-Tech Demos repository.
See API: Core for bootstrap() options.
| Guide | What it covers |
|---|---|
| API: Core | bootstrap, game loop, camera, Timer, core types |
| Overlay Guide | engine HUD subsystem, toggle, custom rows, layout |
| API: Rendering | primitives, sprites, text, post-process, frame capture |
| API: Palette | palette setup, presets, effects, serialization |
| API: Assets | sprite sheets, bitmap fonts, asset loading |
| Input Guide | pointer, keyboard, gamepad |
| Palette Guide | palette-first workflow, offsets, effects |
| Palette Presets | built-in preset reference and exact color data |
| Post-Process Effects | effect chain, built-in effects, custom effects |
| Bitmap Fonts | .btfont format and BMFont conversion |
WebGPU support varies by browser:
| Browser | Version | Status |
|---|---|---|
| Chrome/Edge | 113+ | Enabled by default |
| Firefox | 141+ (Windows) | Enabled by default; 145+/147+ on macOS; Nightly on Linux/Android |
| Safari | 26+ | Enabled by default; Safari 18-25 available via Feature Flags |
When WebGPU is unavailable the engine falls back to the Canvas 2D software renderer automatically. The software path
also works on browsers that do not expose WebGPU globals at all (for example Firefox on Linux without Nightly); the
WebGPU renderer is loaded only when WebGPU init succeeds. By default the engine draws a stats overlay after each frame:
measured FPS, configured target FPS, the active backend name (via BT.activeBackend), logical resolution, and a short
demo title derived from document.title. The overlay body starts hidden with a bitmap toggle hint in the
bottom-left corner; toggle it with ~ (Backquote) or a primary press in the bottom-left corner. Use
isOverlayVisibleAtStart: true to show the body on the first frame, isOverlayToggleHintVisible: false to hide the
hint icon on immersive demos, or isOverlayEnabled: false to disable the overlay entirely in configure(). Use
BT.activeBackend to read which backend is running ('webgpu', 'software', or null before init).
Contributor workflow, scripts, release process, and repository tooling docs live in Developer Experience and CONTRIBUTING.md.
ISC
