subagent transcript
session 18e25685 · workflow wf_23df5b1c
delegated task
You are an adversarial verifier for a FreeCAD-wasm code review. A reviewer claims: SEVERITY: critical CATEGORY: render-path-bypass FILE: src/Gui/WasmG
▸ Magik
You are an adversarial verifier for a FreeCAD-wasm code review. A reviewer claims:
SEVERITY: critical
CATEGORY: render-path-bypass
FILE: src/Gui/WasmGLFixedFunc.cpp
SUMMARY: Coin's primary vertex-array/VBO draw path resolves glDrawArrays/glDrawElements to emscripten's real WebGL2 functions, bypassing the fixed-function emulation entirely, so faces/edges never reach ff_setup_and_draw and the viewport stays blank.
DETAIL/EVIDENCE: The emulation intercepts draws only by defining C symbols glDrawArrays/glDrawElements (WasmGLFixedFunc.cpp:397-406). But Coin does NOT call those symbols directly for its main path; it calls through function pointers glue->glDrawArrays/glue->glDrawElements, populated by PROC()/cc_glglue_getprocaddress (src/3rdParty/coin/src/glue/gl.cpp:1286-1287). That resolver checks emscripten_GetProcAddress FIRST and returns on success (gl.cpp:619-628); glDrawArrays/glDrawElements/glDrawRangeElements are WebGL2 core so emscripten returns its native implementation and fcWasmResolveGL is never consulted. Worse, fcWasmResolveGL (WasmGLFixedFunc.cpp:508-525) does not even list glDrawArrays/glDrawElements/glDrawRangeElements, so reordering alone would not fix it. Net effect: SoVertexArrayIndexer::render -> cc_glglue_glDrawElements (gl.cpp:3439-3444) calls the real WebGL2 glDrawElements with no valid program/attrib state (Coin's glVertexPointer/glEnableClientState DID resolve to our emulation because they are absent from emscripten's table, so FF state is captured but never consumed). Result: GL error / nothing drawn for every VBO-or-vertex-array face and edge set (the dominant FreeCAD path via SoBrepFaceSet -> SoIndexedFaceSet -> SoVertexArrayIndexer). Only immediate-mode geometry calling glBegin/glVertex directly by symbol (SoGL.cpp, small meshes) renders. FIX: add glDrawArrays/glDrawElements/glDrawRangeElements to fcWasmResolveGL AND make cc_glglue_getprocaddress consult fcWasmResolveGL before emscripten_GetProcAddress for these; add a glDrawRangeElements C entry that funnels into ff_setup_and_draw (currently missing).
RECOMMENDATION: (none)
Independently verify by reading the actual file(s) and running git/grep as needed (repo at /home/magik6k/lcad-wasm/freecad-port/FreeCAD, toolchain at /opt/toolchains, deploy at /home/magik6k/lcad-wasm/freecad-port/deploy-gui). Default to REFUTED if you cannot confirm the evidence. Adjust severity to what the evidence actually supports.
Fable
thinking
I need to dig into whether Coin's vertex array and VBO drawing path actually bypasses the fixed-function emulation layer and calls emscripten's native WebGL2 functions directly, or if it still goes through the emulation.
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/WasmGLFixedFunc.cpp"}
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin/src/glue/gl.cpp", "limit": 60, "offset": 600}
Fable
thinking
I'm tracing through Coin's function resolution mechanism to verify whether glDrawArrays and glDrawElements are being resolved through the PROC macro and getprocaddress, which would route them to emscripten's native WebGL2 implementation rather than Coin's fixed-function emulation. I need to check the specific code locations and confirm what emscripten_GetProcAddress actually returns for these draw functions.
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin/src/glue/gl.cpp", "limit": 40, "offset": 1270}
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin/src/glue/gl.cpp", "limit": 40, "offset": 3420}
Fable
thinking
I'm tracing through how Coin sets up the OpenGL function pointers—it populates the draw array and draw elements functions through a macro that calls into the emscripten layer. The critical piece is whether emscripten's GetProcAddress actually returns valid pointers for those specific functions.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin && grep -rn \"define PROC\" src/glue/gl.cpp; echo \"---indexer---\"; grep -rn \"cc_glglue_glDrawElements\\|cc_glglue_glDrawArrays\\|cc_glglue_glDrawRangeElements…
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin && grep -rn \"PROC(w,\" src/glue/gl.cpp | head -3; echo \"=== PROC def context ===\"; grep -n \"PROC\" src/glue/gl.cpp | grep -i \"define\\|cc_glglue_getprocaddr…
Fable
thinking
There are two competing PROC macro definitions—one at line 881 that uses `cc_glglue_getprocaddress` and another at line 924 that directly takes the address of the function symbol with `(&_func_)`. The critical question is which one is active when the vertex-array initialization happens at lines 1286-1287, because if the second definition is in effect, then `w->glDrawArrays` would be assigned the address of our emulated C symbol rather than a dynamically loaded function.
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin/src/glue/gl.cpp", "limit": 80, "offset": 870}
Fable
thinking
The key difference hinges on whether HAVE_DYNAMIC_LINKING is defined. With it enabled, the WASM build uses the emscripten dynamic linking path to resolve function pointers. Without it, we're doing static binding where function pointers directly reference our emulated C symbols, so something like `w->glDrawArrays = &glDrawArrays` would point to our own implementation.
There's a potential symbol clash here since emscripten also defines glDrawArrays, but our strong symbol in WasmGLFixedFunc.cpp should win at link time. The real question is which mode the WASM build is actually using, so I need to check the build configuration for HAVE_DYNAMIC_LINKING.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin && grep -rn \"HAVE_DYNAMIC_LINKING\" include/ src/ config* CMakeLists.txt 2>/dev/null | head; echo \"=== config.h files ===\"; find . -name \"config.h\" -o -name…
Fable
thinking
I should check the actual generated config.h file in the build directory to verify whether HAVE_DYNAMIC_LINKING is defined there.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port && find . -path ./FreeCAD/src -prune -o -name \"config.h\" -print 2>/dev/null | head -20; echo \"=== all config.h under build ===\"; find . -name \"config.h\" 2>/dev/null | grep -i \"coin…
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port && ls; echo \"=== deploy dir ===\"; ls deploy-gui 2>/dev/null | head; echo \"=== find build dirs ===\"; find . -maxdepth 3 -type d -name \"*build*\" 2>/dev/null | head; find . -name \"con…
Fable
thinking
I need to track down where HAVE_DYNAMIC_LINKING is being defined in the WASM build setup, so I'm checking the toolchain's build directory and any CMake configuration files to understand how coin gets compiled in this environment.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port && grep -rln \"HAVE_DYNAMIC_LINKING\\|DYNAMIC_LINKING\" toolchain/ 2>/dev/null | head; echo \"=== find coin config in toolchain ===\"; find /opt/toolchains -name \"config.h\" 2>/dev/null …
Fable
⚙ Bash {"command": "grep -n \"HAVE_DYNAMIC_LINKING\\|HAVE_DL\\|HAVE_DLFCN\" /opt/toolchains/src/freecad-gui-build/src/3rdParty/coin/src/config.h"}
Fable
thinking
I'm seeing how the dynamic linking configuration works in the coin build—HAVE_DYNAMIC_LINKING is set to 1, which means function pointers like glDrawArrays get resolved at runtime through cc_glglue_getprocaddress rather than being statically linked. So when the code assigns `w->glDrawArrays = PROC(w, glDrawArrays)`, it's actually calling the dynamic lookup function with the string name of the OpenGL function.
On the Emscripten side, when cc_glglue_getprocaddress is called, it first tries emscripten_GetProcAddress to resolve the function name. The key question is whether Emscripten's GetProcAddress implementation actually has glDrawArrays and glDrawElements in its function table—these are cor …
Fable
⚙ Bash {"command": "cd /opt/toolchains/emsdk/upstream/emscripten && grep -rln \"emscripten_GetProcAddress\\|_webgl_getProcAddress\\|glDrawArrays\" system/lib/ 2>/dev/null | head; echo \"=== find gl proc address impl ===\"; find system/lib -name \"…
Fable
⚙ Bash {"command": "cd /opt/toolchains/emsdk/upstream/emscripten && sed -n '1,80p' system/lib/gl/libprocaddr.c"}
Fable
thinking
I need to track down the `emscripten_GetProcAddress` function definition—it's probably generated somewhere, so I should check the gl.c file and the proc address lookup table it creates.
Fable
⚙ Bash {"command": "cd /opt/toolchains/emsdk/upstream/emscripten && grep -rn \"emscripten_GetProcAddress\\|_emscripten_GetProcAddress\\|GetProcAddress\" system/lib/gl/gl.c | head; echo \"=== webgl_internal_funcs.h glDrawArrays ===\"; grep -n \"glD…
Fable
⚙ Bash {"command": "cd /opt/toolchains/emsdk/upstream/emscripten && sed -n '240,290p' system/lib/gl/gl.c"}
Fable
thinking
I'm checking whether `emscripten_GetProcAddress` properly handles WebGL1 core functions like `glDrawArrays` and `glDrawElements` through `emscripten_webgl1_get_proc_address`, but this functionality depends on the `GL_ENABLE_GET_PROC_ADDRESS` flag being defined.
The real question is whether Coin's draw path actually bypasses the fixed-function emulation by getting a native pointer to `glDrawArrays`, or if the proc address lookup returns NULL when the flag isn't set, which would cause the glue function to be nulled out instead.
Fable
⚙ Bash {"command": "cd /opt/toolchains/emsdk/upstream/emscripten && grep -n \"get_proc_address\\|GL_ENABLE_GET_PROC_ADDRESS\\|glDrawArrays\\|RETURN_FN\\|glDrawElements\" system/lib/gl/webgl1.c | head -40"}
Fable
⚙ Bash {"command": "cd /opt/toolchains/emsdk/upstream/emscripten && sed -n '644,700p' system/lib/gl/webgl1.c | grep -n \"GL_ENABLE\\|glDrawArrays\\|glDrawElements\\|get_proc_address\\|#if\\|#endif\\|return\""}
Fable
⚙ Bash {"command": "cd /opt/toolchains/emsdk/upstream/emscripten && sed -n '600,650p' system/lib/gl/webgl1.c"}
Fable
thinking
I need to check if `emscripten_webgl1_get_proc_address` is itself wrapped by the GL_ENABLE_GET_PROC_ADDRESS guard, so I'm looking at where the function definition starts and ends.
Fable
⚙ Bash {"command": "cd /opt/toolchains/emsdk/upstream/emscripten && awk 'NR>=646 && NR<=760' system/lib/gl/webgl1.c | grep -n \"GL_ENABLE_GET_PROC_ADDRESS\\|emscripten_webgl1_get_proc_address\\|RETURN_FN(glDrawArrays)\\|RETURN_FN(glDrawElements)\\…
Fable
⚙ Bash {"command": "cd /opt/toolchains/emsdk/upstream/emscripten && grep -n \"RETURN_FN\\b\\|define RETURN_FN\\|#define RETURN_FN\" system/lib/gl/webgl1.c | head -3; echo \"=== how RETURN_FN defined ===\"; grep -rn \"define RETURN_FN\" system/lib/…
Fable
⚙ Bash {"command": "cd /opt/toolchains/emsdk/upstream/emscripten && sed -n '60,90p' system/lib/gl/webgl_internal.h"}
Fable
thinking
In single-threaded builds, `emscripten_webgl1_get_proc_address` returns the native WebGL2 wrapper for functions like `glDrawArrays`, but in multithreaded builds it returns the C wrapper instead. The key question is whether `emscripten_webgl1_get_proc_address` itself is controlled by the GL_ENABLE_GET_PROC_ADDRESS flag.
If that flag is disabled (which is the default), then `emscripten_GetProcAddress` might return NULL, causing the proc address lookup to fail and leaving `glVertexPointer` as NULL — which would force Coin to fall back to immediate mode rendering with `glBegin`/`glVertex` calls that do go through the emulation layer. I need to check whether the function is actually gated by tha …
Fable
⚙ Bash {"command": "cd /opt/toolchains/emsdk/upstream/emscripten && awk 'NR>=560 && NR<=770 {print NR\": \"$0}' system/lib/gl/webgl1.c | grep -E \"#if|#endif|#else|get_proc_address|GL_ENABLE\" "}
Fable
⚙ Bash {"command": "cd /opt/toolchains/emsdk/upstream/emscripten && awk 'NR>=760 && NR<=1050 {print NR\": \"$0}' system/lib/gl/webgl1.c | grep -E \"#if|#endif|#else|get_proc_address|GL_ENABLE|^[0-9]+: \\}\" | head -30"}
Fable
thinking
I'm tracing through the preprocessor directives in this code—the `#if GL_ENABLE_GET_PROC_ADDRESS` at line 604 wraps the entire `emscripten_webgl1_get_proc_address` function that starts at 646 and closes at 826, with the matching `#endif` at 828. I've confirmed there's no `#endif` between the initial directive and the function definition, so the whole function is indeed guarded by that conditional.
This creates an interesting dependency: if `GL_ENABLE_GET_PROC_ADDRESS` is disabled, the function wouldn't exist, but the coin code in gl.cpp explicitly calls `emscripten_GetProcAddress`, which would cause a link error unless the flag is enabled. Since the FreeCAD build successfully links, that me …
Fable
⚙ Bash {"command": "cd /opt/toolchains/emsdk/upstream/emscripten && awk 'NR>=244 && NR<=282 {print NR\": \"$0}' system/lib/gl/gl.c | grep -E \"#if|#endif|#else|GL_ENABLE|emscripten_GetProcAddress|void\\*\""}
Fable
⚙ Bash {"command": "cd /opt/toolchains/emsdk/upstream/emscripten && grep -n \"GL_ENABLE_GET_PROC_ADDRESS\\|^#if\\|^#endif\\|^#else\" system/lib/gl/gl.c | sed -n '1,40p'"}
Fable
thinking
The conditional compilation flag GL_ENABLE_GET_PROC_ADDRESS must be enabled in the FreeCAD WebAssembly build since coin's gl.cpp references emscripten_GetProcAddress and the build successfully links. Let me check the link flags to confirm this.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port && grep -rn \"GL_ENABLE_GET_PROC_ADDRESS\\|GET_PROC_ADDRESS\\|MAX_WEBGL_VERSION\\|LEGACY_GL_EMULATION\\|PTHREAD\\|USE_PTHREADS\\|pthread\" stage-*.md toolchain/ FreeCAD/CMakeLists.txt 2>/…
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port && grep -rn \"sGL_ENABLE\\|GET_PROC\\|LEGACY_GL\\|MAX_WEBGL\\|sFULL_ES\\|WASM_LINK\\|EXPORTED\\|-sUSE_WEBGL2\\|link\" toolchain/*.sh 2>/dev/null | grep -i \"gl\\|webgl\\|link\\|proc\" | h…
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port && sed -n '1,80p' toolchain/configure-stage2.sh | grep -n \"GL\\|WEBGL\\|PROC\\|PTHREAD\\|LINK\\|-s\" "}
Fable
thinking
I'm noticing a custom GL emulation library being used, and I need to track down where the actual linker flags and emcc settings are configured for the GUI executable—likely in the configure-stage2 script or related build configuration.
The core question is whether glDrawArrays ultimately resolves through emscripten's native implementation or through the custom emulation. Given that HAVE_DYNAMIC_LINKING is enabled, function pointers go through cc_glglue_getprocaddress, which on emscripten calls emscripten_GetProcAddress first. If GL_ENABLE_GET_PROC_ADDRESS is enabled, that returns the native emscripten_glDrawArrays, bypassing the emulation entirely—which would support the reviewer's claim. S …
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/deploy-gui && ls -la FreeCAD.js FreeCAD.wasm; echo \"=== pthread? ===\"; grep -c \"pthread\\|PThread\\|Worker\" FreeCAD.js; echo \"=== emscripten_glDrawArrays / GetProcAddress in JS ===\"…
Fable
thinking
The JS loader is only 523KB while the wasm binary is 188MB, and I'm seeing `emscripten_glDrawArrays` referenced 12 times in the glue code, which means the WebGL layer is importing it from wasm. A pthread grep returned just 1 match, confirming this is essentially single-threaded with no worker support. I need to dig into the deployed JS and check the wasm symbols to understand how glDrawArrays is actually wired up.
Now I'm checking whether `emscripten_GetProcAddress` exists in the wasm (which would indicate GL_ENABLE_GET_PROC_ADDRESS is enabled) and whether the glDrawArrays symbol resolves to our emulation or points to a function table instead. I'll use wasm inspection tools to search for th …
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/deploy-gui && grep -o \"emscripten_glDrawArrays[a-zA-Z_]*\\|_emscripten_glDrawArrays\" FreeCAD.js | sort | uniq -c; echo \"=== is it async? check drawArrays wrapper ===\"; grep -o \"_emsc…
Fable
thinking
Since this is a single-threaded build without pthread markers, `emscripten_glDrawArrays` is the actual JS library function that gets returned. Now I need to verify that GL_ENABLE_GET_PROC_ADDRESS is enabled in this build so that `emscripten_GetProcAddress` exists as a real function in the wasm—I'll check the wasm's name section since the binary is too large to search directly.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/deploy-gui && grep -c \"emscripten_GetProcAddress\\|emscripten_webgl1_get_proc_address\\|emscripten_webgl2_get_proc_address\" FreeCAD.wasm 2>/dev/null; echo \"=== strings for ff_ / fcWasm…
Fable
thinking
The grep command didn't return anything, likely due to binary file handling issues. I should try using strings or a different approach, maybe checking the wasm name section with specialized tools or Python instead. The grep -a flag might be worth attempting as well.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/deploy-gui && strings -n 8 FreeCAD.wasm 2>/dev/null | grep -E \"emscripten_GetProcAddress|webgl1_get_proc_address|fcWasmResolveGL|ff_setup_and_draw|emscripten_glDrawArrays\" | sort | uniq…
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/deploy-gui && which wasm-objdump llvm-nm wasm2wat 2>/dev/null; ls /opt/toolchains/emsdk/upstream/bin/ 2>/dev/null | grep -i \"nm\\|objdump\\|wasm\" | head; echo \"=== try python strings =…
Fable
thinking
The name section shows cc_glglue_getprocaddress appearing once, but emscripten_GetProcAddress has a count of zero, which suggests it was either inlined or the name section got stripped during compilation. I should use llvm-objdump or llvm-nm to get a clearer picture of what's actually in the wasm binary.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/deploy-gui && /opt/toolchains/emsdk/upstream/bin/llvm-nm FreeCAD.wasm 2>/dev/null | grep -iE \"GetProcAddress|get_proc_address|fcWasmResolveGL|ff_setup_and_draw|glDrawArrays|glDrawElement…
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/deploy-gui && /opt/toolchains/emsdk/upstream/bin/llvm-nm FreeCAD.wasm 2>&1 | head -5; echo \"=== count symbols ===\"; /opt/toolchains/emsdk/upstream/bin/llvm-nm FreeCAD.wasm 2>/dev/null |…
Fable
thinking
The symbols are minified through the build process, so I can't rely on symbol names to reveal what's actually happening. That one reference to "cc_glglue_getprocaddress" is likely just a debug string or assertion. I have enough from analyzing the source code, so now I need to carefully evaluate the actual claim while considering the ff_setup_and_draw path and how drawElements interacts with the VBO.
Let me walk through the reviewer's core argument step by step. The main VBO path in Coin does call through glue function pointers for glDrawArrays and glDrawElements — I've confirmed this in the source files. Those pointers get populated via PROC macros, which with dynamic linking enabled resolv …
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin && sed -n '590,625p' src/shapenodes/SoFaceSet.cpp"}
Fable
thinking
I need to trace back where `dova` gets initialized, since it's probably controlled by whether the system supports vertex arrays.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin && sed -n '560,592p' src/shapenodes/SoFaceSet.cpp | grep -n \"dova\\|has_vertex_array\\|vertexarray\\|canRenderAsVertexArray\\|SoVBO\"; echo \"=== broader ===\";…
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin && sed -n '578,586p' src/shapenodes/SoFaceSet.cpp; echo \"=== startVertexArray gating ===\"; grep -rn \"cc_glglue_has_vertex_array\\|has_vertex_array\" src/shape…
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin && find src -name \"SoVBO.cpp\"; echo \"=== shouldRenderAsVertexArrays ===\"; grep -rn \"shouldRenderAsVertexArrays\" src/ --include=*.cpp | head; "}
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin && sed -n '405,430p' src/rendering/SoVBO.cpp"}
Fable
thinking
So `shouldRenderAsVertexArrays` doesn't actually check for vertex array support — it only looks at the data size threshold and a global flag. The actual gating on `cc_glglue_has_vertex_array` happens elsewhere, in the `startVertexArray` method within SoShape.cpp. But here's the issue: the FaceSet code calls `startVertexArray`, yet the draw call using `cc_glglue_glDrawArrays` executes unconditionally whenever the vertex array flag is true, regardless of what `startVertexArray` returns.
I need to trace where `cc_glglue_has_vertex_array` is actually checked to prevent vertex array rendering from happening. Let me look more carefully at `SoShape::startVertexArray` and see if there's an earlier …
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin && grep -rn \"startVertexArray\\|has_vertex_array\\|glVertexPointer\" src/shapenodes/SoShape.cpp | head"}
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin && sed -n '1563,1695p' src/shapenodes/SoShape.cpp"}
Fable
thinking
I'm tracing through the vertex array initialization logic and hitting a contradiction — startVertexArray doesn't check for vertex array support before calling the pointer functions, but those functions rely on glVertexPointer being set, which gets nulled out when glTexCoordPointer is missing. So the chain breaks somewhere in the resolution logic.
Let me think through the fallback chain more carefully. After emscripten and fcWasmResolveGL fail, there are several other resolution paths like dlsym that might actually find these symbols depending on how the library was compiled and linked. But the key insight is that if all those texture and array functions truly resolve to NULL, then has_verte …
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin && grep -rn \"cc_glglue_has_vertex_array\\|has_vertex_array\" src/ include/ --include=*.cpp --include=*.h | grep -v \"SoVertexArray\\b\" | head -30"}
Fable
thinking
I'm tracing through the vertex array feature detection in SoGLDriverDatabase and how SoIndexedFaceSet checks for it around line 532. Looking at the relevant code path in FreeCAD where SoBrepFaceSet inherits from something similar to SoIndexedFaceSet, and checking whether SoFaceSet has a shouldRenderAsVertexArr method.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin && sed -n '525,560p' src/shapenodes/SoIndexedFaceSet.cpp; echo \"=== has_vertex_array impl ===\"; sed -n '3358,3366p' src/glue/gl.cpp"}
Fable
thinking
The key insight is that when `has_vertex_array` is FALSE due to missing OpenGL function pointers, the `dova` flag for `SoIndexedFaceSet` becomes FALSE, which forces the rendering to fall back to immediate-mode rendering instead of using vertex arrays.
However, `SoFaceSet` might not include the same `has_vertex_array` check in its `dova` calculation, which could cause it to attempt vertex array rendering and crash when calling `glVertexPointer` with a NULL function pointer—though FreeCAD uses `SoBrepFaceSet`, not `SoFaceSet`.
The core technical claim being reviewed is whether `glDrawArrays` and `glDrawElements` bypass emulation by resolving to native Emscripten functions, but the actual mec …
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/deploy-gui/screenshot.png"}
Fable
thinking
The screenshot is just the FreeCAD welcome screen, so it doesn't actually show whether the 3D viewport can render geometry. I should check the research notes and stage-4 documentation to see what the actual rendering status is and whether this issue has already been tested.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port && grep -n \"glDrawArrays\\|glDrawElements\\|getprocaddress\\|GetProcAddress\\|has_vertex_array\\|immediate\\|blank\\|VBO\\|vertex array\\|fcWasmResolveGL\\|render\" stage-4-webgl-coin-ba…
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port && sed -n '1,60p' stage-4-webgl-coin-backend.md"}
Fable
thinking
I'm looking at WasmGLFixedFunc.cpp as the Stage-4 backend and trying to determine whether SoBrepFaceSet actually uses the vertex array path in this build, or if it falls back to immediate mode rendering. The key question is whether has_vertex_array actually matters for this particular geometry type. I've gathered enough technical details to make a decision about how this rendering path works.
All the core facts check out: dynamic linking is enabled, the resolver prioritizes emscripten's GetProcAddress, which maps glDrawArrays and glDrawElements to native functions while fcWasmResolveGL doesn't define them, and Coin's main rendering path calls through the glue functions. The vertex pointer f …
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin && grep -n \"cc_dl_handle_with_gl_symbols\\|cc_dl_sym\\|dlsym\\|dlopen\\|cc_dl_open\\|__EMSCRIPTEN__\" src/glue/dl.cpp | head -30"}
Fable
thinking
I'm realizing that `cc_dl_sym` relies on `dlsym` in emscripten, but this only works reliably when the program is built with dynamic linking enabled through `MAIN_MODULE`. For typical static WebAssembly builds, accessing internal symbols via `dlopen(NULL)` and `dlsym` won't function unless specific emulation flags are set.
Since FreeCAD appears to be compiled as a single monolithic 188MB static WebAssembly module without dynamic linking, `dlopen(NULL)` might return a handle but `dlsym` can't resolve internal C symbols like `glTexCoordPointer` unless they're explicitly exported—which they almost certainly aren't. This means those function pointers resolve to NULL, causing `has_vertex_array()` …
Fable
⚙ Bash {"command": "find src -name \"SoBrepFaceSet.cpp\" -o -name \"SoBrepEdgeSet.cpp\" | head; echo \"=== grep VertexArrayIndexer / has_vertex_array / DrawElements in SoBrep ===\"; grep -rn \"SoVertexArrayIndexer\\|has_vertex_array\\|SO_GL_VERTEX…
Fable
⚙ Bash {"command": "grep -n \"GLRender\\|glDrawElements\\|glDrawArrays\\|glBegin\\|VertexArray\\|glVertexPointer\\|renderShape\\|SoGL\" src/Mod/Part/Gui/SoBrepFaceSet.cpp | head -40"}
Fable
thinking
SoBrepFaceSet delegates its rendering to its parent class SoIndexedFaceSet through inherited::GLRender, which then checks whether vertex arrays are supported in the GL driver before proceeding with the actual rendering.
The key issue is that the vertex array code path is gated behind SO_GL_VERTEX_ARRAY, which depends on cc_glglue_has_vertex_array being true — but if that's false, none of the vertex array rendering paths execute at all, meaning the reviewer's claim about a dominant path bypassing emulation doesn't hold. However, I haven't yet confirmed whether dlsym actually fails to resolve the internal symbols, since emscripten static builds with certain flags might export them anyway, whi …
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port && grep -rn \"immediate\\|has_vertex_array\\|glDrawElements\\|glDrawArrays\\|glBegin\\|vertex array\\|ff_setup_and_draw\\|VBO\\|getprocaddress\" research/R3-graphics-coin-webgl.md 2>/dev/…
Fable
thinking
The research indicates that VBO paths are the primary implementation for shapes, with immediate mode serving as a fallback for edge cases, but the current WasmGLFixedFunc.cpp is really just a temporary Stage-4 workaround. I need to check whether the runtime actually resolves has_vertex_array to true or false by looking at what dlsym returns for those missing functions during the build.
Now I'm checking the emscripten build configuration—specifically the MAIN_MODULE and dynamic linking settings—to see if dlopen(NULL) can actually find the vertex array symbols in the coin library, and I need to trace through the actual emcc link flags and cmake settings to understand what's being exported and …
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port && grep -rn \"MAIN_MODULE\\|SIDE_MODULE\\|EXPORT_ALL\\|dlopen\\|-sDYNAMIC\\|LINKABLE\\|ERROR_ON_UNDEFINED\\|EXPORTED_FUNCTIONS\\|WASM_BIGINT\\|-pthread\\|USE_PTHREADS\" toolchain/ FreeCAD…
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port && grep -rln \"GL_ENABLE_GET_PROC_ADDRESS\\|MAIN_MODULE\\|LEGACY_GL_EMULATION\\|sFULL_ES\\|MAX_WEBGL\" FreeCAD/ 2>/dev/null | head; echo \"=== search wasm link flags in cmake ===\"; find …
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port && grep -n \"GL_ENABLE_GET_PROC_ADDRESS\\|MAIN_MODULE\\|LEGACY_GL\\|FULL_ES\\|MAX_WEBGL\\|GET_PROC\\|-sUSE\\|pthread\\|PTHREAD\\|WasmGLFixedFunc\\|emcc\\|target_link\\|LINK_FLAGS\\|-s \" …
Fable
thinking
I'm confirming the emscripten flags are set correctly—`-sGL_ENABLE_GET_PROC_ADDRESS=1`, `-sMAX_WEBGL_VERSION=2`, and `-sFULL_ES3=1`—which means `emscripten_GetProcAddress` is available and can resolve WebGL functions like `glDrawArrays` and `glDrawElements` to native implementations. There's no `MAIN_MODULE` flag, so dynamic symbol resolution won't work for internal symbols, and no threading support is enabled.
Let me check lines 130-165 to verify there's no `MAIN_MODULE` and see if anything else stands out.
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Main/CMakeLists.txt", "offset": 130, "limit": 40}
Fable
thinking
I'm noticing a discrepancy in the documentation—the CMake comment describes the 3D viewport as non-rendering until the real WebGL2 backend arrives, but the WasmGLFixedFunc.cpp header claims it's already the Stage-4 backend that replaced the earlier no-op shim. The CMake comment seems outdated and doesn't match the current state of the code.
The linker flag `-Wl,--allow-multiple-definition` is significant here: when both WasmGLFixedFunc.cpp and emscripten's libGL define glDrawArrays, the linker uses the first definition it encounters, but the getprocaddress path returns the JS-layer emscripten_glDrawArrays symbol instead, which is a separate symbol name entirely.
For the vertex array functi …
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin/src/shapenodes/SoFaceSet.cpp", "offset": 580, "limit": 12}
Fable
thinking
SoFaceSet also gates vertex array rendering on the same has_vertex_array check, so with that flag disabled, everything routes through immediate mode and our emulation handles it. This confirms the reviewer's proposed mechanism doesn't actually produce the effect they claimed, since the VA/VBO path gets blocked entirely.
I've verified those four critical functions—glTexCoordPointer, glIndexPointer, glInterleavedArrays, and glArrayElement—are genuinely absent from the WebAssembly GL resolution table, which is why has_vertex_array returns FALSE. Even if glglue_allow_newer_opengl also forces it FALSE, the outcome is the same: the vertex array path is disabled, which refutes the reviewer's mecha …
Fable
I have gathered enough evidence. Let me verify one final detail about the four unresolvable vertex-array functions being absent from the resolver.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui && grep -n \"glTexCoordPointer\\|glIndexPointer\\|glInterleavedArrays\\|glArrayElement\" WasmGLFixedFunc.cpp; echo \"=== confirm none in fcWasmResolveGL table (between lin…
Fable
thinking
Looking at the symbol resolution in fcWasmResolveGL, I'm finding that glTexCoordPointer, glInterleavedArrays, glIndexPointer, and glArrayElement either don't exist as symbols or aren't resolvable through getprocaddress, which means has_vertex_array() returns false. So the reviewer's technical observations about the resolver are accurate, but their conclusion about why the viewport is blank appears to be wrong—the issue isn't that the dominant VA/VBO path is bypassing emulation.
Instead, that path is completely blocked off by the has_vertex_array() check returning false, forcing geometry to fall back to immediate-mode rendering with glBegin/glVertex calls, which the emulation does handle pro …
Fable
I have completed my verification. Let me record my findings.
## Summary of investigation
The reviewer's individual factual observations are **correct**:
- `HAVE_DYNAMIC_LINKING 1` is set in the generated coin config (`/opt/toolchains/src/freecad-gui-build/src/3rdParty/coin/src/config.h:152`), so `PROC(w, glDrawArrays)` expands to `cc_glglue_getprocaddress` (gl.cpp:881, 1286-1287).
- `cc_glglue_getprocaddress` consults `emscripten_GetProcAddress` **first** and returns on success (gl.cpp:619-628), before `fcWasmResolveGL`.
- The build sets `-sGL_ENABLE_GET_PROC_ADDRESS=1`, `-sMAX_WEBGL_VERSION=2`, `-sFULL_ES3=1` (Main/CMakeLists.txt:144-149), it is single-threaded, and `emscripten_webgl1_get_proc_address` returns native `emscripten_glDrawArrays`/`glDrawElements` for those names (webgl1.c:687-688, webgl_internal.h:78). So `glue->glDrawArrays/glDrawElements` do point at native WebGL2, and `fcWasmResolveGL` (WasmGLFixedFunc.cpp:508-525) does **not** list the draw entries.
But the reviewer's **causal conclusion is wrong**. The same resolver also fails to resolve the *other* functions Coin requires for its vertex-array path: `glTexCoordPointer`, `glIndexPointer`, `glInterleavedArrays`, and `glArrayElement` are absent from emscripten's table (not WebGL; `LEGACY_GL_EMULATION` is off) **and** absent from `fcWasmResolveGL` (verified: 0 hits in the table), and there is no `MAIN_MODULE`, so `cc_dl_sym` cannot find them either. `PROC` returns NULL for all four, which triggers gl.cpp:1290-1307 to set `w->glVertexPointer = NULL`, making `cc_glglue_has_vertex_array()` return FALSE (gl.cpp:3360-3364).
Every standard shape node gates its VA/VBO draw on this: `SoIndexedFaceSet.cpp:544`, `SoFaceSet.cpp:587` (and peers) all AND-in `SoGLDriverDatabase::isSupported(glue, SO_GL_VERTEX_ARRAY)` (registered to `cc_glglue_has_vertex_array`, SoGLDriverDatabase.cpp:239). With it FALSE, `dova` is FALSE, so `cc_glglue_glDrawArrays/glDrawElements` and `SoVertexArrayIndexer::render` are never reached; geometry falls back to immediate-mode `glBegin/glVertex`, which the emulation *does* handle (`ffBegin/ffVertex/ffEnd`). FreeCAD's `SoBrepFaceSet::GLRender` delegates to `inherited::GLRender` (SoBrepFaceSet.cpp:706/721), i.e. the gated `SoIndexedFaceSet` path — so the cited chain does not bypass the emulation.
Thus the described critical symptom (native `glDrawElements` consuming captured-but-unbound FF state → blank viewport for all VA/VBO faces/edges) does not occur; the path is disabled by a gate the reviewer overlooked.
Fable
⚙ StructuredOutput {"verdict": "REFUTED", "reason": "The reviewer's sub-facts are individually true: with HAVE_DYNAMIC_LINKING=1 (confirmed in the generated coin config.h) PROC uses cc_glglue_getprocaddress, which consults emscripten_GetProcAddress first (gl.…