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


Last updated: 2026-06-14