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>
2026-01-07 09:42:31 +01:00
|
|
|
/**
|
|
|
|
|
* 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>
|
2026-01-07 13:37:08 +01:00
|
|
|
#include <gal/opengl/shader.h>
|
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>
2026-01-07 09:42:31 +01:00
|
|
|
|
|
|
|
|
// 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; };
|
2026-01-07 13:37:08 +01:00
|
|
|
struct OPENGL_GAL_shader { typedef KIGFX::SHADER* KIGFX::OPENGL_GAL::*type; };
|
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>
2026-01-07 09:42:31 +01:00
|
|
|
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>;
|
2026-01-07 13:37:08 +01:00
|
|
|
template struct rob<OPENGL_GAL_shader, &KIGFX::OPENGL_GAL::m_shader>;
|
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>
2026-01-07 09:42:31 +01:00
|
|
|
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;
|
|
|
|
|
}
|
2026-01-07 13:37:08 +01:00
|
|
|
|
|
|
|
|
KIGFX::SHADER* GetGALShader(KIGFX::OPENGL_GAL* gal) {
|
|
|
|
|
return gal->*result<OPENGL_GAL_shader>::ptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DeactivateGALShader(KIGFX::OPENGL_GAL* gal) {
|
|
|
|
|
KIGFX::SHADER* shader = gal->*result<OPENGL_GAL_shader>::ptr;
|
|
|
|
|
if (shader) {
|
|
|
|
|
shader->Deactivate();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ActivateGALShader(KIGFX::OPENGL_GAL* gal) {
|
|
|
|
|
KIGFX::SHADER* shader = gal->*result<OPENGL_GAL_shader>::ptr;
|
|
|
|
|
if (shader) {
|
|
|
|
|
shader->Use();
|
|
|
|
|
}
|
|
|
|
|
}
|