pcbjam/features/webgl/0003-webgl-gal-implementation-plan.md
Viktor Vaczi a4f444fea8 feat(webgl): Add WebGL GAL test infrastructure (Phase 1)
Add complete test infrastructure for WebGL GAL visual regression testing:

- scripts/test-gal-regression.sh: Master script that builds both backends,
  runs tests, and performs two-level comparison (native vs baseline,
  webgl vs native)
- scripts/build-gal-webgl-test.sh: WASM build using Makefile with em++
- tests/gal-regression/wasm/: WebGL test harness (stub WEBGL_GAL)
- tests/e2e/gal-webgl.spec.ts: Playwright test for screenshot capture

Fix Homebrew Emscripten environment in scripts/common/env.sh:
- Set EMSDK_PYTHON for Python 3.10+ (em++ reads this, not $PYTHON)
- Add bundled LLVM to PATH (Emscripten needs its clang with WASM backend)

Verified: Native vs Baseline passes (28/28), WebGL generates blank
screenshots as expected (WEBGL_GAL implementation is Phase 2).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 16:11:31 +01:00

8.1 KiB

WebGL GAL Port - Master Plan

Goal

Port KiCad's GAL (Graphics Abstraction Layer) from OpenGL to WebGL to enable full KiCad functionality in the browser. Use the existing 28-scenario test suite to verify visual parity between native OpenGL and WebGL implementations.

Current State

  • GAL Test Harness: 28 scenarios covering 100% of GAL API (~70 methods)
  • Native Test: C++ binary using real OPENGL_GAL, outputs PNGs to tests/gal-regression/baseline/
  • KiCad WASM: Already runs in browser but crashes on OpenGL compatibility issues
  • OpenGL GAL: ~9,500 lines of C++/GLSL using mix of legacy GL and modern VBOs

Key Decisions

  • Approach: Copy and modify existing OpenGL GAL code
  • Scope: Full feature parity (all 28 scenarios)
  • Location: Develop in tests/gal-regression/ first, move to KiCad later

Architecture

Two-backend test architecture:

