feat(webgl): Enable all 28 scenarios for WebGL test harness

- Update wasm/Makefile to compile ALL scenario files using wildcard
- Add conditional compilation to scenario_bitmap.cpp (#ifdef __EMSCRIPTEN__)
  to handle OpenGL-specific shader workaround code for native only
- Change canvas dimensions to 1600x1200 to match native baseline (2x Retina)
- Set white background color to match native screenshots
- Update gal-webgl.spec.ts to hide UI overlay before taking screenshots
- Improve test-gal-regression.sh with better ImageMagick comparison

All 28 scenarios now compile and run on both native and WebGL backends.
Native vs baseline: PASSED (28/28 matching)
WebGL rendering: In progress (primitives not yet visible)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Viktor Vaczi 2026-01-08 11:11:03 +01:00
commit d0e2500048
8 changed files with 129 additions and 35 deletions

View file

@ -203,10 +203,10 @@
canvas.style.position = 'absolute';
canvas.style.left = '0';
canvas.style.top = '0';
canvas.width = 800;
canvas.height = 600;
canvas.style.width = '800px';
canvas.style.height = '600px';
canvas.width = 1600;
canvas.height = 1200;
canvas.style.width = '1600px';
canvas.style.height = '1200px';
canvas.oncontextmenu = function(e) { e.preventDefault(); };
canvas.addEventListener("webglcontextlost", function(e) {
setStatus('WebGL context lost!');

View file

@ -138,10 +138,16 @@ test.describe('GAL WebGL Regression Tests', () => {
await expect(canvas).toBeVisible({ timeout: 5000 });
// Hide controls overlay before screenshot (it sits on top of canvas)
await page.locator('#controls-overlay').evaluate(el => el.style.visibility = 'hidden');
// Screenshot the canvas (matching native 800x600 output)
const screenshotPath = path.join(OUTPUT_DIR, `gal-${scenarioName}.png`);
await canvas.screenshot({ path: screenshotPath });
// Restore overlay for manual debugging
await page.locator('#controls-overlay').evaluate(el => el.style.visibility = 'visible');
// Print console logs for first scenario (debugging)
if (scenarioIndex === 0) {
console.log('\n=== Console logs ===');
@ -174,11 +180,17 @@ test.describe('GAL WebGL Regression Tests', () => {
// Wait for rendering
await page.waitForTimeout(50);
// Hide controls overlay before screenshot
await page.locator('#controls-overlay').evaluate(el => el.style.visibility = 'hidden');
// Screenshot the canvas
const canvas = await page.locator('#canvas');
const screenshotPath = path.join(OUTPUT_DIR, `gal-${scenarioName}.png`);
await canvas.screenshot({ path: screenshotPath });
// Restore overlay
await page.locator('#controls-overlay').evaluate(el => el.style.visibility = 'visible');
console.log(`[${i + 1}/28] ${scenarioName}`);
}

View file

@ -7,10 +7,6 @@
* In OPENGL_GAL, it uses GL_BITMAP_CACHE to create GPU textures from wxImage data.
* Position is controlled via Save/Translate/Restore, not by arguments to DrawBitmap.
*
* IMPORTANT: DrawBitmap uses legacy OpenGL immediate mode (glBegin/glVertex3f/glEnd)
* which is incompatible with active shaders. We must deactivate the shader before
* calling DrawBitmap and reactivate it afterward.
*
* This scenario demonstrates:
* 1. Basic bitmap rendering with checkerboard pattern
* 2. Gradient patterns (horizontal, vertical, radial)
@ -19,13 +15,18 @@
* 5. Multiple bitmaps in a scene
*/
#include <GL/glew.h>
#include <gal/graphics_abstraction_layer.h>
#include <gal/opengl/opengl_gal.h>
#include "../native/bitmap_base_stub.h"
#include "../native/gal_test_accessor.h"
#include <cmath>
// Native-only includes for shader workaround
// WebGL uses a different shader architecture and doesn't need this workaround
#ifndef __EMSCRIPTEN__
#include <GL/glew.h>
#include <gal/opengl/opengl_gal.h>
#include "../native/gal_test_accessor.h"
#endif
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
@ -34,6 +35,10 @@ namespace GALTest {
using KIGFX::COLOR4D;
using KIGFX::GAL;
#ifndef __EMSCRIPTEN__
// Native build only: OpenGL-specific code
using KIGFX::OPENGL_GAL;
// Setup state needed for DrawBitmap (width/height cached per scenario)
@ -84,10 +89,24 @@ static void DrawBitmapWithShaderFix(GAL* gal, const BITMAP_BASE& bitmap, double
ActivateGALShader(oglGal);
}
#else
// WebGL build: Use DrawBitmap directly through the abstract interface
// WEBGL_GAL::DrawBitmap should be implemented using modern GL (VAO/VBO) not immediate mode
static void DrawBitmapWithShaderFix(GAL* gal, const BITMAP_BASE& bitmap, double alpha = 1.0) {
// In WebGL, just call DrawBitmap directly
// The shader is managed internally by WEBGL_GAL
gal->DrawBitmap(bitmap, alpha);
}
#endif
void RenderBitmap(GAL* gal, int width, int height) {
// Cache viewport size for fixed-function matrix setup
#ifndef __EMSCRIPTEN__
// Cache viewport size for fixed-function matrix setup (native only)
s_viewportWidth = width;
s_viewportHeight = height;
#endif
gal->SetLayerDepth(100);
gal->SetIsFill(true);

View file

@ -94,7 +94,10 @@ KICAD_SRCS = $(KICAD_ROOT)/common/gal/hidpi_gl_canvas.cpp \
$(KICAD_ROOT)/common/gal/gal_display_options.cpp
# Test scenario sources (shared with native test)
SCENARIO_SRCS = ../scenarios/gal_test_scenarios.cpp
# Include ALL scenario files - they should use the abstract GAL interface only.
# The harness decides whether to use OPENGL_GAL (native) or WEBGL_GAL (wasm).
SCENARIO_SRCS = ../scenarios/gal_test_scenarios.cpp \
$(wildcard ../scenarios/scenario_*.cpp)
# GLU tesselator - stub (no actual implementation needed for basic shapes)
GLU_SRCS =

View file

@ -27,9 +27,9 @@
#include <cstdio>
// Canvas dimensions (matching native test)
static const int CANVAS_WIDTH = 800;
static const int CANVAS_HEIGHT = 600;
// Canvas dimensions (matching native baseline at 2x Retina scale)
static const int CANVAS_WIDTH = 1600;
static const int CANVAS_HEIGHT = 1200;
// Global state
static int g_currentScenario = -1;
@ -166,7 +166,8 @@ bool GALTestApp::OnInit() {
// Set up the GAL
g_gal->SetScreenSize(VECTOR2I(CANVAS_WIDTH, CANVAS_HEIGHT));
g_gal->ResizeScreen(CANVAS_WIDTH, CANVAS_HEIGHT);
g_gal->SetClearColor(KIGFX::COLOR4D(0.1, 0.1, 0.15, 1.0));
// Use white background to match native baseline screenshots
g_gal->SetClearColor(KIGFX::COLOR4D(1.0, 1.0, 1.0, 1.0));
// CRITICAL: Set worldUnitLength for 1:1 world-to-screen coordinate mapping
// GAL default worldUnitLength is for PCB nanometers, which would compress

View file

@ -203,10 +203,10 @@
canvas.style.position = 'absolute';
canvas.style.left = '0';
canvas.style.top = '0';
canvas.width = 800;
canvas.height = 600;
canvas.style.width = '800px';
canvas.style.height = '600px';
canvas.width = 1600;
canvas.height = 1200;
canvas.style.width = '1600px';
canvas.style.height = '1200px';
canvas.oncontextmenu = function(e) { e.preventDefault(); };
canvas.addEventListener("webglcontextlost", function(e) {
setStatus('WebGL context lost!');

View file

@ -2050,11 +2050,11 @@ void WEBGL_GAL::Flush()
void WEBGL_GAL::ClearScreen()
{
// Clear screen
// Clear screen with the configured clear color
m_compositor->SetBuffer( WEBGL_COMPOSITOR::DIRECT_RENDERING );
// NOTE: Black used here instead of m_clearColor; it will be composited later
glClearColor( 0, 0, 0, 1 );
// Use m_clearColor for background (set via SetClearColor())
glClearColor( m_clearColor.r, m_clearColor.g, m_clearColor.b, m_clearColor.a );
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT );
}