From 8fd5d772202368135bbe0692eebd7052a6999b25 Mon Sep 17 00:00:00 2001 From: Hanif Koh Date: Thu, 10 Sep 2026 13:31:58 +0800 Subject: [PATCH] Draw the Preview From a Reduced Toolpath Set While the User Is Dragging The preview's frame cost is linear in the number of visible segments and nothing else: at 46 % of the layers the frame is 45 % of the time. A tall wipe tower puts enough segments on screen that dragging the camera or a slider cannot keep up, and no amount of shaving per-segment work fixes that - only drawing fewer segments does. Everything cheaper was measured first and ruled out. Of the remaining frame, 31 % is vertex shading and 69 % is triangle setup, so both halves scale with segment count. Screen-space decimation is useless here: the median extrusion segment is 2.5 mm and only 0.4 % are sub-pixel at full-plate zoom, so a threshold small enough to be invisible removes nothing. Merging collinear runs is no better - 2.6 % of this print's moves are collinear with the one before. So the reduction has to be one the user can see, which means it only happens while they are dragging, and only if they ask for it. update_enabled_entities() already walks every vertex to decide what is visible; it now fills a second pair of index buffers in the same walk, dropping the interior infill roles and keeping one layer in four. Because both sets are built together, switching between them is a buffer binding - no rebuild, no per-frame CPU work, which is what made occlusion culling unattractive. GLCanvas3D flips the flag while the mouse is down on the canvas or either slider is live, and asks for one more frame when the level changes so the full-detail frame lands on release. The top of the visible layer range is always kept whole: it is the surface being looked at, and the only layer drawn at full colour in top-layer-only mode. On the 636-layer, 351k-vertex three-filament fixture the set drops from 448467 segments to 85661 (19 %), and a frame during a drag goes from 298 ms to 52 ms. Off by default, behind Preferences > Graphics > G-code Preview. With it off the preview is pixel identical to before, and with it on the preview at rest is pixel identical to the preview with it off - the reduced set is only ever bound mid-drag. Verified both, plus that releasing the mouse restores full detail at an unchanged camera. The OpenGL ES path keeps a single set of entities and is untouched. --- src/libslic3r/AppConfig.cpp | 3 ++ src/libvgcode/include/Viewer.hpp | 10 ++++ src/libvgcode/src/Settings.hpp | 7 +++ src/libvgcode/src/Viewer.cpp | 15 ++++++ src/libvgcode/src/ViewerImpl.cpp | 86 +++++++++++++++++++++++++++++--- src/libvgcode/src/ViewerImpl.hpp | 36 +++++++++++++ src/slic3r/GUI/GCodeViewer.cpp | 10 ++++ src/slic3r/GUI/GCodeViewer.hpp | 8 +++ src/slic3r/GUI/GLCanvas3D.cpp | 9 +++- src/slic3r/GUI/Preferences.cpp | 17 +++++++ 10 files changed, 192 insertions(+), 9 deletions(-) diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index d26296cd69..f2b87d4030 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -203,6 +203,9 @@ void AppConfig::set_defaults() set("seq_top_layer_only", "1"); // ORCA: darken the layers the preview layer slider is not scrubbed to + if (get("preview_reduced_detail_while_dragging").empty()) + set_bool("preview_reduced_detail_while_dragging", false); + if (get("preview_dim_previous_layers").empty()) set_bool("preview_dim_previous_layers", false); diff --git a/src/libvgcode/include/Viewer.hpp b/src/libvgcode/include/Viewer.hpp index 5141245d96..6624577de6 100644 --- a/src/libvgcode/include/Viewer.hpp +++ b/src/libvgcode/include/Viewer.hpp @@ -98,6 +98,16 @@ public: // bool is_dim_previous_layers() const; void set_dim_previous_layers(bool value); + // + // ORCA: draw the preview from a reduced set of entities - the interior infill roles dropped and + // one layer in every set_reduced_detail_layer_stride() kept, always keeping the top of the + // visible layer range. Meant to be held only while the user drags the camera or a slider: the + // reduced set is built alongside the full one, so toggling it never rebuilds anything. + // Has no effect on the OpenGL ES path. + // + void set_reduced_detail(bool value); + bool is_reduced_detail() const; + void set_reduced_detail_layer_stride(uint32_t value); float get_dim_previous_layers_brightness() const; void set_dim_previous_layers_brightness(float value); // diff --git a/src/libvgcode/src/Settings.hpp b/src/libvgcode/src/Settings.hpp index b3aa371c4a..eb7d5929ac 100644 --- a/src/libvgcode/src/Settings.hpp +++ b/src/libvgcode/src/Settings.hpp @@ -25,6 +25,13 @@ struct Settings // ORCA: how bright those darkened layers are rendered, 1.0 = unchanged, 0.0 = black float dim_previous_layers_brightness{ 0.4f }; bool spiral_vase_mode{ false }; + // ORCA: while the user drags the camera or a slider, the preview can be drawn from a reduced + // set of entities: the interior infill roles dropped and one layer in every + // reduced_detail_layer_stride kept. The reduced sets are built alongside the full ones in + // update_enabled_entities(), so turning this on and off costs nothing but a buffer binding. + // Ignored on the OpenGL ES path, which keeps a single set of entities. + bool reduced_detail{ false }; + uint32_t reduced_detail_layer_stride{ 4 }; // // Required update flags // diff --git a/src/libvgcode/src/Viewer.cpp b/src/libvgcode/src/Viewer.cpp index eb606598e9..5cfa55304b 100644 --- a/src/libvgcode/src/Viewer.cpp +++ b/src/libvgcode/src/Viewer.cpp @@ -77,6 +77,21 @@ bool Viewer::is_dim_previous_layers() const return m_impl->is_dim_previous_layers(); } +void Viewer::set_reduced_detail(bool value) +{ + m_impl->set_reduced_detail(value); +} + +bool Viewer::is_reduced_detail() const +{ + return m_impl->is_reduced_detail(); +} + +void Viewer::set_reduced_detail_layer_stride(uint32_t value) +{ + m_impl->set_reduced_detail_layer_stride(value); +} + void Viewer::set_dim_previous_layers(bool value) { m_impl->set_dim_previous_layers(value); diff --git a/src/libvgcode/src/ViewerImpl.cpp b/src/libvgcode/src/ViewerImpl.cpp index 77fca69fc5..a1cfb5d592 100644 --- a/src/libvgcode/src/ViewerImpl.cpp +++ b/src/libvgcode/src/ViewerImpl.cpp @@ -891,9 +891,15 @@ void ViewerImpl::reset() #else m_enabled_segments_count = 0; m_enabled_options_count = 0; + m_enabled_segments_reduced_count = 0; + m_enabled_options_reduced_count = 0; m_settings_used_for_ranges = std::nullopt; + delete_textures(m_enabled_options_reduced_tex_id); + delete_buffers(m_enabled_options_reduced_buf_id); + delete_textures(m_enabled_segments_reduced_tex_id); + delete_buffers(m_enabled_segments_reduced_buf_id); delete_textures(m_enabled_options_tex_id); delete_buffers(m_enabled_options_buf_id); delete_textures(m_enabled_segments_tex_id); @@ -1141,6 +1147,17 @@ void ViewerImpl::load(GCodeInputData&& gcode_data) glsafe(glGenTextures(1, &m_enabled_options_tex_id)); glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_enabled_options_tex_id)); + // create (but do not fill) the reduced counterparts of the two buffers above + glsafe(glGenBuffers(1, &m_enabled_segments_reduced_buf_id)); + glsafe(glBindBuffer(GL_TEXTURE_BUFFER, m_enabled_segments_reduced_buf_id)); + glsafe(glGenTextures(1, &m_enabled_segments_reduced_tex_id)); + glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_enabled_segments_reduced_tex_id)); + + glsafe(glGenBuffers(1, &m_enabled_options_reduced_buf_id)); + glsafe(glBindBuffer(GL_TEXTURE_BUFFER, m_enabled_options_reduced_buf_id)); + glsafe(glGenTextures(1, &m_enabled_options_reduced_tex_id)); + glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_enabled_options_reduced_tex_id)); + glsafe(glBindBuffer(GL_TEXTURE_BUFFER, 0)); glsafe(glBindTexture(GL_TEXTURE_BUFFER, old_bound_texture)); #endif // ENABLE_OPENGL_ES @@ -1152,6 +1169,17 @@ void ViewerImpl::load(GCodeInputData&& gcode_data) update_colors(); } +#ifndef ENABLE_OPENGL_ES +// ORCA: the roles that sit inside the part and are hidden by its walls from every angle. Dropping +// them is the least visible half of the reduced set built below. +static bool is_interior_infill(EGCodeExtrusionRole role) +{ + return role == EGCodeExtrusionRole::InternalInfill || + role == EGCodeExtrusionRole::SolidInfill || + role == EGCodeExtrusionRole::InternalBridgeInfill; +} +#endif // ENABLE_OPENGL_ES + void ViewerImpl::update_enabled_entities() { if (m_vertices.empty()) @@ -1159,6 +1187,15 @@ void ViewerImpl::update_enabled_entities() std::vector enabled_segments; std::vector enabled_options; +#ifndef ENABLE_OPENGL_ES + // ORCA: the reduced sets are filled by the same walk, so switching to them costs no rebuild. + std::vector enabled_segments_reduced; + std::vector enabled_options_reduced; + const uint32_t layer_stride = std::max(1, m_settings.reduced_detail_layer_stride); + // Whatever else is dropped, the top of the visible layer range is kept whole: it is the surface + // the user is looking at, and in top-layer-only mode it is the only layer drawn at full color. + const uint32_t kept_layer = m_layers.get_view_range()[1]; +#endif // ENABLE_OPENGL_ES Interval range = m_view_range.get_visible(); // when top layer only visualization is enabled, we need to render @@ -1206,6 +1243,15 @@ void ViewerImpl::update_enabled_entities() enabled_options.push_back(static_cast(i)); else enabled_segments.push_back(static_cast(i)); + +#ifndef ENABLE_OPENGL_ES + if (v.layer_id != kept_layer && (v.layer_id % layer_stride) != 0) + continue; + if (v.is_option()) + enabled_options_reduced.push_back(static_cast(i)); + else if (!(v.is_extrusion() && is_interior_infill(v.role))) + enabled_segments_reduced.push_back(static_cast(i)); +#endif // ENABLE_OPENGL_ES } #ifdef ENABLE_OPENGL_ES @@ -1234,6 +1280,19 @@ void ViewerImpl::update_enabled_entities() else glsafe(glBufferData(GL_TEXTURE_BUFFER, 0, nullptr, GL_STATIC_DRAW)); + m_enabled_segments_reduced_count = enabled_segments_reduced.size(); + m_enabled_options_reduced_count = enabled_options_reduced.size(); + + assert(m_enabled_segments_reduced_buf_id > 0); + glsafe(glBindBuffer(GL_TEXTURE_BUFFER, m_enabled_segments_reduced_buf_id)); + glsafe(glBufferData(GL_TEXTURE_BUFFER, enabled_segments_reduced.size() * sizeof(uint32_t), + enabled_segments_reduced.empty() ? nullptr : enabled_segments_reduced.data(), GL_STATIC_DRAW)); + + assert(m_enabled_options_reduced_buf_id > 0); + glsafe(glBindBuffer(GL_TEXTURE_BUFFER, m_enabled_options_reduced_buf_id)); + glsafe(glBufferData(GL_TEXTURE_BUFFER, enabled_options_reduced.size() * sizeof(uint32_t), + enabled_options_reduced.empty() ? nullptr : enabled_options_reduced.data(), GL_STATIC_DRAW)); + glsafe(glBindBuffer(GL_TEXTURE_BUFFER, 0)); #endif // ENABLE_OPENGL_ES @@ -1412,6 +1471,17 @@ void ViewerImpl::toggle_top_layer_only_view_range() update_colors_texture(); } +// ORCA: how many layers the reduced set keeps one of. Changing it changes which vertices land in +// the reduced set, so the sets have to be rebuilt. +void ViewerImpl::set_reduced_detail_layer_stride(uint32_t value) +{ + value = std::max(1, value); + if (m_settings.reduced_detail_layer_stride == value) + return; + m_settings.reduced_detail_layer_stride = value; + m_settings.update_enabled_entities = true; +} + // ORCA: enable/disable darkening of the layers the layer slider is not scrubbed to void ViewerImpl::set_dim_previous_layers(bool value) { @@ -2014,7 +2084,7 @@ void ViewerImpl::render_segments(const Mat4x4& view_matrix, const Mat4x4& projec #ifdef ENABLE_OPENGL_ES if (m_texture_data.get_enabled_segments_count() == 0) #else - if (m_enabled_segments_count == 0) + if (active_segments_count() == 0) #endif // ENABLE_OPENGL_ES return; @@ -2073,10 +2143,10 @@ void ViewerImpl::render_segments(const Mat4x4& view_matrix, const Mat4x4& projec glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_colors_tex_id)); glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32F, m_colors_buf_id)); glsafe(glActiveTexture(GL_TEXTURE3)); - glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_enabled_segments_tex_id)); - glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32UI, m_enabled_segments_buf_id)); + glsafe(glBindTexture(GL_TEXTURE_BUFFER, active_segments_tex_id())); + glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32UI, active_segments_buf_id())); - m_segment_template.render(m_enabled_segments_count); + m_segment_template.render(active_segments_count()); #endif // ENABLE_OPENGL_ES if (curr_cull_face) @@ -2102,7 +2172,7 @@ void ViewerImpl::render_options(const Mat4x4& view_matrix, const Mat4x4& project #ifdef ENABLE_OPENGL_ES if (m_texture_data.get_enabled_options_count() == 0) #else - if (m_enabled_options_count == 0) + if (active_options_count() == 0) #endif // ENABLE_OPENGL_ES return; @@ -2160,10 +2230,10 @@ void ViewerImpl::render_options(const Mat4x4& view_matrix, const Mat4x4& project glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_colors_tex_id)); glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32F, m_colors_buf_id)); glsafe(glActiveTexture(GL_TEXTURE3)); - glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_enabled_options_tex_id)); - glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32UI, m_enabled_options_buf_id)); + glsafe(glBindTexture(GL_TEXTURE_BUFFER, active_options_tex_id())); + glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32UI, active_options_buf_id())); - m_option_template.render(m_enabled_options_count); + m_option_template.render(active_options_count()); #endif // ENABLE_OPENGL_ES if (!curr_cull_face) diff --git a/src/libvgcode/src/ViewerImpl.hpp b/src/libvgcode/src/ViewerImpl.hpp index e52317643d..f9764fa408 100644 --- a/src/libvgcode/src/ViewerImpl.hpp +++ b/src/libvgcode/src/ViewerImpl.hpp @@ -91,6 +91,13 @@ public: // 0.0 = black bool is_dim_previous_layers() const { return m_settings.dim_previous_layers; } void set_dim_previous_layers(bool value); + // + // Draw the preview from the reduced set of entities. Meant to be held only while the user is + // dragging; the sets are already built, so this is just a buffer binding and never rebuilds. + // + void set_reduced_detail(bool value) { m_settings.reduced_detail = value; } + bool is_reduced_detail() const { return m_settings.reduced_detail; } + void set_reduced_detail_layer_stride(uint32_t value); float get_dim_previous_layers_brightness() const { return m_settings.dim_previous_layers_brightness; } void set_dim_previous_layers_brightness(float value); @@ -481,6 +488,15 @@ private: unsigned int m_enabled_options_tex_id{ 0 }; size_t m_enabled_options_count{ 0 }; // + // OpenGL buffers to store the reduced sets drawn while Settings::reduced_detail is set + // + unsigned int m_enabled_segments_reduced_buf_id{ 0 }; + unsigned int m_enabled_segments_reduced_tex_id{ 0 }; + size_t m_enabled_segments_reduced_count{ 0 }; + unsigned int m_enabled_options_reduced_buf_id{ 0 }; + unsigned int m_enabled_options_reduced_tex_id{ 0 }; + size_t m_enabled_options_reduced_count{ 0 }; + // // Caches for size of data sent to gpu, in bytes // size_t m_positions_tex_size{ 0 }; @@ -488,6 +504,26 @@ private: size_t m_colors_tex_size{ 0 }; size_t m_enabled_segments_tex_size{ 0 }; size_t m_enabled_options_tex_size{ 0 }; + + // The set the next draw reads from: the reduced one only while the user is dragging. + size_t active_segments_count() const { + return m_settings.reduced_detail ? m_enabled_segments_reduced_count : m_enabled_segments_count; + } + unsigned int active_segments_buf_id() const { + return m_settings.reduced_detail ? m_enabled_segments_reduced_buf_id : m_enabled_segments_buf_id; + } + unsigned int active_segments_tex_id() const { + return m_settings.reduced_detail ? m_enabled_segments_reduced_tex_id : m_enabled_segments_tex_id; + } + size_t active_options_count() const { + return m_settings.reduced_detail ? m_enabled_options_reduced_count : m_enabled_options_count; + } + unsigned int active_options_buf_id() const { + return m_settings.reduced_detail ? m_enabled_options_reduced_buf_id : m_enabled_options_buf_id; + } + unsigned int active_options_tex_id() const { + return m_settings.reduced_detail ? m_enabled_options_reduced_tex_id : m_enabled_options_tex_id; + } #endif // ENABLE_OPENGL_ES void update_view_full_range(); diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 82cd649fd4..b13f65ce7c 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -1173,6 +1173,7 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const m_viewer.toggle_top_layer_only_view_range(); // ORCA: darken the layers the preview layer slider is not scrubbed to + m_reduced_detail_while_dragging = get_app_config()->get_bool("preview_reduced_detail_while_dragging"); m_viewer.set_dim_previous_layers(get_app_config()->get_bool("preview_dim_previous_layers")); m_viewer.set_dim_previous_layers_brightness(0.01f * std::stoi(get_app_config()->get("preview_dim_previous_layers_brightness"))); @@ -1907,6 +1908,15 @@ void GCodeViewer::update_layers_slider_mode() // TODO m_layers_slider->SetModeAndOnlyExtruder(one_extruder_printed_model, only_extruder); } +bool GCodeViewer::set_interacting(bool interacting) +{ + const bool reduced = m_reduced_detail_while_dragging && interacting; + if (reduced == m_viewer.is_reduced_detail()) + return false; + m_viewer.set_reduced_detail(reduced); + return true; +} + void GCodeViewer::set_layers_z_range(const std::array& layers_z_range) { m_viewer.set_layers_view_range(static_cast(layers_z_range[0]), static_cast(layers_z_range[1])); diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 5ba748775c..f8ebe8d9fc 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -230,6 +230,8 @@ private: bool m_legend_visible{ true }; bool m_legend_enabled{ true }; + // ORCA: whether the reduced-detail-while-dragging preference is on + bool m_reduced_detail_while_dragging{ false }; float m_legend_height; PrintEstimatedStatistics m_print_statistics; @@ -339,6 +341,12 @@ public: void set_dim_previous_layers_brightness(float value) { m_viewer.set_dim_previous_layers_brightness(value); } float get_dim_previous_layers_brightness() const { return m_viewer.get_dim_previous_layers_brightness(); } + // ORCA: while the user drags the camera or a slider, draw the preview from libvgcode's reduced + // toolpath set. Returns true when the detail level actually changed, so the caller can queue the + // frame that puts the full detail back. + bool set_interacting(bool interacting); + void set_reduced_detail_while_dragging(bool value) { m_reduced_detail_while_dragging = value; } + void set_layers_z_range(const std::array& layers_z_range); bool is_legend_shown() const { return m_legend_visible && m_legend_enabled; } diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 8d72405cdd..275eb2d9cf 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -8510,10 +8510,17 @@ void GLCanvas3D::_render_wireframe_overlay() //BBS: GUI refactor: add canvas size as parameters void GLCanvas3D::_render_gcode(int canvas_width, int canvas_height) { - m_gcode_viewer.render(canvas_width, canvas_height, SLIDER_RIGHT_MARGIN * GCODE_VIEWER_SLIDER_SCALE); IMSlider *layers_slider = m_gcode_viewer.get_layers_slider(); IMSlider *moves_slider = m_gcode_viewer.get_moves_slider(); + // ORCA: dragging the camera or either slider is the only time the preview has to keep up with + // continuous input, so that is when the reduced toolpath set earns its visible coarseness. + // A change of detail level needs one more frame to draw the result of the change. + if (m_gcode_viewer.set_interacting(m_mouse.dragging || layers_slider->is_dirty() || moves_slider->is_dirty())) + request_extra_frame(); + + m_gcode_viewer.render(canvas_width, canvas_height, SLIDER_RIGHT_MARGIN * GCODE_VIEWER_SLIDER_SCALE); + if (layers_slider->is_need_post_tick_event()) { auto evt = new wxCommandEvent(EVT_CUSTOMEVT_TICKSCHANGED, m_canvas->GetId()); evt->SetInt((int)layers_slider->get_post_tick_event_type()); diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 08483f3100..6229ce207e 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -1056,6 +1056,16 @@ wxBoxSizer *PreferencesDialog::create_item_checkbox(wxString title, wxString too wxGetApp().mainframe->m_webview->SendCloudProvidersInfo(); } } + // ORCA: apply the reduced-detail preference immediately to the currently loaded preview + else if (param == "preview_reduced_detail_while_dragging") { + if (Plater* plater = wxGetApp().plater()) { + if (GLCanvas3D* canvas = plater->get_preview_canvas3D()) { + canvas->get_gcode_viewer().set_reduced_detail_while_dragging(app_config->get_bool(param)); + canvas->set_as_dirty(); + canvas->request_extra_frame(); + } + } + } // ORCA: apply the preview dimming change immediately to the currently loaded preview else if (param == "preview_dim_previous_layers") { if (m_dim_previous_layers_brightness_input) @@ -1938,6 +1948,13 @@ void PreferencesDialog::create_items() //// GRAPHICS > G-code Preview g_sizer->Add(create_item_title(_L("G-code Preview")), 1, wxEXPAND); + auto item_reduced_detail_while_dragging = create_item_checkbox( + _L("Simplify preview while dragging"), + _L("While dragging the camera or a preview slider, draw only part of the toolpaths so that large prints stay responsive. Internal infill is left out and only every fourth layer is drawn, and the full detail is restored as soon as you let go."), + "preview_reduced_detail_while_dragging" + ); + g_sizer->Add(item_reduced_detail_while_dragging); + auto item_dim_previous_layers = create_item_checkbox( _L("Dim lower layers"), _L("When scrubbing the layer slider in the sliced preview, render the layers below the current one darkened so that only the layer being viewed is shown at full brightness."),