┌─────────────────────────────────────────────────────────────┐
│                   SAME 28 SCENARIO FILES                     │
│            (scenarios/*.cpp - pure GAL API calls)            │
└─────────────────────────────────────────────────────────────┘
                              │
              ┌───────────────┴───────────────┐
              ▼                               ▼
┌─────────────────────────────┐     ┌─────────────────────────┐
│   NATIVE TEST HARNESS       │     │   WEBGL TEST HARNESS    │
│   (gal_native_test.cpp)     │     │   (gal_webgl_test.cpp)  │
│                             │     │                         │
│   Uses: OPENGL_GAL          │     │   Uses: WEBGL_GAL       │
│   Runs: macOS native        │     │   Runs: Browser/WASM    │
└─────────────────────────────┘     └─────────────────────────┘
              │                               │
              ▼                               ▼
┌─────────────────────────────┐     ┌─────────────────────────┐
│  output/native/gal-*.png    │     │  output/webgl/gal-*.png │
└─────────────────────────────┘     └─────────────────────────┘

Master Test Script: scripts/test-gal-regression.sh

This is the only script we run. Single command to build, test, and compare everything:

#!/bin/bash
# Single script to build, run, and compare both backends

# 1. BUILD BOTH
scripts/build-gal-native-test.sh
scripts/build-gal-webgl-test.sh

# 2. RUN BOTH TESTS
./tests/gal-regression/native/build/gal_native_test --output tests/gal-regression/output/native/
npx playwright test gal-webgl.spec.ts  # outputs to tests/gal-regression/output/webgl/

# 3. COMPARE (two-level)
compare_screenshots output/native/ baseline/      # Catch native regressions
compare_screenshots output/webgl/  output/native/ # Verify WebGL matches native

# 4. REPORT
# Exit 0 if all match, exit 1 if any differ

Two-level comparison:

  1. native vs baseline → Catches if native code regressed
  2. webgl vs native → Verifies WebGL implementation matches

Output structure:

tests/gal-regression/
├── baseline/           # Committed reference screenshots
├── output/
│   ├── native/         # Fresh native run
│   └── webgl/          # WebGL run via Playwright

Phases

Phase 1: Master Test Script & WebGL Harness Infrastructure

Create the unified test script and WASM-based test harness.

Deliverables:

  • scripts/test-gal-regression.sh - Master build/test/compare script
  • tests/gal-regression/wasm/ directory structure
  • tests/gal-regression/wasm/Makefile - Emscripten build (use Makefile, not CMake)
  • tests/gal-regression/wasm/gal_webgl_test.cpp - WASM entry point
  • tests/gal-regression/wasm/gal_webgl_test.html - Test page with canvas
  • scripts/build-gal-webgl-test.sh - WASM build script (uses Makefile)
  • tests/e2e/gal-webgl.spec.ts - Playwright spec for screenshots

Build Approach:

  • Use a Makefile with direct em++ calls (like tests/apps/Makefile.wasm)
  • Avoid emcmake cmake which requires Python 3.10+ (system has 3.9.6)
  • Follow the same pattern as build-wasm-test.sh

Verification: Master script builds both, runs native successfully, WebGL loads empty page.

Phase 2: WEBGL_GAL Implementation

Copy and modify OpenGL GAL to create pure WebGL implementation.

Approach: Start with kicad/common/gal/opengl/opengl_gal.cpp, then:

  1. Replace legacy immediate mode (glBegin/glEnd) with VBO-based rendering
  2. Replace GL matrix stack with glm matrices (already used internally)
  3. Adapt shaders for WebGL 2.0 / GLSL ES 3.0
  4. Handle WebGL-specific limitations

Key Files:

  • tests/gal-regression/wasm/webgl_gal.h - Class declaration
  • tests/gal-regression/wasm/webgl_gal.cpp - Main implementation
  • tests/gal-regression/wasm/webgl_shaders.cpp - Shader sources
  • Adapt vertex_manager, gpu_manager as needed

Verification: Scenario 0 (basic-lines) renders, master script compares successfully.

Phase 3: Complete API Coverage

Implement all GAL methods to pass all 28 scenarios.

Method Groups (incremental):

  1. Basic drawing: DrawLine, DrawSegment, DrawCircle, DrawArc
  2. Shapes: DrawRectangle, DrawPolygon, DrawPolyline
  3. Advanced: DrawBezier, DrawBezierArc, DrawArcSegment, DrawSegmentChain
  4. State: Colors, transforms, depth testing, render targets
  5. Groups: BeginGroup, EndGroup, DrawGroup, ChangeGroupColor/Depth
  6. Text: DrawGlyph, DrawGlyphs, BitmapText
  7. Special: DrawGrid, DrawCursor, DrawBitmap

Verification: Run master script after each group - all implemented scenarios match.

Phase 4: Integration with KiCad

Move WEBGL_GAL into KiCad source and enable for browser builds.

Deliverables:

  • Move webgl_gal.* to kicad/common/gal/webgl/
  • CMake integration for Emscripten builds
  • Runtime GAL selection based on platform
  • KiCad WASM builds successfully with WEBGL_GAL

Verification: KiCad loads in browser, opens PCB, renders correctly.

File Structure

tests/gal-regression/
├── baseline/              # Committed reference (from native)
├── output/
│   ├── native/            # Fresh native run
│   └── webgl/             # WebGL run via Playwright
├── native/                # Native C++ harness (existing)
│   ├── gal_native_test.cpp
│   └── ...
├── wasm/                  # WASM harness (new)
│   ├── Makefile           # Use Makefile, not CMake (avoids Python issues)
│   ├── gal_webgl_test.cpp
│   ├── gal_webgl_test.html
│   ├── webgl_gal.h
│   ├── webgl_gal.cpp
│   └── webgl_shaders.cpp
└── scenarios/             # Shared scenarios (existing)

scripts/
├── build-gal-native-test.sh   # existing
├── build-gal-webgl-test.sh    # new
└── test-gal-regression.sh     # new - MASTER SCRIPT

Critical Files to Modify/Create

New files:

  • scripts/test-gal-regression.sh - Master test script
  • scripts/build-gal-webgl-test.sh - WASM build
  • tests/gal-regression/wasm/* - All WASM harness files
  • tests/e2e/gal-webgl.spec.ts - Playwright test

Reference files (copy from):

  • kicad/common/gal/opengl/opengl_gal.cpp (3098 lines)
  • kicad/common/gal/opengl/opengl_gal.h (614 lines)
  • kicad/common/gal/opengl/shader.cpp (298 lines)
  • kicad/common/gal/opengl/vertex_manager.cpp (318 lines)
  • kicad/common/gal/opengl/gpu_manager.cpp (340 lines)