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