feat(webgl): Fix WebGL GAL rendering with proper coordinate setup
- Fix coordinate system for 1:1 pixel mapping (was using nm scale) - Set worldUnitLength to 1/96 to match native test setup - Add SetLookAtPoint and SetZoomFactor for proper view transformation - Fix glDrawBuffers for WebGL 2.0 (array index must match attachment) - Add legacy_gl_stubs.js for wxWidgets compatibility - Add isReady() check to prevent race condition in Playwright tests All 28 GAL test scenarios now render correctly in WebGL. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
d2f1a496f5
commit
18c73c355e
8 changed files with 165 additions and 27 deletions
|
|
@ -129,13 +129,23 @@
|
|||
|
||||
// Run a single scenario
|
||||
function runScenario(index) {
|
||||
if (!wasmModule) return;
|
||||
console.log('[DEBUG JS] runScenario called with index:', index, 'wasmModule:', !!wasmModule);
|
||||
if (!wasmModule) {
|
||||
console.log('[DEBUG JS] wasmModule is null, returning early');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = wasmModule.ccall('runScenario', 'number', ['number'], [index]);
|
||||
if (result === 0) {
|
||||
setStatus(`Rendered: ${SCENARIO_NAMES[index]}`);
|
||||
} else {
|
||||
setStatus(`ERROR: Failed scenario ${index}`);
|
||||
try {
|
||||
console.log('[DEBUG JS] Calling ccall...');
|
||||
const result = wasmModule.ccall('runScenario', 'number', ['number'], [index]);
|
||||
console.log('[DEBUG JS] ccall returned:', result);
|
||||
if (result === 0) {
|
||||
setStatus(`Rendered: ${SCENARIO_NAMES[index]}`);
|
||||
} else {
|
||||
setStatus(`ERROR: Failed scenario ${index}`);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[DEBUG JS] ccall threw error:', e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -182,7 +192,8 @@
|
|||
runAllScenarios,
|
||||
getScenarioName: (index) => SCENARIO_NAMES[index],
|
||||
getTotalScenarios: () => SCENARIO_NAMES.length,
|
||||
getGLCanvas: getGLCanvas
|
||||
getGLCanvas: getGLCanvas,
|
||||
isReady: () => wasmModule !== null
|
||||
};
|
||||
|
||||
// Create canvas immediately (before module loads)
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ test.describe('GAL WebGL Regression Tests', () => {
|
|||
|
||||
// Wait for module to be ready
|
||||
await page.waitForFunction(() => {
|
||||
return (window as any).galTest !== undefined;
|
||||
return (window as any).galTest !== undefined && (window as any).galTest.isReady();
|
||||
}, { timeout: 60000 });
|
||||
|
||||
// Run the scenario
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# Makefile for GAL WebGL Test (WASM)
|
||||
#
|
||||
# Builds WEBGL_GAL (ported from OPENGL_GAL) using wxWidgets WASM build.
|
||||
# Uses LEGACY_GL_EMULATION for legacy OpenGL compatibility.
|
||||
# Uses pure WebGL 2.0 / OpenGL ES 3.0 (no LEGACY_GL_EMULATION).
|
||||
#
|
||||
# Usage:
|
||||
# make # Build
|
||||
|
|
@ -72,7 +72,14 @@ BASE_LDFLAGS = -sALLOW_MEMORY_GROWTH=1 \
|
|||
# No LEGACY_GL_EMULATION - WEBGL_GAL uses VBOs and custom shaders
|
||||
EM_GL_FLAGS = -sFULL_ES3=1 -sMAX_WEBGL_VERSION=2
|
||||
|
||||
LDFLAGS = $(DEBUG_LDFLAGS) $(BASE_LDFLAGS) $(EM_GL_FLAGS) $(WX_LDFLAGS)
|
||||
# Legacy GL stubs for wxWidgets compatibility
|
||||
# wxWidgets GL library may reference legacy functions at runtime, but actual
|
||||
# rendering uses WEBGL_GAL which only uses modern WebGL 2.0 APIs
|
||||
# Use absolute path to ensure the library is found
|
||||
MAKEFILE_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
|
||||
LEGACY_GL_STUBS = --js-library=$(MAKEFILE_DIR)legacy_gl_stubs.js
|
||||
|
||||
LDFLAGS = $(DEBUG_LDFLAGS) $(BASE_LDFLAGS) $(EM_GL_FLAGS) $(LEGACY_GL_STUBS) $(WX_LDFLAGS)
|
||||
|
||||
# Templates
|
||||
JS = $(TOOLS_ROOT)/wx.js
|
||||
|
|
|
|||
|
|
@ -79,6 +79,9 @@ extern "C" {
|
|||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
int runScenario(int scenarioIndex) {
|
||||
printf("[DEBUG] runScenario called with index %d, g_gal=%p\n",
|
||||
scenarioIndex, (void*)g_gal);
|
||||
|
||||
if (scenarioIndex < 0 || scenarioIndex >= g_totalScenarios) {
|
||||
printf("ERROR: Invalid scenario index %d (valid: 0-%d)\n",
|
||||
scenarioIndex, g_totalScenarios - 1);
|
||||
|
|
@ -163,16 +166,20 @@ 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));
|
||||
|
||||
// Set default world coordinates (matching native test)
|
||||
double worldScale = 1.0 / 10000.0; // Convert nm to screen units
|
||||
g_gal->SetWorldUnitLength(worldScale);
|
||||
// CRITICAL: Set worldUnitLength for 1:1 world-to-screen coordinate mapping
|
||||
// GAL default worldUnitLength is for PCB nanometers, which would compress
|
||||
// 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 world boundaries
|
||||
VECTOR2D worldSize(CANVAS_WIDTH / worldScale, CANVAS_HEIGHT / worldScale);
|
||||
BOX2D worldBounds(VECTOR2D(0, 0), worldSize);
|
||||
g_gal->SetWorldScreenMatrix(g_gal->GetWorldScreenMatrix());
|
||||
// Set up coordinate transformation for 1:1 world-to-screen mapping
|
||||
// LookAtPoint should be at center, ZoomFactor of 1.0 gives 1:1 mapping
|
||||
g_gal->SetLookAtPoint(VECTOR2D(CANVAS_WIDTH / 2.0, CANVAS_HEIGHT / 2.0));
|
||||
g_gal->SetZoomFactor(1.0);
|
||||
g_gal->ComputeWorldScreenMatrix();
|
||||
|
||||
// Initialize the compositor with proper context locking
|
||||
g_gal->LockContext(-1); // Lock with init ID
|
||||
|
|
|
|||
|
|
@ -129,13 +129,23 @@
|
|||
|
||||
// Run a single scenario
|
||||
function runScenario(index) {
|
||||
if (!wasmModule) return;
|
||||
console.log('[DEBUG JS] runScenario called with index:', index, 'wasmModule:', !!wasmModule);
|
||||
if (!wasmModule) {
|
||||
console.log('[DEBUG JS] wasmModule is null, returning early');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = wasmModule.ccall('runScenario', 'number', ['number'], [index]);
|
||||
if (result === 0) {
|
||||
setStatus(`Rendered: ${SCENARIO_NAMES[index]}`);
|
||||
} else {
|
||||
setStatus(`ERROR: Failed scenario ${index}`);
|
||||
try {
|
||||
console.log('[DEBUG JS] Calling ccall...');
|
||||
const result = wasmModule.ccall('runScenario', 'number', ['number'], [index]);
|
||||
console.log('[DEBUG JS] ccall returned:', result);
|
||||
if (result === 0) {
|
||||
setStatus(`Rendered: ${SCENARIO_NAMES[index]}`);
|
||||
} else {
|
||||
setStatus(`ERROR: Failed scenario ${index}`);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[DEBUG JS] ccall threw error:', e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -182,7 +192,8 @@
|
|||
runAllScenarios,
|
||||
getScenarioName: (index) => SCENARIO_NAMES[index],
|
||||
getTotalScenarios: () => SCENARIO_NAMES.length,
|
||||
getGLCanvas: getGLCanvas
|
||||
getGLCanvas: getGLCanvas,
|
||||
isReady: () => wasmModule !== null
|
||||
};
|
||||
|
||||
// Create canvas immediately (before module loads)
|
||||
|
|
|
|||
91
tests/gal-regression/wasm/legacy_gl_stubs.js
Normal file
91
tests/gal-regression/wasm/legacy_gl_stubs.js
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
/**
|
||||
* Legacy GL stubs for wxWidgets compatibility
|
||||
*
|
||||
* wxWidgets GL library may reference these functions at runtime even though:
|
||||
* 1. wxGLAPI methods are stubbed as no-ops in C++ for WASM builds
|
||||
* 2. KiCad doesn't use wxGLAPI - it has its own GAL
|
||||
* 3. Our WEBGL_GAL uses modern WebGL 2.0 / OpenGL ES 3.0 only
|
||||
*
|
||||
* These no-op stubs satisfy the linker and runtime without affecting rendering.
|
||||
* All actual rendering goes through WEBGL_GAL which uses VBOs and shaders.
|
||||
*/
|
||||
|
||||
addToLibrary({
|
||||
// Immediate mode rendering (replaced by VBOs in WEBGL_GAL)
|
||||
glBegin: function(mode) {},
|
||||
glEnd: function() {},
|
||||
|
||||
// Vertex submission (replaced by vertex manager in WEBGL_GAL)
|
||||
glVertex2f: function(x, y) {},
|
||||
glVertex3f: function(x, y, z) {},
|
||||
glVertex2d: function(x, y) {},
|
||||
glVertex3d: function(x, y, z) {},
|
||||
glVertex4f: function(x, y, z, w) {},
|
||||
glVertex4d: function(x, y, z, w) {},
|
||||
|
||||
// Color (replaced by vertex attributes/uniforms in WEBGL_GAL)
|
||||
glColor3f: function(r, g, b) {},
|
||||
glColor4f: function(r, g, b, a) {},
|
||||
glColor3d: function(r, g, b) {},
|
||||
glColor4d: function(r, g, b, a) {},
|
||||
glColor3ub: function(r, g, b) {},
|
||||
glColor4ub: function(r, g, b, a) {},
|
||||
|
||||
// Texture coordinates (replaced by vertex attributes in WEBGL_GAL)
|
||||
glTexCoord2f: function(s, t) {},
|
||||
glTexCoord2d: function(s, t) {},
|
||||
|
||||
// Normals (replaced by vertex attributes in WEBGL_GAL)
|
||||
glNormal3f: function(nx, ny, nz) {},
|
||||
glNormal3d: function(nx, ny, nz) {},
|
||||
|
||||
// Matrix mode (replaced by MVP matrix uniform in WEBGL_GAL)
|
||||
glMatrixMode: function(mode) {},
|
||||
glLoadIdentity: function() {},
|
||||
glPushMatrix: function() {},
|
||||
glPopMatrix: function() {},
|
||||
|
||||
// Projection (replaced by computeOrthoMatrix in WEBGL_GAL)
|
||||
glOrtho: function(left, right, bottom, top, near, far) {},
|
||||
glFrustum: function(left, right, bottom, top, near, far) {},
|
||||
|
||||
// Matrix operations (replaced by glm in WEBGL_GAL)
|
||||
glLoadMatrixf: function(m) {},
|
||||
glLoadMatrixd: function(m) {},
|
||||
glMultMatrixf: function(m) {},
|
||||
glMultMatrixd: function(m) {},
|
||||
glTranslatef: function(x, y, z) {},
|
||||
glTranslated: function(x, y, z) {},
|
||||
glRotatef: function(angle, x, y, z) {},
|
||||
glRotated: function(angle, x, y, z) {},
|
||||
glScalef: function(x, y, z) {},
|
||||
glScaled: function(x, y, z) {},
|
||||
|
||||
// Fixed function pipeline (not available in WebGL)
|
||||
glShadeModel: function(mode) {},
|
||||
glLightfv: function(light, pname, params) {},
|
||||
glLightf: function(light, pname, param) {},
|
||||
glMaterialfv: function(face, pname, params) {},
|
||||
glMaterialf: function(face, pname, param) {},
|
||||
glColorMaterial: function(face, mode) {},
|
||||
|
||||
// Legacy client state (replaced by glVertexAttribPointer in WEBGL_GAL)
|
||||
glEnableClientState: function(array) {},
|
||||
glDisableClientState: function(array) {},
|
||||
glVertexPointer: function(size, type, stride, pointer) {},
|
||||
glColorPointer: function(size, type, stride, pointer) {},
|
||||
glTexCoordPointer: function(size, type, stride, pointer) {},
|
||||
glNormalPointer: function(type, stride, pointer) {},
|
||||
|
||||
// Legacy texture environment (replaced by shaders in WEBGL_GAL)
|
||||
glTexEnvf: function(target, pname, param) {},
|
||||
glTexEnvi: function(target, pname, param) {},
|
||||
glTexEnvfv: function(target, pname, params) {},
|
||||
|
||||
// glDrawBuffer (replaced by glDrawBuffers in WebGL 2.0)
|
||||
glDrawBuffer: function(buf) {},
|
||||
|
||||
// Legacy index mode (not supported in WebGL)
|
||||
glIndexi: function(c) {},
|
||||
glIndexf: function(c) {},
|
||||
});
|
||||
|
|
@ -329,9 +329,20 @@ void WEBGL_COMPOSITOR::SetBuffer( unsigned int aBufferHandle )
|
|||
if( m_curFbo != DIRECT_RENDERING )
|
||||
{
|
||||
m_curBuffer = aBufferHandle - 1;
|
||||
|
||||
// WebGL 2.0/OpenGL ES 3.0: use glDrawBuffers instead of glDrawBuffer
|
||||
GLenum drawBuffers[] = { m_buffers[m_curBuffer].attachmentPoint };
|
||||
glDrawBuffers( 1, drawBuffers );
|
||||
// In WebGL 2.0, the draw buffer array index must match the attachment index.
|
||||
// So for GL_COLOR_ATTACHMENTn, we need array[n] = GL_COLOR_ATTACHMENTn
|
||||
GLenum attachmentPoint = m_buffers[m_curBuffer].attachmentPoint;
|
||||
unsigned int attachmentIndex = attachmentPoint - GL_COLOR_ATTACHMENT0;
|
||||
|
||||
// Create draw buffers array with GL_NONE for all entries except the target
|
||||
GLenum drawBuffers[16];
|
||||
for( unsigned int i = 0; i <= attachmentIndex && i < 16; i++ )
|
||||
drawBuffers[i] = GL_NONE;
|
||||
drawBuffers[attachmentIndex] = attachmentPoint;
|
||||
|
||||
glDrawBuffers( attachmentIndex + 1, drawBuffers );
|
||||
checkGlError( "setting draw buffer", __FILE__, __LINE__ );
|
||||
|
||||
glViewport( 0, 0, m_buffers[m_curBuffer].dimensions.x, m_buffers[m_curBuffer].dimensions.y );
|
||||
|
|
|
|||
|
|
@ -751,7 +751,7 @@ void WEBGL_GAL::BeginDrawing()
|
|||
|
||||
m_shader->Use();
|
||||
m_shader->SetParameter( ufm_fontTexture, (int) FONT_TEXTURE_UNIT );
|
||||
m_shader->SetParameter( ufm_fontTextureWidth, (int) font_image.width );
|
||||
m_shader->SetParameter( ufm_fontTextureWidth, (float) font_image.width );
|
||||
m_shader->Deactivate();
|
||||
checkGlError( "setting bitmap font sampler as shader parameter", __FILE__, __LINE__ );
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue