fix(webgl): Correct coordinate system for Retina 2x scaling

- Use 800x600 logical coordinate space (matches native)
- Set ZoomFactor=2.0 to scale content to 1600x1200 canvas
- Match native DPI setting (91)

Results improved from 3 to 7 matching scenarios:
- arcs (0.25%), basic-lines (0.44%), bezier-curves (0.45%)
- line-widths (0.66%), segment-chain (0.89%), segments (0.66%)
- transforms (0.26%)

Many more scenarios now close (<5%):
- arc-segments (3.5%), hole-walls (2.3%), clear-colors (1.4%)
- complex-scene (2.6%), polylines-multi (2.1%), polygons (3.2%)

Remaining issues:
- Filled shapes have slight color differences (~9%)
- Text/glyphs not implemented (60-67%)
- Some features broken (depth-testing, bitmap, transform-api)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Viktor Vaczi 2026-01-08 11:44:59 +01:00
commit 5370871f85

View file

@ -27,9 +27,14 @@
#include <cstdio> #include <cstdio>
// Canvas dimensions (matching native baseline at 2x Retina scale) // Canvas dimensions
// Physical canvas is 1600x1200 (matching native baseline at 2x Retina scale)
// But GAL coordinate space is 800x600 (matching native logical dimensions)
// The 2x scaling happens via devicePixelRatio in the canvas
static const int CANVAS_WIDTH = 1600; static const int CANVAS_WIDTH = 1600;
static const int CANVAS_HEIGHT = 1200; static const int CANVAS_HEIGHT = 1200;
static const int GAL_WIDTH = 800; // Logical coordinate space (matches native)
static const int GAL_HEIGHT = 600;
// Global state // Global state
static int g_currentScenario = -1; static int g_currentScenario = -1;
@ -63,8 +68,8 @@ void renderCurrentScenario() {
// Clear the screen (clears the direct rendering buffer) // Clear the screen (clears the direct rendering buffer)
g_gal->ClearScreen(); g_gal->ClearScreen();
// Render the scenario // Render the scenario using logical coordinate space (matches native)
GALTest::RenderScenario(g_gal, g_currentScenario, CANVAS_WIDTH, CANVAS_HEIGHT); GALTest::RenderScenario(g_gal, g_currentScenario, GAL_WIDTH, GAL_HEIGHT);
// End drawing and present // End drawing and present
g_gal->EndDrawing(); g_gal->EndDrawing();
@ -167,22 +172,21 @@ bool GALTestApp::OnInit() {
g_frame->Show(true); g_frame->Show(true);
// Set up the GAL // Set up the GAL
g_gal->SetScreenSize(VECTOR2I(CANVAS_WIDTH, CANVAS_HEIGHT)); // Native test runs at 800x600 logical but outputs 1600x1200 due to Retina 2x scaling
g_gal->ResizeScreen(CANVAS_WIDTH, CANVAS_HEIGHT); // We need to match this: GAL thinks it's 800x600, but actually renders to 1600x1200
g_gal->SetScreenSize(VECTOR2I(CANVAS_WIDTH, CANVAS_HEIGHT)); // Physical: 1600x1200
g_gal->ResizeScreen(CANVAS_WIDTH, CANVAS_HEIGHT); // Framebuffer: 1600x1200
// Use white background to match native baseline screenshots // Use white background to match native baseline screenshots
g_gal->SetClearColor(KIGFX::COLOR4D(1.0, 1.0, 1.0, 1.0)); g_gal->SetClearColor(KIGFX::COLOR4D(1.0, 1.0, 1.0, 1.0));
// CRITICAL: Set worldUnitLength for 1:1 world-to-screen coordinate mapping // CRITICAL: Set worldUnitLength to match native (which uses DPI=91)
// GAL default worldUnitLength is for PCB nanometers, which would compress g_gal->SetWorldUnitLength(1.0 / 91.0); // Match native KiCad default
// our pixel-scale coordinates (0-800) to tiny values!
// With screenDPI=96 and zoomFactor=1.0, worldUnitLength should be 1/96
g_gal->SetScreenDPI(96);
g_gal->SetWorldUnitLength(1.0 / 96.0);
// Set up coordinate transformation for 1:1 world-to-screen mapping // Set up coordinate transformation to simulate Retina 2x scaling:
// LookAtPoint should be at center, ZoomFactor of 1.0 gives 1:1 mapping // - LookAtPoint at center of LOGICAL space (400, 300)
g_gal->SetLookAtPoint(VECTOR2D(CANVAS_WIDTH / 2.0, CANVAS_HEIGHT / 2.0)); // - ZoomFactor of 2.0 to scale 800x600 content to fill 1600x1200
g_gal->SetZoomFactor(1.0); g_gal->SetLookAtPoint(VECTOR2D(GAL_WIDTH / 2.0, GAL_HEIGHT / 2.0));
g_gal->SetZoomFactor(2.0); // 2x zoom to simulate Retina scaling
g_gal->ComputeWorldScreenMatrix(); g_gal->ComputeWorldScreenMatrix();
// Initialize the compositor with proper context locking // Initialize the compositor with proper context locking