cut the GPU load of moving the mouse over the 3D viewport (#15674)

Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>
This commit is contained in:
Kris Austin
2026-09-18 20:19:37 -03:00
committed by GitHub
co-authored by Rodrigo Faselli
parent e0410db22e
commit 213c6569ab
30 changed files with 876 additions and 269 deletions
+339 -160
View File
@@ -69,6 +69,7 @@
#include <tbb/parallel_for.h>
#include <tbb/spin_mutex.h>
#include <boost/functional/hash.hpp>
#include <boost/log/trivial.hpp>
#include <boost/algorithm/string/predicate.hpp>
@@ -370,7 +371,6 @@ void GLCanvas3D::LayersEditing::render_variable_layer_height_dialog(GLCanvas3D&
void GLCanvas3D::LayersEditing::render_overlay(GLCanvas3D& canvas)
{
render_variable_layer_height_dialog(canvas);
render_active_object_annotations(canvas);
render_profile(canvas);
}
@@ -1191,6 +1191,7 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed)
#endif // ENABLE_RETINA_GL
}
m_timer_set_color.Bind(wxEVT_TIMER, &GLCanvas3D::on_set_color_timer, this);
m_fps_overlay_timer.Bind(wxEVT_TIMER, &GLCanvas3D::on_fps_overlay_timer, this);
load_arrange_settings();
m_selection.set_volumes(&m_volumes.volumes);
@@ -1207,6 +1208,7 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed)
GLCanvas3D::~GLCanvas3D()
{
if (_set_current()) {
m_scene_cache.reset();
if (m_fxaa_texture_id != 0) {
glsafe(::glDeleteTextures(1, &m_fxaa_texture_id));
m_fxaa_texture_id = 0;
@@ -1354,6 +1356,7 @@ void GLCanvas3D::on_change_color_mode(bool is_dark, bool reinit) {
m_gizmos.set_icon_dirty();
}
}
m_dirty = true;
}
const float GLCanvas3D::get_scale() const
@@ -1938,7 +1941,14 @@ bool GLCanvas3D::make_current_for_postinit() {
return _set_current();
}
// Redraws the scene and presents it.
void GLCanvas3D::render(bool only_init)
{
m_presented_signature.reset();
_render_frame(true, only_init);
}
void GLCanvas3D::_render_frame(bool scene_dirty, bool only_init)
{
if (m_in_render) {
// if called recursively, return
@@ -2041,121 +2051,33 @@ void GLCanvas3D::render(bool only_init)
}
}
// draw scene
glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));
// Invalidate the shadow map each frame; only the View3D path below rebuilds it. This keeps
// the Preview / Assemble canvases from sampling a stale map with an outdated light matrix.
m_shadow_map_valid = false;
_render_background();
const bool overlay_tick = m_fps_overlay_tick;
m_fps_overlay_tick = false;
//BBS add partplater rendering logic
bool only_current = false, only_body = false, no_partplate = false;
bool show_grid = true;
GLGizmosManager::EType gizmo_type = m_gizmos.get_current_type();
if (!m_main_toolbar.is_enabled()) {
//only_body = true;
only_current = true;
// An overlay-only frame reuses the last scene pass. The overlay is rebuilt either way, and drawn
// below once it is known whether the frame differs from the one on screen.
const bool reuse_scene = !scene_dirty && _can_reuse_cached_scene(camera);
if (!reuse_scene) {
_render_scene(camera, cnv_size);
if (!overlay_tick)
m_render_stats.increment_scene_fps_counter();
}
else if ((gizmo_type == GLGizmosManager::FdmSupports) || (gizmo_type == GLGizmosManager::Seam) || (gizmo_type == GLGizmosManager::MmSegmentation) || (gizmo_type == GLGizmosManager::FuzzySkin))
no_partplate = true;
else if (gizmo_type == GLGizmosManager::BrimEars && !camera.is_looking_downward())
show_grid = false;
if (m_axes_at_bed_center)
// Design tab: the plate grid is generated from the plate's front-left corner, so it
// floats mid-cell under the modeling-origin triad. Suppress it here; a CAD grid centred
// on the origin is rendered in its place (see _render_cad_grid).
show_grid = false;
/* view3D render*/
int hover_id = (m_hover_plate_idxs.size() > 0)?m_hover_plate_idxs.front():-1;
if (m_canvas_type == ECanvasType::CanvasView3D) {
// m_show_bed gates the plate list too: hiding the bed but leaving its grid and outline
// floating would read as a rendering fault rather than a deliberate view option.
if (!no_partplate && m_show_bed)
_render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), m_show_world_axes);
if (!no_partplate && m_show_bed) //BBS: add outline logic
_render_platelist(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), only_current, only_body, hover_id, true, show_grid);
if (m_axes_at_bed_center && m_show_bed && !no_partplate)
// Design tab: replace the plate's corner-origin grid with the origin-centred CAD grid.
_render_cad_grid(camera.get_view_matrix(), camera.get_projection_matrix());
//BBS: add outline logic
// Depth pass for object-on-object and self shadows; consumed by the gouraud shader below.
_render_shadows(camera.get_view_matrix(), camera.get_projection_matrix());
_render_objects(GLVolumeCollection::ERenderType::Opaque, !m_gizmos.is_running());
_render_sla_slices();
_render_selection();
_render_objects(GLVolumeCollection::ERenderType::Transparent, !m_gizmos.is_running());
_render_wireframe_overlay();
}
/* preview render */
else if (m_canvas_type == ECanvasType::CanvasPreview && m_render_preview) {
_render_objects(GLVolumeCollection::ERenderType::Opaque, !m_gizmos.is_running());
_render_sla_slices();
_render_selection();
_render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), m_show_world_axes);
_render_platelist(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), only_current, true, hover_id);
if (m_canvas_type == ECanvasType::CanvasPreview && m_render_preview)
// BBS: GUI refactor: add canvas size as parameters
_render_gcode(cnv_size.get_width(), cnv_size.get_height());
}
/* assemble render*/
else if (m_canvas_type == ECanvasType::CanvasAssembleView) {
//BBS: add outline logic
//if (m_show_world_axes) {
// m_axes.render();
//}
_render_objects(GLVolumeCollection::ERenderType::Opaque, !m_gizmos.is_running());
_render_selection();
//_render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), show_axes);
_render_plane();
//BBS: add outline logic insteadof selection under assemble view
//_render_selection();
// BBS: add outline logic
_render_objects(GLVolumeCollection::ERenderType::Transparent, !m_gizmos.is_running());
_render_wireframe_overlay();
}
_render_sequential_clearance();
#if ENABLE_RENDER_SELECTION_CENTER
_render_selection_center();
#endif // ENABLE_RENDER_SELECTION_CENTER
// sidebar hints need to be rendered before the gizmos because the depth buffer
// could be invalidated by the following gizmo render methods
_render_selection_sidebar_hints();
_render_current_gizmo();
#if ENABLE_RAYCAST_PICKING_DEBUG
if (m_picking_enabled && !m_mouse.dragging && !m_gizmos.is_dragging() && !m_rectangle_selection.is_dragging())
m_scene_raycaster.render_hit(camera);
#endif // ENABLE_RAYCAST_PICKING_DEBUG
#if ENABLE_SHOW_CAMERA_TARGET
_render_camera_target();
#endif // ENABLE_SHOW_CAMERA_TARGET
if (m_picking_enabled && m_rectangle_selection.is_dragging())
m_rectangle_selection.render(*this);
if (_is_ssao_enabled())
_render_ssao_pass(static_cast<unsigned int>(cnv_size.get_width()), static_cast<unsigned int>(cnv_size.get_height()));
if (_is_fxaa_enabled())
_render_fxaa_pass(static_cast<unsigned int>(cnv_size.get_width()), static_cast<unsigned int>(cnv_size.get_height()));
// Design tab: interactive 2D sketch overlay, drawn over the scene but
// beneath the UI overlays (toolbars, labels).
#ifdef SLIC3R_CAD
if (m_design_sketch_tool != nullptr && m_design_sketch_tool->has_display())
m_design_sketch_tool->render(*this);
#endif
_render_gcode_overlay(cnv_size.get_width(), cnv_size.get_height());
// draw overlays
_render_overlays();
const int current_fps = m_render_stats.get_fps_and_reset_if_needed();
if (_is_fps_overlay_enabled())
if (_is_fps_overlay_enabled()) {
_render_fps_overlay(current_fps);
// The timer requests an overlay-only frame a second from now. A frame it requested
// re-arms it only while a count is above zero.
if (!overlay_tick || current_fps > 0 || m_render_stats.get_scene_fps() > 0)
m_fps_overlay_timer.StartOnce(1000);
}
if (wxGetApp().plater()->is_render_statistic_dialog_visible()) {
ImGui::ShowMetricsWindow();
@@ -2241,14 +2163,149 @@ void GLCanvas3D::render(bool only_init)
wxGetApp().plater()->get_dailytips()->render();
}
wxGetApp().imgui()->render();
ImDrawData* draw_data = wxGetApp().imgui()->end_frame();
std::optional<size_t> signature;
if (_is_frame_skipping_enabled())
signature = _overlay_signature(draw_data);
if (reuse_scene) {
// A reused scene under an unchanged overlay is the frame already on screen.
if (signature.has_value() && signature == m_presented_signature)
return;
m_scene_cache.render(m_background);
}
_render_overlay_toolbars();
wxGetApp().imgui()->render(draw_data);
// On Wayland, eglSwapBuffers blocks when the canvas is hidden or
// occluded. Skip the swap to avoid stalling the render loop.
if (m_canvas->IsShownOnScreen()) {
m_canvas->SwapBuffers();
m_render_stats.increment_fps_counter();
if (!overlay_tick)
m_render_stats.increment_fps_counter();
m_presented_signature = signature;
}
else
m_presented_signature.reset();
}
// Everything drawn into the 3D scene, from the clear to the post processing passes, ending in the
// capture an overlay-only frame reuses.
void GLCanvas3D::_render_scene(const Camera& camera, const Size& cnv_size)
{
// Recorded by PartPlate::render_icons() below, when it runs.
wxGetApp().plater()->get_partplate_list().clear_hover_tooltip();
glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));
// Invalidate the shadow map each frame; only the View3D path below rebuilds it. This keeps
// the Preview / Assemble canvases from sampling a stale map with an outdated light matrix.
m_shadow_map_valid = false;
_render_background();
//BBS add partplater rendering logic
bool only_current = false, only_body = false, no_partplate = false;
bool show_grid = true;
GLGizmosManager::EType gizmo_type = m_gizmos.get_current_type();
if (!m_main_toolbar.is_enabled()) {
//only_body = true;
only_current = true;
}
else if ((gizmo_type == GLGizmosManager::FdmSupports) || (gizmo_type == GLGizmosManager::Seam) || (gizmo_type == GLGizmosManager::MmSegmentation) || (gizmo_type == GLGizmosManager::FuzzySkin))
no_partplate = true;
else if (gizmo_type == GLGizmosManager::BrimEars && !camera.is_looking_downward())
show_grid = false;
if (m_axes_at_bed_center)
// Design tab: the plate grid is generated from the plate's front-left corner, so it
// floats mid-cell under the modeling-origin triad. Suppress it here; a CAD grid centred
// on the origin is rendered in its place (see _render_cad_grid).
show_grid = false;
/* view3D render*/
int hover_id = (m_hover_plate_idxs.size() > 0)?m_hover_plate_idxs.front():-1;
if (m_canvas_type == ECanvasType::CanvasView3D) {
// m_show_bed gates the plate list too: hiding the bed but leaving its grid and outline
// floating would read as a rendering fault rather than a deliberate view option.
if (!no_partplate && m_show_bed)
_render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), m_show_world_axes);
if (!no_partplate && m_show_bed) //BBS: add outline logic
_render_platelist(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), only_current, only_body, hover_id, true, show_grid);
if (m_axes_at_bed_center && m_show_bed && !no_partplate)
// Design tab: replace the plate's corner-origin grid with the origin-centred CAD grid.
_render_cad_grid(camera.get_view_matrix(), camera.get_projection_matrix());
//BBS: add outline logic
// Depth pass for object-on-object and self shadows; consumed by the gouraud shader below.
_render_shadows(camera.get_view_matrix(), camera.get_projection_matrix());
_render_objects(GLVolumeCollection::ERenderType::Opaque, !m_gizmos.is_running());
_render_sla_slices();
_render_selection();
_render_objects(GLVolumeCollection::ERenderType::Transparent, !m_gizmos.is_running());
_render_wireframe_overlay();
}
/* preview render */
else if (m_canvas_type == ECanvasType::CanvasPreview && m_render_preview) {
_render_objects(GLVolumeCollection::ERenderType::Opaque, !m_gizmos.is_running());
_render_sla_slices();
_render_selection();
_render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), m_show_world_axes);
_render_platelist(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), only_current, true, hover_id);
// BBS: GUI refactor: add canvas size as parameters
_render_gcode(cnv_size.get_width(), cnv_size.get_height());
}
/* assemble render*/
else if (m_canvas_type == ECanvasType::CanvasAssembleView) {
//BBS: add outline logic
//if (m_show_world_axes) {
// m_axes.render();
//}
_render_objects(GLVolumeCollection::ERenderType::Opaque, !m_gizmos.is_running());
_render_selection();
//_render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), show_axes);
_render_plane();
//BBS: add outline logic insteadof selection under assemble view
//_render_selection();
// BBS: add outline logic
_render_objects(GLVolumeCollection::ERenderType::Transparent, !m_gizmos.is_running());
_render_wireframe_overlay();
}
_render_sequential_clearance();
#if ENABLE_RENDER_SELECTION_CENTER
_render_selection_center();
#endif // ENABLE_RENDER_SELECTION_CENTER
// sidebar hints need to be rendered before the gizmos because the depth buffer
// could be invalidated by the following gizmo render methods
_render_selection_sidebar_hints();
_render_current_gizmo();
#if ENABLE_RAYCAST_PICKING_DEBUG
if (m_picking_enabled && !m_mouse.dragging && !m_gizmos.is_dragging() && !m_rectangle_selection.is_dragging())
m_scene_raycaster.render_hit(camera);
#endif // ENABLE_RAYCAST_PICKING_DEBUG
#if ENABLE_SHOW_CAMERA_TARGET
_render_camera_target();
#endif // ENABLE_SHOW_CAMERA_TARGET
if (m_picking_enabled && m_rectangle_selection.is_dragging())
m_rectangle_selection.render(*this);
if (_is_ssao_enabled())
_render_ssao_pass(static_cast<unsigned int>(cnv_size.get_width()), static_cast<unsigned int>(cnv_size.get_height()));
if (_is_fxaa_enabled())
_render_fxaa_pass(static_cast<unsigned int>(cnv_size.get_width()), static_cast<unsigned int>(cnv_size.get_height()));
// Design tab: interactive 2D sketch overlay, drawn over the scene but
// beneath the UI overlays (toolbars, labels).
#ifdef SLIC3R_CAD
if (m_design_sketch_tool != nullptr && m_design_sketch_tool->has_display())
m_design_sketch_tool->render(*this);
#endif
_capture_scene_cache(camera);
}
void GLCanvas3D::render_thumbnail(ThumbnailData & thumbnail_data,
@@ -3226,29 +3283,30 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt)
if (!m_initialized)
return;
m_dirty |= m_main_toolbar.update_items_state();
// Toolbar states, notifications and ImGui's own layout settling only touch the overlay.
m_overlay_dirty |= m_main_toolbar.update_items_state();
//BBS: GUI refactor: GLToolbar
m_dirty |= m_assemble_view_toolbar.update_items_state();
m_overlay_dirty |= m_assemble_view_toolbar.update_items_state();
// BBS
//m_dirty |= wxGetApp().plater()->get_view_toolbar().update_items_state();
m_dirty |= wxGetApp().plater()->get_collapse_toolbar().update_items_state();
m_overlay_dirty |= wxGetApp().plater()->get_collapse_toolbar().update_items_state();
// apply() DRAINS the 3D-mouse queue, so only the canvas actually on screen may call it: a
// hidden canvas renders nothing, so the motion it swallowed moves the shared camera without
// ever being drawn and the next visible frame jumps several states at once.
bool mouse3d_controller_applied = _is_shown_on_screen()
&& wxGetApp().plater()->get_mouse3d_controller().apply(wxGetApp().plater()->get_camera());
m_dirty |= mouse3d_controller_applied;
m_dirty |= wxGetApp().plater()->get_notification_manager()->update_notifications(*this);
m_overlay_dirty |= wxGetApp().plater()->get_notification_manager()->update_notifications(*this);
auto gizmo = wxGetApp().plater()->get_view3D_canvas3D()->get_gizmos_manager().get_current();
if (gizmo != nullptr) m_dirty |= gizmo->update_items_state();
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
// ImGuiWrapper::m_requires_extra_frame may have been set by a render made outside of the OnIdle mechanism
bool imgui_requires_extra_frame = wxGetApp().imgui()->requires_extra_frame();
m_dirty |= imgui_requires_extra_frame;
m_overlay_dirty |= imgui_requires_extra_frame;
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
m_dirty |= GLTexture::Compressor::has_compressed_texture_to_refresh();
if (!m_dirty)
if (!m_dirty && !m_overlay_dirty)
return;
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
@@ -3273,19 +3331,24 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt)
m_last_frame_start_time = now;
}
_refresh_if_shown_on_screen();
// Read and cleared before the render; a request made during it is left for the next frame.
const bool scene_dirty = m_dirty;
m_dirty = false;
m_overlay_dirty = false;
_refresh_if_shown_on_screen(scene_dirty);
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
if (m_extra_frame_requested || mouse3d_controller_applied || imgui_requires_extra_frame || wxGetApp().imgui()->requires_extra_frame()) {
#else
if (m_extra_frame_requested || mouse3d_controller_applied) {
m_dirty = true;
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
m_extra_frame_requested = false;
evt.RequestMore();
}
else
m_dirty = false;
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
else if (imgui_requires_extra_frame || wxGetApp().imgui()->requires_extra_frame()) {
// ImGui settling a window or fading a tooltip.
m_overlay_dirty = true;
evt.RequestMore();
}
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
}
void GLCanvas3D::on_char(wxKeyEvent& evt)
@@ -4124,6 +4187,12 @@ void GLCanvas3D::on_set_color_timer(wxTimerEvent& evt)
m_timer_set_color.Stop();
}
void GLCanvas3D::on_fps_overlay_timer(wxTimerEvent& evt)
{
m_fps_overlay_tick = true;
_set_overlay_as_dirty();
wxWakeUpIdle();
}
void GLCanvas3D::schedule_extra_frame(int milliseconds)
{
@@ -4301,11 +4370,18 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp())
mouse_up_cleanup();
render();
// Hovering an ImGui window only changes the overlay.
const bool overlay_only = evt.Moving() && !m_mouse.dragging;
// ImGui takes a press or a release only inside a frame. Motion is rendered from on_idle().
if (evt.ButtonDown() || evt.ButtonUp() || evt.ButtonDClick())
_render_frame(true);
#ifdef SLIC3R_DEBUG_MOUSE_EVENTS
printf((format_mouse_event_debug_message(evt) + " - Consumed by ImGUI\n").c_str());
#endif /* SLIC3R_DEBUG_MOUSE_EVENTS */
m_dirty = true;
if (overlay_only)
_set_overlay_as_dirty();
else
m_dirty = true;
// do not return if dragging or tooltip not empty to allow for tooltip update
// also, do not return if the mouse is moving and also is inside MM gizmo to allow update seed fill selection
if (!m_mouse.dragging && m_tooltip.is_empty() && (m_gizmos.get_current_type() != GLGizmosManager::MmSegmentation || !evt.Moving()))
@@ -4448,6 +4524,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
wxGetApp().obj_list()->selection_changed();
}
// A gizmo that acts on a click or a drag may not request a frame itself.
if (!evt.Moving())
m_dirty = true;
return;
}
@@ -4939,7 +5018,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
if (m_selection.is_empty())
m_gizmos.reset_all_states();
m_dirty = true;
_set_overlay_as_dirty();
}
else
evt.Skip();
@@ -4989,6 +5068,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
void GLCanvas3D::on_paint(wxPaintEvent& evt)
{
m_presented_signature.reset();
if (m_initialized) {
#ifdef __WXMSW__
// Idle events are not dispatched during the Windows resize modal loop,
@@ -5542,7 +5622,11 @@ void GLCanvas3D::update_gizmos_on_off_state()
void GLCanvas3D::handle_sidebar_focus_event(const std::string& opt_key, bool focus_on)
{
m_sidebar_field = focus_on ? opt_key : "";
const std::string field = focus_on ? opt_key : "";
// The gizmo panels report this on every build.
if (m_sidebar_field == field)
return;
m_sidebar_field = field;
//BBS: this event was sent from gizmo now, no need to clear gizmo
//if (!m_sidebar_field.empty())
@@ -6640,6 +6724,9 @@ void GLCanvas3D::render_thumbnail_internal(ThumbnailData& thumbnail_data, const
//if (thumbnail_params.transparent_background)
// glsafe(::glClearColor(1.0f, 1.0f, 1.0f, 1.0f));
BOOST_LOG_TRIVIAL(info) << boost::format("render_thumbnail: finished");
// Puts the canvas viewport back in place of the thumbnail one set above.
wxGetApp().plater()->get_camera().apply_viewport();
}
void GLCanvas3D::render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params,
@@ -6890,9 +6977,6 @@ void GLCanvas3D::render_thumbnail_legacy(ThumbnailData& thumbnail_data, unsigned
#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
debug_output_thumbnail(thumbnail_data);
#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
// restore the default framebuffer size to avoid flickering on the 3D scene
//wxGetApp().plater()->get_camera().apply_viewport();
}
//BBS: GUI refractor
@@ -7441,15 +7525,15 @@ void GLCanvas3D::_update_camera_zoom(double zoom)
m_dirty = true;
}
void GLCanvas3D::_refresh_if_shown_on_screen()
void GLCanvas3D::_refresh_if_shown_on_screen(bool scene_dirty)
{
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());
// Because of performance problems on macOS, where PaintEvents are not delivered
// frequently enough, we call render() here directly when we can.
render();
// frequently enough, we render here directly when we can.
_render_frame(scene_dirty);
}
}
@@ -7841,6 +7925,36 @@ bool GLCanvas3D::_is_fps_overlay_enabled() const
return wxGetApp().app_config != nullptr && wxGetApp().app_config->get_bool(SETTING_OPENGL_SHOW_FPS_OVERLAY);
}
bool GLCanvas3D::_is_scene_cache_enabled() const
{
return wxGetApp().app_config != nullptr && wxGetApp().app_config->get_bool(SETTING_OPENGL_SCENE_CACHE);
}
bool GLCanvas3D::_is_scene_cacheable() const
{
if (!_is_scene_cache_enabled())
return false;
#ifdef SLIC3R_CAD
// The Design tab draws its sketch overlay, ImGui included, inside the scene.
if (m_design_sketch_tool != nullptr && m_design_sketch_tool->has_display())
return false;
#endif
// The scene follows the cursor during a drag, under a gizmo that draws at the cursor, and while
// the cursor is on the layer height bar, where the object shader draws a band at its height.
const GLGizmoBase* gizmo = m_gizmos.get_current();
const bool cursor_on_layers_bar = is_layers_editing_enabled() &&
m_layers_editing.bar_rect_contains(*this, (float)m_mouse.position.x(), (float)m_mouse.position.y());
return !m_mouse.dragging && !m_gizmos.is_dragging() && !m_rectangle_selection.is_dragging() &&
(gizmo == nullptr || !gizmo->render_follows_cursor()) && !cursor_on_layers_bar;
}
bool GLCanvas3D::_is_frame_skipping_enabled() const
{
return wxGetApp().app_config != nullptr && wxGetApp().app_config->get_bool(SETTING_OPENGL_SKIP_IDENTICAL_FRAMES);
}
void GLCanvas3D::_render_fps_overlay(int fps) const
{
if (fps < 0)
@@ -7861,6 +7975,8 @@ void GLCanvas3D::_render_fps_overlay(int fps) const
ImGuiWindowFlags_NoSavedSettings |
ImGuiWindowFlags_NoInputs);
imgui.text(std::string("FPS: ") + std::to_string(fps));
// The subset of those frames that redrew the scene rather than reusing the cached one.
imgui.text(std::string("3D: ") + std::to_string(m_render_stats.get_scene_fps()));
imgui.end();
}
@@ -7873,23 +7989,7 @@ void GLCanvas3D::_render_fxaa_pass(unsigned int width, unsigned int height)
if (shader == nullptr)
return;
if (m_fxaa_texture_id == 0) {
glsafe(::glGenTextures(1, &m_fxaa_texture_id));
glsafe(::glBindTexture(GL_TEXTURE_2D, m_fxaa_texture_id));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
}
glsafe(::glBindTexture(GL_TEXTURE_2D, m_fxaa_texture_id));
if (m_fxaa_texture_size[0] != width || m_fxaa_texture_size[1] != height) {
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr));
m_fxaa_texture_size = { width, height };
}
glsafe(::glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, width, height));
GLTexture::copy_from_framebuffer(m_fxaa_texture_id, m_fxaa_texture_size, width, height, GL_LINEAR);
glsafe(::glDisable(GL_DEPTH_TEST));
glsafe(::glDisable(GL_BLEND));
@@ -8065,6 +8165,51 @@ void GLCanvas3D::_render_ssao_pass(unsigned int width, unsigned int height)
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
}
SceneCache::Key GLCanvas3D::_scene_cache_key(const Camera& camera) const
{
SceneCache::Key key;
const std::array<int, 4>& viewport = camera.get_viewport();
key.size = { { (unsigned int)viewport[2], (unsigned int)viewport[3] } };
key.view_matrix = camera.get_view_matrix();
key.projection_matrix = camera.get_projection_matrix();
// Hover reaches the scene only through the sinking contour a hovered volume draws over
// itself (GLVolumeCollection::render()), the plate icons (PartPlate::render_icons()) and the
// open gizmo's grabbers.
for (size_t i = 0; i < m_volumes.volumes.size(); ++i) {
const GLVolume& volume = *m_volumes.volumes[i];
if (volume.hover != GLVolume::HS_None && volume.is_sinking() && !volume.is_below_printbed())
key.sinking_hover_volume_idxs.emplace_back((int)i);
}
for (int id : m_hover_plate_idxs) {
if (id % PartPlate::GRABBER_COUNT != 0)
key.hover_plate_icon_idxs.emplace_back(id);
}
const GLGizmoBase* gizmo = m_gizmos.get_current();
key.gizmo_hover_id = gizmo != nullptr ? gizmo->get_hover_id() : -1;
key.render_preview = m_render_preview;
return key;
}
bool GLCanvas3D::_can_reuse_cached_scene(const Camera& camera) const
{
return _is_scene_cacheable() && m_scene_cache.matches(_scene_cache_key(camera));
}
void GLCanvas3D::_capture_scene_cache(const Camera& camera)
{
if (!_is_scene_cache_enabled()) {
m_scene_cache.reset();
return;
}
if (!_is_scene_cacheable()) {
m_scene_cache.invalidate();
return;
}
m_scene_cache.capture(_scene_cache_key(camera));
}
void GLCanvas3D::_render_background()
{
bool use_error_color = false;
@@ -8792,7 +8937,12 @@ 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);
m_gcode_viewer.render_scene(canvas_width, canvas_height);
}
void GLCanvas3D::_render_gcode_overlay(int canvas_width, int canvas_height)
{
m_gcode_viewer.render_overlay(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();
@@ -8924,20 +9074,14 @@ void GLCanvas3D::_check_and_update_toolbar_icon_scale()
wxGetApp().set_auto_toolbar_icon_scale(new_scale);
}
// The ImGui half of the overlay, drawn by ImGui at the end of the frame.
void GLCanvas3D::_render_overlays()
{
glsafe(::glDisable(GL_DEPTH_TEST));
_check_and_update_toolbar_icon_scale();
_render_assemble_control();
_render_assemble_info();
_render_separator_toolbar_right();
_render_separator_toolbar_left();
_render_main_toolbar();
_render_collapse_toolbar();
_render_assemble_view_toolbar();
//BBS: GUI refactor: GLToolbar
_render_imgui_select_plate_toolbar();
_render_return_toolbar();
@@ -8945,12 +9089,13 @@ void GLCanvas3D::_render_overlays()
//_render_view_toolbar();
_render_paint_toolbar();
//BBS: GUI refactor: GLToolbar
//move gizmos behind of main
_render_gizmos_overlay();
// The options window of a pressed toolbar item (arrange).
m_main_toolbar.render_item_windows(*this);
m_gizmos.render_overlay_input_window();
if (m_layers_editing.last_object_id >= 0 && m_layers_editing.object_max_z() > 0.0f)
m_layers_editing.render_overlay(*this);
m_layers_editing.render_variable_layer_height_dialog(*this);
auto curr_plate = wxGetApp().plater()->get_partplate_list().get_curr_plate();
auto curr_print_seq = curr_plate->get_real_print_seq();
@@ -8977,6 +9122,40 @@ void GLCanvas3D::_render_overlays()
_render_3d_navigator();
_render_canvas_toolbar();
// Recorded by the scene pass, which a reused frame skips.
wxGetApp().plater()->get_partplate_list().render_hover_tooltip();
}
// The GL half of the overlay.
void GLCanvas3D::_render_overlay_toolbars()
{
glsafe(::glDisable(GL_DEPTH_TEST));
_render_separator_toolbar_right();
_render_separator_toolbar_left();
_render_main_toolbar();
_render_collapse_toolbar();
_render_assemble_view_toolbar();
//BBS: GUI refactor: GLToolbar
//move gizmos behind of main
_render_gizmos_overlay();
if (m_layers_editing.last_object_id >= 0 && m_layers_editing.object_max_z() > 0.0f)
m_layers_editing.render_overlay(*this);
}
size_t GLCanvas3D::_overlay_signature(const ImDrawData* draw_data) const
{
// The recorded ImGui geometry plus the state of the toolbars and the gizmo bar, which draw
// outside ImGui.
size_t hash = ImGuiWrapper::draw_data_signature(draw_data);
for (size_t state_hash : { m_main_toolbar.get_state_hash(), m_separator_toolbar.get_state_hash(),
m_assemble_view_toolbar.get_state_hash(),
wxGetApp().plater()->get_collapse_toolbar().get_state_hash(),
m_gizmos.get_overlay_state_hash() })
boost::hash_combine(hash, state_hash);
return hash;
}
void GLCanvas3D::_render_style_editor()