Engine & Workers
OpenGPEX offloads all CPU-intensive image processing to Web Workers via a unified facade called PixelService. This page explains the engine architecture, worker communication protocol, and WASM integration.
Architecture
┌───────────────────────────────────────────────────────┐
│ Main Thread │
│ │
│ PixelService (facade) │
│ │ │
│ ├── EngineFactory.create() → Canvas2dEngine │
│ │ └── Consumes RenderCommand queue │
│ │ │
│ └── WorkerProxy (RPC bridge) │
│ │ │
└───────────────┼────────────────────────────────────────┘
│ postMessage (Transferable)
▼
┌───────────────────────────────────────────────────────┐
│ Web Worker (processor.worker.ts) │
│ │
│ ┌─────────┐ ┌─────────────┐ ┌──────────────────┐ │
│ │ Decoder │ │ Mipmap Gen │ │ Compositor │ │
│ │ (JPEG, │ │ (pyramid │ │ (merge, export, │ │
│ │ PNG, │ │ levels) │ │ physical crop) │ │
│ │ WebP) │ │ │ │ │ │
│ └─────────┘ └─────────────┘ └──────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ WASM Modules │ │
│ │ • avif_enc.wasm (AVIF encoder) │ │
│ │ • (future: WebP encoder, filters) │ │
│ └─────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────┘
PixelService (Main-Thread Facade)
PixelService is the single entry point for all rendering and image processing operations:
interface PixelService {
// Rendering
renderToBlob(frameId: string, options?: ExportOptions): Promise<Blob>;
renderToCanvas(frameId: string, canvas: HTMLCanvasElement): void;
// Pixel operations
getPixelColor(frameId: string, x: number, y: number): RGBA;
floodFill(frameId: string, x: number, y: number, color: RGBA): Promise<ImageData>;
// Asset management
decodeAsset(blob: Blob): Promise<AssetMeta>;
generateThumbnail(assetId: string, maxSize: number): Promise<ImageBitmap>;
// Engine control
invalidate(frameId?: string): void;
destroy(): void;
}
WorkerProxy (RPC Bridge)
Communication between main thread and Worker uses a Promise-based RPC pattern:
// Main thread sends request
const result = await workerProxy.call('decode', { blob, options });
// Internally:
// 1. Generates unique requestId
// 2. Posts message with Transferable objects
// 3. Returns Promise that resolves when Worker replies with same requestId
Message Protocol
interface WorkerRequest {
id: string; // Unique request ID
method: string; // RPC method name
params: unknown; // Serializable parameters
transfer?: Transferable[]; // Zero-copy buffers
}
interface WorkerResponse {
id: string; // Matches request ID
result?: unknown; // Success payload
error?: string; // Error message (if failed)
transfer?: Transferable[];
}
Canvas2dEngine (Render Backend)
The primary rendering backend consumes a RenderCommand queue:
type RenderCommand =
| { type: 'clear' }
| { type: 'drawTile'; tile: ImageBitmap; dx: number; dy: number }
| { type: 'drawLayer'; layer: Layer; source: CanvasImageSource }
| { type: 'applyMask'; mask: ClipDescriptor }
| { type: 'setTransform'; matrix: DOMMatrix }
| { type: 'save' }
| { type: 'restore' };
This command queue architecture decouples the display list generation (StageComposer) from the actual rendering, allowing future backend swaps (e.g., WebGPU, WASM-based renderer).
WASM Integration
OpenGPEX uses WebAssembly for performance-critical encoding:
| Module | Purpose | Location |
|---|---|---|
avif_enc.wasm |
AVIF image encoding (10-50× faster than JS) | public/wasm/ |
Loading Pattern
// WASM modules are lazy-loaded on first use
const avifEncoder = await loadWasmModule('avif_enc.wasm');
const encoded = avifEncoder.encode(imageData, { quality: 80 });
WASM modules run inside the Web Worker, keeping the main thread completely free.
OffscreenCanvas Usage
The Worker uses OffscreenCanvas for GPU-accelerated compositing without DOM access:
// Inside Worker:
const offscreen = new OffscreenCanvas(width, height);
const ctx = offscreen.getContext('2d')!;
// Draw layers using the same atomic painter as main thread
drawLayerInstance(ctx, layer, bitmap);
// Transfer result back (zero-copy)
const result = offscreen.transferToImageBitmap();
self.postMessage({ id, result }, [result]);
Performance Design
| Aspect | Approach |
|---|---|
| Memory transfers | Transferable objects (zero-copy between threads) |
| Decode caching | LRU cache of 5 raw decoded bitmaps in Worker |
| Tile caching | 500-tile LRU in main thread |
| WASM loading | Lazy (only loaded when format is first used) |
| Error isolation | Worker crashes don't affect main thread UI |
Next Steps
- Rendering Pipeline — Tiled rendering and mipmap details
- Spatial Service — Coordinate transforms for rendering
- Architecture Overview — Where Engine fits in the system
Last updated: 2026-06-14