003 - Rendering System

The Unified Rendering System enables renderer-agnostic code.

The Problem

Imagine: You've programmed a beautiful artwork with Canvas2D. Now you want:

  1. More performance – Thousands of particles stutter, WebGL would be better
  2. Export SVG – For your pen plotter
  3. Different outputs – Live on screen AND as a vector file

Without abstraction, you'd have to write your code three times. With the Unified Rendering System, you write it once.

The Solution

The RenderContext is a facade – a unified interface behind which different renderers operate. Your code calls ctx.drawCircle(), and depending on the active renderer, it ends up on Canvas2D, WebGL, or in an SVG file.

Why Facade? You don't want to worry about implementation details. A circle is a circle, whether it's drawn with arc() (Canvas2D), shader triangles (WebGL), or <circle> (SVG).

Architecture

┌─────────────────────────────────────────────────────────────┐
│                      Artwork Code                           │
│  ┌───────────────────────────────────────────────────────┐  │
│  │              Brush/Agent Code                         │  │
│  └───────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                         Facade                              │
│  ┌───────────────────────────────────────────────────────┐  │
│  │                   RenderContext                       │  │
│  └───────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘
                              │
              ┌───────────────┼───────────────┐
              ▼               ▼               ▼
┌─────────────────────────────────────────────────────────────┐
│                        Renderer                             │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐          │
│  │ Canvas2D-   │  │  WebGL-     │  │   SVG-      │          │
│  │ Renderer    │  │  Renderer   │  │   Renderer  │          │
│  └─────────────┘  └─────────────┘  └─────────────┘          │
└─────────────────────────────────────────────────────────────┘

Components

Class File Function
IRenderer IRenderer.ts Interface for all Renderers
RenderContext RenderContext.ts Facade, selects active Renderer
Canvas2DRenderer Canvas2DRenderer.ts Standard Canvas 2D
WebGLRenderer WebGLRenderer.ts GPU-accelerated
SVGRenderer SVGRenderer.ts Vector Export

RenderContext

The main facade for rendering.

Factory Functions:

Function Description
createCanvas2DContext(canvas) Canvas 2D Renderer
createWebGLContext(canvas) WebGL Renderer
createSVGContext(width, height) SVG Renderer
createAutoContext(canvas) Auto-Detect

Switch Renderer:

const ctx = createAutoContext(canvas);
ctx.setRenderer('webgl');  // or 'canvas2d', 'svg'

IRenderer Interface

All renderers implement this interface.

Primitive Methods:

Method Description
drawCircle(x, y, r, style) Circle
drawRect(x, y, w, h, style) Rectangle
drawLine(x1, y1, x2, y2, style) Line
drawPolygon(points, style) Polygon
drawPath(commands, style) SVG-like Path
drawEllipse(x, y, rx, ry, style) Ellipse

State Methods:

Method Description
save() State to Stack
restore() State from Stack
translate(x, y) Translate
rotate(angle) Rotate (Radians)
scale(sx, sy) Scale
setTransform(matrix) Set Matrix

Utility Methods:

Method Description
clear(color?) Clear Canvas
resize(w, h) Change Size
getImageData() Read Pixels

PrimitiveStyle

Styling for all primitives.

Property Type Description
fill RenderColor Fill Color
stroke RenderColor Stroke Color
strokeWidth number Stroke Width
lineCap 'butt' | 'round' | 'square' Line Ends
lineJoin 'miter' | 'round' | 'bevel' Line Corners
opacity number Transparency (0-1)

RenderColor

Colors can be specified in various formats:

type RenderColor = 
  | string              // '#FF0000', 'red', 'rgb(255,0,0)'
  | { r, g, b, a? }     // Object
  | [r, g, b, a?]       // Array

Utility Functions:

Function Description
parseColor(input) To {r,g,b,a}
colorToHex(color) To #RRGGBB
colorToRgba(color) To rgba(...)

Canvas2DRenderer

Standard renderer for most use cases.

Advantages:

  • Broadest browser support
  • Simple debugging
  • Good performance for 2D

Limitations:

  • No GPU acceleration for complex effects
  • Slower with many particles

WebGLRenderer

GPU-accelerated rendering.

Advantages:

  • High performance with many objects
  • 60fps even with thousands of particles
  • Shader effects possible

Limitations:

  • More complex implementation
  • Not all primitives equally efficient

SVGRenderer

For vector export (e.g., pen plotter).

Advantages:

  • Lossless scaling
  • Perfect for plotter export
  • Layer support

Specifics:

  • No realtime rendering
  • Collects primitives for export
  • Layers via svgLayerName

Export:

const svgCtx = createSVGContext(1000, 1000);
// ... draw ...
const svgString = svgCtx.toSVGString();

Transform2D

Transformation matrix.

interface Transform2D {
  a: number;  // scale x
  b: number;  // skew y
  c: number;  // skew x
  d: number;  // scale y
  e: number;  // translate x
  f: number;  // translate y
}

Helpers:

Function Description
identityTransform() Identity Matrix
createTransform(opts) From translate/rotate/scale

Brush → Style Conversion

import { brushToStyle } from '@carstennichte/cc-toolbox';

const style = brushToStyle(brush);
// → { fill, stroke, strokeWidth, ... }