From 3287ec7616e9cf9e29ac3c2409e8284a43e97c00 Mon Sep 17 00:00:00 2001 From: Viktor Vaczi Date: Fri, 28 Nov 2025 21:22:09 +0100 Subject: [PATCH] Add GL immediate mode shim for Emscripten compatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Create custom JS library (lib/gl_immediate_shim.js) that fixes two Emscripten LEGACY_GL_EMULATION issues without modifying KiCad source: 1. Color-per-vertex requirement: Emscripten requires glColor before each glVertex, but standard OpenGL uses persistent "current color". The shim tracks color state and auto-injects before each vertex. 2. Missing double-precision functions: glVertex2d, glVertex3d, glColor3d, glColor4d are unimplemented in Emscripten (wontfix). The shim provides these as wrappers to float variants. Update test code to use standard OpenGL patterns (color set once, multiple vertices) to verify the shim works correctly. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- lib/gl_immediate_shim.js | 178 ++++++++++++++++++++++++++++++++ tests/wasm-app/Makefile.wasm | 6 +- tests/wasm-app/minimal_test.cpp | 83 +++++++-------- 3 files changed, 222 insertions(+), 45 deletions(-) create mode 100644 lib/gl_immediate_shim.js diff --git a/lib/gl_immediate_shim.js b/lib/gl_immediate_shim.js new file mode 100644 index 0000000..3e81d1e --- /dev/null +++ b/lib/gl_immediate_shim.js @@ -0,0 +1,178 @@ +/** + * gl_immediate_shim.js + * + * Custom OpenGL immediate mode shims for KiCad WASM port. + * Fixes Emscripten LEGACY_GL_EMULATION issues: + * 1. Color-per-vertex requirement - injects color before each vertex automatically + * 2. Missing double-precision functions (glVertex2d, glVertex3d, glColor3d, glColor4d) + * + * Usage: emcc ... --js-library=lib/gl_immediate_shim.js + */ + +addToLibrary({ + // ================================================================== + // GLImmediateShim - State tracking and function wrapping + // ================================================================== + + $GLImmediateShim__deps: ['$GLImmediate'], + $GLImmediateShim__postset: 'GLImmediateShim.init();', + $GLImmediateShim: { + // Current color state (persistent across vertices) + currentColor: null, + + // Track if we're inside glBegin/glEnd block + inBeginEnd: false, + + // Track if color was called since the last vertex + // This prevents double-injection when code already calls color per vertex + colorCalledSinceLastVertex: false, + + // Original functions we're wrapping + origFns: {}, + + // Initialization flag + initialized: false, + + init: function() { + if (GLImmediateShim.initialized) return; + if (typeof GLImmediate === 'undefined') { + // GLImmediate not ready yet, will be called again + console.log('[GLImmediateShim] Waiting for GLImmediate...'); + return; + } + + console.log('[GLImmediateShim] Initializing OpenGL immediate mode shims'); + + // Initialize current color to white (OpenGL default) + GLImmediateShim.currentColor = new Float32Array([1.0, 1.0, 1.0, 1.0]); + + // Store original functions + GLImmediateShim.origFns = { + glBegin: _glBegin, + glEnd: _glEnd, + glVertex2f: _glVertex2f, + glVertex3f: _glVertex3f, + glColor3f: _glColor3f, + glColor4f: _glColor4f, + }; + + // Install shims + _glBegin = GLImmediateShim.shimBegin; + _glEnd = GLImmediateShim.shimEnd; + _glVertex2f = GLImmediateShim.shimVertex2f; + _glVertex3f = GLImmediateShim.shimVertex3f; + _glColor3f = GLImmediateShim.shimColor3f; + _glColor4f = GLImmediateShim.shimColor4f; + + GLImmediateShim.initialized = true; + console.log('[GLImmediateShim] Initialized successfully'); + }, + + // ================================================================== + // Shim implementations + // ================================================================== + + shimBegin: function(mode) { + GLImmediateShim.inBeginEnd = true; + GLImmediateShim.colorCalledSinceLastVertex = false; + GLImmediateShim.origFns.glBegin(mode); + }, + + shimEnd: function() { + GLImmediateShim.inBeginEnd = false; + GLImmediateShim.origFns.glEnd(); + }, + + shimColor3f: function(r, g, b) { + var c = GLImmediateShim.currentColor; + c[0] = r; + c[1] = g; + c[2] = b; + c[3] = 1.0; + + if (GLImmediateShim.inBeginEnd) { + // Mark that color was explicitly called for this vertex + GLImmediateShim.colorCalledSinceLastVertex = true; + } + + GLImmediateShim.origFns.glColor3f(r, g, b); + }, + + shimColor4f: function(r, g, b, a) { + var c = GLImmediateShim.currentColor; + c[0] = r; + c[1] = g; + c[2] = b; + c[3] = a; + + if (GLImmediateShim.inBeginEnd) { + // Mark that color was explicitly called for this vertex + GLImmediateShim.colorCalledSinceLastVertex = true; + } + + GLImmediateShim.origFns.glColor4f(r, g, b, a); + }, + + // Inject current color before each vertex (only if not already called) + injectColor: function() { + if (!GLImmediateShim.inBeginEnd) return; + + // Only inject color if it wasn't already called for this vertex + if (!GLImmediateShim.colorCalledSinceLastVertex) { + var c = GLImmediateShim.currentColor; + GLImmediateShim.origFns.glColor4f(c[0], c[1], c[2], c[3]); + } + + // Reset the flag for the next vertex + GLImmediateShim.colorCalledSinceLastVertex = false; + }, + + shimVertex2f: function(x, y) { + GLImmediateShim.injectColor(); + GLImmediateShim.origFns.glVertex2f(x, y); + }, + + shimVertex3f: function(x, y, z) { + GLImmediateShim.injectColor(); + GLImmediateShim.origFns.glVertex3f(x, y, z); + }, + }, + + // ================================================================== + // Double-precision vertex functions (missing in Emscripten) + // ================================================================== + + glVertex2d__deps: ['glVertex2f'], + glVertex2d: function(x, y) { + _glVertex2f(x, y); + }, + + glVertex3d__deps: ['glVertex3f'], + glVertex3d: function(x, y, z) { + _glVertex3f(x, y, z); + }, + + glVertex4d__deps: ['glVertex4f'], + glVertex4d: function(x, y, z, w) { + _glVertex4f(x, y, z, w); + }, + + // ================================================================== + // Double-precision color functions + // ================================================================== + + glColor3d__deps: ['glColor3f'], + glColor3d: function(r, g, b) { + _glColor3f(r, g, b); + }, + + glColor4d__deps: ['glColor4f'], + glColor4d: function(r, g, b, a) { + _glColor4f(r, g, b, a); + }, +}); + +// Ensure GLImmediateShim is included in the build +if (typeof extraLibraryFuncs !== 'undefined') { + extraLibraryFuncs.push('$GLImmediateShim'); +} diff --git a/tests/wasm-app/Makefile.wasm b/tests/wasm-app/Makefile.wasm index 91029a5..2b6bbeb 100644 --- a/tests/wasm-app/Makefile.wasm +++ b/tests/wasm-app/Makefile.wasm @@ -20,9 +20,13 @@ CXXFLAGS = -O2 $(WX_CXXFLAGS) # Note: Cannot combine LEGACY_GL_EMULATION with FULL_ES2 - they are mutually exclusive EM_GL_FLAGS = -sLEGACY_GL_EMULATION -sMAX_WEBGL_VERSION=2 +# GL immediate mode shim - fixes Emscripten's color-per-vertex requirement +# and provides missing glVertex2d/glColor4d implementations +GL_SHIM = ../../lib/gl_immediate_shim.js + LDFLAGS = -sALLOW_MEMORY_GROWTH -sERROR_ON_UNDEFINED_SYMBOLS=0 \ -s "EXPORTED_RUNTIME_METHODS=['HEAPU8','HEAP8','HEAP32']" \ - $(EM_GL_FLAGS) $(WX_LDFLAGS) + $(EM_GL_FLAGS) --js-library=$(GL_SHIM) $(WX_LDFLAGS) JS = $(TOOLS_ROOT)/wx.js HTML = $(TOOLS_ROOT)/template.html diff --git a/tests/wasm-app/minimal_test.cpp b/tests/wasm-app/minimal_test.cpp index 7090230..0f6cc0d 100644 --- a/tests/wasm-app/minimal_test.cpp +++ b/tests/wasm-app/minimal_test.cpp @@ -352,13 +352,16 @@ void GLTestCanvas::Render() } // Test 1: Immediate Mode Drawing (glBegin/glEnd) - KiCad uses this heavily +// Using STANDARD OpenGL patterns (color once, multiple vertices) +// This tests the GL shim that handles Emscripten's color-per-vertex requirement void GLTestCanvas::TestImmediateMode() { wxPrintf("[GL TEST] Testing immediate mode (glBegin/glEnd)...\n"); + wxPrintf("[GL TEST] Using STANDARD OpenGL patterns (color set once per primitive)\n"); fflush(stdout); // Test GL_TRIANGLES with glVertex3f and glColor3f - // RGB triangle - each vertex has a different color + // RGB triangle - each vertex has a different color (smooth shading) glBegin(GL_TRIANGLES); glColor3f(1.0f, 0.0f, 0.0f); // Red glVertex3f(-1.0f, -0.5f, 0.0f); @@ -369,50 +372,39 @@ void GLTestCanvas::TestImmediateMode() glEnd(); // Test GL_QUADS with glVertex2f and glColor4f - // NOTE: Emscripten's immediate mode requires color per vertex, not OpenGL's "current color" semantic + // STANDARD OPENGL: Set color ONCE, then multiple vertices + // The GL shim should inject color before each vertex automatically + glColor4f(1.0f, 1.0f, 0.0f, 0.8f); // Yellow, semi-transparent - SET ONCE glBegin(GL_QUADS); - glColor4f(1.0f, 1.0f, 0.0f, 0.8f); // Yellow, semi-transparent - glVertex2f(-1.8f, -1.8f); - glColor4f(1.0f, 1.0f, 0.0f, 0.8f); + glVertex2f(-1.8f, -1.8f); // All 4 vertices use the same yellow color glVertex2f(-1.2f, -1.8f); - glColor4f(1.0f, 1.0f, 0.0f, 0.8f); glVertex2f(-1.2f, -1.2f); - glColor4f(1.0f, 1.0f, 0.0f, 0.8f); glVertex2f(-1.8f, -1.2f); glEnd(); - // Test GL_LINES with glVertex3f (glVertex2d not supported in Emscripten) - // NOTE: color per vertex required + // Test GL_LINES with standard pattern + // STANDARD OPENGL: Set color ONCE, then multiple vertices + glColor3f(1.0f, 1.0f, 1.0f); // White - SET ONCE glBegin(GL_LINES); - glColor3f(1.0f, 1.0f, 1.0f); // White glVertex3f(-1.5f, 1.5f, 0.0f); - glColor3f(1.0f, 1.0f, 1.0f); glVertex3f(1.5f, 1.5f, 0.0f); glEnd(); - // Test GL_LINE_STRIP - // NOTE: color per vertex required + // Test GL_LINE_STRIP with standard pattern + glColor3f(0.0f, 1.0f, 1.0f); // Cyan - SET ONCE glBegin(GL_LINE_STRIP); - glColor3f(0.0f, 1.0f, 1.0f); // Cyan glVertex3f(1.2f, -1.8f, 0.0f); - glColor3f(0.0f, 1.0f, 1.0f); glVertex3f(1.4f, -1.4f, 0.0f); - glColor3f(0.0f, 1.0f, 1.0f); glVertex3f(1.6f, -1.6f, 0.0f); - glColor3f(0.0f, 1.0f, 1.0f); glVertex3f(1.8f, -1.2f, 0.0f); glEnd(); - // Test GL_LINE_LOOP - // NOTE: color per vertex required + // Test GL_LINE_LOOP with standard pattern + glColor3f(1.0f, 0.0f, 1.0f); // Magenta - SET ONCE glBegin(GL_LINE_LOOP); - glColor3f(1.0f, 0.0f, 1.0f); // Magenta glVertex3f(1.2f, 1.2f, 0.0f); - glColor3f(1.0f, 0.0f, 1.0f); glVertex3f(1.8f, 1.2f, 0.0f); - glColor3f(1.0f, 0.0f, 1.0f); glVertex3f(1.8f, 1.8f, 0.0f); - glColor3f(1.0f, 0.0f, 1.0f); glVertex3f(1.2f, 1.8f, 0.0f); glEnd(); @@ -421,26 +413,24 @@ void GLTestCanvas::TestImmediateMode() } // Test 2: Matrix Operations - KiCad uses glPushMatrix/glPopMatrix extensively +// Using STANDARD OpenGL patterns (color once, multiple vertices) void GLTestCanvas::TestMatrixOperations() { wxPrintf("[GL TEST] Testing matrix operations...\n"); fflush(stdout); - // Draw centered triangle - // NOTE: Emscripten requires color per vertex + // Draw centered triangle with standard GL pattern glPushMatrix(); glTranslatef(0.0f, 0.0f, 0.0f); + glColor3f(0.5f, 0.5f, 0.5f); // Gray - SET ONCE glBegin(GL_TRIANGLES); - glColor3f(0.5f, 0.5f, 0.5f); glVertex3f(-0.3f, -0.3f, 0.0f); - glColor3f(0.5f, 0.5f, 0.5f); glVertex3f(0.3f, -0.3f, 0.0f); - glColor3f(0.5f, 0.5f, 0.5f); glVertex3f(0.0f, 0.3f, 0.0f); glEnd(); glPopMatrix(); - // Draw 4 rotated/translated copies + // Draw 4 rotated/translated copies with standard GL pattern for (int i = 0; i < 4; i++) { glPushMatrix(); float angle = i * 90.0f; @@ -451,20 +441,16 @@ void GLTestCanvas::TestMatrixOperations() glRotatef(angle, 0.0f, 0.0f, 1.0f); glScalef(0.5f, 0.5f, 1.0f); - // Draw colored square - // NOTE: Emscripten requires color per vertex + // Draw colored square with standard GL pattern float r = (i == 0 || i == 3) ? 1.0f : 0.3f; float g = (i == 1 || i == 3) ? 1.0f : 0.3f; float b = (i == 2 || i == 3) ? 1.0f : 0.3f; + glColor3f(r, g, b); // SET ONCE before glBegin glBegin(GL_QUADS); - glColor3f(r, g, b); glVertex3f(-0.5f, -0.5f, 0.0f); - glColor3f(r, g, b); glVertex3f(0.5f, -0.5f, 0.0f); - glColor3f(r, g, b); glVertex3f(0.5f, 0.5f, 0.0f); - glColor3f(r, g, b); glVertex3f(-0.5f, 0.5f, 0.0f); glEnd(); glPopMatrix(); @@ -530,6 +516,7 @@ void GLTestCanvas::TestVertexArrays() } // Test 4: State Management - glEnable/glDisable, blending +// Using STANDARD OpenGL patterns with separate glBegin/glEnd for each color void GLTestCanvas::TestStateManagement() { wxPrintf("[GL TEST] Testing state management...\n"); @@ -539,24 +526,30 @@ void GLTestCanvas::TestStateManagement() glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - // Draw overlapping semi-transparent squares + // Draw overlapping semi-transparent squares using standard GL pattern + // Each quad has its own color set before glBegin + + // Red square + glColor4f(1.0f, 0.0f, 0.0f, 0.5f); // SET ONCE glBegin(GL_QUADS); - // Red square - glColor4f(1.0f, 0.0f, 0.0f, 0.5f); glVertex2f(-1.0f, -1.0f); glVertex2f(0.5f, -1.0f); glVertex2f(0.5f, 0.5f); glVertex2f(-1.0f, 0.5f); + glEnd(); - // Green square - glColor4f(0.0f, 1.0f, 0.0f, 0.5f); + // Green square + glColor4f(0.0f, 1.0f, 0.0f, 0.5f); // SET ONCE + glBegin(GL_QUADS); glVertex2f(-0.5f, -0.5f); glVertex2f(1.0f, -0.5f); glVertex2f(1.0f, 1.0f); glVertex2f(-0.5f, 1.0f); + glEnd(); - // Blue square - glColor4f(0.0f, 0.0f, 1.0f, 0.5f); + // Blue square + glColor4f(0.0f, 0.0f, 1.0f, 0.5f); // SET ONCE + glBegin(GL_QUADS); glVertex2f(0.0f, 0.0f); glVertex2f(1.5f, 0.0f); glVertex2f(1.5f, 1.5f); @@ -570,13 +563,14 @@ void GLTestCanvas::TestStateManagement() } // Test 5: Texture coordinates (no actual texture, just testing the calls) +// Note: glTexCoord is per-vertex anyway, and color is set once before glBegin void GLTestCanvas::TestTexCoords() { wxPrintf("[GL TEST] Testing texture coordinates...\n"); fflush(stdout); + glColor3f(0.8f, 0.8f, 0.8f); // SET ONCE glBegin(GL_QUADS); - glColor3f(0.8f, 0.8f, 0.8f); glTexCoord2f(0.0f, 0.0f); glVertex2f(-1.0f, -1.0f); glTexCoord2f(1.0f, 0.0f); glVertex2f(1.0f, -1.0f); glTexCoord2f(1.0f, 1.0f); glVertex2f(1.0f, 1.0f); @@ -588,13 +582,14 @@ void GLTestCanvas::TestTexCoords() } // Test 6: Normal vectors (for lighting, which we're not testing but calls should work) +// Note: glNormal is per-vertex, and color is set once before glBegin void GLTestCanvas::TestNormals() { wxPrintf("[GL TEST] Testing normal vectors...\n"); fflush(stdout); + glColor3f(0.7f, 0.7f, 0.9f); // SET ONCE glBegin(GL_TRIANGLES); - glColor3f(0.7f, 0.7f, 0.9f); glNormal3f(0.0f, 0.0f, 1.0f); glVertex3f(-1.0f, -1.0f, 0.0f); glNormal3f(0.0f, 0.0f, 1.0f);