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"}
tool result
(eval):1: no matches found: --include=*.cpp
zshexit:1: parameter HISTDB_FD does not contain a file descriptor
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui && grep -rln \"NaviCube\" --include=*.cpp --include=*.h | head", "description": "Find NaviCube"}
tool result
(eval):1: no matches found: --include=*.cpp
zshexit:1: parameter HISTDB_FD does not contain a file descriptor
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"}
tool result
BitmapFactory.cpp
GraphvizView.cpp
FontScaledSVG.h
FontScaledSVG.cpp
GraphvizView.h
QtAll.h
ViewProviderImagePlane.cpp
zshexit:1: parameter HISTDB_FD does not contain a file descriptor
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui && grep -rln \"NaviCube\\|NavigationCube\" . 2>/dev/null | head", "description": "Find NaviCube"}
tool result
FreeCADGui._View3DInventorViewer.pyi
OverlayManager.cpp
OverlayParams.py
NaviCube.h
OverlayWidgets.cpp
NaviCube.cpp
OverlayParams.cpp
OverlayParams.h
SplitView3DInventor.cpp
View3DInventor.cpp
zshexit:1: parameter HISTDB_FD does not contain a file descriptor
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 …
tool result
34:#include <QSvgRenderer>
214:                icon = pixmapFromSvg(content, QSize(64, 64));
248:    // as very first test check whether the pixmap is in the cache
286:    return QPixmap(Gui::BitmapFactory().pixmapFromSvg("help-browser", QSize(16, 16)));
289:QPixmap BitmapFactoryInst::pixmapFromSvg(
325:            icon = pixmapFromSvg(content, size * dpr, colorMapping);
336:QPixmap BitmapFactoryInst::pixmapFromSvg(
356:    QSvgRenderer svg;
362:    svg.render(&p);
zshexit:1: parameter   …[truncated]
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/BitmapFactory.cpp", "offset": 230, "limit": 160}
tool result
230	
231	    if (!px.isNull()) {
232	        icon.addPixmap(px);
233	        return icon;
234	    }
235	    else {
236	        return fallback;
237	    }
238	
239	    return icon;
240	}
241	
242	QPixmap BitmapFactoryInst::pixmap(const char* name) const
243	{
244	    if (!name || *name == '\0') {
245	        return {};
246	    }
247	
248	    // as very first test check whether the pixmap is in the cache
249	    QMap<std::string, QPixmap>::Iterator it = d->xpmCache.find(name);
250	    if (it != d-  …[truncated]
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"}
tool result
Gui/BitmapFactory.cpp
Gui/FontScaledSVG.h
Gui/GraphvizView.h
Gui/QtAll.h
Gui/FontScaledSVG.cpp
Gui/GraphvizView.cpp
Gui/ViewProviderImagePlane.cpp
Mod/AddonManager/AddonManagerOptions.py
Mod/AddonManager/addonmanager_icon_utilities.py
Mod/AddonManager/AddonManagerTest/gui/test_icon_utilities.py
Mod/AddonManager/Widgets/spinner.py
Mod/BIM/ArchPrecast.py
Mod/AddonManager/addonmanager_firstrun.py
Mod/BIM/bimcommands/BimWindow.py
Mod/CAM/CAMTests/TestPathToolShapeIcon.py
Mod/CAM/TestCAMApp.py
Mod/CA  …[truncated]
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"}
tool result
488:        actOverlay.setIcon(BitmapFactory().pixmap("qss:overlay/icons/overlay_light.svg"));
489:        actNoAutoMode.setIcon(BitmapFactory().pixmap("qss:overlay/icons/mode_light.svg"));
490:        actTaskShow.setIcon(BitmapFactory().pixmap("qss:overlay/icons/taskshow_light.svg"));
491:        actEditShow.setIcon(BitmapFactory().pixmap("qss:overlay/icons/editshow_light.svg"));
492:        actEditHide.setIcon(BitmapFactory().pixmap("qss:overlay/icons/edithide_light.svg"));
493:        ac  …[truncated]
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"}
tool result
1	// SPDX-License-Identifier: LGPL-2.1-or-later
2	/****************************************************************************
3	 *                                                                          *
4	 *   Copyright (c) 2025 Alfredo Monclus <alfredomonclus@gmail.com>          *
5	 *                                                                          *
6	 *   This file is part of FreeCAD.                                          *
7	 *                                                  …[truncated]
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"}
tool result
54:#include <QPainterPath>
756:            QPainter paint;
759:                QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform
764:            paint.drawText(QRect(0, 0, texSize, texSize), Qt::AlignCenter, text);
767:            paint.drawText(QRect(0, offset, texSize, texSize), Qt::AlignCenter, text);
zshexit:1: parameter HISTDB_FD does not contain a file descriptor
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src && grep -rln \"FontScaledSVG\" . 2>/dev/null", "description": "Where FontScaledSVG used"}
tool result
Gui/FontScaledSVG.cpp
Gui/FontScaledSVG.h
Gui/CMakeLists.txt
Mod/PartDesign/Gui/TaskHoleParameters.ui
zshexit:1: parameter HISTDB_FD does not contain a file descriptor
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…
tool result
Action.cpp:883:    clearRecentFilesListAction.setIcon(QIcon(QStringLiteral(":/icons/edit-delete.svg")));
NotificationArea.cpp:160:        notificationArea = QIcon(QStringLiteral(":/icons/InTray.svg"));
FileDialog.cpp:1274:        return QIcon(QStringLiteral(":/icons/freecad-doc.png"));
PythonConsole.cpp:495:        QIcon(QStringLiteral(":/icons/edit-delete.svg")),
TaskCommandLink.cpp:100:        docItem->setIcon(0, QIcon(QStringLiteral(":/icons/Document.svg")));
ViewProviderImagePlane.cpp:172  …[truncated]
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…
tool result
ExpressionBinding.cpp:24:#include <QPixmapCache>
ExpressionBinding.cpp:331:    if (QPixmapCache::find(key, &icon)) {
ExpressionBinding.cpp:337:        QPixmapCache::insert(key, icon);
InputField.cpp:26:#include <QPixmapCache>
InputField.cpp:163:    if (QPixmapCache::find(key, &icon)) {
InputField.cpp:169:        QPixmapCache::insert(key, icon);
QtAll.h:97:#include <QPixmapCache>
---CoinDim---
../Mod/TechDraw/Gui/QGCustomSvg.cpp
../Mod/TechDraw/Gui/AppTechDrawGuiPy.cpp
../Mod/TechDraw/Gui/QGC  …[truncated]
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}
tool result
180	    if (it != d->xpmCache.end()) {
181	        px = it.value();
182	        return true;
183	    }
184	    return false;
185	}
186	
187	QIcon BitmapFactoryInst::iconFromTheme(const char* name, const QIcon& fallback)
188	{
189	    if (!d->useIconTheme) {
190	        return iconFromDefaultTheme(name, fallback);
191	    }
192	
193	    QString iconName = QString::fromUtf8(name);
194	    QIcon icon = QIcon::fromTheme(iconName, fallback);
195	    if (icon.isNull()) {
196	        QPixmap px = pixma  …[truncated]
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…
tool result
295:    static qreal dpr = getMaximumDPR();
511:    qreal dpr1 = p1.devicePixelRatio();
512:    qreal dpr2 = p2.devicePixelRatio();
551:    qreal dpr = getMaximumDPR();
721:qreal BitmapFactoryInst::getMaximumDPR()
726:        dpr = std::max(screen->devicePixelRatio(), dpr);
---
    static qreal dpr = getMaximumDPR();

    // If an absolute path is given
    QPixmap icon;
    QString iconPath;
    QString fn = QString::fromUtf8(name);
    if (QFile(fn).exists()) {
        iconPath =   …[truncated]
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…
tool result
2483:            QPainter painter(&image);
2485:            painter.drawImage(0, 0, img);
2836:#ifdef __EMSCRIPTEN__
2837:    // The Image render type snapshots the scene via grabFramebuffer() and blits
2875:                    QOpenGLFramebufferObject::blitFramebuffer(framebuffer, fbo);
2881:                            "Framebuffer blit is unavailable; falling back to a single-sample "
2936:        QPainter painter(&image);
2938:        painter.drawImage(0, 0, res);
3018:        QPainter painte  …[truncated]
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/View3DInventorViewer.cpp", "offset": 4680, "limit": 80}
tool result
4680	{
4681	    navigation->setSpinningAnimationEnabled(enable);
4682	}
4683	
4684	/**
4685	 * @return Whether or not it is possible to start any animation
4686	 */
4687	bool View3DInventorViewer::isAnimationEnabled() const
4688	{
4689	#ifdef __EMSCRIPTEN__
4690	    // Camera animations (viewAll/moveCameraTo) drive a QAbstractAnimation timer
4691	    // loop that does not cooperate with Emscripten asyncify — a programmatic
4692	    // fitAll()/ViewFit suspends and never resumes. Take the immedia  …[truncated]
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…
tool result
Exit code 1
100:    return TreeParams::getVisibilityIcon();
153:    Connection connectIcon;
164:        connectIcon = viewObject->signalChangeIcon.connect(
165:            std::bind(&DocumentObjectData::slotChangeIcon, this)
289:        QIcon icon, icon2;
295:    void slotChangeIcon()
439:    // rendering as transparent overlay.
472:    // override the overlay stylesheet's blanket ::item { color } for text fading.
740:        QIcon icon(*documentPixmap);
742:            icon.pixmap(docu  …[truncated]
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"}
tool result
/opt/toolchains/qtsrc/qtsvg/src/plugins/iconengines/svgiconengine/qsvgiconengine.cpp
/opt/toolchains/qtsrc/qtsvg-build/plugins/iconengines
/opt/toolchains/qtsrc/qtsvg-build/src/plugins/iconengines
/opt/toolchains/qtsrc/qtsvg-build/plugins/iconengines/libqsvgicon.a
/opt/toolchains/qtsrc/qtsvg-build/plugins/iconengines/libqsvgicon.prl
/opt/toolchains/qtsrc/qtsvg/src/plugins/iconengines
/opt/toolchains/qtsrc/qtsvg/src/plugins/iconengines/CMakeLists.txt
/opt/toolchains/qtsrc/qtsvg/src/plugins/iconen  …[truncated]
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…
tool result
11:#include "qsvgrenderer.h"
54:    bool tryLoad(QSvgRenderer *renderer, QIcon::Mode mode, QIcon::State state);
55:    QIcon::Mode loadDataForModeAndState(QSvgRenderer *renderer, QIcon::Mode mode, QIcon::State state);
85:QSize QSvgIconEngine::actualSize(const QSize &size, QIcon::Mode mode,
99:    QPixmap pm = pixmap(size, mode, state);
114:bool QSvgIconEnginePrivate::tryLoad(QSvgRenderer *renderer, QIcon::Mode mode, QIcon::State state)
119:        if (renderer->load(maybeUncompress(buf)))
125:    …[truncated]
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}
tool result
179	QPixmap QSvgIconEngine::scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state,
180	                                     qreal scale)
181	{
182	    QPixmap pm;
183	
184	    QString pmckey(d->pmcKey(size, mode, state, scale));
185	    if (QPixmapCache::find(pmckey, &pm))
186	        return pm;
187	
188	    if (!d->addedPixmaps.isEmpty()) {
189	        const auto realSize = size * scale;
190	        const auto key = d->hashKey(mode, state);
191	        auto it = d->addedPixmaps.c  …[truncated]
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}
tool result
2820	    }
2821	    else if (format == "GL_RGB32F") {
2822	        return GL_RGB32F_ARB;
2823	    }
2824	    else if (format == "GL_RGBA32F") {
2825	        return GL_RGBA32F_ARB;
2826	    }
2827	    else {
2828	        QOpenGLFramebufferObjectFormat fboFormat;
2829	        return fboFormat.internalTextureFormat();
2830	    }
2831	    // NOLINTEND
2832	}
2833	
2834	void View3DInventorViewer::setRenderType(RenderType type)
2835	{
2836	#ifdef __EMSCRIPTEN__
2837	    // The Image render type snapsh  …[truncated]
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…
tool result
CMakeLists.txt
WasmGLWidget.h
WasmGLWidget.cpp
WasmGLFixedFunc.cpp
View3DInventorViewer.cpp
Quarter/QuarterWidget.cpp
Quarter/QuarterWidgetP.cpp
---
Quarter/DragDropHandler.cpp:163:  this->quarterwidget->viewport()->update();
Quarter/QuarterWidget.cpp:250:        update(); // force update when changing window mode
Quarter/QuarterWidget.cpp:254:        update(); // fixes flickering on some systems
Quarter/QuarterWidget.cpp:922:    this->actualRedraw();
Quarter/QuarterWidget.cpp:1024:  // When sty  …[truncated]
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/WasmGLWidget.cpp"}
tool result
1	/***************************************************************************
2	 *   wasm offscreen-FBO viewport for Quarter — see WasmGLWidget.h.          *
3	 ***************************************************************************/
4	
5	#include "PreCompiled.h"
6	#ifdef __EMSCRIPTEN__
7	
8	#include "WasmGLWidget.h"
9	
10	#include <QOpenGLContext>
11	#include <QOffscreenSurface>
12	#include <QOpenGLFramebufferObject>
13	#include <QResizeEvent>
14	
15	using namespace Gui;
16	
17	WasmGLWidge  …[truncated]
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…
tool result
71:#include <QPainter>
72:#include "../WasmGLWidget.h"
145:// black. CustomGLWidget instead derives from Gui::WasmGLWidget — a raster widget
149:class CustomGLWidget : public Gui::WasmGLWidget {
154:     : Gui::WasmGLWidget(parent), myFormat(format)
162://We need to avoid buffer swapping when initializing a QPainter on this widget
220:        QuarterWidget* qw = qobject_cast<QuarterWidget*>(parentWidget());
250:        update(); // force update when changing window mode
254:        update(); //   …[truncated]
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}
tool result
820	  }
821	}
822	
823	bool
824	QuarterWidget::updateDevicePixelRatio() {
825	    qreal dev_pix_ratio = 1.0;
826	    QWidget* winwidg = window();
827	    QWindow* win = nullptr;
828	    if(winwidg) {
829	        win = winwidg->windowHandle();
830	    }
831	    if(win) {
832	        dev_pix_ratio = win->devicePixelRatio();
833	    }
834	    else {
835	        dev_pix_ratio = ((QGuiApplication*)QGuiApplication::instance())->devicePixelRatio();
836	    }
837	    if(PRIVATE(this)->device_pixel_ratio  …[truncated]
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…
tool result
1057:    // Set up foreground, overlaid scenegraph.
1058:    this->foregroundroot = new SoSeparator;
1059:    this->foregroundroot->ref();
1060:    this->foregroundroot->setName("foregroundroot");
1076:    auto foregroundLightModel = new SoLightModel;
1077:    foregroundLightModel->model = SoLightModel::BASE_COLOR;
1079:    auto foregroundBaseColor = new SoBaseColor;
1080:    foregroundBaseColor->rgb = SbColor(1, 1, 0);
1103:    this->foregroundroot->addChild(cam);
1104:    this->foregroundroot-  …[truncated]
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…
tool result
Exit code 1
Quarter/QuarterWidget.cpp:286:  QGraphicsScene* scene = new QGraphicsScene(this);
Quarter/QuarterWidget.cpp:287:  setScene(scene);
Quarter/QuarterWidget.cpp:359:  this->setSceneGraph(nullptr);
Quarter/QuarterWidget.cpp:639:QuarterWidget::setSceneGraph(SoNode * node)
Quarter/QuarterWidget.cpp:673:  PRIVATE(this)->soeventmanager->setSceneGraph(superscene);
Quarter/QuarterWidget.cpp:674:  PRIVATE(this)->sorendermanager->setSceneGraph(superscene);
Quarter/QuarterWidget.cpp:716:    PRIVAT  …[truncated]
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…
tool result
7:#include "qwasmcompositor.h"
18:QWasmBackingStore::QWasmBackingStore(QWasmCompositor *compositor, QWindow *window)
19:    : QPlatformBackingStore(window), m_compositor(compositor)
40:void QWasmBackingStore::flush(QWindow *window, const QRegion &region, const QPoint &offset)
43:    m_dirty |= region;
45:    QRect updateRect = region.boundingRect();
46:    updateRect.translate(offset);
48:    m_compositor->handleBackingStoreFlush(this->window(), updateRect);
51:void QWasmBackingStore::updateText  …[truncated]
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…
tool result
34:#include <QPainter>
41:#include <QPainterPath>
102:        update();
129:    void update()
437:        _actFloat.setIcon(BitmapFactory().pixmap("qss:overlay/icons/float.svg"));
438:        _actOverlay.setIcon(BitmapFactory().pixmap("qss:overlay/icons/overlay.svg"));
439:        _actClose.setIcon(BitmapFactory().pixmap("qss:overlay/icons/close.svg"));
532:            OverlayStyleSheet::instance()->update();
1839:                // visualRect(). However, if we are using QGraphicsEffects  …[truncated]
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"…
tool result
33:#include <QPainter>
45:#include <QPainterPath>
339:void OverlayProxyWidget::paintEvent(QPaintEvent*)
344:    QPainter painter(this);
419:    proxyWidget = new OverlayProxyWidget(this);
420:    proxyWidget->hide();
421:    _setOverlayMode(proxyWidget, OverlayOption::Enable);
551:    proxyWidget->hide();
627:    if (titleBar->isVisible() && titleBar->rect().contains(titleBar->mapFromGlobal(_pos))) {
672:void OverlayTabWidget::paintEvent(QPaintEvent* ev)
679:    QTabWidget::paintEvent(  …[truncated]
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}
tool result
2158	void OverlayTitleBar::paintEvent(QPaintEvent*)
2159	{
2160	    if (!titleItem) {
2161	        return;
2162	    }
2163	
2164	    QDockWidget* dock = qobject_cast<QDockWidget*>(parentWidget());
2165	    int vertical = false;
2166	    int flags = Qt::AlignCenter;
2167	    if (!dock) {
2168	        OverlayTabWidget* tabWidget = qobject_cast<OverlayTabWidget*>(parentWidget());
2169	        if (tabWidget) {
2170	            switch (tabWidget->getDockArea()) {
2171	                case Qt::TopDock  …[truncated]
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}
tool result
2790	OverlayGraphicsEffect::OverlayGraphicsEffect(QObject* parent)
2791	    : QGraphicsEffect(parent)
2792	    , _enabled(false)
2793	    , _size(1, 1)
2794	    , _blurRadius(2.0f)
2795	    , _color(0, 0, 0, 80)
2796	{}
2797	
2798	QT_BEGIN_NAMESPACE
2799	extern Q_WIDGETS_EXPORT void qt_blurImage(
2800	    QPainter* p,
2801	    QImage& blurImage,
2802	    qreal radius,
2803	    bool quality,
2804	    bool alphaOnly,
2805	    int transposed = 0
2806	);
2807	QT_END_NAMESPACE
2808	
2809	void Overlay  …[truncated]
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…
tool result
Gui/ViewProvider.cpp
---getIcon impl---
QIcon ViewProvider::getIcon() const
{
    return mergeGreyableOverlayIcons(Gui::BitmapFactory().pixmap(sPixmap));
}
zshexit:1: parameter HISTDB_FD does not contain a file descriptor
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}
tool result
90	    return nullptr;
91	}
92	
93	class OverlayStyleSheet: public ParameterGrp::ObserverType
94	{
95	public:
96	    OverlayStyleSheet()
97	    {
98	        handle = App::GetApplication().GetParameterGroupByPath(
99	            "User parameter:BaseApp/Preferences/MainWindow"
100	        );
101	
102	        update();
103	
104	        handle->Attach(this);
105	    }
106	
107	    static OverlayStyleSheet* instance()
108	    {
109	        static OverlayStyleSheet* instance;
110	
111	        if (!ins  …[truncated]
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)…
tool result
./Gui/Stylesheets/overlay/Freecad Overlay.qss
---grep svg in overlay qss---
---count svg refs---
ugrep: warning: ./Gui/Stylesheets/overlay/Freecad: No such file or directory
./Gui/Stylesheets/overlay/Freecad: 
ugrep: warning: Overlay.qss: No such file or directory
Overlay.qss: 
zshexit:1: parameter HISTDB_FD does not contain a file descriptor
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>/…
tool result
6
---main stylesheets svg counts---
86 ./Gui/Stylesheets/FreeCAD.qss
0 ./Gui/Stylesheets/defaults.qss
zshexit:1: parameter HISTDB_FD does not contain a file descriptor
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\\…
tool result
Exit code 1
    return mergeGreyableOverlayIcons(Gui::BitmapFactory().pixmap(sPixmap));
}
QIcon ViewProvider::mergeGreyableOverlayIcons(const QIcon& orig) const
{
    auto vector = getExtensionsDerivedFromType<Gui::ViewProviderExtension>();

    QIcon overlayedIcon = orig;

    for (Gui::ViewProviderExtension* ext : vector) {
        if (!ext->ignoreOverlayIcon()) {
            overlayedIcon = ext->extensionMergeGreyableOverlayIcons(overlayedIcon);
        }
    }

    return overl  …[truncated]
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…
tool result
Exit code 1
102:        update();
125:            OverlayManager::instance()->refresh(nullptr, true);
129:    void update()
400:            refresh();
405:        Application::Instance->signalActivateView.connect([this](const MDIView*) { refresh(); });
407:            refresh();
410:            refresh();
412:        Application::Instance->signalActivateWorkbench.connect([this](const char*) { refresh(); });
460:        refresh();
525:        refresh();
529:    void refresh(QWidget* wid  …[truncated]
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\"…
tool result
Exit code 1
130:  qproperty-icon: url(:/icons/icons/NavigationBlender_light.svg);
183:  image: url(qss:@IconsLocationFolderName/splitter_vertical-lightgray.svg);
187:  image: url(qss:@IconsLocationFolderName/splitter_horizontal-lightgray.svg);
297:  image:url(qss:@IconsLocationFolderName/check-mark-@StylesheetIconsColor.svg);
309:  image: url(qss:@IconsLocationFolderName/check-mark-lightgray.svg);
316:  image: url(qss:@IconsLocationFolderName/indeterminate-mark-@StylesheetIconsColor.svg);
323:    …[truncated]
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…
tool result
OverlayWidgets.cpp:374:    _graphicsEffect = new OverlayGraphicsEffect(splitter);
OverlayWidgets.cpp:375:    splitter->setGraphicsEffect(_graphicsEffect);
OverlayWidgets.cpp:377:    _graphicsEffectTab = new OverlayGraphicsEffect(this);
OverlayWidgets.cpp:378:    _graphicsEffectTab->setEnabled(false);
OverlayWidgets.cpp:379:    tabBar()->setGraphicsEffect(_graphicsEffectTab);
OverlayWidgets.cpp:1178:            _graphicsEffectTab->setEnabled(false);
OverlayWidgets.cpp:1195:                _  …[truncated]
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…
tool result
619:    if (!count() || (!isOverlaid() && !isTransparent()) || !isVisible()) {
773:bool OverlayTabWidget::effectEnabled() const
775:    return _effectEnabled;
780:    _effectEnabled = enable;
1366:        widget->setAttribute(Qt::WA_NoSystemBackground, option != OverlayOption::Disable);
1367:        widget->setAttribute(Qt::WA_TranslucentBackground, option != OverlayOption::Disable);
1411:bool OverlayTabWidget::isTransparent() const
1430:    if (option != QueryOption::QueryOverlay && curr  …[truncated]
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…
tool result
Structured output provided successfully