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>
This commit is contained in:
Viktor Vaczi 2026-01-07 09:42:31 +01:00
commit 490f531521
46 changed files with 4075 additions and 0 deletions

View 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` |

View 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)"

View 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)"

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

View 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")

View 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

View 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);

View 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;
}

View 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

View 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()

File diff suppressed because one or more lines are too long

View 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

File diff suppressed because one or more lines are too long

View 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

File diff suppressed because one or more lines are too long

View 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

View file

@ -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);
}
}

View file

@ -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

View file

@ -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);
}
}

View file

@ -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

View file

@ -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);
}
}

View file

@ -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

View file

@ -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);
}
}

View file

@ -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

View file

@ -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);
}
}

View file

@ -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

View file

@ -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);
}
}

View file

@ -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

View file

@ -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);
}
}

View file

@ -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

View 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

View 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

File diff suppressed because it is too large Load diff

View 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

View 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

View 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