Design canvas: gate direct-render hacks behind sw-GL detection

Mirror of snaporca-cad f3b665b. DesignCanvas had ~38 call-sites doing
`set_as_dirty(); render()` directly, tagged "llvmpipe: force repaint" —
software-GL workarounds from the headless :10 test box. On hardware GL this
bypasses the dirty/refresh cycle and burns frames outside the paint event.
Funnel all 38 through a new request_repaint() helper that branches on the
cached GL renderer string: hardware GL gets set_as_dirty()+wxGLCanvas::Refresh()
(render() runs inside the paint cycle), software GL keeps the direct render(),
undetected backend takes the safe direct path. Behaviour on the llvmpipe :10
box is byte-identical; hardware GL invalidates the canonical editor way.

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-06-29 06:11:22 +02:00
co-authored by Claude Opus 4.8
parent 2929f66881
commit a121e30a13
2 changed files with 71 additions and 43 deletions
+64 -43
View File
@@ -10,6 +10,7 @@
#include "libslic3r/TriangleMesh.hpp" #include "libslic3r/TriangleMesh.hpp"
#include "3DScene.hpp" #include "3DScene.hpp"
#include "libslic3r/Config.hpp" #include "libslic3r/Config.hpp"
#include <boost/algorithm/string/predicate.hpp>
#include <wx/glcanvas.h> #include <wx/glcanvas.h>
#include <wx/sizer.h> #include <wx/sizer.h>
@@ -87,12 +88,12 @@ DesignCanvas::DesignCanvas(wxWindow* parent)
[this, commit](double v) { [this, commit](double v) {
m_sketch_tool.set_inline_busy(false); m_sketch_tool.set_inline_busy(false);
if (commit) commit(v); if (commit) commit(v);
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); } request_repaint();
}, },
[this, cancel]() { [this, cancel]() {
m_sketch_tool.set_inline_busy(false); m_sketch_tool.set_inline_busy(false);
if (cancel) cancel(); if (cancel) cancel();
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); } request_repaint();
}); });
}; };
// Let the tool force-close the field (keep-as-drawn) — polyline right-click/double-click // Let the tool force-close the field (keep-as-drawn) — polyline right-click/double-click
@@ -165,6 +166,29 @@ static ColorRGBA body_palette(int body_idx)
return kPalette[((body_idx % n) + n) % n]; return kPalette[((body_idx % n) + n) % n];
} }
void DesignCanvas::request_repaint()
{
if (!m_canvas)
return;
m_canvas->set_as_dirty();
if (m_sw_gl < 0) {
// Cache the backend once it's known; the renderer string is empty until
// GL is initialised, so stay "unknown" and take the safe direct path till then.
const std::string& r = OpenGLManager::get_gl_info().get_renderer();
if (!r.empty())
m_sw_gl = (boost::icontains(r, "llvmpipe") || boost::icontains(r, "softpipe") ||
boost::icontains(r, "swrast") || boost::icontains(r, "software")) ? 1 : 0;
}
if (m_sw_gl == 0) {
if (m_canvas_widget)
m_canvas_widget->Refresh(); // hardware GL: paint cycle drives render()
} else {
m_canvas->render(); // software GL or backend not yet known
}
}
void DesignCanvas::reload(bool keep_view) void DesignCanvas::reload(bool keep_view)
{ {
m_canvas->reset_volumes(); m_canvas->reset_volumes();
@@ -403,13 +427,13 @@ void DesignCanvas::set_on_solve_state(std::function<void(int, bool, bool)> cb)
void DesignCanvas::apply_segment_length(double len) void DesignCanvas::apply_segment_length(double len)
{ {
m_sketch_tool.apply_segment_length(len); m_sketch_tool.apply_segment_length(len);
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); } request_repaint();
} }
void DesignCanvas::keep_segment_as_drawn() void DesignCanvas::keep_segment_as_drawn()
{ {
m_sketch_tool.keep_segment_as_drawn(); m_sketch_tool.keep_segment_as_drawn();
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); } request_repaint();
} }
void DesignCanvas::set_on_sketch_selection_changed(std::function<void(int)> cb) void DesignCanvas::set_on_sketch_selection_changed(std::function<void(int)> cb)
@@ -464,13 +488,13 @@ ColorRGBA DesignCanvas::body_color(int body) const
void DesignCanvas::begin_move_body(int body, const Vec3d& pivot, const Transform3d& base_xform) void DesignCanvas::begin_move_body(int body, const Vec3d& pivot, const Transform3d& base_xform)
{ {
m_sketch_tool.set_move_gizmo(body, pivot, base_xform); m_sketch_tool.set_move_gizmo(body, pivot, base_xform);
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); } // llvmpipe: force repaint request_repaint();
} }
void DesignCanvas::clear_move_gizmo() void DesignCanvas::clear_move_gizmo()
{ {
m_sketch_tool.clear_move_gizmo(); m_sketch_tool.clear_move_gizmo();
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); } request_repaint();
} }
bool DesignCanvas::moving_body() const { return m_sketch_tool.moving_body(); } bool DesignCanvas::moving_body() const { return m_sketch_tool.moving_body(); }
@@ -483,14 +507,14 @@ void DesignCanvas::set_on_body_move_changed(std::function<void(int, const Transf
bool DesignCanvas::begin_fillet_gizmo(const Vec3d& body_centroid, double radius) bool DesignCanvas::begin_fillet_gizmo(const Vec3d& body_centroid, double radius)
{ {
const bool ok = m_sketch_tool.set_fillet_gizmo(body_centroid, radius); const bool ok = m_sketch_tool.set_fillet_gizmo(body_centroid, radius);
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); } // llvmpipe: force repaint request_repaint();
return ok; return ok;
} }
void DesignCanvas::clear_fillet_gizmo() void DesignCanvas::clear_fillet_gizmo()
{ {
m_sketch_tool.clear_fillet_gizmo(); m_sketch_tool.clear_fillet_gizmo();
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); } request_repaint();
} }
bool DesignCanvas::filleting() const { return m_sketch_tool.filleting(); } bool DesignCanvas::filleting() const { return m_sketch_tool.filleting(); }
@@ -504,7 +528,7 @@ void DesignCanvas::begin_hole_gizmo(const SketchPlane& plane, double x, double y
double diameter, double depth, bool through) double diameter, double depth, bool through)
{ {
m_sketch_tool.set_hole_gizmo(plane, x, y, diameter, depth, through); m_sketch_tool.set_hole_gizmo(plane, x, y, diameter, depth, through);
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); } // llvmpipe: force repaint request_repaint();
} }
void DesignCanvas::set_hole_face_bounds(bool has, double umin, double umax, double vmin, double vmax) void DesignCanvas::set_hole_face_bounds(bool has, double umin, double umax, double vmin, double vmax)
@@ -515,7 +539,7 @@ void DesignCanvas::set_hole_face_bounds(bool has, double umin, double umax, doub
void DesignCanvas::clear_hole_gizmo() void DesignCanvas::clear_hole_gizmo()
{ {
m_sketch_tool.clear_hole_gizmo(); m_sketch_tool.clear_hole_gizmo();
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); } request_repaint();
} }
bool DesignCanvas::holing() const { return m_sketch_tool.holing(); } bool DesignCanvas::holing() const { return m_sketch_tool.holing(); }
@@ -529,13 +553,13 @@ void DesignCanvas::begin_thread_gizmo(const SketchPlane& plane, double x, double
double radius, double height) double radius, double height)
{ {
m_sketch_tool.set_thread_gizmo(plane, x, y, radius, height); m_sketch_tool.set_thread_gizmo(plane, x, y, radius, height);
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); } // llvmpipe: force repaint request_repaint();
} }
void DesignCanvas::clear_thread_gizmo() void DesignCanvas::clear_thread_gizmo()
{ {
m_sketch_tool.clear_thread_gizmo(); m_sketch_tool.clear_thread_gizmo();
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); } request_repaint();
} }
bool DesignCanvas::threading() const { return m_sketch_tool.threading(); } bool DesignCanvas::threading() const { return m_sketch_tool.threading(); }
@@ -549,13 +573,13 @@ void DesignCanvas::begin_shell_gizmo(const Vec3d& face_centroid, const Vec3d& in
double thickness) double thickness)
{ {
m_sketch_tool.set_shell_gizmo(face_centroid, inward_dir, thickness); m_sketch_tool.set_shell_gizmo(face_centroid, inward_dir, thickness);
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); } // llvmpipe: force repaint request_repaint();
} }
void DesignCanvas::clear_shell_gizmo() void DesignCanvas::clear_shell_gizmo()
{ {
m_sketch_tool.clear_shell_gizmo(); m_sketch_tool.clear_shell_gizmo();
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); } request_repaint();
} }
bool DesignCanvas::shelling() const { return m_sketch_tool.shelling(); } bool DesignCanvas::shelling() const { return m_sketch_tool.shelling(); }
@@ -569,13 +593,13 @@ void DesignCanvas::begin_revolve_gizmo(const SketchPlane& plane, const Vec2d& ce
int axis_sel, double angle, bool flip) int axis_sel, double angle, bool flip)
{ {
m_sketch_tool.set_revolve_gizmo(plane, centroid, axis_sel, angle, flip); m_sketch_tool.set_revolve_gizmo(plane, centroid, axis_sel, angle, flip);
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); } // llvmpipe: force repaint request_repaint();
} }
void DesignCanvas::clear_revolve_gizmo() void DesignCanvas::clear_revolve_gizmo()
{ {
m_sketch_tool.clear_revolve_gizmo(); m_sketch_tool.clear_revolve_gizmo();
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); } request_repaint();
} }
bool DesignCanvas::revolving() const { return m_sketch_tool.revolving(); } bool DesignCanvas::revolving() const { return m_sketch_tool.revolving(); }
@@ -589,13 +613,13 @@ void DesignCanvas::begin_pattern_gizmo(const SketchPlane& plane, const Vec3d& bo
bool circular, int count, int dir, double spacing, double angle) bool circular, int count, int dir, double spacing, double angle)
{ {
m_sketch_tool.set_pattern_gizmo(plane, body_centroid, circular, count, dir, spacing, angle); m_sketch_tool.set_pattern_gizmo(plane, body_centroid, circular, count, dir, spacing, angle);
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); } // llvmpipe: force repaint request_repaint();
} }
void DesignCanvas::clear_pattern_gizmo() void DesignCanvas::clear_pattern_gizmo()
{ {
m_sketch_tool.clear_pattern_gizmo(); m_sketch_tool.clear_pattern_gizmo();
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); } request_repaint();
} }
bool DesignCanvas::patterning() const { return m_sketch_tool.patterning(); } bool DesignCanvas::patterning() const { return m_sketch_tool.patterning(); }
@@ -618,20 +642,20 @@ void DesignCanvas::set_on_place_on_face(std::function<bool()> cb)
void DesignCanvas::select_body(int body) void DesignCanvas::select_body(int body)
{ {
m_sketch_tool.select_body(body); m_sketch_tool.select_body(body);
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); } // redraw the overlay (llvmpipe) request_repaint();
} }
void DesignCanvas::set_extrude_gizmo(const SketchPlane& plane, const Vec2d& centroid, void DesignCanvas::set_extrude_gizmo(const SketchPlane& plane, const Vec2d& centroid,
double depth, double depth2, bool two_sided, bool flip) double depth, double depth2, bool two_sided, bool flip)
{ {
m_sketch_tool.set_extrude_gizmo(plane, centroid, depth, depth2, two_sided, flip); m_sketch_tool.set_extrude_gizmo(plane, centroid, depth, depth2, two_sided, flip);
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); } // llvmpipe: force repaint request_repaint();
} }
void DesignCanvas::clear_extrude_gizmo() void DesignCanvas::clear_extrude_gizmo()
{ {
m_sketch_tool.clear_extrude_gizmo(); m_sketch_tool.clear_extrude_gizmo();
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); } request_repaint();
} }
void DesignCanvas::set_on_extrude_depth_changed(std::function<void(double, bool)> cb) void DesignCanvas::set_on_extrude_depth_changed(std::function<void(double, bool)> cb)
@@ -657,16 +681,14 @@ void DesignCanvas::set_on_undo_redo(std::function<void(bool)> cb)
void DesignCanvas::set_display_sketches(std::vector<DesignSketchTool::DisplaySketch> ds) void DesignCanvas::set_display_sketches(std::vector<DesignSketchTool::DisplaySketch> ds)
{ {
m_sketch_tool.set_display_sketches(std::move(ds)); m_sketch_tool.set_display_sketches(std::move(ds));
// Direct render: under llvmpipe a scheduled Refresh() often doesn't repaint // Overlay changed programmatically (no mouse event) — force a repaint.
// unless some other event (e.g. a modal close) forces it, so programmatic request_repaint();
// overlay changes (hide/show, re-solve) could leave a stale overlay.
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); }
} }
void DesignCanvas::set_datum_planes(std::vector<SketchPlane> planes) void DesignCanvas::set_datum_planes(std::vector<SketchPlane> planes)
{ {
m_sketch_tool.set_datum_planes(std::move(planes)); m_sketch_tool.set_datum_planes(std::move(planes));
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); } // llvmpipe: force repaint request_repaint();
} }
void DesignCanvas::set_readout(const std::string& text) void DesignCanvas::set_readout(const std::string& text)
@@ -711,13 +733,13 @@ void DesignCanvas::set_body_hidden(bool on)
void DesignCanvas::delete_selected_sketch_entities() void DesignCanvas::delete_selected_sketch_entities()
{ {
m_sketch_tool.delete_selected(); m_sketch_tool.delete_selected();
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); } request_repaint();
} }
void DesignCanvas::clear_sketch_selection() void DesignCanvas::clear_sketch_selection()
{ {
m_sketch_tool.clear_selection(); m_sketch_tool.clear_selection();
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); } request_repaint();
} }
DesignSketchTool::DimType DesignCanvas::sketch_dimension_kind() const DesignSketchTool::DimType DesignCanvas::sketch_dimension_kind() const
@@ -733,7 +755,7 @@ double DesignCanvas::sketch_dimension_current() const
void DesignCanvas::apply_sketch_dimension(double v) void DesignCanvas::apply_sketch_dimension(double v)
{ {
m_sketch_tool.apply_dimension(v); m_sketch_tool.apply_dimension(v);
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); } request_repaint();
} }
void DesignCanvas::open_inline_value(double current, std::function<void(double)> commit, void DesignCanvas::open_inline_value(double current, std::function<void(double)> commit,
@@ -764,12 +786,12 @@ void DesignCanvas::open_inline_value(double current, std::function<void(double)>
[this, commit](double v) { [this, commit](double v) {
m_sketch_tool.set_inline_busy(false); m_sketch_tool.set_inline_busy(false);
if (commit) commit(v); if (commit) commit(v);
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); } request_repaint();
}, },
[this, cancel]() { [this, cancel]() {
m_sketch_tool.set_inline_busy(false); m_sketch_tool.set_inline_busy(false);
if (cancel) cancel(); if (cancel) cancel();
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); } request_repaint();
}); });
} }
@@ -786,21 +808,20 @@ DesignSketchTool::DimType DesignCanvas::pending_dimension_type() const
void DesignCanvas::set_sketch_dimension_value(double v) void DesignCanvas::set_sketch_dimension_value(double v)
{ {
m_sketch_tool.set_dimension_value(v); m_sketch_tool.set_dimension_value(v);
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); } request_repaint();
} }
void DesignCanvas::cancel_sketch_dimension() void DesignCanvas::cancel_sketch_dimension()
{ {
m_sketch_tool.cancel_dimension_value(); m_sketch_tool.cancel_dimension_value();
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); } request_repaint();
} }
void DesignCanvas::begin_constrain(const SketchProfile& prof, const SketchPlane& plane) void DesignCanvas::begin_constrain(const SketchProfile& prof, const SketchPlane& plane)
{ {
m_sketch_tool.begin_constrain(prof, plane); m_sketch_tool.begin_constrain(prof, plane);
// The overlay must appear immediately (no mouse move to trigger a repaint); // The overlay must appear immediately (no mouse move to trigger a repaint).
// a direct render() is the proven path under llvmpipe. request_repaint();
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); }
} }
void DesignCanvas::begin_imported_transform( void DesignCanvas::begin_imported_transform(
@@ -808,7 +829,7 @@ void DesignCanvas::begin_imported_transform(
const SketchPlane& plane, const Vec2d& offset, double scale_x, double scale_y) const SketchPlane& plane, const Vec2d& offset, double scale_x, double scale_y)
{ {
m_sketch_tool.begin_imported_transform(feat, base_regions, plane, offset, scale_x, scale_y); m_sketch_tool.begin_imported_transform(feat, base_regions, plane, offset, scale_x, scale_y);
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); } request_repaint();
} }
void DesignCanvas::set_on_imported_transform(std::function<void(int, Vec2d, double, double)> cb) void DesignCanvas::set_on_imported_transform(std::function<void(int, Vec2d, double, double)> cb)
@@ -821,7 +842,7 @@ void DesignCanvas::end_constrain()
// cancel() clears m_active + the picked-segment/entity indices, so the // cancel() clears m_active + the picked-segment/entity indices, so the
// constrain overlay (highlighted picks) disappears on the next render. // constrain overlay (highlighted picks) disappears on the next render.
m_sketch_tool.cancel(); m_sketch_tool.cancel();
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); } request_repaint();
} }
bool DesignCanvas::is_constraining() const { return m_sketch_tool.is_constraining(); } bool DesignCanvas::is_constraining() const { return m_sketch_tool.is_constraining(); }
@@ -834,14 +855,14 @@ bool DesignCanvas::selected_segment(int& a, int& b) const
void DesignCanvas::update_constrain_profile(const std::vector<Vec2d>& pts) void DesignCanvas::update_constrain_profile(const std::vector<Vec2d>& pts)
{ {
m_sketch_tool.set_profile_points(pts); m_sketch_tool.set_profile_points(pts);
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); } request_repaint();
} }
void DesignCanvas::begin_constrain_entities(const std::vector<SketchEntity>& ents, void DesignCanvas::begin_constrain_entities(const std::vector<SketchEntity>& ents,
const SketchPlane& plane) const SketchPlane& plane)
{ {
m_sketch_tool.begin_constrain_entities(ents, plane); m_sketch_tool.begin_constrain_entities(ents, plane);
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); } request_repaint();
} }
bool DesignCanvas::is_constraining_entities() const bool DesignCanvas::is_constraining_entities() const
@@ -867,19 +888,19 @@ bool DesignCanvas::pick0_point(Vec2d& out) const
void DesignCanvas::update_constrain_entities(const std::vector<SketchEntity>& ents) void DesignCanvas::update_constrain_entities(const std::vector<SketchEntity>& ents)
{ {
m_sketch_tool.set_constrain_entities(ents); m_sketch_tool.set_constrain_entities(ents);
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); } request_repaint();
} }
void DesignCanvas::set_constraint_highlight(std::vector<int> entities) void DesignCanvas::set_constraint_highlight(std::vector<int> entities)
{ {
m_sketch_tool.set_constraint_highlight(std::move(entities)); m_sketch_tool.set_constraint_highlight(std::move(entities));
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); } request_repaint();
} }
void DesignCanvas::set_constraint_glyphs(std::vector<SketchEntityConstraintDef> cons) void DesignCanvas::set_constraint_glyphs(std::vector<SketchEntityConstraintDef> cons)
{ {
m_sketch_tool.set_constraint_glyphs(std::move(cons)); m_sketch_tool.set_constraint_glyphs(std::move(cons));
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); } request_repaint();
} }
}} // namespace Slic3r::GUI }} // namespace Slic3r::GUI
+7
View File
@@ -203,9 +203,16 @@ public:
private: private:
void reload(bool keep_view); void reload(bool keep_view);
// Repaint the embedded canvas the right way for the active GL backend:
// hardware GL gets a scheduled wxEVT_PAINT (render() runs inside the paint
// cycle); software GL (llvmpipe etc.) gets a direct render() because a
// scheduled Refresh() is frequently dropped there. Backend cached on first use.
void request_repaint();
wxGLCanvas* m_canvas_widget{nullptr}; wxGLCanvas* m_canvas_widget{nullptr};
GLCanvas3D* m_canvas{nullptr}; GLCanvas3D* m_canvas{nullptr};
int m_sw_gl{-1}; // -1 unknown, 0 hardware GL, 1 software GL
Bed3D m_bed; Bed3D m_bed;
Model m_model; Model m_model;
bool m_first_frame{true}; bool m_first_frame{true};