feat(webgl): Add GAL native test harness for visual regression testing
Add a standalone test application that compiles KiCad's actual OPENGL_GAL against system wxWidgets to generate baseline screenshots for comparing native OpenGL rendering against WebGL rendering in the browser. Architecture: - Compiles 18 KiCad GAL source files from the kicad submodule - Uses template-based private member accessor (safer than #define private public) - Generates shader C++ files from GLSL (10 shader pairs for SMAA AA) - Minimal stubs for KiCad dependencies (PGM_BASE, ADVANCED_CFG, etc.) Test coverage: - 11 scenarios testing ~19% of GAL API - basic-lines, line-widths, circles, arcs, rectangles, polygons - alpha-blending, transforms, grid-cursor, segments, complex-scene Key insights documented: - GAL uses world-to-screen transformation requiring 1:1 pixel mapping - FBO reading required for clean screenshots on macOS - Layer depth needed for proper z-ordering in complex scenes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
287
features/webgl/0002-gal-native-test-architecture.md
Normal file
|
|
@ -0,0 +1,287 @@
|
|||
# GAL Native Test Harness Architecture
|
||||
|
||||
## 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` |
|
||||
71
scripts/build-gal-native-test.sh
Executable file
|
|
@ -0,0 +1,71 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# Build script for the native GAL test harness
|
||||
#
|
||||
# This builds a standalone test application that uses KiCad's actual OPENGL_GAL
|
||||
# to render test scenarios and generate baseline screenshots.
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
TEST_DIR="$PROJECT_ROOT/tests/gal-regression/native"
|
||||
BUILD_DIR="$TEST_DIR/build"
|
||||
LOG_FILE="$PROJECT_ROOT/tests/logs/gal-native-build.log"
|
||||
|
||||
# Create log directory
|
||||
mkdir -p "$(dirname "$LOG_FILE")"
|
||||
|
||||
echo "Building GAL Native Test..."
|
||||
echo " Test dir: $TEST_DIR"
|
||||
echo " Build dir: $BUILD_DIR"
|
||||
echo " Log file: $LOG_FILE"
|
||||
|
||||
# Create build directory
|
||||
mkdir -p "$BUILD_DIR"
|
||||
cd "$BUILD_DIR"
|
||||
|
||||
# Generate shader headers if needed
|
||||
if [ ! -d "$TEST_DIR/generated" ] || [ -z "$(ls -A $TEST_DIR/generated 2>/dev/null)" ]; then
|
||||
echo "Generating shader headers..."
|
||||
python3 "$TEST_DIR/generate_shaders.py" >> "$LOG_FILE" 2>&1
|
||||
fi
|
||||
|
||||
# Configure
|
||||
echo "Running CMake..."
|
||||
cmake .. >> "$LOG_FILE" 2>&1
|
||||
CMAKE_STATUS=$?
|
||||
|
||||
if [ $CMAKE_STATUS -ne 0 ]; then
|
||||
echo "CMake configuration failed! Check $LOG_FILE for details."
|
||||
echo ""
|
||||
echo "Last 50 lines of log:"
|
||||
tail -50 "$LOG_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Build
|
||||
echo "Building..."
|
||||
make -j$(sysctl -n hw.ncpu 2>/dev/null || echo 4) >> "$LOG_FILE" 2>&1
|
||||
BUILD_STATUS=$?
|
||||
|
||||
if [ $BUILD_STATUS -ne 0 ]; then
|
||||
echo "Build failed! Check $LOG_FILE for details."
|
||||
echo ""
|
||||
echo "Last 100 lines of log:"
|
||||
tail -100 "$LOG_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Build successful!"
|
||||
echo "Executable: $BUILD_DIR/gal_native_test"
|
||||
echo ""
|
||||
echo "To run:"
|
||||
echo " $BUILD_DIR/gal_native_test"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " --output <dir> Output directory for baseline PNGs"
|
||||
echo " --width <w> Canvas width (default: 800)"
|
||||
echo " --height <h> Canvas height (default: 600)"
|
||||
echo " --show Show window (default: headless)"
|
||||
62
scripts/build-wxwidgets-native.sh
Executable file
|
|
@ -0,0 +1,62 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# Build wxWidgets for native macOS (not WASM)
|
||||
# This is used for the native GAL test harness
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
WX_SRC="$PROJECT_ROOT/wxwidgets"
|
||||
BUILD_DIR="$PROJECT_ROOT/build-native/wxwidgets"
|
||||
INSTALL_DIR="$PROJECT_ROOT/build-native/wxwidgets-install"
|
||||
LOG_FILE="$PROJECT_ROOT/tests/logs/wxwidgets-native-build.log"
|
||||
|
||||
mkdir -p "$(dirname "$LOG_FILE")"
|
||||
|
||||
echo "Building wxWidgets for native macOS..."
|
||||
echo " Source: $WX_SRC"
|
||||
echo " Build: $BUILD_DIR"
|
||||
echo " Install: $INSTALL_DIR"
|
||||
echo " Log: $LOG_FILE"
|
||||
|
||||
# Create build directory
|
||||
mkdir -p "$BUILD_DIR"
|
||||
cd "$BUILD_DIR"
|
||||
|
||||
# Set up paths for homebrew libraries
|
||||
export PKG_CONFIG_PATH="/opt/homebrew/lib/pkgconfig:/opt/homebrew/opt/libpng/lib/pkgconfig:$PKG_CONFIG_PATH"
|
||||
export CPPFLAGS="-I/opt/homebrew/include $CPPFLAGS"
|
||||
export LDFLAGS="-L/opt/homebrew/lib $LDFLAGS"
|
||||
|
||||
# Configure wxWidgets for native macOS with OpenGL support
|
||||
echo "Configuring..."
|
||||
"$WX_SRC/configure" \
|
||||
--prefix="$INSTALL_DIR" \
|
||||
--disable-shared \
|
||||
--with-opengl \
|
||||
--with-osx_cocoa \
|
||||
--disable-webview \
|
||||
--disable-mediactrl \
|
||||
--with-libpng=sys \
|
||||
--with-libjpeg=sys \
|
||||
--with-libtiff=sys \
|
||||
--with-zlib=sys \
|
||||
>> "$LOG_FILE" 2>&1
|
||||
|
||||
# Build
|
||||
echo "Building (this may take a while)..."
|
||||
make -j$(sysctl -n hw.ncpu 2>/dev/null || echo 4) >> "$LOG_FILE" 2>&1
|
||||
|
||||
# Install
|
||||
echo "Installing..."
|
||||
make install >> "$LOG_FILE" 2>&1
|
||||
|
||||
echo ""
|
||||
echo "wxWidgets native build complete!"
|
||||
echo "wx-config: $INSTALL_DIR/bin/wx-config"
|
||||
echo ""
|
||||
echo "To use in CMake:"
|
||||
echo " set(wxWidgets_CONFIG_EXECUTABLE $INSTALL_DIR/bin/wx-config)"
|
||||
echo " find_package(wxWidgets REQUIRED COMPONENTS core base gl)"
|
||||
BIN
tests/gal-regression/baseline/gal-alpha-blending.png
Normal file
|
After Width: | Height: | Size: 76 KiB |
BIN
tests/gal-regression/baseline/gal-arcs.png
Normal file
|
After Width: | Height: | Size: 79 KiB |
BIN
tests/gal-regression/baseline/gal-basic-lines.png
Normal file
|
After Width: | Height: | Size: 81 KiB |
BIN
tests/gal-regression/baseline/gal-circles.png
Normal file
|
After Width: | Height: | Size: 91 KiB |
BIN
tests/gal-regression/baseline/gal-complex-scene.png
Normal file
|
After Width: | Height: | Size: 79 KiB |
BIN
tests/gal-regression/baseline/gal-grid-cursor.png
Normal file
|
After Width: | Height: | Size: 76 KiB |
BIN
tests/gal-regression/baseline/gal-line-widths.png
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
tests/gal-regression/baseline/gal-polygons.png
Normal file
|
After Width: | Height: | Size: 78 KiB |
BIN
tests/gal-regression/baseline/gal-rectangles.png
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
tests/gal-regression/baseline/gal-segments.png
Normal file
|
After Width: | Height: | Size: 77 KiB |
BIN
tests/gal-regression/baseline/gal-transforms.png
Normal file
|
After Width: | Height: | Size: 78 KiB |
120
tests/gal-regression/native/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
cmake_minimum_required(VERSION 3.16)
|
||||
project(gal_native_test)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# Suppress deprecation warnings on macOS
|
||||
if(APPLE)
|
||||
add_compile_definitions(GL_SILENCE_DEPRECATION)
|
||||
endif()
|
||||
|
||||
# Find wxWidgets (system installation)
|
||||
set(wxWidgets_CONFIG_EXECUTABLE "/opt/homebrew/bin/wx-config")
|
||||
find_package(wxWidgets REQUIRED COMPONENTS core base gl)
|
||||
include(${wxWidgets_USE_FILE})
|
||||
|
||||
# Find other required packages
|
||||
find_package(OpenGL REQUIRED)
|
||||
find_package(GLEW REQUIRED)
|
||||
|
||||
# KiCad source root
|
||||
set(KICAD_ROOT ${CMAKE_SOURCE_DIR}/../../../kicad)
|
||||
|
||||
# Generated shader files
|
||||
set(SHADER_SOURCES
|
||||
${CMAKE_SOURCE_DIR}/generated/glsl_kicad_frag.cpp
|
||||
${CMAKE_SOURCE_DIR}/generated/glsl_kicad_vert.cpp
|
||||
${CMAKE_SOURCE_DIR}/generated/glsl_smaa_base.cpp
|
||||
${CMAKE_SOURCE_DIR}/generated/glsl_smaa_pass_1_frag_color.cpp
|
||||
${CMAKE_SOURCE_DIR}/generated/glsl_smaa_pass_1_frag_luma.cpp
|
||||
${CMAKE_SOURCE_DIR}/generated/glsl_smaa_pass_1_vert.cpp
|
||||
${CMAKE_SOURCE_DIR}/generated/glsl_smaa_pass_2_frag.cpp
|
||||
${CMAKE_SOURCE_DIR}/generated/glsl_smaa_pass_2_vert.cpp
|
||||
${CMAKE_SOURCE_DIR}/generated/glsl_smaa_pass_3_frag.cpp
|
||||
${CMAKE_SOURCE_DIR}/generated/glsl_smaa_pass_3_vert.cpp
|
||||
)
|
||||
|
||||
# KiCad OpenGL GAL sources
|
||||
set(KICAD_GAL_SOURCES
|
||||
${KICAD_ROOT}/common/gal/opengl/opengl_gal.cpp
|
||||
${KICAD_ROOT}/common/gal/opengl/gl_context_mgr.cpp
|
||||
${KICAD_ROOT}/common/gal/opengl/shader.cpp
|
||||
${KICAD_ROOT}/common/gal/opengl/vertex_manager.cpp
|
||||
${KICAD_ROOT}/common/gal/opengl/vertex_item.cpp
|
||||
${KICAD_ROOT}/common/gal/opengl/vertex_container.cpp
|
||||
${KICAD_ROOT}/common/gal/opengl/cached_container.cpp
|
||||
${KICAD_ROOT}/common/gal/opengl/cached_container_gpu.cpp
|
||||
${KICAD_ROOT}/common/gal/opengl/cached_container_ram.cpp
|
||||
${KICAD_ROOT}/common/gal/opengl/noncached_container.cpp
|
||||
${KICAD_ROOT}/common/gal/opengl/gpu_manager.cpp
|
||||
${KICAD_ROOT}/common/gal/opengl/opengl_compositor.cpp
|
||||
${KICAD_ROOT}/common/gal/opengl/antialiasing.cpp
|
||||
${KICAD_ROOT}/common/gal/opengl/utils.cpp
|
||||
${KICAD_ROOT}/common/gal/opengl/gl_resources.cpp
|
||||
${KICAD_ROOT}/common/gal/graphics_abstraction_layer.cpp
|
||||
${KICAD_ROOT}/common/gal/color4d.cpp
|
||||
${KICAD_ROOT}/common/gal/gal_display_options.cpp
|
||||
${KICAD_ROOT}/common/gal/hidpi_gl_canvas.cpp
|
||||
)
|
||||
|
||||
# Our stub and test files
|
||||
set(STUB_SOURCES
|
||||
${CMAKE_SOURCE_DIR}/kicad_stubs.cpp
|
||||
${CMAKE_SOURCE_DIR}/gal_test_accessor.cpp
|
||||
${CMAKE_SOURCE_DIR}/../scenarios/gal_test_scenarios.cpp
|
||||
)
|
||||
|
||||
# Main executable
|
||||
add_executable(gal_native_test
|
||||
gal_native_test.cpp
|
||||
${STUB_SOURCES}
|
||||
${KICAD_GAL_SOURCES}
|
||||
${SHADER_SOURCES}
|
||||
)
|
||||
|
||||
# Include directories - our stubs first, then KiCad, then wxWidgets
|
||||
target_include_directories(gal_native_test PRIVATE
|
||||
# Our stubs (for KiCad-specific types not in wxWidgets)
|
||||
${CMAKE_SOURCE_DIR}
|
||||
${CMAKE_SOURCE_DIR}/../scenarios
|
||||
${CMAKE_SOURCE_DIR}/generated
|
||||
|
||||
# KiCad includes
|
||||
${KICAD_ROOT}/include
|
||||
${KICAD_ROOT}/include/gal
|
||||
${KICAD_ROOT}/include/gal/opengl
|
||||
${KICAD_ROOT}/common # For gal/opengl/antialiasing.h
|
||||
|
||||
# KiCad libs
|
||||
${KICAD_ROOT}/libs/kimath/include
|
||||
${KICAD_ROOT}/libs/kimath/include/math
|
||||
${KICAD_ROOT}/libs/kimath/include/geometry
|
||||
${KICAD_ROOT}/libs/core/include
|
||||
|
||||
# KiCad thirdparty
|
||||
${KICAD_ROOT}/thirdparty/thread-pool
|
||||
|
||||
# System includes
|
||||
${OPENGL_INCLUDE_DIR}
|
||||
${GLEW_INCLUDE_DIRS}
|
||||
|
||||
# Homebrew dependencies
|
||||
/opt/homebrew/include # nlohmann-json, clipper2
|
||||
)
|
||||
|
||||
# Link libraries
|
||||
target_link_libraries(gal_native_test
|
||||
${wxWidgets_LIBRARIES}
|
||||
OpenGL::GL
|
||||
GLEW::GLEW
|
||||
)
|
||||
|
||||
# Compile definitions
|
||||
target_compile_definitions(gal_native_test PRIVATE
|
||||
GAL_TEST_BUILD=1
|
||||
)
|
||||
|
||||
message(STATUS "KiCad root: ${KICAD_ROOT}")
|
||||
message(STATUS "wxWidgets libraries: ${wxWidgets_LIBRARIES}")
|
||||
message(STATUS "Building GAL native test with actual KiCad OPENGL_GAL sources")
|
||||
26
tests/gal-regression/native/config.h
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// Minimal config.h for native GAL test
|
||||
// Based on KiCad's generated config
|
||||
#ifndef KICAD_CONFIG_H
|
||||
#define KICAD_CONFIG_H
|
||||
|
||||
// Version info
|
||||
#define KICAD_MAJOR_VERSION 8
|
||||
#define KICAD_MINOR_VERSION 0
|
||||
#define KICAD_PATCH_VERSION 0
|
||||
|
||||
// Enable OpenGL
|
||||
#define KICAD_USE_OCC 0
|
||||
#define KICAD_USE_EGL 0
|
||||
|
||||
// Platform detection
|
||||
#ifdef __APPLE__
|
||||
#define KICAD_MACOS 1
|
||||
#endif
|
||||
|
||||
// Math
|
||||
#define KICAD_USE_STDROUND 1
|
||||
|
||||
// Ensure types are properly sized
|
||||
#include <cstdint>
|
||||
|
||||
#endif // KICAD_CONFIG_H
|
||||
312
tests/gal-regression/native/gal_native_test.cpp
Normal file
|
|
@ -0,0 +1,312 @@
|
|||
/**
|
||||
* Native GAL Test Application
|
||||
*
|
||||
* This app uses KiCad's actual OPENGL_GAL to render test scenarios
|
||||
* and captures baseline screenshots for visual regression testing.
|
||||
*
|
||||
* The test scenarios call GAL API methods (gal->DrawLine(), gal->DrawCircle(), etc.)
|
||||
* which are rendered by the real OPENGL_GAL implementation.
|
||||
*/
|
||||
|
||||
// Our stubs - includes wx/wx.h from system wxWidgets
|
||||
#include "kicad_stubs.h"
|
||||
|
||||
// KiCad GAL headers
|
||||
#include <gal/graphics_abstraction_layer.h>
|
||||
#include <gal/opengl/opengl_gal.h>
|
||||
|
||||
// stb_image_write for PNG output
|
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
#include "stb_image_write.h"
|
||||
|
||||
// Standard library
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
|
||||
// Test accessor for private GAL members
|
||||
#include "gal_test_accessor.h"
|
||||
|
||||
// Test scenarios
|
||||
#include "gal_test_scenarios.h"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
// Global config
|
||||
static std::string g_outputDir = "../baseline";
|
||||
static int g_width = 800;
|
||||
static int g_height = 600;
|
||||
static bool g_showWindow = false;
|
||||
|
||||
/**
|
||||
* Save a screenshot by reading directly from the compositor's FBO
|
||||
* This bypasses macOS framebuffer reading issues by reading the FBO directly
|
||||
*/
|
||||
bool SaveScreenshot(const std::string& path, KIGFX::OPENGL_GAL* gal, int width, int height) {
|
||||
glFinish();
|
||||
|
||||
// Try reading from FBO directly
|
||||
std::vector<uint8_t> pixels;
|
||||
int readWidth = 0, readHeight = 0;
|
||||
|
||||
GLuint fboId = GetCompositorMainFBO(gal);
|
||||
unsigned int bufferHandle = GetMainBufferHandle(gal);
|
||||
GLuint textureId = GetCompositorMainBufferTexture(gal);
|
||||
|
||||
std::cout << "FBO ID: " << fboId << ", Buffer handle: " << bufferHandle
|
||||
<< ", Texture ID: " << textureId << std::endl;
|
||||
|
||||
if (ReadCompositorFBOPixels(gal, pixels, &readWidth, &readHeight)) {
|
||||
std::cout << "Read from FBO: " << readWidth << "x" << readHeight << std::endl;
|
||||
} else {
|
||||
std::cerr << "Failed to read from FBO, falling back to texture read" << std::endl;
|
||||
// Fallback to texture read
|
||||
readWidth = width;
|
||||
readHeight = height;
|
||||
pixels.resize(readWidth * readHeight * 4);
|
||||
glBindTexture(GL_TEXTURE_2D, textureId);
|
||||
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
|
||||
}
|
||||
|
||||
// Check for GL errors
|
||||
GLenum err = glGetError();
|
||||
if (err != GL_NO_ERROR) {
|
||||
std::cerr << "GL error: 0x" << std::hex << err << std::dec << std::endl;
|
||||
}
|
||||
|
||||
// Debug: Check if we got any non-black pixels and show some sample values
|
||||
bool hasContent = false;
|
||||
int nonBlackCount = 0;
|
||||
for (size_t i = 0; i < pixels.size(); i += 4) {
|
||||
if (pixels[i] > 0 || pixels[i+1] > 0 || pixels[i+2] > 0) {
|
||||
hasContent = true;
|
||||
nonBlackCount++;
|
||||
if (nonBlackCount <= 3) {
|
||||
int pixelIdx = i / 4;
|
||||
int px = pixelIdx % readWidth;
|
||||
int py = pixelIdx / readWidth;
|
||||
std::cout << " Sample pixel at (" << px << "," << py << "): RGBA("
|
||||
<< (int)pixels[i] << "," << (int)pixels[i+1] << ","
|
||||
<< (int)pixels[i+2] << "," << (int)pixels[i+3] << ")" << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::cout << "Has non-black content: " << (hasContent ? "YES" : "NO");
|
||||
if (hasContent) std::cout << " (" << nonBlackCount << " non-black pixels)";
|
||||
std::cout << std::endl;
|
||||
|
||||
// Flip vertically (OpenGL has origin at bottom-left)
|
||||
stbi_flip_vertically_on_write(1);
|
||||
|
||||
int result = stbi_write_png(path.c_str(), readWidth, readHeight, 4, pixels.data(), readWidth * 4);
|
||||
|
||||
if (result) {
|
||||
std::cout << "Saved: " << path << std::endl;
|
||||
} else {
|
||||
std::cerr << "Failed to save: " << path << std::endl;
|
||||
}
|
||||
|
||||
return result != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test frame containing the OPENGL_GAL canvas
|
||||
*/
|
||||
class GALTestFrame : public wxFrame {
|
||||
public:
|
||||
GALTestFrame()
|
||||
: wxFrame(nullptr, wxID_ANY, "GAL Native Test", wxDefaultPosition, wxSize(g_width, g_height))
|
||||
{
|
||||
// Create display options
|
||||
KIGFX::GAL_DISPLAY_OPTIONS displayOptions;
|
||||
KIGFX::VC_SETTINGS vcSettings;
|
||||
|
||||
std::cout << "Creating OPENGL_GAL instance...\n";
|
||||
|
||||
try {
|
||||
m_gal = new KIGFX::OPENGL_GAL(
|
||||
vcSettings,
|
||||
displayOptions,
|
||||
this, // parent window
|
||||
nullptr, // mouse listener
|
||||
nullptr, // paint listener
|
||||
"GAL Native Test" // name
|
||||
);
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Failed to create OPENGL_GAL: " << e.what() << std::endl;
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "OPENGL_GAL created successfully\n";
|
||||
|
||||
// Set up sizer
|
||||
wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
|
||||
sizer->Add(m_gal, 1, wxEXPAND);
|
||||
SetSizer(sizer);
|
||||
|
||||
// Bind close event
|
||||
Bind(wxEVT_CLOSE_WINDOW, &GALTestFrame::OnClose, this);
|
||||
|
||||
// Run tests after the frame is shown
|
||||
CallAfter(&GALTestFrame::RunTests);
|
||||
}
|
||||
|
||||
~GALTestFrame() {
|
||||
// GAL will be deleted by wxWidgets when frame closes
|
||||
}
|
||||
|
||||
void OnClose(wxCloseEvent& event) {
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
void RunTests() {
|
||||
if (!m_gal) {
|
||||
wxTheApp->ExitMainLoop();
|
||||
return;
|
||||
}
|
||||
|
||||
// Create output directory
|
||||
fs::create_directories(g_outputDir);
|
||||
|
||||
// Set up the viewport - use ResizeScreen to properly initialize compositor
|
||||
m_gal->ResizeScreen(g_width, g_height);
|
||||
m_gal->SetClearColor(KIGFX::COLOR4D(0.1, 0.1, 0.15, 1.0));
|
||||
|
||||
// CRITICAL: Set worldUnitLength for 1:1 world-to-screen coordinate mapping
|
||||
// GAL default worldUnitLength is for PCB nanometers (3.937e-8), which would
|
||||
// compress our pixel-scale coordinates (0-800) to ~0.003 screen pixels total!
|
||||
// We need: worldScale = screenDPI * worldUnitLength * zoomFactor = 1.0
|
||||
// With screenDPI=91 and zoomFactor=1.0, worldUnitLength should be 1/91
|
||||
m_gal->SetWorldUnitLength(1.0 / ADVANCED_CFG::GetCfg().m_ScreenDPI);
|
||||
|
||||
// Set up coordinate transformation for 1:1 world-to-screen mapping
|
||||
// LookAtPoint should be at center, ZoomFactor of 1.0 gives 1:1 mapping
|
||||
m_gal->SetLookAtPoint(VECTOR2D(g_width / 2.0, g_height / 2.0));
|
||||
m_gal->SetZoomFactor(1.0);
|
||||
m_gal->ComputeWorldScreenMatrix();
|
||||
|
||||
// Get framebuffer size for screenshots
|
||||
wxSize clientSize = m_gal->GetClientSize();
|
||||
double scaleFactor = m_gal->GetContentScaleFactor();
|
||||
int fbWidth = (int)(clientSize.GetWidth() * scaleFactor);
|
||||
int fbHeight = (int)(clientSize.GetHeight() * scaleFactor);
|
||||
|
||||
std::cout << "Canvas size: " << g_width << "x" << g_height << "\n";
|
||||
std::cout << "Client size: " << clientSize.GetWidth() << "x" << clientSize.GetHeight() << "\n";
|
||||
std::cout << "Framebuffer size: " << fbWidth << "x" << fbHeight << "\n";
|
||||
std::cout << "Scale factor: " << scaleFactor << "\n\n";
|
||||
|
||||
// Get scenario count
|
||||
int scenarioCount = GALTest::GetScenarioCount();
|
||||
std::cout << "Running " << scenarioCount << " test scenarios...\n\n";
|
||||
|
||||
// Run each scenario
|
||||
int passed = 0;
|
||||
for (int i = 0; i < scenarioCount; i++) {
|
||||
const char* name = GALTest::GetScenarioName(i);
|
||||
std::cout << "Scenario " << i << ": " << name << "... ";
|
||||
|
||||
// Render using proper GAL sequence
|
||||
m_gal->LockContext(i);
|
||||
m_gal->BeginDrawing();
|
||||
|
||||
// Must call SetTarget after BeginDrawing to set m_currentManager
|
||||
m_gal->SetTarget(KIGFX::TARGET_NONCACHED);
|
||||
|
||||
// Clear all targets (FBOs) to prevent content accumulation between scenarios
|
||||
m_gal->ClearTarget(KIGFX::TARGET_NONCACHED);
|
||||
|
||||
// Clear the screen (window framebuffer)
|
||||
m_gal->ClearScreen();
|
||||
|
||||
// Render the scenario using GAL API
|
||||
GALTest::RenderScenario(m_gal, i, g_width, g_height);
|
||||
|
||||
// EndDrawing renders vertices to FBO, composites to screen, swaps buffers
|
||||
m_gal->EndDrawing();
|
||||
m_gal->UnlockContext(i);
|
||||
|
||||
// Save screenshot by reading directly from compositor's texture
|
||||
std::string filename = g_outputDir + "/gal-" + name + ".png";
|
||||
if (SaveScreenshot(filename, m_gal, fbWidth, fbHeight)) {
|
||||
std::cout << "OK\n";
|
||||
passed++;
|
||||
} else {
|
||||
std::cout << "FAILED\n";
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "\nResults: " << passed << "/" << scenarioCount << " scenarios saved\n";
|
||||
|
||||
m_passed = passed;
|
||||
m_total = scenarioCount;
|
||||
|
||||
// Close if not showing window
|
||||
if (!g_showWindow) {
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
int GetPassed() const { return m_passed; }
|
||||
int GetTotal() const { return m_total; }
|
||||
|
||||
private:
|
||||
KIGFX::OPENGL_GAL* m_gal = nullptr;
|
||||
int m_passed = 0;
|
||||
int m_total = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Test application
|
||||
*/
|
||||
class GALTestApp : public wxApp {
|
||||
public:
|
||||
bool OnInit() override {
|
||||
// Parse command line
|
||||
for (int i = 1; i < argc; i++) {
|
||||
wxString arg = argv[i];
|
||||
if (arg == "--output" && i + 1 < argc) {
|
||||
g_outputDir = argv[++i].ToStdString();
|
||||
} else if (arg == "--width" && i + 1 < argc) {
|
||||
g_width = wxAtoi(argv[++i]);
|
||||
} else if (arg == "--height" && i + 1 < argc) {
|
||||
g_height = wxAtoi(argv[++i]);
|
||||
} else if (arg == "--show") {
|
||||
g_showWindow = true;
|
||||
} else if (arg == "--help") {
|
||||
std::cout << "Usage: gal_native_test [options]\n";
|
||||
std::cout << " --output <dir> Output directory for baseline PNGs\n";
|
||||
std::cout << " --width <w> Canvas width (default: 800)\n";
|
||||
std::cout << " --height <h> Canvas height (default: 600)\n";
|
||||
std::cout << " --show Show window (default: headless)\n";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "GAL Native Test - OPENGL_GAL Baseline Generator\n";
|
||||
std::cout << "================================================\n\n";
|
||||
|
||||
// Initialize GLEW
|
||||
glewExperimental = GL_TRUE;
|
||||
|
||||
m_frame = new GALTestFrame();
|
||||
m_frame->Show(true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int OnExit() override {
|
||||
if (m_frame) {
|
||||
return (m_frame->GetPassed() == m_frame->GetTotal()) ? 0 : 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
private:
|
||||
GALTestFrame* m_frame = nullptr;
|
||||
};
|
||||
|
||||
wxIMPLEMENT_APP(GALTestApp);
|
||||
113
tests/gal-regression/native/gal_test_accessor.cpp
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
/**
|
||||
* GAL Test Accessor Implementation
|
||||
*
|
||||
* Uses a member pointer technique to access private members without
|
||||
* the #define private public hack that breaks standard library headers.
|
||||
*/
|
||||
|
||||
#include "gal_test_accessor.h"
|
||||
#include "kicad_stubs.h"
|
||||
|
||||
#include <gal/opengl/opengl_gal.h>
|
||||
#include <gal/opengl/opengl_compositor.h>
|
||||
|
||||
// Template-based private member accessor trick
|
||||
// See: https://bloglitb.blogspot.com/2010/07/access-to-private-members-thats-easy.html
|
||||
|
||||
template<typename Tag>
|
||||
struct result {
|
||||
typedef typename Tag::type type;
|
||||
static type ptr;
|
||||
};
|
||||
|
||||
template<typename Tag>
|
||||
typename result<Tag>::type result<Tag>::ptr;
|
||||
|
||||
template<typename Tag, typename Tag::type p>
|
||||
struct rob : result<Tag> {
|
||||
struct filler {
|
||||
filler() { result<Tag>::ptr = p; }
|
||||
};
|
||||
static filler filler_obj;
|
||||
};
|
||||
|
||||
template<typename Tag, typename Tag::type p>
|
||||
typename rob<Tag, p>::filler rob<Tag, p>::filler_obj;
|
||||
|
||||
// Tags for the private members we need to access
|
||||
struct OPENGL_GAL_compositor { typedef KIGFX::OPENGL_COMPOSITOR* KIGFX::OPENGL_GAL::*type; };
|
||||
struct OPENGL_GAL_mainBuffer { typedef unsigned int KIGFX::OPENGL_GAL::*type; };
|
||||
struct OPENGL_COMPOSITOR_mainFbo { typedef GLuint KIGFX::OPENGL_COMPOSITOR::*type; };
|
||||
|
||||
// Instantiate the accessors
|
||||
template struct rob<OPENGL_GAL_compositor, &KIGFX::OPENGL_GAL::m_compositor>;
|
||||
template struct rob<OPENGL_GAL_mainBuffer, &KIGFX::OPENGL_GAL::m_mainBuffer>;
|
||||
template struct rob<OPENGL_COMPOSITOR_mainFbo, &KIGFX::OPENGL_COMPOSITOR::m_mainFbo>;
|
||||
|
||||
GLuint GetCompositorMainBufferTexture(KIGFX::OPENGL_GAL* gal) {
|
||||
// Get the compositor pointer using the member pointer accessor
|
||||
KIGFX::OPENGL_COMPOSITOR* compositor = gal->*result<OPENGL_GAL_compositor>::ptr;
|
||||
unsigned int mainBuffer = gal->*result<OPENGL_GAL_mainBuffer>::ptr;
|
||||
|
||||
if (compositor && mainBuffer > 0) {
|
||||
return compositor->GetBufferTexture(mainBuffer);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void GetCompositorBufferSize(KIGFX::OPENGL_GAL* gal, int* width, int* height) {
|
||||
KIGFX::OPENGL_COMPOSITOR* compositor = gal->*result<OPENGL_GAL_compositor>::ptr;
|
||||
|
||||
if (compositor) {
|
||||
VECTOR2I size = compositor->GetScreenSize();
|
||||
*width = size.x;
|
||||
*height = size.y;
|
||||
} else {
|
||||
*width = 0;
|
||||
*height = 0;
|
||||
}
|
||||
}
|
||||
|
||||
GLuint GetCompositorMainFBO(KIGFX::OPENGL_GAL* gal) {
|
||||
KIGFX::OPENGL_COMPOSITOR* compositor = gal->*result<OPENGL_GAL_compositor>::ptr;
|
||||
if (compositor) {
|
||||
return compositor->*result<OPENGL_COMPOSITOR_mainFbo>::ptr;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned int GetMainBufferHandle(KIGFX::OPENGL_GAL* gal) {
|
||||
return gal->*result<OPENGL_GAL_mainBuffer>::ptr;
|
||||
}
|
||||
|
||||
bool ReadCompositorFBOPixels(KIGFX::OPENGL_GAL* gal, std::vector<uint8_t>& pixels, int* width, int* height) {
|
||||
KIGFX::OPENGL_COMPOSITOR* compositor = gal->*result<OPENGL_GAL_compositor>::ptr;
|
||||
unsigned int mainBuffer = gal->*result<OPENGL_GAL_mainBuffer>::ptr;
|
||||
|
||||
if (!compositor || mainBuffer == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
GLuint mainFbo = compositor->*result<OPENGL_COMPOSITOR_mainFbo>::ptr;
|
||||
|
||||
// Get size from compositor
|
||||
VECTOR2I size = compositor->GetScreenSize();
|
||||
*width = size.x;
|
||||
*height = size.y;
|
||||
|
||||
// Compute attachment point: GL_COLOR_ATTACHMENT0 + (mainBuffer - 1)
|
||||
GLenum attachmentPoint = GL_COLOR_ATTACHMENT0_EXT + (mainBuffer - 1);
|
||||
|
||||
// Bind the FBO for reading
|
||||
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, mainFbo);
|
||||
glReadBuffer(attachmentPoint);
|
||||
|
||||
// Read pixels
|
||||
pixels.resize((*width) * (*height) * 4);
|
||||
glReadPixels(0, 0, *width, *height, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
|
||||
|
||||
// Unbind FBO
|
||||
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
29
tests/gal-regression/native/gal_test_accessor.h
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* GAL Test Accessor
|
||||
*
|
||||
* Provides access to OPENGL_GAL internal members for testing purposes.
|
||||
* This uses a technique to access private members without modifying KiCad headers.
|
||||
*/
|
||||
|
||||
#ifndef GAL_TEST_ACCESSOR_H
|
||||
#define GAL_TEST_ACCESSOR_H
|
||||
|
||||
#include <GL/glew.h>
|
||||
#include <vector>
|
||||
|
||||
// Forward declarations
|
||||
namespace KIGFX {
|
||||
class OPENGL_GAL;
|
||||
class OPENGL_COMPOSITOR;
|
||||
}
|
||||
|
||||
// Test accessor functions - implemented in gal_test_accessor.cpp
|
||||
GLuint GetCompositorMainBufferTexture(KIGFX::OPENGL_GAL* gal);
|
||||
void GetCompositorBufferSize(KIGFX::OPENGL_GAL* gal, int* width, int* height);
|
||||
GLuint GetCompositorMainFBO(KIGFX::OPENGL_GAL* gal);
|
||||
unsigned int GetMainBufferHandle(KIGFX::OPENGL_GAL* gal);
|
||||
|
||||
// Read pixels directly from the compositor's FBO
|
||||
bool ReadCompositorFBOPixels(KIGFX::OPENGL_GAL* gal, std::vector<uint8_t>& pixels, int* width, int* height);
|
||||
|
||||
#endif // GAL_TEST_ACCESSOR_H
|
||||
102
tests/gal-regression/native/generate_shaders.py
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generate C++ shader headers from KiCad GLSL files.
|
||||
|
||||
This script converts GLSL shader files to C++ headers compatible with
|
||||
KiCad's BUILTIN_SHADERS namespace.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
def convert_shader_to_cpp(source_path, var_name):
|
||||
"""Convert a shader file to C++ header content."""
|
||||
with open(source_path, 'rb') as f:
|
||||
data = f.read()
|
||||
|
||||
# Convert to hex array
|
||||
hex_values = ', '.join(f'0x{b:02x}' for b in data)
|
||||
hex_values += ', 0x00' # Null terminate
|
||||
|
||||
array_size = len(data)
|
||||
|
||||
header_content = f"""// Auto-generated shader header from {os.path.basename(source_path)}
|
||||
#ifndef {var_name.upper()}_H
|
||||
#define {var_name.upper()}_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace KIGFX {{
|
||||
namespace BUILTIN_SHADERS {{
|
||||
extern std::string {var_name};
|
||||
}}
|
||||
}}
|
||||
|
||||
#endif // {var_name.upper()}_H
|
||||
"""
|
||||
|
||||
cpp_content = f"""// Auto-generated from {os.path.basename(source_path)}
|
||||
#include <string>
|
||||
#include "{var_name}.h"
|
||||
|
||||
namespace KIGFX {{
|
||||
namespace BUILTIN_SHADERS {{
|
||||
|
||||
static unsigned char {var_name}_bytes[] = {{ {hex_values} }};
|
||||
|
||||
std::string {var_name} = std::string(reinterpret_cast<char const*>({var_name}_bytes), {array_size});
|
||||
|
||||
}}
|
||||
}}
|
||||
"""
|
||||
|
||||
return header_content, cpp_content
|
||||
|
||||
def main():
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
kicad_root = os.path.abspath(os.path.join(script_dir, '..', '..', '..', 'kicad'))
|
||||
shaders_dir = os.path.join(kicad_root, 'common', 'gal', 'shaders')
|
||||
|
||||
output_dir = os.path.join(script_dir, 'generated')
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
shaders = [
|
||||
('kicad_frag.glsl', 'glsl_kicad_frag'),
|
||||
('kicad_vert.glsl', 'glsl_kicad_vert'),
|
||||
('smaa_base.glsl', 'glsl_smaa_base'),
|
||||
('smaa_pass_1_frag_color.glsl', 'glsl_smaa_pass_1_frag_color'),
|
||||
('smaa_pass_1_frag_luma.glsl', 'glsl_smaa_pass_1_frag_luma'),
|
||||
('smaa_pass_1_vert.glsl', 'glsl_smaa_pass_1_vert'),
|
||||
('smaa_pass_2_frag.glsl', 'glsl_smaa_pass_2_frag'),
|
||||
('smaa_pass_2_vert.glsl', 'glsl_smaa_pass_2_vert'),
|
||||
('smaa_pass_3_frag.glsl', 'glsl_smaa_pass_3_frag'),
|
||||
('smaa_pass_3_vert.glsl', 'glsl_smaa_pass_3_vert'),
|
||||
]
|
||||
|
||||
generated_cpp_files = []
|
||||
|
||||
for shader_file, var_name in shaders:
|
||||
source_path = os.path.join(shaders_dir, shader_file)
|
||||
|
||||
if not os.path.exists(source_path):
|
||||
print(f"Warning: {source_path} not found, skipping")
|
||||
continue
|
||||
|
||||
header_content, cpp_content = convert_shader_to_cpp(source_path, var_name)
|
||||
|
||||
header_path = os.path.join(output_dir, f'{var_name}.h')
|
||||
cpp_path = os.path.join(output_dir, f'{var_name}.cpp')
|
||||
|
||||
with open(header_path, 'w') as f:
|
||||
f.write(header_content)
|
||||
|
||||
with open(cpp_path, 'w') as f:
|
||||
f.write(cpp_content)
|
||||
|
||||
generated_cpp_files.append(cpp_path)
|
||||
print(f"Generated {var_name}.h and {var_name}.cpp")
|
||||
|
||||
print(f"\nGenerated {len(generated_cpp_files)} shader files in {output_dir}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
13
tests/gal-regression/native/generated/glsl_kicad_frag.cpp
Normal file
13
tests/gal-regression/native/generated/glsl_kicad_frag.h
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
// Auto-generated shader header from kicad_frag.glsl
|
||||
#ifndef GLSL_KICAD_FRAG_H
|
||||
#define GLSL_KICAD_FRAG_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace KIGFX {
|
||||
namespace BUILTIN_SHADERS {
|
||||
extern std::string glsl_kicad_frag;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // GLSL_KICAD_FRAG_H
|
||||
13
tests/gal-regression/native/generated/glsl_kicad_vert.cpp
Normal file
13
tests/gal-regression/native/generated/glsl_kicad_vert.h
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
// Auto-generated shader header from kicad_vert.glsl
|
||||
#ifndef GLSL_KICAD_VERT_H
|
||||
#define GLSL_KICAD_VERT_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace KIGFX {
|
||||
namespace BUILTIN_SHADERS {
|
||||
extern std::string glsl_kicad_vert;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // GLSL_KICAD_VERT_H
|
||||
13
tests/gal-regression/native/generated/glsl_smaa_base.cpp
Normal file
13
tests/gal-regression/native/generated/glsl_smaa_base.h
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
// Auto-generated shader header from smaa_base.glsl
|
||||
#ifndef GLSL_SMAA_BASE_H
|
||||
#define GLSL_SMAA_BASE_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace KIGFX {
|
||||
namespace BUILTIN_SHADERS {
|
||||
extern std::string glsl_smaa_base;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // GLSL_SMAA_BASE_H
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// Auto-generated from smaa_pass_1_frag_color.glsl
|
||||
#include <string>
|
||||
#include "glsl_smaa_pass_1_frag_color.h"
|
||||
|
||||
namespace KIGFX {
|
||||
namespace BUILTIN_SHADERS {
|
||||
|
||||
static unsigned char glsl_smaa_pass_1_frag_color_bytes[] = { 0x76, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x20, 0x76, 0x65, 0x63, 0x32, 0x20, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x3b, 0x0a, 0x76, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x5b, 0x33, 0x5d, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x32, 0x44, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x54, 0x65, 0x78, 0x3b, 0x0a, 0x0a, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x28, 0x29, 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x67, 0x6c, 0x5f, 0x46, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x2e, 0x78, 0x79, 0x20, 0x3d, 0x20, 0x53, 0x4d, 0x41, 0x41, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x45, 0x64, 0x67, 0x65, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x53, 0x28, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x2c, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x2c, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x54, 0x65, 0x78, 0x29, 0x2e, 0x78, 0x79, 0x3b, 0x0a, 0x7d, 0x00 };
|
||||
|
||||
std::string glsl_smaa_pass_1_frag_color = std::string(reinterpret_cast<char const*>(glsl_smaa_pass_1_frag_color_bytes), 170);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// Auto-generated shader header from smaa_pass_1_frag_color.glsl
|
||||
#ifndef GLSL_SMAA_PASS_1_FRAG_COLOR_H
|
||||
#define GLSL_SMAA_PASS_1_FRAG_COLOR_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace KIGFX {
|
||||
namespace BUILTIN_SHADERS {
|
||||
extern std::string glsl_smaa_pass_1_frag_color;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // GLSL_SMAA_PASS_1_FRAG_COLOR_H
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// Auto-generated from smaa_pass_1_frag_luma.glsl
|
||||
#include <string>
|
||||
#include "glsl_smaa_pass_1_frag_luma.h"
|
||||
|
||||
namespace KIGFX {
|
||||
namespace BUILTIN_SHADERS {
|
||||
|
||||
static unsigned char glsl_smaa_pass_1_frag_luma_bytes[] = { 0x76, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x20, 0x76, 0x65, 0x63, 0x32, 0x20, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x3b, 0x0a, 0x76, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x5b, 0x33, 0x5d, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x32, 0x44, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x54, 0x65, 0x78, 0x3b, 0x0a, 0x0a, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x28, 0x29, 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x67, 0x6c, 0x5f, 0x46, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x2e, 0x78, 0x79, 0x20, 0x3d, 0x20, 0x53, 0x4d, 0x41, 0x41, 0x4c, 0x75, 0x6d, 0x61, 0x45, 0x64, 0x67, 0x65, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x53, 0x28, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x2c, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x2c, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x54, 0x65, 0x78, 0x29, 0x2e, 0x78, 0x79, 0x3b, 0x0a, 0x7d, 0x00 };
|
||||
|
||||
std::string glsl_smaa_pass_1_frag_luma = std::string(reinterpret_cast<char const*>(glsl_smaa_pass_1_frag_luma_bytes), 169);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// Auto-generated shader header from smaa_pass_1_frag_luma.glsl
|
||||
#ifndef GLSL_SMAA_PASS_1_FRAG_LUMA_H
|
||||
#define GLSL_SMAA_PASS_1_FRAG_LUMA_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace KIGFX {
|
||||
namespace BUILTIN_SHADERS {
|
||||
extern std::string glsl_smaa_pass_1_frag_luma;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // GLSL_SMAA_PASS_1_FRAG_LUMA_H
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// Auto-generated from smaa_pass_1_vert.glsl
|
||||
#include <string>
|
||||
#include "glsl_smaa_pass_1_vert.h"
|
||||
|
||||
namespace KIGFX {
|
||||
namespace BUILTIN_SHADERS {
|
||||
|
||||
static unsigned char glsl_smaa_pass_1_vert_bytes[] = { 0x76, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x5b, 0x33, 0x5d, 0x3b, 0x0a, 0x76, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x20, 0x76, 0x65, 0x63, 0x32, 0x20, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x3b, 0x0a, 0x0a, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x28, 0x29, 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x20, 0x3d, 0x20, 0x67, 0x6c, 0x5f, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x54, 0x65, 0x78, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x30, 0x2e, 0x73, 0x74, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x53, 0x4d, 0x41, 0x41, 0x45, 0x64, 0x67, 0x65, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x56, 0x53, 0x28, 0x20, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x2c, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x67, 0x6c, 0x5f, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x66, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x28, 0x29, 0x3b, 0x0a, 0x0a, 0x7d, 0x00 };
|
||||
|
||||
std::string glsl_smaa_pass_1_vert = std::string(reinterpret_cast<char const*>(glsl_smaa_pass_1_vert_bytes), 179);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// Auto-generated shader header from smaa_pass_1_vert.glsl
|
||||
#ifndef GLSL_SMAA_PASS_1_VERT_H
|
||||
#define GLSL_SMAA_PASS_1_VERT_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace KIGFX {
|
||||
namespace BUILTIN_SHADERS {
|
||||
extern std::string glsl_smaa_pass_1_vert;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // GLSL_SMAA_PASS_1_VERT_H
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// Auto-generated from smaa_pass_2_frag.glsl
|
||||
#include <string>
|
||||
#include "glsl_smaa_pass_2_frag.h"
|
||||
|
||||
namespace KIGFX {
|
||||
namespace BUILTIN_SHADERS {
|
||||
|
||||
static unsigned char glsl_smaa_pass_2_frag_bytes[] = { 0x76, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x20, 0x76, 0x65, 0x63, 0x32, 0x20, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x3b, 0x0a, 0x76, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x20, 0x76, 0x65, 0x63, 0x32, 0x20, 0x70, 0x69, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x3b, 0x0a, 0x76, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x5b, 0x33, 0x5d, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x32, 0x44, 0x20, 0x65, 0x64, 0x67, 0x65, 0x73, 0x54, 0x65, 0x78, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x32, 0x44, 0x20, 0x61, 0x72, 0x65, 0x61, 0x54, 0x65, 0x78, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x32, 0x44, 0x20, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x54, 0x65, 0x78, 0x3b, 0x0a, 0x0a, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x28, 0x29, 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x67, 0x6c, 0x5f, 0x46, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x3d, 0x20, 0x53, 0x4d, 0x41, 0x41, 0x42, 0x6c, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x57, 0x65, 0x69, 0x67, 0x68, 0x74, 0x43, 0x61, 0x6c, 0x63, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x53, 0x28, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x2c, 0x20, 0x70, 0x69, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x2c, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x2c, 0x20, 0x65, 0x64, 0x67, 0x65, 0x73, 0x54, 0x65, 0x78, 0x2c, 0x20, 0x61, 0x72, 0x65, 0x61, 0x54, 0x65, 0x78, 0x2c, 0x20, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x54, 0x65, 0x78, 0x2c, 0x20, 0x76, 0x65, 0x63, 0x34, 0x28, 0x30, 0x2e, 0x2c, 0x30, 0x2e, 0x2c, 0x30, 0x2e, 0x2c, 0x30, 0x2e, 0x29, 0x29, 0x3b, 0x0a, 0x7d, 0x00 };
|
||||
|
||||
std::string glsl_smaa_pass_2_frag = std::string(reinterpret_cast<char const*>(glsl_smaa_pass_2_frag_bytes), 299);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// Auto-generated shader header from smaa_pass_2_frag.glsl
|
||||
#ifndef GLSL_SMAA_PASS_2_FRAG_H
|
||||
#define GLSL_SMAA_PASS_2_FRAG_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace KIGFX {
|
||||
namespace BUILTIN_SHADERS {
|
||||
extern std::string glsl_smaa_pass_2_frag;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // GLSL_SMAA_PASS_2_FRAG_H
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// Auto-generated from smaa_pass_2_vert.glsl
|
||||
#include <string>
|
||||
#include "glsl_smaa_pass_2_vert.h"
|
||||
|
||||
namespace KIGFX {
|
||||
namespace BUILTIN_SHADERS {
|
||||
|
||||
static unsigned char glsl_smaa_pass_2_vert_bytes[] = { 0x76, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x5b, 0x33, 0x5d, 0x3b, 0x0a, 0x76, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x20, 0x76, 0x65, 0x63, 0x32, 0x20, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x3b, 0x0a, 0x76, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x20, 0x76, 0x65, 0x63, 0x32, 0x20, 0x70, 0x69, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x3b, 0x0a, 0x0a, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x28, 0x29, 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x20, 0x3d, 0x20, 0x67, 0x6c, 0x5f, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x54, 0x65, 0x78, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x30, 0x2e, 0x73, 0x74, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x53, 0x4d, 0x41, 0x41, 0x42, 0x6c, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x57, 0x65, 0x69, 0x67, 0x68, 0x74, 0x43, 0x61, 0x6c, 0x63, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x56, 0x53, 0x28, 0x20, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x2c, 0x20, 0x70, 0x69, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x2c, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x20, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x67, 0x6c, 0x5f, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x3d, 0x20, 0x66, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x28, 0x29, 0x3b, 0x0a, 0x7d, 0x00 };
|
||||
|
||||
std::string glsl_smaa_pass_2_vert = std::string(reinterpret_cast<char const*>(glsl_smaa_pass_2_vert_bytes), 222);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// Auto-generated shader header from smaa_pass_2_vert.glsl
|
||||
#ifndef GLSL_SMAA_PASS_2_VERT_H
|
||||
#define GLSL_SMAA_PASS_2_VERT_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace KIGFX {
|
||||
namespace BUILTIN_SHADERS {
|
||||
extern std::string glsl_smaa_pass_2_vert;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // GLSL_SMAA_PASS_2_VERT_H
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// Auto-generated from smaa_pass_3_frag.glsl
|
||||
#include <string>
|
||||
#include "glsl_smaa_pass_3_frag.h"
|
||||
|
||||
namespace KIGFX {
|
||||
namespace BUILTIN_SHADERS {
|
||||
|
||||
static unsigned char glsl_smaa_pass_3_frag_bytes[] = { 0x76, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x20, 0x76, 0x65, 0x63, 0x32, 0x20, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x3b, 0x0a, 0x76, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x32, 0x44, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x54, 0x65, 0x78, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x32, 0x44, 0x20, 0x62, 0x6c, 0x65, 0x6e, 0x64, 0x54, 0x65, 0x78, 0x3b, 0x0a, 0x0a, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x28, 0x29, 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x67, 0x6c, 0x5f, 0x46, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x3d, 0x20, 0x53, 0x4d, 0x41, 0x41, 0x4e, 0x65, 0x69, 0x67, 0x68, 0x62, 0x6f, 0x72, 0x68, 0x6f, 0x6f, 0x64, 0x42, 0x6c, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x53, 0x28, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x2c, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x2c, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x54, 0x65, 0x78, 0x2c, 0x20, 0x62, 0x6c, 0x65, 0x6e, 0x64, 0x54, 0x65, 0x78, 0x29, 0x3b, 0x0a, 0x7d, 0x00 };
|
||||
|
||||
std::string glsl_smaa_pass_3_frag = std::string(reinterpret_cast<char const*>(glsl_smaa_pass_3_frag_bytes), 201);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// Auto-generated shader header from smaa_pass_3_frag.glsl
|
||||
#ifndef GLSL_SMAA_PASS_3_FRAG_H
|
||||
#define GLSL_SMAA_PASS_3_FRAG_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace KIGFX {
|
||||
namespace BUILTIN_SHADERS {
|
||||
extern std::string glsl_smaa_pass_3_frag;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // GLSL_SMAA_PASS_3_FRAG_H
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// Auto-generated from smaa_pass_3_vert.glsl
|
||||
#include <string>
|
||||
#include "glsl_smaa_pass_3_vert.h"
|
||||
|
||||
namespace KIGFX {
|
||||
namespace BUILTIN_SHADERS {
|
||||
|
||||
static unsigned char glsl_smaa_pass_3_vert_bytes[] = { 0x76, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3b, 0x0a, 0x76, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x20, 0x76, 0x65, 0x63, 0x32, 0x20, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x3b, 0x0a, 0x0a, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x28, 0x29, 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x20, 0x3d, 0x20, 0x67, 0x6c, 0x5f, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x54, 0x65, 0x78, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x30, 0x2e, 0x73, 0x74, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x53, 0x4d, 0x41, 0x41, 0x4e, 0x65, 0x69, 0x67, 0x68, 0x62, 0x6f, 0x72, 0x68, 0x6f, 0x6f, 0x64, 0x42, 0x6c, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x56, 0x53, 0x28, 0x20, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x2c, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x20, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x67, 0x6c, 0x5f, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x3d, 0x20, 0x66, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x28, 0x29, 0x3b, 0x0a, 0x7d, 0x00 };
|
||||
|
||||
std::string glsl_smaa_pass_3_vert = std::string(reinterpret_cast<char const*>(glsl_smaa_pass_3_vert_bytes), 181);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// Auto-generated shader header from smaa_pass_3_vert.glsl
|
||||
#ifndef GLSL_SMAA_PASS_3_VERT_H
|
||||
#define GLSL_SMAA_PASS_3_VERT_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace KIGFX {
|
||||
namespace BUILTIN_SHADERS {
|
||||
extern std::string glsl_smaa_pass_3_vert;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // GLSL_SMAA_PASS_3_VERT_H
|
||||
269
tests/gal-regression/native/kicad_stubs.cpp
Normal file
|
|
@ -0,0 +1,269 @@
|
|||
/**
|
||||
* KiCad stubs implementation for native GAL test build
|
||||
*
|
||||
* Provides implementations for symbols declared in KiCad headers
|
||||
* but not included in the GAL source files we compile.
|
||||
*/
|
||||
|
||||
#include <GL/glew.h>
|
||||
#include <wx/wx.h>
|
||||
#include <wx/glcanvas.h>
|
||||
#include <wx/snglinst.h>
|
||||
|
||||
// Forward-declared types in pgm_base.h need complete definitions
|
||||
// before we can implement PGM_BASE constructor/destructor
|
||||
class SETTINGS_MANAGER {};
|
||||
class LIBRARY_MANAGER {};
|
||||
class BACKGROUND_JOBS_MONITOR {};
|
||||
class NOTIFICATIONS_MANAGER {};
|
||||
|
||||
// Include KiCad headers
|
||||
#include <pgm_base.h>
|
||||
#include <advanced_config.h>
|
||||
#include <gal/opengl/gl_context_mgr.h> // For GL_CONTEXT_MANAGER definition
|
||||
|
||||
//=============================================================================
|
||||
// PGM_BASE minimal implementation
|
||||
// Note: We can't inherit from PGM_BASE as GetGLContextManager isn't virtual.
|
||||
// Instead we define our own simple singleton that returns a GL_CONTEXT_MANAGER.
|
||||
//=============================================================================
|
||||
|
||||
// The real Pgm() needs to return a PGM_BASE reference.
|
||||
// PGM_BASE has GetGLContextManager() which GAL uses.
|
||||
|
||||
// Create a minimal subclass with the functionality we need
|
||||
class PGM_BASE_TEST : public PGM_BASE {
|
||||
public:
|
||||
PGM_BASE_TEST() : PGM_BASE() {}
|
||||
|
||||
// Implement required pure virtual
|
||||
void MacOpenFile(const wxString& aFileName) override {}
|
||||
};
|
||||
|
||||
static PGM_BASE_TEST* s_pgmInstance = nullptr;
|
||||
|
||||
PGM_BASE& Pgm() {
|
||||
if (!s_pgmInstance) {
|
||||
s_pgmInstance = new PGM_BASE_TEST();
|
||||
}
|
||||
return *s_pgmInstance;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// ADVANCED_CFG singleton
|
||||
//=============================================================================
|
||||
|
||||
const ADVANCED_CFG& ADVANCED_CFG::GetCfg() {
|
||||
static ADVANCED_CFG instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// Build version
|
||||
//=============================================================================
|
||||
|
||||
const char* GetBuildVersion() {
|
||||
return "8.0.0-test";
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// Additional stub symbols needed for linking
|
||||
//=============================================================================
|
||||
|
||||
#include <gal/gal_display_options.h>
|
||||
#include <core/observable.h>
|
||||
|
||||
namespace UTIL {
|
||||
namespace DETAIL {
|
||||
|
||||
OBSERVABLE_BASE::OBSERVABLE_BASE() {}
|
||||
OBSERVABLE_BASE::~OBSERVABLE_BASE() {}
|
||||
void OBSERVABLE_BASE::on_observers_empty() {}
|
||||
void OBSERVABLE_BASE::enter_iteration() {}
|
||||
void OBSERVABLE_BASE::leave_iteration() {}
|
||||
void OBSERVABLE_BASE::add_observer(void*) {}
|
||||
void OBSERVABLE_BASE::remove_observer(void*) {}
|
||||
|
||||
} // namespace DETAIL
|
||||
} // namespace UTIL
|
||||
|
||||
// KIFONT stubs - only define symbols not already inline in headers
|
||||
#include <font/font.h>
|
||||
|
||||
namespace KIFONT {
|
||||
|
||||
FONT* FONT::GetFont(const wxString&, bool, bool, const std::vector<wxString>*, bool) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Note: FONT::Draw methods are inline in the header, don't redefine them
|
||||
|
||||
const METRICS& METRICS::Default() {
|
||||
static METRICS m;
|
||||
return m;
|
||||
}
|
||||
|
||||
void OUTLINE_GLYPH::Triangulate(std::function<void(const VECTOR2I&, const VECTOR2I&, const VECTOR2I&)>) const {}
|
||||
|
||||
} // namespace KIFONT
|
||||
|
||||
// SHAPE_POLY_SET stub
|
||||
#include <geometry/shape_poly_set.h>
|
||||
|
||||
bool SHAPE_POLY_SET::IsTriangulationUpToDate() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
// KIID niluuid
|
||||
#include <kiid.h>
|
||||
|
||||
KIID niluuid;
|
||||
|
||||
// Include environment.h for ENV_VAR_MAP typedef
|
||||
#include <settings/environment.h>
|
||||
|
||||
// PGM_BASE virtual methods that need implementations
|
||||
wxApp& PGM_BASE::App() {
|
||||
return *wxTheApp;
|
||||
}
|
||||
|
||||
COMMON_SETTINGS* PGM_BASE::GetCommonSettings() const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const wxString& PGM_BASE::GetExecutablePath() const {
|
||||
static wxString empty;
|
||||
return empty;
|
||||
}
|
||||
|
||||
ENV_VAR_MAP& PGM_BASE::GetLocalEnvVariables() const {
|
||||
static ENV_VAR_MAP empty;
|
||||
return empty;
|
||||
}
|
||||
|
||||
bool PGM_BASE::SetLanguage(wxString&, bool) { return false; }
|
||||
const wxString& PGM_BASE::GetTextEditor(bool) { static wxString s; return s; }
|
||||
void PGM_BASE::SetTextEditor(const wxString&) {}
|
||||
wxString PGM_BASE::GetLanguageTag() { return wxString(); }
|
||||
void PGM_BASE::SetLanguagePath() {}
|
||||
void PGM_BASE::ReadPdfBrowserInfos() {}
|
||||
bool PGM_BASE::SetLocalEnvVariable(const wxString&, const wxString&) { return false; }
|
||||
void PGM_BASE::SetLocalEnvVariables() {}
|
||||
void PGM_BASE::WritePdfBrowserInfos() {}
|
||||
void PGM_BASE::SetLanguageIdentifier(int) {}
|
||||
const wxString PGM_BASE::AskUserForPreferredEditor(const wxString&) { return wxString(); }
|
||||
|
||||
PGM_BASE::PGM_BASE() {
|
||||
m_singleton.Init(); // Initialize GL_CONTEXT_MANAGER
|
||||
}
|
||||
PGM_BASE::~PGM_BASE() {}
|
||||
|
||||
//=============================================================================
|
||||
// Additional linker stubs
|
||||
//=============================================================================
|
||||
|
||||
// UI dialog stubs
|
||||
void DisplayError(wxWindow*, const wxString&) {}
|
||||
void DisplayErrorMessage(wxWindow*, const wxString&, const wxString&) {}
|
||||
|
||||
// OpenGL info stub
|
||||
void SetOpenGLInfo(const char*, const char*, const char*) {}
|
||||
|
||||
// Math logging stub
|
||||
void kimathLogOverflow(double, const char*) {}
|
||||
|
||||
// Arc geometry stub
|
||||
#include <geometry/eda_angle.h>
|
||||
int GetArcToSegmentCount(int aRadius, int aArcError, const EDA_ANGLE& aArcAngle) {
|
||||
// Simple approximation: 1 segment per 10 degrees
|
||||
double degrees = std::abs(aArcAngle.AsDegrees());
|
||||
return std::max(1, (int)(degrees / 10.0));
|
||||
}
|
||||
|
||||
// Bezier curve stub
|
||||
#include <bezier_curves.h>
|
||||
void BEZIER_POLY::GetPoly(std::vector<VECTOR2D>& aOutput, double aMinSegLen) {
|
||||
// Return just start and end points
|
||||
if (!m_ctrlPts.empty()) {
|
||||
aOutput.push_back(m_ctrlPts.front());
|
||||
aOutput.push_back(m_ctrlPts.back());
|
||||
}
|
||||
}
|
||||
|
||||
// DPI scaling stub
|
||||
#include <dpi_scaling.h>
|
||||
double DPI_SCALING::GetDefaultScaleFactor() { return 1.0; }
|
||||
|
||||
// Advanced config constructor
|
||||
ADVANCED_CFG::ADVANCED_CFG() {
|
||||
// Initialize m_ScreenDPI - KiCad default is 91
|
||||
m_ScreenDPI = 91;
|
||||
}
|
||||
|
||||
// Cursor store stub
|
||||
#include <gal/cursors.h>
|
||||
const WX_CURSOR_TYPE CURSOR_STORE::GetCursor(KICURSOR aCursor, bool aHiDPI) {
|
||||
return wxCursor(wxCURSOR_ARROW);
|
||||
}
|
||||
|
||||
// Singleton init/destructor
|
||||
#include <singleton.h>
|
||||
void KICAD_SINGLETON::Init() {
|
||||
m_GLContextManager = new GL_CONTEXT_MANAGER();
|
||||
// Skip thread pool for now
|
||||
}
|
||||
KICAD_SINGLETON::~KICAD_SINGLETON() {
|
||||
delete m_GLContextManager;
|
||||
m_GLContextManager = nullptr;
|
||||
}
|
||||
|
||||
// TEXT_ATTRIBUTES constructor
|
||||
#include <font/text_attributes.h>
|
||||
TEXT_ATTRIBUTES::TEXT_ATTRIBUTES(KIFONT::FONT* aFont) {}
|
||||
|
||||
// KIID constructors
|
||||
KIID::KIID() {}
|
||||
KIID::KIID(int aValue) {}
|
||||
|
||||
// UTF8 stubs
|
||||
#include <core/utf8.h>
|
||||
UTF8::UTF8(const wxString& s) : m_s(s.ToUTF8().data()) {}
|
||||
int UTF8::uni_forward(const unsigned char* aSequence, unsigned* aResult) {
|
||||
if (aSequence && *aSequence) {
|
||||
if (aResult) *aResult = (unsigned)*aSequence;
|
||||
return 1;
|
||||
}
|
||||
if (aResult) *aResult = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// UTIL::LINK stubs
|
||||
namespace UTIL {
|
||||
LINK::LINK() : token_(nullptr), observer_(nullptr) {}
|
||||
LINK::LINK(std::shared_ptr<DETAIL::OBSERVABLE_BASE::IMPL> token, void* observer)
|
||||
: token_(token), observer_(observer) {}
|
||||
LINK::LINK(LINK&& other) : token_(std::move(other.token_)), observer_(other.observer_) {
|
||||
other.observer_ = nullptr;
|
||||
}
|
||||
LINK::~LINK() {}
|
||||
LINK& LINK::operator=(LINK&& other) {
|
||||
token_ = std::move(other.token_);
|
||||
observer_ = other.observer_;
|
||||
other.observer_ = nullptr;
|
||||
return *this;
|
||||
}
|
||||
void LINK::reset() { token_.reset(); observer_ = nullptr; }
|
||||
LINK::operator bool() const { return token_ != nullptr; }
|
||||
}
|
||||
|
||||
// VC_SETTINGS::Reset stub
|
||||
#include <view/view_controls.h>
|
||||
namespace KIGFX {
|
||||
void VC_SETTINGS::Reset() {}
|
||||
}
|
||||
|
||||
// KIFONT::FONT::Draw stub (the version with cursor position)
|
||||
namespace KIFONT {
|
||||
void FONT::Draw(KIGFX::GAL*, const wxString&, const VECTOR2I&, const VECTOR2I&,
|
||||
const TEXT_ATTRIBUTES&, const METRICS&) const {}
|
||||
} // namespace KIFONT
|
||||
27
tests/gal-regression/native/kicad_stubs.h
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
/**
|
||||
* KiCad stubs for native GAL test build
|
||||
*
|
||||
* This header ONLY provides includes - no type definitions.
|
||||
* Implementations of missing symbols are in kicad_stubs.cpp.
|
||||
*/
|
||||
|
||||
#ifndef KICAD_STUBS_H
|
||||
#define KICAD_STUBS_H
|
||||
|
||||
// GLEW must come before any OpenGL headers (including wx/glcanvas.h)
|
||||
#include <GL/glew.h>
|
||||
|
||||
// System wxWidgets
|
||||
#include <wx/wx.h>
|
||||
#include <wx/glcanvas.h>
|
||||
|
||||
// Profiler macros - no-op
|
||||
#ifndef PROF_TIMER
|
||||
#define PROF_TIMER(name)
|
||||
#endif
|
||||
|
||||
#ifndef PROF_END
|
||||
#define PROF_END(name)
|
||||
#endif
|
||||
|
||||
#endif // KICAD_STUBS_H
|
||||
1724
tests/gal-regression/native/stb_image_write.h
Normal file
66
tests/gal-regression/native/trace_helpers.h
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
// Stub trace_helpers.h for native GAL test
|
||||
// Provides simplified TRACE_MANAGER that doesn't use wxWidgets macros
|
||||
|
||||
#ifndef TRACE_HELPERS_H_STUB
|
||||
#define TRACE_HELPERS_H_STUB
|
||||
|
||||
#include <wx/wx.h>
|
||||
#include <map>
|
||||
#include <cstdarg>
|
||||
|
||||
// Forward declaration
|
||||
class wxArrayString;
|
||||
|
||||
// Trace channel names (extern declarations)
|
||||
extern const wxString traceGalProfile;
|
||||
extern const wxString traceOpenGL;
|
||||
extern const wxString traceGalCachedContainer;
|
||||
extern const wxString traceShader;
|
||||
|
||||
// Definitions
|
||||
inline const wxString traceGalProfile = "KICAD_GAL_PROFILE";
|
||||
inline const wxString traceOpenGL = "KICAD_OPENGL";
|
||||
inline const wxString traceGalCachedContainer = "KICAD_GAL_CACHED_CONTAINER";
|
||||
inline const wxString traceShader = "KICAD_SHADER";
|
||||
|
||||
// TRACE_MANAGER - simplified stub
|
||||
class TRACE_MANAGER {
|
||||
public:
|
||||
TRACE_MANAGER() : m_globalTraceEnabled(false), m_printAllTraces(false) {}
|
||||
~TRACE_MANAGER() {}
|
||||
|
||||
static TRACE_MANAGER& Instance() {
|
||||
static TRACE_MANAGER instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
// Trace methods - variadic template version
|
||||
template<typename... Args>
|
||||
void Trace(const wxString&, Args...) {
|
||||
// No-op - tracing disabled for test
|
||||
}
|
||||
|
||||
// wxWidgets-style methods for compatibility
|
||||
void DoTrace(const wxString&, const char*, ...) {}
|
||||
void DoTraceUtf8(const wxString&, const char*, ...) {}
|
||||
|
||||
bool IsTraceEnabled(const wxString&) const {
|
||||
return false; // Always disabled for test
|
||||
}
|
||||
|
||||
private:
|
||||
std::map<wxString, bool> m_enabledTraces;
|
||||
bool m_globalTraceEnabled;
|
||||
bool m_printAllTraces;
|
||||
};
|
||||
|
||||
// KI_TRACE macro - no-op since IsTraceEnabled always returns false
|
||||
#define KI_TRACE(aWhat, ...) \
|
||||
if (TRACE_MANAGER::Instance().IsTraceEnabled(aWhat)) { \
|
||||
TRACE_MANAGER::Instance().Trace(aWhat, __VA_ARGS__); \
|
||||
}
|
||||
|
||||
// Dump function stub
|
||||
inline wxString dump(const wxArrayString&) { return wxString(); }
|
||||
|
||||
#endif // TRACE_HELPERS_H_STUB
|
||||
567
tests/gal-regression/scenarios/gal_test_scenarios.cpp
Normal file
|
|
@ -0,0 +1,567 @@
|
|||
/**
|
||||
* GAL Test Scenarios Implementation
|
||||
*
|
||||
* Uses KiCad's GAL API to render test patterns.
|
||||
* These serve as baselines for WEBGL_GAL visual regression testing.
|
||||
*/
|
||||
|
||||
#include "gal_test_scenarios.h"
|
||||
#include "kicad_stubs.h" // For COLOR4D, VECTOR2D, EDA_ANGLE
|
||||
|
||||
// Include GAL header for the actual class definition
|
||||
#include <gal/graphics_abstraction_layer.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14159265358979323846
|
||||
#endif
|
||||
|
||||
namespace GALTest {
|
||||
|
||||
// Import KIGFX types
|
||||
using KIGFX::COLOR4D;
|
||||
using KIGFX::GAL;
|
||||
|
||||
// Scenario names
|
||||
static const char* SCENARIO_NAMES[] = {
|
||||
"basic-lines",
|
||||
"line-widths",
|
||||
"circles",
|
||||
"arcs",
|
||||
"rectangles",
|
||||
"polygons",
|
||||
"alpha-blending",
|
||||
"transforms",
|
||||
"grid-cursor",
|
||||
"segments",
|
||||
"complex-scene"
|
||||
};
|
||||
|
||||
static const int SCENARIO_COUNT = sizeof(SCENARIO_NAMES) / sizeof(SCENARIO_NAMES[0]);
|
||||
|
||||
int GetScenarioCount() {
|
||||
return SCENARIO_COUNT;
|
||||
}
|
||||
|
||||
const char* GetScenarioName(int index) {
|
||||
if (index >= 0 && index < SCENARIO_COUNT) {
|
||||
return SCENARIO_NAMES[index];
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// Scenario Implementations using GAL API
|
||||
//=============================================================================
|
||||
|
||||
// Scenario 0: Basic lines
|
||||
static void RenderBasicLines(KIGFX::GAL* gal, int width, int height) {
|
||||
double cx = width / 2.0;
|
||||
double cy = height / 2.0;
|
||||
double len = std::min(width, height) * 0.35;
|
||||
|
||||
gal->SetLineWidth(1.0);
|
||||
gal->SetIsFill(false);
|
||||
gal->SetIsStroke(true);
|
||||
|
||||
// Horizontal line (red)
|
||||
gal->SetStrokeColor(COLOR4D(1.0, 0.2, 0.2, 1.0));
|
||||
gal->DrawLine(VECTOR2D(cx - len, cy), VECTOR2D(cx + len, cy));
|
||||
|
||||
// Vertical line (green)
|
||||
gal->SetStrokeColor(COLOR4D(0.2, 1.0, 0.2, 1.0));
|
||||
gal->DrawLine(VECTOR2D(cx, cy - len), VECTOR2D(cx, cy + len));
|
||||
|
||||
// Diagonal lines (blue, yellow)
|
||||
gal->SetStrokeColor(COLOR4D(0.2, 0.2, 1.0, 1.0));
|
||||
gal->DrawLine(VECTOR2D(cx - len, cy - len), VECTOR2D(cx + len, cy + len));
|
||||
|
||||
gal->SetStrokeColor(COLOR4D(1.0, 1.0, 0.2, 1.0));
|
||||
gal->DrawLine(VECTOR2D(cx - len, cy + len), VECTOR2D(cx + len, cy - len));
|
||||
|
||||
// Radial lines (white)
|
||||
gal->SetStrokeColor(COLOR4D(0.8, 0.8, 0.8, 1.0));
|
||||
for (int i = 0; i < 8; i++) {
|
||||
double angle = M_PI * i / 8;
|
||||
double x = cos(angle) * len * 0.8;
|
||||
double y = sin(angle) * len * 0.8;
|
||||
gal->DrawLine(VECTOR2D(cx + x * 0.3, cy + y * 0.3), VECTOR2D(cx + x, cy + y));
|
||||
}
|
||||
}
|
||||
|
||||
// Scenario 1: Line widths
|
||||
static void RenderLineWidths(KIGFX::GAL* gal, int width, int height) {
|
||||
double widths[] = {0.5, 1.0, 2.0, 3.0, 5.0, 8.0, 12.0};
|
||||
int count = sizeof(widths) / sizeof(widths[0]);
|
||||
|
||||
double margin = 50.0;
|
||||
double spacing = (height - 2 * margin) / (count + 1);
|
||||
|
||||
gal->SetIsFill(false);
|
||||
gal->SetIsStroke(true);
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
double y = margin + (i + 1) * spacing;
|
||||
|
||||
// Color gradient
|
||||
double t = (double)i / (count - 1);
|
||||
gal->SetStrokeColor(COLOR4D(1.0 - t * 0.5, 0.3 + t * 0.4, 0.2 + t * 0.6, 1.0));
|
||||
gal->SetLineWidth(widths[i]);
|
||||
gal->DrawLine(VECTOR2D(margin, y), VECTOR2D(width - margin, y));
|
||||
}
|
||||
}
|
||||
|
||||
// Scenario 2: Circles
|
||||
static void RenderCircles(KIGFX::GAL* gal, int width, int height) {
|
||||
double cx = width / 2.0;
|
||||
double cy = height / 2.0;
|
||||
|
||||
// Filled circles
|
||||
gal->SetIsFill(true);
|
||||
gal->SetIsStroke(false);
|
||||
|
||||
gal->SetFillColor(COLOR4D(0.8, 0.2, 0.2, 0.8));
|
||||
gal->DrawCircle(VECTOR2D(cx - 150, cy - 100), 60);
|
||||
|
||||
gal->SetFillColor(COLOR4D(0.2, 0.8, 0.2, 0.8));
|
||||
gal->DrawCircle(VECTOR2D(cx, cy - 100), 80);
|
||||
|
||||
gal->SetFillColor(COLOR4D(0.2, 0.2, 0.8, 0.8));
|
||||
gal->DrawCircle(VECTOR2D(cx + 150, cy - 100), 50);
|
||||
|
||||
// Stroked circles
|
||||
gal->SetIsFill(false);
|
||||
gal->SetIsStroke(true);
|
||||
gal->SetLineWidth(2.0);
|
||||
|
||||
gal->SetStrokeColor(COLOR4D(1.0, 0.5, 0.0, 1.0));
|
||||
gal->DrawCircle(VECTOR2D(cx - 150, cy + 100), 70);
|
||||
|
||||
gal->SetStrokeColor(COLOR4D(0.0, 1.0, 1.0, 1.0));
|
||||
gal->DrawCircle(VECTOR2D(cx, cy + 100), 90);
|
||||
|
||||
gal->SetStrokeColor(COLOR4D(1.0, 0.0, 1.0, 1.0));
|
||||
gal->DrawCircle(VECTOR2D(cx + 150, cy + 100), 55);
|
||||
|
||||
// Concentric circles
|
||||
gal->SetStrokeColor(COLOR4D(0.6, 0.6, 0.6, 1.0));
|
||||
gal->SetLineWidth(1.0);
|
||||
for (double r = 20; r <= 200; r += 30) {
|
||||
gal->DrawCircle(VECTOR2D(cx, cy), r);
|
||||
}
|
||||
}
|
||||
|
||||
// Scenario 3: Arcs
|
||||
static void RenderArcs(KIGFX::GAL* gal, int width, int height) {
|
||||
double cx = width / 2.0;
|
||||
double cy = height / 2.0;
|
||||
double radius = std::min(width, height) * 0.25;
|
||||
|
||||
gal->SetIsFill(false);
|
||||
gal->SetIsStroke(true);
|
||||
gal->SetLineWidth(3.0);
|
||||
|
||||
// 90 degree arcs in each quadrant
|
||||
gal->SetStrokeColor(COLOR4D(1.0, 0.3, 0.3, 1.0));
|
||||
gal->DrawArc(VECTOR2D(cx, cy), radius,
|
||||
EDA_ANGLE(0, DEGREES_T), EDA_ANGLE(90, DEGREES_T));
|
||||
|
||||
gal->SetStrokeColor(COLOR4D(0.3, 1.0, 0.3, 1.0));
|
||||
gal->DrawArc(VECTOR2D(cx, cy), radius,
|
||||
EDA_ANGLE(90, DEGREES_T), EDA_ANGLE(90, DEGREES_T));
|
||||
|
||||
gal->SetStrokeColor(COLOR4D(0.3, 0.3, 1.0, 1.0));
|
||||
gal->DrawArc(VECTOR2D(cx, cy), radius,
|
||||
EDA_ANGLE(180, DEGREES_T), EDA_ANGLE(90, DEGREES_T));
|
||||
|
||||
gal->SetStrokeColor(COLOR4D(1.0, 1.0, 0.3, 1.0));
|
||||
gal->DrawArc(VECTOR2D(cx, cy), radius,
|
||||
EDA_ANGLE(270, DEGREES_T), EDA_ANGLE(90, DEGREES_T));
|
||||
|
||||
// Inner arcs
|
||||
gal->SetLineWidth(2.0);
|
||||
gal->SetStrokeColor(COLOR4D(0.8, 0.5, 0.8, 1.0));
|
||||
gal->DrawArc(VECTOR2D(cx, cy), radius * 0.6,
|
||||
EDA_ANGLE(30, DEGREES_T), EDA_ANGLE(120, DEGREES_T));
|
||||
|
||||
gal->SetStrokeColor(COLOR4D(0.5, 0.8, 0.8, 1.0));
|
||||
gal->DrawArc(VECTOR2D(cx, cy), radius * 0.6,
|
||||
EDA_ANGLE(210, DEGREES_T), EDA_ANGLE(120, DEGREES_T));
|
||||
}
|
||||
|
||||
// Scenario 4: Rectangles
|
||||
static void RenderRectangles(KIGFX::GAL* gal, int width, int height) {
|
||||
double margin = 50.0;
|
||||
|
||||
// Filled rectangles
|
||||
gal->SetIsFill(true);
|
||||
gal->SetIsStroke(false);
|
||||
|
||||
gal->SetFillColor(COLOR4D(0.8, 0.2, 0.2, 0.9));
|
||||
gal->DrawRectangle(VECTOR2D(margin, margin), VECTOR2D(margin + 120, margin + 80));
|
||||
|
||||
gal->SetFillColor(COLOR4D(0.2, 0.8, 0.2, 0.9));
|
||||
gal->DrawRectangle(VECTOR2D(margin + 140, margin), VECTOR2D(margin + 240, margin + 100));
|
||||
|
||||
gal->SetFillColor(COLOR4D(0.2, 0.2, 0.8, 0.9));
|
||||
gal->DrawRectangle(VECTOR2D(margin + 260, margin), VECTOR2D(margin + 340, margin + 120));
|
||||
|
||||
// Stroked rectangles
|
||||
gal->SetIsFill(false);
|
||||
gal->SetIsStroke(true);
|
||||
gal->SetLineWidth(2.0);
|
||||
|
||||
gal->SetStrokeColor(COLOR4D(1.0, 0.5, 0.0, 1.0));
|
||||
gal->DrawRectangle(VECTOR2D(margin, height - margin - 100),
|
||||
VECTOR2D(margin + 150, height - margin - 20));
|
||||
|
||||
gal->SetStrokeColor(COLOR4D(0.0, 1.0, 1.0, 1.0));
|
||||
gal->DrawRectangle(VECTOR2D(margin + 170, height - margin - 110),
|
||||
VECTOR2D(margin + 280, height - margin - 20));
|
||||
|
||||
gal->SetStrokeColor(COLOR4D(1.0, 0.0, 1.0, 1.0));
|
||||
gal->DrawRectangle(VECTOR2D(margin + 300, height - margin - 90),
|
||||
VECTOR2D(margin + 390, height - margin - 20));
|
||||
|
||||
// Nested rectangles
|
||||
gal->SetLineWidth(1.0);
|
||||
double cx = width / 2.0;
|
||||
double cy = height / 2.0;
|
||||
for (int i = 0; i < 6; i++) {
|
||||
double t = (double)i / 5;
|
||||
gal->SetStrokeColor(COLOR4D(t, 0.5, 1.0 - t, 1.0));
|
||||
double size = 30 + i * 25;
|
||||
gal->DrawRectangle(VECTOR2D(cx - size, cy - size * 0.6),
|
||||
VECTOR2D(cx + size, cy + size * 0.6));
|
||||
}
|
||||
}
|
||||
|
||||
// Scenario 5: Polygons
|
||||
static void RenderPolygons(KIGFX::GAL* gal, int width, int height) {
|
||||
double cx = width / 2.0;
|
||||
double cy = height / 2.0;
|
||||
|
||||
// Triangle (filled)
|
||||
gal->SetIsFill(true);
|
||||
gal->SetIsStroke(false);
|
||||
gal->SetFillColor(COLOR4D(1.0, 0.3, 0.3, 0.8));
|
||||
|
||||
std::deque<VECTOR2D> triangle = {
|
||||
VECTOR2D(cx - 200, cy - 50),
|
||||
VECTOR2D(cx - 130, cy - 50),
|
||||
VECTOR2D(cx - 165, cy - 120)
|
||||
};
|
||||
gal->DrawPolygon(triangle);
|
||||
|
||||
// Square (filled)
|
||||
gal->SetFillColor(COLOR4D(0.3, 1.0, 0.3, 0.8));
|
||||
std::deque<VECTOR2D> square = {
|
||||
VECTOR2D(cx - 50, cy - 50),
|
||||
VECTOR2D(cx + 30, cy - 50),
|
||||
VECTOR2D(cx + 30, cy - 130),
|
||||
VECTOR2D(cx - 50, cy - 130)
|
||||
};
|
||||
gal->DrawPolygon(square);
|
||||
|
||||
// Pentagon (filled)
|
||||
gal->SetFillColor(COLOR4D(0.3, 0.3, 1.0, 0.8));
|
||||
std::deque<VECTOR2D> pentagon;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
double angle = -M_PI / 2 + 2 * M_PI * i / 5;
|
||||
pentagon.push_back(VECTOR2D(cx + 165 + cos(angle) * 50, cy - 90 + sin(angle) * 50));
|
||||
}
|
||||
gal->DrawPolygon(pentagon);
|
||||
|
||||
// Star (stroked)
|
||||
gal->SetIsFill(false);
|
||||
gal->SetIsStroke(true);
|
||||
gal->SetLineWidth(2.0);
|
||||
gal->SetStrokeColor(COLOR4D(1.0, 0.8, 0.0, 1.0));
|
||||
|
||||
std::deque<VECTOR2D> star;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
double angle = -M_PI / 2 + M_PI * i / 5;
|
||||
double r = (i % 2 == 0) ? 60 : 30;
|
||||
star.push_back(VECTOR2D(cx + cos(angle) * r, cy + 80 + sin(angle) * r));
|
||||
}
|
||||
// Draw as polyline (closed)
|
||||
std::vector<VECTOR2D> starVec(star.begin(), star.end());
|
||||
starVec.push_back(star.front()); // Close the shape
|
||||
gal->DrawPolyline(starVec);
|
||||
|
||||
// Hexagon (stroked)
|
||||
gal->SetStrokeColor(COLOR4D(0.8, 0.3, 0.8, 1.0));
|
||||
std::deque<VECTOR2D> hexagon;
|
||||
for (int i = 0; i < 6; i++) {
|
||||
double angle = M_PI * i / 3;
|
||||
hexagon.push_back(VECTOR2D(cx - 150 + cos(angle) * 45, cy + 80 + sin(angle) * 45));
|
||||
}
|
||||
std::vector<VECTOR2D> hexVec(hexagon.begin(), hexagon.end());
|
||||
hexVec.push_back(hexagon.front()); // Close the shape
|
||||
gal->DrawPolyline(hexVec);
|
||||
}
|
||||
|
||||
// Scenario 6: Alpha blending
|
||||
static void RenderAlphaBlending(KIGFX::GAL* gal, int width, int height) {
|
||||
double cx = width / 2.0;
|
||||
double cy = height / 2.0;
|
||||
|
||||
gal->SetIsFill(true);
|
||||
gal->SetIsStroke(false);
|
||||
|
||||
// Overlapping rectangles with different alphas
|
||||
gal->SetFillColor(COLOR4D(1.0, 0.0, 0.0, 0.5));
|
||||
gal->DrawRectangle(VECTOR2D(cx - 150, cy - 80), VECTOR2D(cx + 30, cy + 40));
|
||||
|
||||
gal->SetFillColor(COLOR4D(0.0, 1.0, 0.0, 0.5));
|
||||
gal->DrawRectangle(VECTOR2D(cx - 60, cy - 80), VECTOR2D(cx + 120, cy + 40));
|
||||
|
||||
gal->SetFillColor(COLOR4D(0.0, 0.0, 1.0, 0.5));
|
||||
gal->DrawRectangle(VECTOR2D(cx - 105, cy - 20), VECTOR2D(cx + 75, cy + 100));
|
||||
|
||||
// Overlapping circles
|
||||
gal->SetFillColor(COLOR4D(1.0, 1.0, 0.0, 0.4));
|
||||
gal->DrawCircle(VECTOR2D(cx - 180, cy + 100), 60);
|
||||
|
||||
gal->SetFillColor(COLOR4D(0.0, 1.0, 1.0, 0.4));
|
||||
gal->DrawCircle(VECTOR2D(cx - 130, cy + 100), 60);
|
||||
|
||||
gal->SetFillColor(COLOR4D(1.0, 0.0, 1.0, 0.4));
|
||||
gal->DrawCircle(VECTOR2D(cx - 155, cy + 60), 60);
|
||||
|
||||
// Alpha gradient
|
||||
for (int i = 0; i < 8; i++) {
|
||||
double alpha = 0.1 + i * 0.1;
|
||||
gal->SetFillColor(COLOR4D(0.5, 0.5, 0.5, alpha));
|
||||
gal->DrawRectangle(VECTOR2D(cx + 50 + i * 30, cy - 50),
|
||||
VECTOR2D(cx + 75 + i * 30, cy + 50));
|
||||
}
|
||||
}
|
||||
|
||||
// Scenario 7: Transforms
|
||||
static void RenderTransforms(KIGFX::GAL* gal, int width, int height) {
|
||||
double cx = width / 2.0;
|
||||
double cy = height / 2.0;
|
||||
|
||||
gal->SetIsFill(false);
|
||||
gal->SetIsStroke(true);
|
||||
gal->SetLineWidth(2.0);
|
||||
|
||||
// Original (reference)
|
||||
gal->SetStrokeColor(COLOR4D(0.5, 0.5, 0.5, 1.0));
|
||||
gal->DrawRectangle(VECTOR2D(cx - 230, cy - 120), VECTOR2D(cx - 170, cy - 80));
|
||||
|
||||
// Translated
|
||||
gal->Save();
|
||||
gal->Translate(VECTOR2D(100, 20));
|
||||
gal->SetStrokeColor(COLOR4D(1.0, 0.3, 0.3, 1.0));
|
||||
gal->DrawRectangle(VECTOR2D(cx - 230, cy - 120), VECTOR2D(cx - 170, cy - 80));
|
||||
gal->Restore();
|
||||
|
||||
// Rotated
|
||||
gal->Save();
|
||||
gal->Translate(VECTOR2D(cx, cy - 80));
|
||||
gal->Rotate(30.0 * M_PI / 180.0);
|
||||
gal->SetStrokeColor(COLOR4D(0.3, 1.0, 0.3, 1.0));
|
||||
gal->DrawRectangle(VECTOR2D(-30, -20), VECTOR2D(30, 20));
|
||||
gal->Restore();
|
||||
|
||||
// Scaled
|
||||
gal->Save();
|
||||
gal->Translate(VECTOR2D(cx + 120, cy - 80));
|
||||
gal->Scale(VECTOR2D(1.5, 0.8));
|
||||
gal->SetStrokeColor(COLOR4D(0.3, 0.3, 1.0, 1.0));
|
||||
gal->DrawRectangle(VECTOR2D(-30, -20), VECTOR2D(30, 20));
|
||||
gal->Restore();
|
||||
|
||||
// Combined transforms
|
||||
gal->Save();
|
||||
gal->Translate(VECTOR2D(cx, cy + 80));
|
||||
gal->Rotate(45.0 * M_PI / 180.0);
|
||||
gal->Scale(VECTOR2D(1.2, 1.2));
|
||||
gal->SetStrokeColor(COLOR4D(1.0, 1.0, 0.3, 1.0));
|
||||
gal->DrawRectangle(VECTOR2D(-40, -25), VECTOR2D(40, 25));
|
||||
gal->Restore();
|
||||
|
||||
// Nested transforms
|
||||
gal->Save();
|
||||
gal->Translate(VECTOR2D(cx - 150, cy + 100));
|
||||
gal->SetStrokeColor(COLOR4D(0.8, 0.4, 0.8, 1.0));
|
||||
gal->DrawCircle(VECTOR2D(0, 0), 30);
|
||||
|
||||
gal->Save();
|
||||
gal->Rotate(60.0 * M_PI / 180.0);
|
||||
gal->Translate(VECTOR2D(50, 0));
|
||||
gal->SetStrokeColor(COLOR4D(0.4, 0.8, 0.8, 1.0));
|
||||
gal->DrawCircle(VECTOR2D(0, 0), 20);
|
||||
gal->Restore();
|
||||
|
||||
gal->Restore();
|
||||
}
|
||||
|
||||
// Scenario 8: Grid and cursor
|
||||
static void RenderGridCursor(KIGFX::GAL* gal, int width, int height) {
|
||||
gal->SetIsFill(false);
|
||||
gal->SetIsStroke(true);
|
||||
|
||||
// Grid
|
||||
gal->SetStrokeColor(COLOR4D(0.3, 0.3, 0.4, 0.5));
|
||||
gal->SetLineWidth(1.0);
|
||||
|
||||
double gridSize = 40.0;
|
||||
for (double x = gridSize; x < width; x += gridSize) {
|
||||
gal->DrawLine(VECTOR2D(x, 0), VECTOR2D(x, height));
|
||||
}
|
||||
for (double y = gridSize; y < height; y += gridSize) {
|
||||
gal->DrawLine(VECTOR2D(0, y), VECTOR2D(width, y));
|
||||
}
|
||||
|
||||
// Major grid
|
||||
gal->SetStrokeColor(COLOR4D(0.4, 0.4, 0.5, 0.7));
|
||||
double majorGridSize = 200.0;
|
||||
gal->SetLineWidth(2.0);
|
||||
for (double x = majorGridSize; x < width; x += majorGridSize) {
|
||||
gal->DrawLine(VECTOR2D(x, 0), VECTOR2D(x, height));
|
||||
}
|
||||
for (double y = majorGridSize; y < height; y += majorGridSize) {
|
||||
gal->DrawLine(VECTOR2D(0, y), VECTOR2D(width, y));
|
||||
}
|
||||
|
||||
// Cursor (crosshair)
|
||||
double cx = width / 2.0;
|
||||
double cy = height / 2.0;
|
||||
double cursorSize = 30.0;
|
||||
|
||||
gal->SetLineWidth(2.0);
|
||||
gal->SetStrokeColor(COLOR4D(1.0, 1.0, 1.0, 1.0));
|
||||
gal->DrawLine(VECTOR2D(cx - cursorSize, cy), VECTOR2D(cx + cursorSize, cy));
|
||||
gal->DrawLine(VECTOR2D(cx, cy - cursorSize), VECTOR2D(cx, cy + cursorSize));
|
||||
|
||||
// Cursor circle
|
||||
gal->SetStrokeColor(COLOR4D(1.0, 1.0, 0.0, 0.8));
|
||||
gal->DrawCircle(VECTOR2D(cx, cy), cursorSize * 0.8);
|
||||
}
|
||||
|
||||
// Scenario 9: Segments (thick lines with round caps)
|
||||
static void RenderSegments(KIGFX::GAL* gal, int width, int height) {
|
||||
double margin = 80.0;
|
||||
|
||||
// Horizontal segments with different widths
|
||||
double widths[] = {5.0, 10.0, 20.0, 30.0};
|
||||
int count = sizeof(widths) / sizeof(widths[0]);
|
||||
|
||||
gal->SetIsFill(true);
|
||||
gal->SetIsStroke(false);
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
double y = margin + i * 80;
|
||||
double w = widths[i];
|
||||
|
||||
gal->SetFillColor(COLOR4D(0.3 + i * 0.2, 0.5, 0.8 - i * 0.15, 1.0));
|
||||
|
||||
// DrawSegment draws a rounded segment
|
||||
gal->DrawSegment(VECTOR2D(margin, y), VECTOR2D(width - margin, y), w);
|
||||
}
|
||||
|
||||
// Diagonal segment
|
||||
gal->SetFillColor(COLOR4D(1.0, 0.5, 0.2, 1.0));
|
||||
gal->DrawSegment(VECTOR2D(margin, height - margin),
|
||||
VECTOR2D(width / 2.0, height - margin - 150), 15.0);
|
||||
}
|
||||
|
||||
// Scenario 10: Complex scene (PCB-like)
|
||||
static void RenderComplexScene(KIGFX::GAL* gal, int width, int height) {
|
||||
// Use layer depths to ensure proper z-ordering
|
||||
// Lower depth = closer to camera (drawn on top with GL_LESS)
|
||||
|
||||
// Background "board" - deepest layer
|
||||
gal->SetLayerDepth(100);
|
||||
gal->SetIsFill(true);
|
||||
gal->SetIsStroke(false);
|
||||
gal->SetFillColor(COLOR4D(0.1, 0.3, 0.1, 1.0));
|
||||
gal->DrawRectangle(VECTOR2D(50, 50), VECTOR2D(width - 50, height - 50));
|
||||
|
||||
// Traces (copper) - middle layer
|
||||
gal->SetLayerDepth(50);
|
||||
gal->SetIsFill(false);
|
||||
gal->SetIsStroke(true);
|
||||
gal->SetStrokeColor(COLOR4D(0.8, 0.6, 0.2, 1.0));
|
||||
gal->SetLineWidth(3.0);
|
||||
|
||||
// Horizontal traces
|
||||
for (int i = 0; i < 5; i++) {
|
||||
double y = 100 + i * 100;
|
||||
gal->DrawLine(VECTOR2D(80, y), VECTOR2D(width - 80, y));
|
||||
}
|
||||
|
||||
// Vertical traces
|
||||
for (int i = 0; i < 6; i++) {
|
||||
double x = 100 + i * 120;
|
||||
gal->DrawLine(VECTOR2D(x, 80), VECTOR2D(x, height - 80));
|
||||
}
|
||||
|
||||
// Pads (circles) - above traces
|
||||
gal->SetLayerDepth(30);
|
||||
gal->SetIsFill(true);
|
||||
gal->SetIsStroke(false);
|
||||
gal->SetFillColor(COLOR4D(0.9, 0.7, 0.3, 1.0));
|
||||
for (int i = 0; i < 5; i++) {
|
||||
for (int j = 0; j < 6; j++) {
|
||||
double x = 100 + j * 120;
|
||||
double y = 100 + i * 100;
|
||||
gal->DrawCircle(VECTOR2D(x, y), 15);
|
||||
}
|
||||
}
|
||||
|
||||
// Holes - above pads
|
||||
gal->SetLayerDepth(20);
|
||||
gal->SetFillColor(COLOR4D(0.1, 0.1, 0.1, 1.0));
|
||||
for (int i = 0; i < 5; i++) {
|
||||
for (int j = 0; j < 6; j++) {
|
||||
double x = 100 + j * 120;
|
||||
double y = 100 + i * 100;
|
||||
gal->DrawCircle(VECTOR2D(x, y), 5);
|
||||
}
|
||||
}
|
||||
|
||||
// Component outline - top layer
|
||||
gal->SetLayerDepth(10);
|
||||
gal->SetIsFill(false);
|
||||
gal->SetIsStroke(true);
|
||||
gal->SetStrokeColor(COLOR4D(1.0, 1.0, 0.0, 1.0));
|
||||
gal->SetLineWidth(1.0);
|
||||
gal->DrawRectangle(VECTOR2D(width / 2 - 80, height / 2 - 40),
|
||||
VECTOR2D(width / 2 + 80, height / 2 + 40));
|
||||
|
||||
// Reference designator placeholder - topmost
|
||||
gal->SetLayerDepth(5);
|
||||
gal->SetStrokeColor(COLOR4D(1.0, 1.0, 1.0, 1.0));
|
||||
gal->DrawRectangle(VECTOR2D(width / 2 - 20, height / 2 - 15),
|
||||
VECTOR2D(width / 2 + 20, height / 2 + 15));
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// Main dispatch function
|
||||
//=============================================================================
|
||||
|
||||
void RenderScenario(KIGFX::GAL* gal, int index, int width, int height) {
|
||||
switch (index) {
|
||||
case 0: RenderBasicLines(gal, width, height); break;
|
||||
case 1: RenderLineWidths(gal, width, height); break;
|
||||
case 2: RenderCircles(gal, width, height); break;
|
||||
case 3: RenderArcs(gal, width, height); break;
|
||||
case 4: RenderRectangles(gal, width, height); break;
|
||||
case 5: RenderPolygons(gal, width, height); break;
|
||||
case 6: RenderAlphaBlending(gal, width, height); break;
|
||||
case 7: RenderTransforms(gal, width, height); break;
|
||||
case 8: RenderGridCursor(gal, width, height); break;
|
||||
case 9: RenderSegments(gal, width, height); break;
|
||||
case 10: RenderComplexScene(gal, width, height); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace GALTest
|
||||
40
tests/gal-regression/scenarios/gal_test_scenarios.h
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
/**
|
||||
* GAL Test Scenarios
|
||||
*
|
||||
* Defines test scenarios for visual regression testing of GAL rendering.
|
||||
* These scenarios use KiCad's GAL API to test the rendering pipeline.
|
||||
*/
|
||||
|
||||
#ifndef GAL_TEST_SCENARIOS_H
|
||||
#define GAL_TEST_SCENARIOS_H
|
||||
|
||||
// Forward declaration
|
||||
namespace KIGFX {
|
||||
class GAL;
|
||||
}
|
||||
|
||||
namespace GALTest {
|
||||
|
||||
/**
|
||||
* Get the number of test scenarios
|
||||
*/
|
||||
int GetScenarioCount();
|
||||
|
||||
/**
|
||||
* Get the name of a scenario
|
||||
*/
|
||||
const char* GetScenarioName(int index);
|
||||
|
||||
/**
|
||||
* Render a scenario using KiCad's GAL API
|
||||
*
|
||||
* @param gal Pointer to the GAL instance (OPENGL_GAL or WEBGL_GAL)
|
||||
* @param index Scenario index
|
||||
* @param width Canvas width in logical pixels
|
||||
* @param height Canvas height in logical pixels
|
||||
*/
|
||||
void RenderScenario(KIGFX::GAL* gal, int index, int width, int height);
|
||||
|
||||
} // namespace GALTest
|
||||
|
||||
#endif // GAL_TEST_SCENARIOS_H
|
||||