Harden the GL canvases against dropped frames and UI-thread freezes

Viewport: GLCanvas3D::on_idle() cleared m_dirty even when
_refresh_if_shown_on_screen() rendered nothing because the canvas was not on
screen yet — a frame requested while the notebook was still showing a page got
silently swallowed and the viewport stayed blank until some later event dirtied
it again. _refresh_if_shown_on_screen() now reports whether it rendered, and
on_idle keeps the canvas dirty when it did not. Covers Design/Prepare/Preview.

Freeze: a big STEP (17.8 MB) spent ~40-50 s inside OCCT on the UI thread
(read_step_solids + recompute), so the window stopped repainting and the
compositor marked the app unresponsive. Both now run on a worker thread behind
an app-modal pulsing progress dialog: the UI keeps painting and the document
cannot be touched while the worker owns it. OCCT's Standard_Failure is not a
std::exception, so the worker catches it explicitly — an escaping exception
would terminate the process.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q
This commit is contained in:
Tommaso Bianchi
2026-07-15 00:04:25 +02:00
co-authored by Claude Opus 4.8
parent 6d9368276c
commit 01aa6903f0
3 changed files with 66 additions and 13 deletions
+15 -10
View File
@@ -3267,7 +3267,10 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt)
m_last_frame_start_time = now;
}
_refresh_if_shown_on_screen();
// Not on screen yet (e.g. the notebook is still showing this page): nothing was rendered,
// so keep the frame pending instead of dropping it — clearing m_dirty here leaves the
// canvas blank until some later event happens to dirty it again.
const bool rendered = _refresh_if_shown_on_screen();
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
if (m_extra_frame_requested || mouse3d_controller_applied || imgui_requires_extra_frame || wxGetApp().imgui()->requires_extra_frame()) {
@@ -3279,7 +3282,7 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt)
evt.RequestMore();
}
else
m_dirty = false;
m_dirty = !rendered;
}
void GLCanvas3D::on_char(wxKeyEvent& evt)
@@ -7251,16 +7254,18 @@ void GLCanvas3D::_update_camera_zoom(double zoom)
m_dirty = true;
}
void GLCanvas3D::_refresh_if_shown_on_screen()
bool GLCanvas3D::_refresh_if_shown_on_screen()
{
if (_is_shown_on_screen()) {
const Size& cnv_size = get_canvas_size();
_resize((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height());
if (!_is_shown_on_screen())
return false;
// Because of performance problems on macOS, where PaintEvents are not delivered
// frequently enough, we call render() here directly when we can.
render();
}
const Size& cnv_size = get_canvas_size();
_resize((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height());
// Because of performance problems on macOS, where PaintEvents are not delivered
// frequently enough, we call render() here directly when we can.
render();
return true;
}
void GLCanvas3D::_picking_pass()