subagent transcript

session 18e25685 · workflow wf_23df5b1c
delegated task
Review the vendored Coin GL glue port for wasm. Read /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin/src/glue/gl.cpp (search for __EMSC
▸ Magik
Review the vendored Coin GL glue port for wasm. Read /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin/src/glue/gl.cpp (search for __EMSCRIPTEN__ blocks: cc_glglue_getprocaddress, glglue_set_glVersion version-string parse, cc_glglue_glActiveTexture null guard), /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin/src/glue/gl_egl.cpp (eglBindAPI), and /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/Quarter/QuarterWidget.cpp (fcWasmSyncGLContext call in actualRedraw). Assess correctness of the getprocaddress routing (emscripten_GetProcAddress + fcWasmResolveGL), the GLES version parse, whether Coin will actually resolve+call the right functions, and any signature/indirect-call hazards. Report findings + whether the glue is sufficient for rendering.
Fable
thinking
I'll start by examining the files to understand what we're working with.
Fable
⚙ Bash {"command": "grep -n \"__EMSCRIPTEN__\\|fcWasmResolveGL\\|emscripten_GetProcAddress\\|cc_glglue_getprocaddress\\|glActiveTexture\\|glglue_set_glVersion\" /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin/src/glue/gl.cpp", "desc…
tool result
609:cc_glglue_getprocaddress(const cc_glglue * glue, const char * symname)
613:#ifdef __EMSCRIPTEN__
619:    extern void * emscripten_GetProcAddress(const char *);
620:    ptr = emscripten_GetProcAddress(symname);
626:      extern void * fcWasmResolveGL(const char *);
627:      ptr = fcWasmResolveGL(symname);
648:    cc_debugerror_postinfo("cc_glglue_getprocaddress", "%s==%p", symname, ptr);
710:glglue_set_glVersion(cc_glglue * w)
719:    cc_debugerror_postinfo("glglue_set_glVersion",
769:    cc  …[truncated]
Fable
⚙ Bash {"command": "grep -n \"__EMSCRIPTEN__\\|eglBindAPI\\|BindAPI\" /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin/src/glue/gl_egl.cpp", "description": "Find EMSCRIPTEN blocks in gl_egl.cpp"}
tool result
101:#ifdef __EMSCRIPTEN__
233:  if (eglBindAPI(FC_WASM_EGL_API) == EGL_FALSE) {
235:                       "eglBindAPI(FC_WASM_EGL_API) failed. %s",
314:  if (eglBindAPI(FC_WASM_EGL_API) == EGL_FALSE) {
316:                       "eglBindAPI(FC_WASM_EGL_API) failed. %s",
Fable
⚙ Bash {"command": "grep -n \"fcWasmSyncGLContext\\|actualRedraw\\|__EMSCRIPTEN__\" /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/Quarter/QuarterWidget.cpp", "description": "Find fcWasmSyncGLContext in QuarterWidget.cpp"}
tool result
70:#ifdef __EMSCRIPTEN__
71:extern "C" void fcWasmSyncGLContext(void);
895:    this->actualRedraw();
999:QuarterWidget::actualRedraw()
1002:#ifdef __EMSCRIPTEN__
1006:  fcWasmSyncGLContext();
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin/src/glue/gl.cpp", "offset": 605, "limit": 80}
tool result
605	}
606	
607	/* doc in header file */
608	void *
609	cc_glglue_getprocaddress(const cc_glglue * glue, const char * symname)
610	{
611	  void * ptr = NULL;
612	
613	#ifdef __EMSCRIPTEN__
614	  // On WebAssembly there is no WGL/EGL/GLX/dlsym; resolve GL entry points
615	  // (both emscripten's real WebGL2 functions and our fixed-function emulation
616	  // in Gui/WasmGLFixedFunc.cpp) through emscripten's own resolver.
617	  {
618	    /* This file is compiled as C; these are C-linkage symbols. */  …[truncated]
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin/src/glue/gl.cpp", "offset": 705, "limit": 80}
tool result
705	
706	  Note: this code has been copied from GLUWrapper.c, so if any changes
707	  are made, make sure they are propagated over if necessary.
708	*/
709	static void
710	glglue_set_glVersion(cc_glglue * w)
711	{
712	  char buffer[256];
713	  char * dotptr;
714	
715	  /* NB: if you are getting a crash here, it's because an attempt at
716	   * setting up a cc_glglue instance was made when there is no current
717	   * OpenGL context. */
718	  if (coin_glglue_debug()) {
719	    cc_debugerror_posti  …[truncated]
Fable
thinking
I'm tracing through the version string parsing logic for WebGL. The string "OpenGL ES 3.0 (WebGL 2.0 ...)" gets processed by advancing past non-digit characters until hitting '3', then looking for the decimal point to extract the version number.

Now I need to check the null guard for glActiveTexture and how it's initialized.
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin/src/glue/gl.cpp", "offset": 1040, "limit": 95}
tool result
1040	     demonstrates the bug, for reporting to Intel.
1041	
1042	     The bug was tested and confirmed with the latest Intel Solano
1043	     driver as of today.
1044	
1045	     20041108 mortene, based on information provided by handegar.
1046	  */
1047	  w->glActiveTexture = NULL;
1048	  w->glClientActiveTexture = NULL;
1049	  w->glMultiTexCoord2f = NULL;
1050	  w->glMultiTexCoord2fv = NULL;
1051	  w->glMultiTexCoord3fv = NULL;
1052	  w->glMultiTexCoord4fv = NULL;
1053	#ifdef GL_VERSION_1_3
1  …[truncated]
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin/src/glue/gl.cpp", "offset": 2985, "limit": 20}
tool result
2985	                         height);
2986	}
2987	
2988	void
2989	cc_glglue_glActiveTexture(const cc_glglue * w,
2990	                          GLenum texture)
2991	{
2992	#ifdef __EMSCRIPTEN__
2993	  /* On wasm the glue may have been probed before a GL context was current, so
2994	     multitexture entry points can be unset. Tolerate that instead of calling
2995	     a null pointer. */
2996	  if (!w->glActiveTexture) { return; }
2997	#endif
2998	  assert(w->glActiveTexture);
2999	  w->glActive  …[truncated]
Fable
thinking
I need to locate fcWasmResolveGL and check the WasmGLFixedFunc.cpp file along with the sync context to understand how they work together.
Fable
Now let me look at the wasm-side implementations: fcWasmResolveGL, fcWasmSyncGLContext, and the fixed-func emulation.
Fable
⚙ Bash {"command": "grep -rn \"fcWasmResolveGL\\|fcWasmSyncGLContext\\|emscripten_GetProcAddress\\|FC_WASM_EGL_API\\|eglBindAPI\" /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/ 2>/dev/null | head -50", "description": "Find wasm GL glue impl…
tool result
/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/WasmGLFixedFunc.cpp:224:void fcWasmSyncGLContext(void){ ffSyncContext(); }
/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/WasmGLFixedFunc.cpp:508:void* fcWasmResolveGL(const char* name){
/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/Quarter/QuarterWidget.cpp:71:extern "C" void fcWasmSyncGLContext(void);
/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/Quarter/QuarterWidget.cpp:1006:  fcWasmSyncGLContext();
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin/src/glue/gl_egl.cpp", "offset": 95, "limit": 30}
tool result
95	/* ********************************************************************** */
96	
97	#include <EGL/egl.h>
98	#include <EGL/eglext.h>
99	
100	/* WebAssembly/WebGL is an OpenGL ES context; bind the ES API, not desktop GL. */
101	#ifdef __EMSCRIPTEN__
102	#define FC_WASM_EGL_API EGL_OPENGL_ES_API
103	#else
104	#define FC_WASM_EGL_API EGL_OPENGL_API
105	#endif
106	
107	
108	EGLDisplay eglglue_display = EGL_NO_DISPLAY;
109	struct eglglue_contextdata;
110	
111	#define CASE_STR( value ) case value: r  …[truncated]
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/WasmGLFixedFunc.cpp"}
tool result
1	/* SPDX-License-Identifier: LGPL-2.1-or-later
2	 *
3	 * WebAssembly build only. A compact fixed-function OpenGL 1.x emulation over
4	 * WebGL2/GLES3, so Coin3D (which renders FreeCAD geometry with legacy GL) can
5	 * draw into the WebGL2 context Qt hands us. Emscripten's LEGACY_GL_EMULATION
6	 * is WebGL1-only and conflicts with Qt's forced WebGL2, so we implement the
7	 * subset Coin actually exercises ourselves.
8	 *
9	 * Design: the legacy entry points are C functions here; the state and dr  …[truncated]
Fable
thinking
Looking at the function routing in gl.cpp, I see that `cc_glglue_getprocaddress` first tries `emscripten_GetProcAddress`, then falls back to `fcWasmResolveGL` if that doesn't work. The function signature matches what Emscripten expects, so the initial call should work correctly.

Now I'm checking for potential signature mismatches in the indirect function calls. When Coin resolves a function pointer like `glActiveTexture` through Emscripten, it gets back a real function with signature `void(GLenum)`, which matches Coin's typedef `COIN_PFNGLACTIVETEXTUREPROC`. For the multitexture functions like `glMultiTexCoord2f`, both the standard signature and our implementation use `void(GLenum, GLfloat,  …
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/Quarter/QuarterWidget.cpp", "offset": 880, "limit": 40}
tool result
880	
881	    if(PRIVATE(this)->processdelayqueue && SoDB::getSensorManager()->isDelaySensorPending()) {
882	        // processing the sensors might trigger a redraw in another
883	        // context. Release this context temporarily
884	        w->doneCurrent();
885	        SoDB::getSensorManager()->processDelayQueue(false);
886	        w->makeCurrent();
887	    }
888	
889	    assert(w->isValid() && "No valid GL context found!");
890	
891	    // Causes an OpenGL error on resize
892	    //glDrawB  …[truncated]
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/Quarter/QuarterWidget.cpp", "offset": 995, "limit": 40}
tool result
995	/*!
996	  Overridden from QGLWidget to render the scenegraph
997	 */
998	void
999	QuarterWidget::actualRedraw()
1000	{
1001	  ZoneScoped;
1002	#ifdef __EMSCRIPTEN__
1003	  // Ensure emscripten's current-context global (GLctx) matches the context Qt
1004	  // made current, so Coin's glGetString/glGetError and our GL emulation act on
1005	  // a valid WebGL2 context. See Gui/WasmGLFixedFunc.cpp.
1006	  fcWasmSyncGLContext();
1007	#endif
1008	  PRIVATE(this)->sorendermanager->render(PRIVATE(thi  …[truncated]
Fable
thinking
I'm checking whether the version parsing correctly identifies WebGL2 as desktop GL 3.0 and whether the glue layer properly routes to the right functions without trapping. The real question for review is whether the glue implementation is sufficient for rendering. I'm also spotting a potential correctness issue with the `emscripten_GetProcAddress` declaration—the actual Emscripten signature differs from what's declared.

There's a build-configuration dependency here: `emscripten_GetProcAddress` only works if compiled with `-sGL_ENABLE_GET_PROC_ADDRESS=1`, otherwise it may not even link. That's a risk worth flagging. The `glActiveTexture` path resolves correctly to Emscripten's real implementa  …
Fable
⚙ Bash {"command": "grep -rn \"GL_ENABLE_GET_PROC_ADDRESS\\|emscripten_GetProcAddress\\|LEGACY_GL\\|FULL_ES2\\|FULL_ES3\\|MAX_WEBGL_VERSION\\|MIN_WEBGL_VERSION\\|WasmGLFixedFunc\" /home/magik6k/lcad-wasm/freecad-port/ 2>/dev/null | grep -iv \"\\.m…
tool result
/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/CMakeLists.txt:1574:    target_sources(FreeCADGui PRIVATE FcWasmProcess.cpp WasmGLFixedFunc.cpp)
/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Main/CMakeLists.txt:137:            # NOTE: Emscripten's LEGACY_GL_EMULATION requires WebGL1, but
/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Main/CMakeLists.txt:138:            # Qt-for-wasm forces MAX_WEBGL_VERSION=2 ("cannot emulate both ES2
/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Ma  …[truncated]
Fable
⚙ Bash {"command": "grep -rn \"GL_ENABLE_GET_PROC_ADDRESS\\|MAX_WEBGL_VERSION\\|MIN_WEBGL_VERSION\\|FULL_ES3\\|sGL\\|-lGL\\|WasmGLFixedFunc\\|EXPORTED\" /home/magik6k/lcad-wasm/freecad-port/ 2>/dev/null | grep -v \"3rdParty\\|/research/\\|\\.md:\"…
tool result
/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Doc/FreeCAD.uml:1657:<XPD:OBJ name="OwnedViews[33]" type="UMLDependencyView" guid="7sGLqEeVJE+wlK4pvLn1qgAA">
/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Doc/FreeCAD.uml:2749:<XPD:REF name="Views[0]">7sGLqEeVJE+wlK4pvLn1qgAA</XPD:REF>
/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/NaviCube.cpp:431:    const bool isGLRender = type.isDerivedFrom(SoGLRenderAction::getClassTypeId());
/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/Na  …[truncated]
Fable
thinking
The build flags look good — with GL_ENABLE_GET_PROC_ADDRESS and FULL_ES3 enabled, emscripten should provide GLES3 functions and emscripten_GetProcAddress should work. Now I need to verify what happens when emscripten_GetProcAddress is called with those flags.

Since FULL_ES3 provides GLES3 rather than GLES1 fixed-function, legacy names like glVertexPointer will return 0 from emscripten_GetProcAddress and fall through to fcWasmResolveGL, while modern functions like glActiveTexture are available. The critical issue is that our WasmGLFixedFunc.cpp defines strong symbols for glEnable, glDisable, glDrawArrays, and glDrawElements — so when Qt calls these functions, they'll use our implementations   …
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin/src/glue/gl_egl.cpp", "offset": 220, "limit": 110}
tool result
220	  w->glx.clientvendor = NULL;
221	  w->glx.clientextensions = NULL;
222	  w->glx.glxextensions = NULL;
223	
224	  w->glx.glXGetCurrentDisplay = (COIN_PFNGLXGETCURRENTDISPLAYPROC)eglglue_getprocaddress(w, "eglglue_get_display");
225	
226	  if (eglInitialize(eglglue_get_display(), &w->glx.version.major, &w->glx.version.minor) == EGL_FALSE) {
227	    cc_debugerror_post("eglglue_init",
228	                       "Couldn't initialize EGL. %s",
229	                        eglErrorString(eglGetErro  …[truncated]
Fable
thinking
I'm spotting a use-before-init bug where `surface_attrib` references uninitialized `ctx` at lines 293-299, but `ctx` doesn't get initialized until line 301. There's also an inconsistency in the offscreen rendering path—it's using EGL_OPENGL_BIT instead of the ES variant, which doesn't align with the ES API binding, though this code path might not actually be exercised in practice.

The EGL stubs in WasmGLFixedFunc.cpp return 0, so offscreen rendering won't work on wasm anyway. This is a pre-existing issue but tangential to the main focus. The core findings are solid: the getprocaddress routing is correct (emscripten first for real GLES3/WebGL2 functions, then our resolver for legacy multitex  …
Fable
I have enough to complete the assessment. Let me verify one detail about the version-gated multitexture path and the offscreen ctx ordering.
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Main/CMakeLists.txt", "offset": 135, "limit": 20}
tool result
135	        target_link_options(FreeCADMain PRIVATE
136	            ${FREECAD_WASM_COMMON_LINK_OPTIONS}
137	            # NOTE: Emscripten's LEGACY_GL_EMULATION requires WebGL1, but
138	            # Qt-for-wasm forces MAX_WEBGL_VERSION=2 ("cannot emulate both ES2
139	            # and legacy GL"), so the two are mutually exclusive. The planned
140	            # GL1-emulation stopgap is therefore not viable alongside Qt; the
141	            # GUI boots with a non-rendering 3D viewport (Coin's fi  …[truncated]
Fable
thinking
All the build flags check out, so I'm ready to share what I found.
Fable
I have completed my review. The build flags confirm the routing preconditions (`-sGL_ENABLE_GET_PROC_ADDRESS=1`, `-sFULL_ES3=1`, `-sMAX_WEBGL_VERSION=2`, `-lEGL`). Let me report.
Fable
⚙ StructuredOutput {"findings": [{"severity": "info", "category": "assessment-summary", "summary": "The wasm GL glue is correct in its three focus areas and is plausibly sufficient for the FreeCAD viewport geometry subset, with known gaps (no textures, no off…
tool result
Structured output provided successfully