Add GL immediate mode shim for Emscripten compatibility
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 <noreply@anthropic.com>
This commit is contained in:
parent
0db33b10f7
commit
3287ec7616
3 changed files with 222 additions and 45 deletions
178
lib/gl_immediate_shim.js
Normal file
178
lib/gl_immediate_shim.js
Normal file
|
|
@ -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');
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue