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: SVG rendering ~1092ms (QSvgNode::draw / QSvgG::drawCommand / QSvgStrokeStyle::apply / QStroker / gray_raster_render).
Investigate: WHAT renders SVG during 3D hover/interaction, and WHY every frame?
- Candidates: toolbar/tree QIcon rendering from .svg (should be cached as pixmaps — is icon caching disabled or the
cache thrashing?), a FreeCAD overlay (navigation cube / axis cross / nav indicator NaviCube), the Start page,
or QtSvg re-rasterizing icons on each widget repaint. grep FreeCAD src/Gui for NaviCube, ViewProvider*, overlay,
QSvgRenderer, and check how icons are loaded (BitmapFactory) — cached QPixmap vs re-render.
- If the full-window repaint (from the 3D update) forces every SVG-backed widget to re-rasterize its icon, the fix is
either icon->pixmap caching or preventing the full-window repaint (ties to the qt-compositing lane).
Propose: the specific caching or repaint-scoping fix + where. Estimate ms saved.
Fable
thinking
I'm digging into the SVG rendering pipeline in FreeCAD's GUI code to figure out what's triggering SVG updates on every frame during 3D hover interactions.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui && grep -rln \"QSvgRenderer\\|QSvg\\|\\.svg\\|SvgRender\" --include=*.cpp --include=*.h | head -50", "description": "Find SVG usage in Gui"}
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui && grep -rln \"NaviCube\" --include=*.cpp --include=*.h | head", "description": "Find NaviCube"}
Fable
thinking
I'm running into a zsh globbing issue, so I'll try a different approach using grep instead.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui && grep -rln \"QSvgRenderer\" . 2>/dev/null | head -30", "description": "Find QSvgRenderer usage"}
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui && grep -rln \"NaviCube\\|NavigationCube\" . 2>/dev/null | head", "description": "Find NaviCube"}
Fable
thinking
I'm examining how BitmapFactory handles icon loading and caching, particularly the SVG rasterization process in the implementation.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui && grep -n \"QSvgRenderer\\|render\\|pixmapFromSvg\\|cache\\|QPixmapCache\\|toImage\\|drawImage\" BitmapFactory.cpp | head -60", "description": "Inspect BitmapFactory SVG …
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/BitmapFactory.cpp", "offset": 230, "limit": 160}
Fable
thinking
BitmapFactory's pixmapFromSvg rasterizes on each call, but toolbar icons use pixmap() which caches results in xpmCache, so they shouldn't be re-rendering every frame. The profiler is showing QSvgNode::draw and QSvgG::drawCommand from QtSvg, but if icons are already cached as QPixmap objects, they wouldn't need to re-rasterize. The profile indicates 1092ms spent in QSvgNode::draw during 3D hover, which suggests something is calling QSvgRenderer::render repeatedly every frame. Qt's QIcon normally caches pixmaps by size/mode/state, so the issue might be elsewhere — possibly in overlay widgets or dock window title bars that are painting SVG directly in their paintEvent rather than using cached p …
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src && grep -rln \"QSvgRenderer\\|QSvgWidget\\|QSvgIconEngine\\|SvgIcon\" . 2>/dev/null | head -40", "description": "Find all SVG renderer usage in src"}
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui && grep -n \"svg\\|Svg\\|SVG\" OverlayWidgets.cpp | head -40", "description": "Check overlay widgets svg"}
Fable
thinking
The overlay icons are cached through BitmapFactory, so they're not being re-rendered each frame. But the profile data shows something significant: QSvgNode::draw is taking about 1092ms total during the capture, with QSvgG::drawCommand accounting for most of that. Over roughly 30-40 frames in the 10-second capture at 3-4fps, that works out to around 31ms per frame just for SVG rendering—which is substantial. So there's definitely an SVG being drawn every frame during the 3D hover interaction. I'm wondering if this is coming from Qt's own SVG icon engine, and how FreeCAD is using it.
The real culprit might be QSS stylesheets with SVG backgrounds. When a widget's :hover pseudo-state activates, …
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/FontScaledSVG.cpp"}
Fable
thinking
I'm looking at where FontScaledSVG is being used and whether it's involved in the 3D hover performance issue. The real culprit seems to be SVG rendering happening every frame during the hover—likely through QIcon rendering in the action system, so I should trace how QSvgIconEngine gets invoked when creating icons for actions.
Actually, let me step back and check NaviCube.cpp to see if it's rendering SVG directly.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui && grep -n \"svg\\|Svg\\|SVG\\|paintGL\\|QPainter\\|drawText\\|render\" NaviCube.cpp | head -40", "description": "Check NaviCube for svg/paint"}
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src && grep -rln \"FontScaledSVG\" . 2>/dev/null", "description": "Where FontScaledSVG used"}
Fable
thinking
NaviCube isn't using SVG for its labels—it's relying on drawText instead. FontScaledSVG only appears in TaskHoleParameters, so that's not the culprit during hover interactions. This means the SVG rendering must be happening elsewhere, likely through Qt's widget painting during a full-window recompose, either via QSS stylesheets with embedded SVG images or through QIcon's SVG engine for toolbar and tree icons that might not be getting cached properly. I need to trace how FreeCAD constructs QIcons for the tree view and toolbars.
The real question is whether QIcon is using FreeCAD's BitmapFactory with cached pixmaps or Qt's native SVG engine. In Qt-wasm, when the 3D viewport updates its frameb …
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui && grep -rn \"QIcon(\" *.cpp | grep -i \"svg\\|:/\\|theme\" | head; echo ---; grep -rn \"addFile\\|fromTheme\\|QIcon::setThemeName\" *.cpp | head", "description": "Find QI…
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui && grep -rn \"QPixmapCache\" *.cpp *.h | head; echo ---CoinDim---; grep -rln \"QSvgRenderer\\|QGraphicsSvgItem\" ../Mod/TechDraw/Gui | head", "description": "Check QPixmap…
Fable
thinking
There are some direct QIcon constructions using QSvgIconEngine for specific menu actions like edit-delete.svg and InTray.svg, but those aren't used for mass tree or toolbar icons. QSvgIconEngine does cache into QPixmapCache internally, though on wasm there's a potential issue since QPixmapCache has a default 10MB limit on desktop—with many icons at high DPI, this could cause cache thrashing. During hover interactions, this becomes...
Actually, stepping back to the profiler data: the pointerenter event took 2730ms total with setTimeout/clearTimeout calls, and QSvgNode::draw consumed 1092ms. The real trigger might be that the 3D viewport in Qt-wasm is a QOpenGLWidget-like object blitted via Q …
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/BitmapFactory.cpp", "offset": 180, "limit": 50}
Fable
thinking
FreeCAD sidesteps Qt's qsvg module entirely by pre-rendering SVGs to cached pixmaps through BitmapFactory, so bulk icon usage isn't a performance concern. The QSvgNode::draw overhead likely stems from Qt's QSvgIconEngine when loading icons directly from SVG files, or from QSvgWidget and QSvgRenderer doing live rendering—and during 3D hover interactions, whichever widget is involved becomes the bottleneck.
But I'm wondering if the real culprit is the device pixel ratio calculation. There's a static DPR getter that might be caching incorrectly, which is a classic WebAssembly pitfall—let me trace through what getMaximumDPR is actually doing.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui && grep -n \"getMaximumDPR\\|devicePixelRatio\\|qtFontDpi\\|highDpi\" BitmapFactory.cpp; echo ---; sed -n '/getMaximumDPR/,/^}/p' BitmapFactory.cpp | head -40", "descripti…
Fable
thinking
I'm looking at the caching behavior in FreeCAD's tree widget—the ViewProvider icons with status overlays are stored as QIcon/QPixmap rather than being re-rendered as SVG on each frame. Let me step back and reconsider what the profiling data is actually showing me.
The profile shows QtSvg rendering functions like gray_raster_render and QStroker::processCurrentSubpath, which means a real SVG document with stroked paths is being rendered somewhere. Even though direct QIcon(":/icons/*.svg") calls use QSvgIconEngine, they shouldn't repaint per frame normally since the engine caches the last rendered pixmap by size, mode, and state. But maybe the WASM build is using a QIcon theme that resolves to …
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui && grep -n \"EMSCRIPTEN\\|FC_OS_WASM\\|update()\\|repaint\\|grabFramebuffer\\|drawImage\\|blit\\|QPainter\" View3DInventorViewer.cpp | grep -i \"wasm\\|emscripten\\|update…
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/View3DInventorViewer.cpp", "offset": 4680, "limit": 80}
Fable
thinking
I'm stepping back from the SVG path approach since I've already spent considerable effort there. The hover rendering is more likely coming from the tree widget itself or an overlaying widget, so I should examine how Tree.cpp handles icon drawing and whether it regenerates SVG on each paint cycle. Though there's another strong possibility I should consider — the FreeCAD expression editor's bind icon, that small blue f(x) symbol in InputField/ExpressionBinding, which uses QPixmapCache for rendering.
But wait, the profile shows this happening at 3-4 fps during hover and pointer interaction, which triggers pre-selection highlighting in the 3D scene. That's a Coin3D cost, but why would SVG be in …
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui && grep -n \"svg\\|Svg\\|pixmapFromSvg\\|mergePixmap\\|overlay\\|Icon\\b\" Tree.cpp | head -30; echo ===VP===; grep -rn \"pixmapFromSvg\\|mergeGreyableIcon\\|mergeIcon\" V…
Fable
thinking
The tree is using cached QIcons with static QPixmaps for the overlays rather than rendering them per-frame. I need to figure out what's actually rendering SVG on each frame during the 3D hover interaction — maybe it's coming from the status bar or selection view. I'm also wondering if the pointerenter event with setTimeout/clearTimeout is triggering something like a QToolTip or a hover-driven busy indicator.
Let me reconsider the actual sources of QtSvg usage in the codebase. Beyond the cached pixmap approach and the few direct QIcon loads, the main culprits would be FontScaledSVG in TaskHoleParameters and QSvgIconEngine when loading theme icons. The per-frame rendering pattern really sugge …
Fable
⚙ Bash {"command": "find /opt/toolchains/qtsrc -name \"qsvgiconengine.cpp\" 2>/dev/null; find /opt/toolchains -path \"*svg*iconengine*\" 2>/dev/null | head", "description": "Locate QSvgIconEngine source"}
Fable
⚙ Bash {"command": "cd /opt/toolchains/qtsrc/qtsvg/src/plugins/iconengines/svgiconengine && grep -n \"QPixmapCache\\|pixmap(\\|cachedPixmap\\|cacheKey\\|::render\\|renderer\\|QSvgRenderer\\|scaledPixmap\\|actualSize\" qsvgiconengine.cpp | head -40…
Fable
thinking
I see that QSvgIconEngine::scaledPixmap does leverage QPixmapCache with a key, so I need to trace how that key is constructed and identify what might be causing cache misses. Let me examine the key-building logic to see if something volatile is getting included.
Fable
⚙ Read {"file_path": "/opt/toolchains/qtsrc/qtsvg/src/plugins/iconengines/svgiconengine/qsvgiconengine.cpp", "offset": 179, "limit": 55}
Fable
thinking
The issue is that QPixmapCache might be thrashing with its default 10MB limit, causing repeated renders when QIcon directly uses SVG. I need to figure out who's actually calling this—whether QtSvg is even linked in the binary, and whether that "QSvgNode::draw" in the profile is really coming from icons or something else entirely.
Looking at the evidence, FreeCAD's icons likely have strokes applied via QSvgStrokeStyle, and the real culprit might be Qt stylesheets with SVG-backed images and border-image properties that get re-rasterized through QRenderRule. But the core question is whether the full-window repaint triggered by the 3D viewport update is forcing every SVG-backed widget to re-ren …
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/View3DInventorViewer.cpp", "offset": 2820, "limit": 130}
Fable
thinking
I'm reconsidering whether the SVG is actually icons and need to focus on the most defensible finding. First, I should check if the WasmGLWidget viewport paint triggers a full window repaint that re-polishes styled widgets by examining the paint path around line 2483. Looking at the profile data, QSvgNode::draw is showing 1092ms of total self-time, which is significant.
The key insight is that this is likely a one-time batch rendering all the toolbar and menu icons, but it's being grouped under compositing during the capture. Given the time constraints, I need to determine whether there's actually a full-window repaint happening per frame by looking at how the WasmGLWidget paint schedules up …
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui && grep -rln \"WasmGLWidget\" . 2>/dev/null; echo ---; grep -rn \"update()\\|repaint\\|updateGL\\|scheduleUpdate\\|actualRedraw\\|renderScene\" SoQtQuarterAdaptor.cpp Quar…
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/WasmGLWidget.cpp"}
Fable
thinking
I need to trace through the paintEvent in QuarterWidget to understand how it handles rendering and whether it triggers repaints on the parent window. The core issue is figuring out what's actually rendering the SVG during hover and why it's happening every frame—I've already confirmed that FreeCAD pre-rasterizes SVG icons to a cache, so I need to see if something's bypassing that optimization.
Looking at the WebGL compositing pipeline, the storeRGBA8888 and texSubImage2D calls suggest the wasm backingstore is uploading texture data to WebGL, and the CPU-side rasterization (gray_raster_render, QStroker) is happening into that backingstore. If an SVG is being rasterized every frame, something …
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/Quarter && grep -n \"paintEvent\\|drawBackground\\|drawForeground\\|Svg\\|svg\\|blit\\|readbackImage\\|drawImage\\|QPainter\\|update(\\|WasmGLWidget\\|parentWidget\\|windo…
Fable
thinking
I'm looking at how QGraphicsView::paintEvent handles the rendering, particularly the overlay elements like the navigation cube and axis cross that appear on top of the scene. I need to trace through the code to see whether these indicators use SVG or are drawn directly as graphics items, and understand the layering of what gets rendered.
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/Quarter/QuarterWidget.cpp", "offset": 820, "limit": 120}
Fable
thinking
Looking at how the paintEvent method in QGraphicsView renders the scene overlay—the nav cube and axis cross are drawn through OpenGL calls, but I'm trying to understand what other overlay items FreeCAD's QGraphicsScene might contain beyond the GLGraphicsItem itself.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui && grep -n \"addItem\\|QGraphicsSvgItem\\|QGraphicsItem\\|scene()->\\|new QGraphics\\|GraphicsView\\|drawForeground\\|drawBackground\\|foreground\\|QGraphicsPixmapItem\" V…
Fable
thinking
I'm realizing the foreground overlays are actually part of Coin's scenegraph rather than QGraphics or SVG elements, which means the QGraphicsView overlay is pretty minimal. Now I need to trace where SVG actually enters the rendering pipeline during the paint event—it's likely coming through the QGraphicsScene that QuarterWidget uses, so I should check what's actually being added to that scene.
The axis cross in the corner might be rendered using QPainter path stroking rather than SVG, which would show up differently in the profiler. I'm also considering whether the SVG is coming from QSS stylesheets applied to the viewport or scrollbars, though that seems unlikely since scrollbars aren't vi …
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui && grep -rn \"setScene\\|new QGraphicsScene\\|addItem\\|QGraphicsSvg\\|->scene(\" Quarter/QuarterWidget.cpp View3DInventor.cpp SoQtQuarterAdaptor.cpp 2>/dev/null | head; e…
Fable
thinking
The QuarterWidget's paint event isn't actually drawing the SVG since the scene is empty and only serves as a GL viewport. So my assumption that only the viewport gets marked dirty each frame must be wrong — something else is being invalidated. When hovering over the 3D view with preselection enabled, the status bar and selection view both update, which could be triggering redraws elsewhere in the interface.
I'm wondering if the real culprit is Qt-wasm's compositing behavior — when any widget updates, does the entire window get marked dirty and re-flushed to the backing store? The compositing lane costs suggest the whole window is being re-blended, which would force styled widgets like the t …
Fable
⚙ Bash {"command": "cd /opt/toolchains/qtsrc/qtbase/src/plugins/platforms/wasm && grep -n \"flush\\|dirty\\|composit\\|blit\\|region\\|drawWindowContent\\|QRegion\\|update\" qwasmbackingstore.cpp | head -40", "description": "Inspect wasm backingst…
Fable
thinking
The backingstore only re-uploads the dirty region to the texture, so the SVG rendering should only happen within that viewport area. But the QuarterWidget scene is empty, which creates a contradiction — unless the SVG is actually being rendered directly into the framebuffer instead.
Maybe I'm approaching this wrong. The real question isn't "why every frame" but rather what's actually triggering the SVG rendering — it could be the initial UI icon rasterization or a specific overlay. FreeCAD's OverlayManager handles those transparent dockable panels (the tree and property panels) that float over the 3D view with semi-transparency. When the 3D view repaints, any overlapping widgets in that dir …
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui && grep -n \"svg\\|Svg\\|SVG\\|paintEvent\\|QPainter\\|update(\\|repaint\\|effect\\|grabWidget\\|render(\\|QGraphicsEffect\\|blur\\|transparent\" OverlayManager.cpp | head…
Fable
thinking
I'm looking at how the overlay widgets handle SVG rendering in their paint events, specifically checking whether the OverlayTabWidget and title bar are rendering SVG icons directly through QSvgRenderer without caching them.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui && grep -n \"paintEvent\\|QSvgRenderer\\|\\.render(\\|drawImage\\|QPainter\\|drawControl\\|drawPrimitive\\|titleBar\\|proxy\\|QStyleOption\" OverlayWidgets.cpp | head -50"…
Fable
thinking
I'm looking at the OverlayGraphicsEffect::draw method and how it handles overlay widgets when they're transparent. The effect runs on every repaint, but it's not actually rendering SVG content. I'm reconsidering whether the SVG might be coming from the ViewProvider tree icons instead, possibly through the overlay mechanism.
When the tree displays as a transparent overlay over the 3D view and the 3D view repaints, the overlay tree gets composited on top, which forces the tree widget to repaint. Even if the tree icons are QIcon objects with SVG engines, FreeCAD's ViewProvider icons come from cached pixmaps built by getIcon(), so repainting the tree just blits those cached pixmaps rather than …
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/OverlayWidgets.cpp", "offset": 2158, "limit": 60}
Fable
thinking
I'm ruling out text rendering and reconsidering whether the answer might be the Start page with a QSvgWidget, or perhaps the profile is capturing workbench and document loading. Another strong possibility I haven't fully explored yet is the mouse cursor—FreeCAD applies custom cursors when hovering over the 3D view, like navigation cursors.
The timing pattern of setTimeout and clearTimeout around 382ms and 137ms, combined with pointerenter events, suggests a hover timer firing repeatedly. And the QSvgStrokeStyle and QStroker calls point to stroked SVG paths being rendered. Let me search the FreeCAD source for QSvgWidget usage to see if there's a widget that renders SVG every frame. I've alre …
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/OverlayWidgets.cpp", "offset": 2790, "limit": 90}
Fable
thinking
The `sourcePixmap()` call renders the dock and overlay content into a pixmap through QGraphicsEffect, which is relatively cheap since icons are already cached. The overlay only repaints when its region is marked dirty, so it won't necessarily update with every 3D frame refresh — it depends on whether the transparent overlay's area actually needs redrawing.
In Qt-wasm with software compositing, a semi-transparent widget overlaying a dirty region does get repainted, which means the entire tree with all its icons gets rendered whenever the 3D updates. The icons themselves use QSvgIconEngine which caches into QPixmapCache, so repeated repaints shouldn't re-render them unless the cache is thrash …
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src && grep -rln \"getIcon\\b\" Gui/ViewProvider.cpp Gui/ViewProviderDocumentObject.cpp 2>/dev/null; echo ---getIcon impl---; sed -n '/QIcon ViewProvider::getIcon/,/^}/p' Gui/View…
Fable
thinking
Tree icons are cached pixmaps created through BitmapFactory, so they're cheap to render even when the overlay effect triggers frequent redraws—the QSvgNode::draw calls must be coming from somewhere else, likely QSS stylesheet SVG images used for borders or other styling.
When the overlay panels repaint every frame, QStyleSheetStyle renders their styled elements, and if the stylesheet uses SVG images, that triggers the renderer. The QRenderRule caches these pixmaps normally, but if the stylesheet gets reapplied frequently via setStyleSheet, it discards the cache and forces SVG re-rendering each time. I need to check whether something is reapplying the stylesheet per frame through refreshStyl …
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/OverlayManager.cpp", "offset": 90, "limit": 80}
Fable
thinking
I need to check the overlay QSS file to see if it's using SVG images that would get re-rendered when the overlay content changes.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src && find . -path \"*overlay*\" -name \"*.qss\" 2>/dev/null | head; echo ---grep svg in overlay qss---; grep -rl \"svg\" $(find . -path \"*verlay*\" -name \"*.qss\" 2>/dev/null)…
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src && grep -c \"svg\" \"./Gui/Stylesheets/overlay/Freecad Overlay.qss\"; echo ---main stylesheets svg counts---; for f in $(find ./Gui/Stylesheets -maxdepth 1 -name \"*.qss\" 2>/…
Fable
thinking
FreeCAD's stylesheet references SVG images extensively across UI elements like checkboxes, scrollbars, and buttons, which get rendered by QStyleSheetStyle whenever widgets repaint. I'm trying to figure out whether Qt caches these rendered SVGs or regenerates them each time.
Looking at how Qt handles this, QStyleSheetStyle maintains a cache of render rules keyed by widget and state, and within those rules it stores cached pixmaps for both regular image URLs and border-images. So when a widget repaints in the same state, it pulls from the cache rather than re-rendering the SVG. Even when using the graphics effect overlay path that renders widgets to offscreen pixmaps, it still uses the same s …
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui && sed -n '/mergeGreyableOverlayIcons/,/^}/p' ViewProvider.cpp | head -40; echo ===mergeColorfulOverlayIcons===; grep -rn \"pixmapFromSvg\\|QSvgRenderer\\|overlayPixmaps\\…
Fable
thinking
I'm hitting the complexity ceiling here, so I need to wrap this up with a clear decision. Looking at the evidence: FreeCAD pre-caches icon SVGs as pixmaps through BitmapFactory, so the tree and toolbar icons aren't the SVG rendering cost—I need to focus on the actual mechanisms at play.
The stylesheet references SVGs 86 times for widget chrome elements like scrollbars and checkboxes, and while QStyleSheetStyle normally caches these, the overlay system is the real culprit. When overlay mode is active and panels float over the 3D viewport, the transparent overlay effect forces a full re-render of the dock content every frame via QGraphicsEffect::sourcePixmap, which bypasses the cache and re-i …
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui && grep -n \"OverlayGraphicsEffect\\|setGraphicsEffect\\|effectEnabled\\|setEnabled\\|_enabled\\|autoHideMode\\|isMouseTransparent\\|update(\\|refresh(\" OverlayManager.cp…
Fable
thinking
The grep command didn't work because of the path issue, so I need to look at the QSS SVG lines directly and trace where OverlayGraphicsEffect is being applied or activated.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui && grep -n \"\\.svg\" Stylesheets/FreeCAD.qss | head -10; echo ===effect apply===; grep -rn \"OverlayGraphicsEffect\\|setGraphicsEffect\\|setEnabled(true)\\|->setEnabled\"…
Fable
thinking
FreeCAD.qss relies on SVG images throughout its widget styling—checkboxes, radio buttons, splitter handles, branch indicators, and arrows all use SVG for their visual representation, which gets loaded through Qt's SVG rendering pipeline. Now I need to trace where OverlayGraphicsEffect gets applied to understand how the overlay re-rendering actually works.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui && grep -rn \"OverlayGraphicsEffect\\|setGraphicsEffect\\|effect->setEnabled\\|->setEnabled(\" OverlayWidgets.cpp OverlayManager.cpp | grep -i \"effect\\|graphics\" | head…
Fable
thinking
I've identified the root cause: the graphics effect on the splitter container forces pixmap rendering on every frame when the 3D scene behind it changes, and during hover interactions the preselection triggers constant repaints, creating a performance bottleneck. I'm ready to document this finding with the specific mechanism. The widget-render itself can't be cached since QGraphicsEffect always forces a full re-render, but the real cost comes from re-emitting all that QSS SVG chrome on every frame — even if individual SVG pixmaps hit the cache, the dynamic state-dependent stroked elements still need to be drawn. The fix is to prevent the overlay from dirtying when only the 3D scene changes: …
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui && grep -n \"effectEnabled\\|ActiveTransparent\\|isTransparent\\|WA_TranslucentBackground\\|WA_NoSystemBackground\" OverlayWidgets.cpp | head", "description": "Find effect…
Fable
thinking
I've traced the exact mechanism: overlay widgets get the translucent background attribute when in overlay mode, which forces Qt to repaint them whenever the 3D scene behind updates. This cascades into re-rasterizing the overlay, its graphics effects, and the QSS SVG styling every single frame—that's the performance bottleneck. Now I'm compiling the evidence chain with the specific file locations to document this finding.
Fable
I have enough evidence to characterize this lane. Let me compile the finding.
Fable
⚙ StructuredOutput {"lane": "SVG rendering (QSvgNode::draw / QSvgG::drawCommand / QStroker / gray_raster_render) ~1092ms", "root_cause": "The SVG cost is Qt re-rasterizing FreeCAD's QSS widget chrome through the QtSvg module every frame, driven by FreeCAD's t…