pcbjam/docs/features/archive/webgl/0002-gal-native-test-architecture.md
2026-06-05 12:16:13 +02:00

289 lines
10 KiB
Markdown

# GAL Native Test Harness Architecture
> **ARCHIVED / HISTORICAL** — the WebGL-GAL backend described here was implemented (see `kicad/common/gal/webgl/`). Live test docs: [`tests/gal-regression/README.md`](../../../../tests/gal-regression/README.md). Kept for design rationale.
## Overview
The GAL native test harness is a standalone macOS application that compiles KiCad's actual `OPENGL_GAL` rendering engine against system wxWidgets. It generates baseline PNG screenshots for visual regression testing of WebGL rendering in the WASM build.
**Purpose**: Compare native OpenGL rendering (ground truth) against WebGL rendering in the browser to detect visual regressions.
---
## Architecture
```
┌─────────────────────────────────────────────────────────────────────┐
│ GAL Native Test │
├─────────────────────────────────────────────────────────────────────┤
│ gal_native_test.cpp (wxApp + wxFrame) │
│ └─ Creates OPENGL_GAL on wxGLCanvas │
│ └─ Runs 11 test scenarios │
│ └─ Captures FBO → PNG for each │
├─────────────────────────────────────────────────────────────────────┤
│ kicad_stubs.cpp │ gal_test_accessor.cpp │
│ • PGM_BASE singleton │ • Template accessor for │
│ • ADVANCED_CFG │ private OPENGL_GAL members │
│ • KIFONT stubs │ • FBO reading │
│ • Observable stubs │ │
├─────────────────────────────────────────────────────────────────────┤
│ KiCad OPENGL_GAL (from submodule) │
│ kicad/common/gal/opengl/*.cpp (18 source files) │
├─────────────────────────────────────────────────────────────────────┤
│ System Dependencies │
│ wxWidgets (via homebrew) │ GLEW │ OpenGL │
└─────────────────────────────────────────────────────────────────────┘
```
---
## GAL Code Source
The test harness compiles **KiCad's actual OPENGL_GAL** from the kicad submodule:
### Source Files (`/kicad/common/gal/opengl/`)
| File | Purpose |
|------|---------|
| `opengl_gal.cpp` | Main GAL implementation - drawing primitives |
| `opengl_compositor.cpp` | FBO management, layer compositing |
| `antialiasing.cpp` | SMAA antialiasing implementation |
| `vertex_manager.cpp` | Vertex buffer accumulation |
| `vertex_item.cpp` | Individual vertex items |
| `vertex_container.cpp` | Vertex storage base class |
| `cached_container.cpp` | Cached geometry container |
| `cached_container_gpu.cpp` | GPU-resident cached geometry |
| `cached_container_ram.cpp` | RAM-backed cached geometry |
| `noncached_container.cpp` | Per-frame geometry |
| `gpu_manager.cpp` | GPU buffer management |
| `shader.cpp` | GLSL shader compilation/linking |
| `utils.cpp` | OpenGL utility functions |
| `gl_resources.cpp` | OpenGL resource management |
| `hidpi_gl_canvas.cpp` | HiDPI-aware GL canvas |
| `graphics_abstraction_layer.cpp` | GAL base class |
| `color4d.cpp` | Color handling |
| `gal_display_options.cpp` | Display options |
### Build Configuration (`CMakeLists.txt`)
```cmake
# Links against system wxWidgets
find_program(WX_CONFIG_EXECUTABLE wx-config
HINTS /opt/homebrew/bin /usr/local/bin)
# Includes KiCad headers from submodule
target_include_directories(gal_native_test PRIVATE
${KICAD_SOURCE}/include
${KICAD_SOURCE}/include/gal
${KICAD_SOURCE}/include/gal/opengl
${KICAD_SOURCE}/libs/kimath/include
${KICAD_SOURCE}/libs/core/include
)
```
---
## Generated Files
### Location
`/tests/gal-regression/native/generated/`
### Purpose
GLSL shaders must be embedded as strings at runtime. `generate_shaders.py` converts shader source files to C++ hex arrays.
### Generator Script
`generate_shaders.py` reads from `/kicad/common/gal/shaders/` and creates:
| Shader | Generated Files | Purpose |
|--------|-----------------|---------|
| `kicad.frag` | `glsl_kicad_frag.cpp/h` | Fragment shader (coloring) |
| `kicad.vert` | `glsl_kicad_vert.cpp/h` | Vertex shader (transforms) |
| `smaa_base.glsl` | `glsl_smaa_base.cpp/h` | SMAA common structures |
| `smaa_pass_1_*.glsl` | 3 file pairs | SMAA edge detection |
| `smaa_pass_2_*.glsl` | 2 file pairs | SMAA blending weights |
| `smaa_pass_3_*.glsl` | 2 file pairs | SMAA neighborhood blending |
### Generated Code Structure
```cpp
// generated/glsl_kicad_frag.cpp
namespace KIGFX {
namespace BUILTIN_SHADERS {
static unsigned char glsl_kicad_frag_bytes[] = { 0x2f, 0x2a, ... };
std::string glsl_kicad_frag = std::string(
reinterpret_cast<char const*>(glsl_kicad_frag_bytes), 4233);
}}
```
---
## Test Harness Components
### `gal_native_test.cpp`
Main test driver with wxApp/wxFrame:
1. Creates `OPENGL_GAL` on a wxGLCanvas
2. Configures coordinate system for 1:1 world-to-screen mapping
3. Iterates through test scenarios
4. Captures FBO contents as PNG screenshots
Key configuration:
```cpp
// Critical: Set 1:1 world-to-screen mapping
// GAL default is for PCB nanometers (3.937e-8), which would
// compress pixel coordinates (0-800) to ~0.003 screen pixels
m_gal->SetWorldUnitLength(1.0 / ADVANCED_CFG::GetCfg().m_ScreenDPI);
```
### `kicad_stubs.cpp`
Minimal implementations for KiCad symbols not included in GAL:
- `PGM_BASE` singleton with `GL_CONTEXT_MANAGER`
- `ADVANCED_CFG` with `m_ScreenDPI = 91`
- `KIFONT` stubs (returns nullptr for fonts)
- `OBSERVABLE_BASE` observer pattern stubs
- UI dialog stubs (`DisplayError`, etc.)
### `gal_test_accessor.cpp`
Uses C++ template technique to access private members:
```cpp
// Access private OPENGL_GAL members without modifying headers
template<typename T, T> struct steal_impl;
template<typename T, T ptr>
struct steal_impl {
friend T get(steal_impl*) { return ptr; }
};
```
Provides:
- `GetCompositorMainBufferTexture()` - For screenshot reading
- `GetCompositorMainFBO()` - FBO ID access
- `ReadCompositorFBOPixels()` - Direct pixel readback
### `gal_test_scenarios.cpp`
11 rendering test scenarios using the GAL API.
---
## Test Coverage Analysis
### Current Scenarios
| # | Name | GAL Features Tested |
|---|------|-------------------|
| 0 | basic-lines | `DrawLine`, `SetStrokeColor`, `SetLineWidth` |
| 1 | line-widths | `DrawLine` with varying widths (0.5 to 12.0) |
| 2 | circles | `DrawCircle` (filled and stroked) |
| 3 | arcs | `DrawArc` |
| 4 | rectangles | `DrawRectangle` (filled and stroked) |
| 5 | polygons | `DrawPolygon`, `DrawPolyline` |
| 6 | alpha-blending | `SetFillColor` with alpha transparency |
| 7 | transforms | `Save`, `Restore`, `Rotate`, `Translate`, `Scale` |
| 8 | grid-cursor | `DrawGrid`, `DrawCursor` |
| 9 | segments | `DrawSegment` |
| 10 | complex-scene | `SetLayerDepth` (z-ordering) |
### Coverage: ~19% of GAL API (14 of 73 methods)
### Missing Coverage (Priority Order)
**High Priority** (Critical for KiCad functionality):
1. `DrawBitmap()` - Image/icon rendering
2. `DrawCurve()` - Bezier curves
3. `DrawGlyph()` / `BitmapText()` - Text rendering
4. `DrawSegmentChain()` - Complex paths
5. `DrawArcSegment()` - Filled arc segments
**Medium Priority** (Performance-critical features):
6. Group methods: `BeginGroup()`, `EndGroup()`, `DrawGroup()`, `ClearCache()`
7. Render targets: `SetTarget()`, offscreen rendering
8. `SetNegativeDrawMode()` - Gerber-style rendering
**Low Priority** (Already implicit or not relevant to WASM):
9. `EnableDepthTest()` - Implicit in complex-scene
10. Context locking - Single-threaded in WASM
---
## Code Quality Assessment
### Strengths
1. **Clean separation** - Stubs, accessor, scenarios in separate files
2. **Minimal stubs** - Only implements what's needed
3. **Safe accessor** - Template technique avoids `#define private public`
4. **Standard shader embedding** - Common practice for GL applications
### Architecture Decisions
| Decision | Rationale |
|----------|-----------|
| Compile actual KiCad GAL | Ground truth for comparison |
| System wxWidgets | Native OpenGL context |
| FBO reading | Clean screenshots without window capture |
| Hex shader embedding | Runtime shader loading like KiCad |
---
## Running the Test
### Build
```bash
./scripts/build-gal-native-test.sh
```
### Execute
```bash
./tests/gal-regression/native/build/gal_native_test --output ./baselines
```
### Options
| Flag | Description |
|------|-------------|
| `--output <dir>` | Output directory for PNG files |
| `--width <w>` | Canvas width (default: 800) |
| `--height <h>` | Canvas height (default: 600) |
| `--show` | Show window instead of headless |
---
## Next Steps
### Immediate: Expand Test Coverage
1. Add `DrawBitmap` scenario with simple test image
2. Add `DrawCurve` (Bezier) scenario
3. Add text rendering scenario (requires KIFONT implementation)
4. Add `DrawSegmentChain` scenario
5. Add `DrawArcSegment` scenario
### Future: WebGL Comparison
1. Run same scenarios in WASM build
2. Compare native vs WebGL screenshots
3. Automate regression detection
---
## File Reference
| File | Location |
|------|----------|
| Test driver | `tests/gal-regression/native/gal_native_test.cpp` |
| Stubs | `tests/gal-regression/native/kicad_stubs.cpp` |
| Private accessor | `tests/gal-regression/native/gal_test_accessor.cpp` |
| Test scenarios | `tests/gal-regression/scenarios/gal_test_scenarios.cpp` |
| CMake config | `tests/gal-regression/native/CMakeLists.txt` |
| Shader generator | `tests/gal-regression/native/generate_shaders.py` |
| Build script | `scripts/build-gal-native-test.sh` |