subagent transcript
session 17f09f89 · workflow wf_2d2ac42d
delegated task
CONTEXT — FreeCAD is ported to WebAssembly (emscripten 4.0.12, clang-22, JSPI, native wasm-EH). 3D rendering is Coin3D (Open Inventor) via Quarter, re
▸ Magik
CONTEXT — FreeCAD is ported to WebAssembly (emscripten 4.0.12, clang-22, JSPI, native wasm-EH).
3D rendering is Coin3D (Open Inventor) via Quarter, rendered to an OFFSCREEN FBO then blitted into
a Qt widget via QPainter (Qt-for-wasm has NO native GL widget compositing — the whole Qt UI is a
SOFTWARE raster surface blitted to an HTML canvas). WebGL2 via emscripten (FULL_ES3, LEGACY_GL_EMULATION
off but Coin uses a GLES2 path). COIN_VBO=0 (client vertex arrays), COIN_VERTEX_ARRAYS=1 (VA fast path
default-on for indexed face/line sets). qtFontDpi=80.
KEY SOURCE LOCATIONS (find others yourself with grep/find):
- FreeCAD 3D viewer + FBO->QPainter blit: /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/View3DInventorViewer.cpp
(also SoQtQuarterAdaptor / Quarter / SoFCVectorizeAction). The wasm viewport work is documented — grep for
__EMSCRIPTEN__ / FC_OS_WASM / grabFramebuffer / QOpenGLFramebufferObject / drawImage / readPixels in src/Gui.
- Coin3D source: locate it (find / -name SoAsciiText.cpp -o -name so_glglue.c 2>/dev/null; try
/home/magik6k/lcad-wasm/freecad-artifacts, pyside-port/build/pivy-coin, /opt/toolchains). The GLES2/immediate
path is in src/rendering/ (SoGL*.cpp) + src/glue/gl.cpp (cc_glglue). SoAsciiText is src/nodes/SoAsciiText.cpp.
- Emscripten GL emulation (the ffVertex/ffNormal3/ffSyncContext/emitImm JS functions seen in the profile are
emscripten's FIXED-FUNCTION immediate-mode GLES2 emulation): /opt/toolchains/emsdk/upstream/emscripten/src/
(library_webgl.js / library_glemu.js / lib/libglemu / GLImmediate). Confirm where ff*/emitImm come from.
- Qt raster paint (RGBA64 conversions): /opt/toolchains/qtsrc/qtbase/src/gui/painting/qdrawhelper.cpp
(storeRGBA8888FromRGBA64PM / fetchRGBA8888ToRGBA64PM / comp_func_SourceOver_rgb64). Qt-wasm backingstore:
/opt/toolchains/qtsrc/qtbase/src/plugins/platforms/wasm/qwasmbackingstore.cpp / qwasmwindow.cpp / qwasmscreen.cpp.
- The relink/build to test a fix: cmake build at /opt/toolchains/src/freecad-gui-build; relink via
/tmp/claude-1000/.../scratchpad/femrelink-s2.sh (this session's scratchpad). Do NOT run builds — analysis only.
THE PROFILE (bottom-up, self-time, ~10s capture during hover/pointer interaction at 3-4 fps):
Qt SOFTWARE COMPOSITING (~2.3s total):
storeRGBA8888FromRGBA64PM 363ms, fetchRGBA8888ToRGBA64PM 250ms, comp_func_SourceOver_rgb64 104ms,
fetchTransformed64<TextureBlendType 2> 224ms, qt_rectfill_nonpremul_rgba 132ms, blend_color_generic 147ms,
QSvgNode::draw 1092ms(total) / QSvgG::drawCommand 1087ms / QSvgStrokeStyle::apply / QStroker::processCurrentSubpath 124ms,
gray_raster_render 247ms, readPixels 113ms, texSubImage2D 83ms, do_mirror 33ms, QImage::copy 66ms.
3D SCENE (Coin, ~1.7s):
SoAsciiText::GLRender 314+40ms + SoAsciiTextP::setUpGlyphs + cc_glyph3d_ref + cc_flw_get_vector_advance,
immediate-mode glue ffVertex 268ms + ffNormal3 40ms + ffSyncContext 86ms + emitImm + glVertex3f/glVertex3fv,
getParameter 390ms + getError 42ms (redundant GL state queries via wasm->JS->WebGL),
bufferData 465ms (client-array re-upload every draw?), sogl_render_sphere 130ms + SoSphere::GLRender,
SoSeparator::GLRenderBelowPath 1335ms(total) / SoAction::traverse 1562ms(total) / SoSeparator::cullTest 51ms.
TIMERS: setTimeout 382ms + clearTimeout 137ms. Event: pointerenter 635ms self / 2730ms total.
You have Read/Grep/Glob/Bash (read-only: grep/find/nm ok; do NOT edit files or run builds).
Return ONLY the requested structured fields. Be concrete: cite file:line, the exact mechanism, and a
SPECIFIC code-level fix (what to change), with an impact estimate (ms saved / % of frame) and effort.
LANE: Redundant GL state queries + buffer churn: getParameter 390ms, getError 42ms, bufferData 465ms, ffSyncContext 86ms.
Investigate: synchronous glGet*/glGetError and per-draw bufferData are very expensive in wasm (each is a wasm->JS->WebGL
round-trip, and glGetError forces a GPU flush/sync).
- getParameter/getError: who calls them per-frame? Coin's cc_glglue queries GL limits/state; SoGLLazyElement or the
error-checking in the GL glue. Is there per-draw glGetError (Coin's COIN_GLERROR_CHECKING or a debug path) that can be
disabled? Is cc_glglue re-querying context params instead of caching? grep Coin src/glue/gl.cpp + rendering for
glGetError/glGetInteger per frame.
- bufferData 465ms: with COIN_VBO=0 (client arrays), emscripten's GL layer uploads client vertex arrays to a temp VBO via
bufferData on EVERY draw call (FULL_ES3 client-array emulation). Confirm this is the mechanism. Could persistent VBOs for
static geometry (enable COIN_VBO with bufferData, avoiding glMapBuffer) eliminate per-frame re-upload? Or does emscripten
offer a client-array fast path? Check emscripten library_webgl.js client-array handling.
Propose: (a) disable any per-frame glGetError; (b) ensure cc_glglue caches state; (c) evaluate persistent-VBO for static
geometry to kill per-frame bufferData. Estimate ms saved + risk.
Fable
thinking
I need to dig into Coin3D's GL integration code and how emscripten handles client arrays to verify these specific claims.
Fable
⚙ Bash {"command": "find / -name \"gl.cpp\" -path \"*glue*\" 2>/dev/null; echo \"---\"; find / -name \"so_glglue.c\" 2>/dev/null; echo \"---COIN ROOT---\"; find / -type d -name \"Coin*\" 2>/dev/null | head; find / -name \"SoGLLazyElement.cpp\" 2>/…
Fable
⚙ Bash {"command": "find / -name \"library_webgl.js\" 2>/dev/null; echo \"---glemu---\"; find / -name \"library_glemu.js\" 2>/dev/null; find /opt/toolchains/emsdk -name \"*.js\" -path \"*emscripten*\" 2>/dev/null | grep -iE \"glemu|webgl\" | head"…
Fable
thinking
I'm examining how Coin GL handles glGetError and glGet* calls, and looking into emscripten's approach to managing client-side arrays.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin; grep -rn \"glGetError\\|GLERROR_CHECKING\\|cc_glerror\" src/glue/gl.cpp | head -40", "description": "Find glGetError usage in Coin glue"}
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin; grep -rn \"glGetError\\|GLERROR_CHECKING\\|glFlush\\|glFinish\" src/rendering/*.cpp | head -40", "description": "Find glGetError in Coin rendering"}
Fable
thinking
I'm investigating the cc_glglue_glerror_check function and tracing where that 390ms getParameter bottleneck is coming from — specifically looking for which code is calling glGetInteger or glGetFloat repeatedly during rendering.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin; grep -rn \"cc_glglue_glerror_check\\|glglue_glerror\\|COIN_GLERROR\\|GLERROR_CHECKING\" src/ | head -30", "description": "Find glerror check usage across Coin"}
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin; grep -rn \"glGetInteger\\|glGetFloat\\|glGetBoolean\\|glIsEnabled\\|glGetString\" src/elements/GL/SoGLLazyElement.cpp src/rendering/SoGL.cpp | head -40", "descr…
Fable
thinking
COIN_GLERROR_DEBUGGING is disabled by default, so those timing spikes for glGetError and getParameter are coming from emscripten's GL layer rather than Coin itself. I need to dig into emscripten's libwebgl.js and libglemu.js to see where these calls are happening in the client-array and immediate-mode rendering paths, particularly around the ffVertex and emitImm functions in GLImmediate.
The bufferData call taking 465ms suggests that client array emulation is uploading to a temporary VBO on every draw call, so let me search through libglemu.js to confirm this behavior.
Fable
⚙ Bash {"command": "cd /opt/toolchains/emsdk/upstream/emscripten/src/lib; grep -n \"bufferData\\|getError\\|getParameter\\|clientBuffers\\|ffVertex\\|emitImm\\|ffSyncContext\\|preDrawHandleClientVertexAttribBindings\\|MAX_TEMP\" libglemu.js | head…
Fable
⚙ Bash {"command": "cd /opt/toolchains/emsdk/upstream/emscripten/src/lib; grep -n \"bufferData\\|getError\\|getParameter\\|clientBuffers\\|__glGetBufferBinding\\|preDrawHandleClientVertexAttribBindings\\|tempVertexBuffers\\|_glDrawArrays\\|_glDraw…
Fable
thinking
I'm diving into the client-array upload mechanism, specifically examining how the temporary vertex buffer gets created and bound during each draw call, along with how the client vertex attribute bindings are handled beforehand.
Fable
⚙ Read {"file_path": "/opt/toolchains/emsdk/upstream/emscripten/src/lib/libwebgl.js", "offset": 460, "limit": 140}
Fable
thinking
The key mechanism here is how client-side vertex attribute arrays are handled in FULL_ES2/FULL_ES3 emulation. On every draw call, the system runs through each enabled client-side attribute, grabs a temporary vertex buffer, and uploads the client array data via bufferSubData. Since the ring buffer gets pre-allocated after the first frame, subsequent calls just reuse the cached buffer instead of allocating new ones.
But the profile is showing bufferData taking 465ms, not bufferSubData, which is puzzling. Let me reconsider whether there's a different code path at play here — maybe the immediate mode path in libglemu.js is the culprit. The immediate mode rendering (used by things like SoAsciiTe …
Fable
⚙ Bash {"command": "cd /opt/toolchains/emsdk/upstream/emscripten/src/lib; sed -n '2500,2560p' libglemu.js", "description": "Inspect glemu immediate flush buffer upload"}
Fable
⚙ Bash {"command": "cd /opt/toolchains/emsdk/upstream/emscripten/src/lib; sed -n '2560,2610p' libglemu.js", "description": "Immediate flush bufferSubData"}
Fable
thinking
I'm seeing that the immediate mode rendering path through glBegin/glVertex3f is the main bottleneck — each vertex call in JavaScript adds up to 268ms of overhead as it populates GLImmediate.vertexData. Let me check how FreeCAD configures vertex arrays versus immediate mode in the SoGL implementation to understand whether it's using client-side arrays or staying in immediate mode. I should search through Coin's codebase to find where getParameter is being called frequently in the rendering pipeline, particularly in hot paths like SoGLRenderAction and viewport setup during traversal.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin; grep -rn \"glGetIntegerv\\|glGetFloatv\\|glGetBooleanv\\|glGetString\\b\" src/nodes/SoAsciiText.cpp src/shapenodes/*.cpp src/elements/GL/*.cpp src/actions/SoGLR…
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin; grep -rln \"glGetIntegerv\\|glGetFloatv\\|glGetBooleanv\" src/elements/ | head; echo \"=== SoAsciiText glBegin/glVertex ===\"; grep -n \"glBegin\\|glVertex\\|gl…
Fable
thinking
I'm noticing some potential performance issues with OpenGL calls that aren't compatible with WebGL or GLES2 — specifically `glGetBooleanv(GL_RGBA_MODE)` in SoGLLazyElement that could trigger errors each time it runs. I should check how frequently this gets called and look at the surrounding code. There are also similar concerns with `GL_MAX_CLIP_PLANES` and `GL_MAX_LIGHTS` queries in the clip plane and light ID elements that seem to be executing repeatedly.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin; sed -n '410,445p' src/elements/GL/SoGLLazyElement.cpp", "description": "Context of GL_RGBA_MODE query"}
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin; find src -name \"SoAsciiText.cpp\"; find src -iname \"*asciitext*\"", "description": "Find SoAsciiText source"}
Fable
thinking
I'm noticing that `glGetBooleanv(GL_RGBA_MODE)` in the lazy element initialization is causing GL errors in GLES2/WebGL since those constants don't exist there, and each call triggers a round-trip that returns undefined. This happens during init rather than per draw call, so it's probably not the source of the 390ms bottleneck. Now I'm shifting focus to examine SoAsciiText.
GLRender, which is the largest self-time consumer at 314+40ms — it uses immediate mode rendering with vector glyphs through glBegin/glVertex calls.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin; grep -n \"glBegin\\|glVertex\\|glNormal\\|glEnd\\|GLRender\\|render(\\|beginShape\\|glGenBuffers\\|glBindBuffer\\|glDrawArrays\\|COIN_VBO\\|SoVertexArray\\|vbo\…
Fable
thinking
So the bottleneck is in how SoAsciiText renders glyphs—each triangle vertex triggers a JavaScript call through emscripten that accumulates vertex data, and when the glyph finishes rendering, it flushes that data to a GPU buffer. The 268ms cost comes from this repeated cycle of building and flushing vertex buffers for every glyph. Now I need to figure out which text strings are actually being rendered to understand where the time is going.
During interaction in the 3D view, dimension labels and UI elements like the navigation cube are constantly re-rendering, and across those 30-40 frames captured, each frame spends roughly 8-10ms just on this text rendering. The bigger issue is likely the b …
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin; echo \"=== SoVBO / vertexarray decision ===\"; grep -rn \"COIN_VERTEX_ARRAYS\\|COIN_VBO\\|glDrawArrays\\|glDrawElements\\|glGenBuffers\\|SoGLVBOElement\\|should…
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin; grep -rn \"glDrawElements\\|glDrawArrays\\|glVertexPointer\\|glNormalPointer\\|glEnableClientState\\|bindBuffer\\|glBindBuffer\" src/rendering/SoGL.cpp | head; …
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin; grep -n \"glDrawElements\\|glVertexPointer\\|glNormalPointer\\|glEnableClientState\\|bindBuffer\\|VBO\\|vbo\\|bufferData\\|render(\" src/rendering/SoVertexArray…
Fable
thinking
I'm tracing through the render function to understand how it decides between using persistent VBO indices versus client-side arrays, which depends on the shouldCreateVBO logic in SoGLVBOElement. Now I need to look at how vertex coordinates are being handled in the shape rendering pipeline, particularly in SoShape and SoIndexedFaceSet.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin; grep -rn \"renderasvbo\\|shouldCreateVBO\\|->render(state\" src/shapenodes/SoShape.cpp src/rendering/*.cpp | head -30", "description": "Find renderasvbo decisio…
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin; sed -n '385,460p' src/rendering/SoVBO.cpp", "description": "shouldCreateVBO logic"}
Fable
thinking
I'm tracing through how the VBO configuration works—when COIN_VBO is disabled, the vbo_enabled flag stays false, which prevents vertex buffer objects from being created, so the system falls back to client-side arrays that get uploaded to the GPU via bufferSubData on each draw call. The vertex array rendering path, though, depends on a separate vbo_render_as_vertex_arrays flag rather than vbo_enabled.
The core issue is that with COIN_VBO=0, Emscripten's client-array emulation re-uploads all the vertex, normal, and color data to temporary VBOs every single frame through bufferSubData calls—that's where the 465ms bottleneck is coming from. The solution is to enable COIN_VBO=1, which makes Coin …
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin; grep -n \"vbo_enabled\\|vbo_render_as_vertex_arrays\\|COIN_VBO\\|COIN_VERTEX_ARRAY\\|coin_getenv\" src/rendering/SoVBO.cpp | head; echo \"=== bindBuffer/setBuff…
Fable
thinking
Looking at how SoVBO::bindBuffer works — it caches the GL buffer per context in vbohash, so the expensive operations like glBufferData only happen once, and then subsequent frames just call glBindBuffer. That's the persistent-VBO optimization in action. Now I need to check the environment variable gating around line 166-190 to see how vbo_enabled is controlled.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin; sed -n '160,200p' src/rendering/SoVBO.cpp", "description": "vbo_enabled env default"}
Fable
thinking
COIN_VBO defaults to 1, but the environment variable is explicitly set to 0, which forces the use of client arrays and causes emscripten to re-upload buffers on each draw call. The solution is to remove or override that COIN_VBO=0 setting—I need to locate where it's being set in the FreeCAD wasm code.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port; grep -rn \"COIN_VBO\\|COIN_VERTEX_ARRAYS\" FreeCAD/src --include=*.cpp --include=*.h --include=*.js --include=*.html 2>/dev/null | grep -iv \"3rdParty/coin\" | head -20; echo \"=== searc…
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port; grep -rn \"COIN_VBO\" FreeCAD/src 2>/dev/null | grep -v 3rdParty/coin | head -20; echo \"=== also artifacts/html/js ===\"; grep -rln \"COIN_VBO\" . 2>/dev/null | grep -viE \"3rdParty/coi…
Fable
thinking
I see that Application.cpp already sets COIN_VBO=1 at line 569, but the deployed HTML/JavaScript might be overriding it with COIN_VBO=0. Let me look at the context around that line to check if there's any conditional logic or competing settings.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port; sed -n '550,590p' FreeCAD/src/Gui/Application.cpp; echo \"=== deploy html COIN_VBO ===\"; grep -n \"COIN_VBO\\|COIN_VERTEX_ARRAYS\" deploy/index.html deploy-va/index.html 2>/dev/null | h…
Fable
thinking
I found where the deploy HTML is forcing client arrays by setting the Emscripten Module environment variable to disable VBOs. There's a comment in the code explaining the reasoning behind this choice, so let me check what it says to understand if there's an underlying bug preventing VBOs from working properly.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port; sed -n '85,105p' deploy/index.html", "description": "Read COIN_VBO rationale in deploy html"}
Fable
thinking
I'm questioning the reasoning behind the COIN_VBO setting. The comment claims WebGL2 lacks glMapBuffer support, but looking at the actual code, SoVBO uses glBufferData for uploads, not glMapBuffer. I need to dig deeper into setBufferData and allocBufferData to see if there's actually a glMapBuffer call somewhere that would justify this constraint.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin; grep -n \"glMapBuffer\\|MapBuffer\\|glBufferData\\|glBufferSubData\\|setBufferData\\|allocBufferData\" src/rendering/SoVBO.cpp", "description": "Check SoVBO for…
Fable
thinking
I'm realizing the stated reason for disabling COIN_VBO is incorrect—SoVBO never actually uses glMapBuffer in its upload path, only glBufferData, so the WebGL2 incompatibility claim doesn't hold up.
Now I need to trace through the full vertex data pipeline to confirm VBOs are properly created and bound during rendering. I'm checking whether SoGLVBOElement correctly binds vertex and normal VBOs when the shape renders, and verifying that glDrawElements with the VBO offset actually works end-to-end.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin; grep -rn \"SoVBO\\|vbo\\|bindBuffer\\|VertexPointer\\|renderasvbo\\|shouldCreateVBO\" src/shapenodes/soshape.cpp 2>/dev/null | head -30; echo \"=== glue glBuffe…
Fable
thinking
The VBO feature detection hinges on both glBufferData and glMapBuffer being available—if either is missing, VBO support gets disabled. I'm looking at the critical section around line 1430-1433 to understand how this check works, particularly since WebGL2 through emscripten doesn't expose the classic glMapBuffer function, though it does provide glMapBufferRange as an alternative.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin; sed -n '1420,1445p' src/glue/gl.cpp", "description": "VBO feature detection requiring glMapBuffer"}
Fable
thinking
The core issue is that VBO support depends on functions like glMapBuffer and glGetBufferPointerv that don't exist in WebGL2/GLES2 — emscripten only provides glMapBufferRange in ES3. When glMapBuffer is unavailable, the entire vertex buffer object chain fails, causing the capability check to return false and disabling VBO support.
The solution is to relax the feature-detection gate in gl.cpp. SoVBO only actually uses glGenBuffers, glBindBuffer, glBufferData, glBufferSubData, and glDeleteBuffers — it never calls glMapBuffer, glUnmapBuffer, glGetBufferSubData, or glGetBufferPointerv. So I should remove those unnecessary requirements from the capability check and let VBOs work with just the fun …
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/coin; grep -rn \"glMapBuffer\\|glUnmapBuffer\\|glGetBufferSubData\\|glGetBufferParameteriv\\|glGetBufferPointerv\" src/rendering/ src/elements/GL/ src/shapenodes/ 2>/…
Fable
thinking
Looking at Coin's actual VBO rendering implementation, it only uses the basic buffer operations like glGenBuffers, glBindBuffer, glBufferData, and glDeleteBuffers—plus glBufferSubData for updates. The stricter gate at gl.cpp:1426-1444 that checks for additional buffer operations is overly restrictive since those functions never get called in the render hot path.
There's also a comment in SoIndexedFaceSet.cpp:557 that reflects the team's mistaken assumption about this. Let me check whether emscripten actually provides a glMapBuffer stub—if it does, then the gate should pass and something else must be disabling VBO instead.
Fable
⚙ Bash {"command": "cd /opt/toolchains/emsdk/upstream/emscripten/src/lib; grep -rn \"glMapBuffer\\b\\|glUnmapBuffer\\|glGetBufferPointerv\\|glGetBufferSubData\\|glMapBufferRange\" libwebgl.js libwebgl2.js libglemu.js 2>/dev/null | head; echo \"===…
Fable
thinking
So Emscripten does provide MapBufferRange and related functions in WebGL2, but it emulates them through getBufferSubData and bufferSubData operations rather than true memory mapping. The key issue is that classic glMapBuffer isn't exported at all—Emscripten only supports the newer MapBufferRange variant, which means when Coin tries to use the traditional mapping approach...
Actually, let me verify this by checking what symbols are actually available. A grep through libwebgl.js confirms glMapBufferRange and glGetBufferPointerv are there, but plain glMapBuffer is completely absent. That's the smoking gun—the gate fails because the function pointer is NULL, which disables the entire VBO subsys …
Fable
I have a complete, evidence-backed picture. Let me write up the findings.
Fable
⚙ StructuredOutput {"lane": "Redundant GL state queries + buffer churn (getParameter 390ms, getError 42ms, bufferData 465ms, ffSyncContext 86ms)", "root_cause": "The frame draws geometry as CLIENT vertex arrays (COIN_VBO forced to 0), so emscripten's client-a…