diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 42f3e28bfa..c8a2c4984d 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -283,6 +283,12 @@ void AppConfig::set_defaults() set(SETTING_OPENGL_FPS_CAP, std::to_string(fps_cap)); } + if (get(SETTING_OPENGL_SCENE_CACHE).empty()) + set_bool(SETTING_OPENGL_SCENE_CACHE, true); + + if (get(SETTING_OPENGL_SKIP_IDENTICAL_FRAMES).empty()) + set_bool(SETTING_OPENGL_SKIP_IDENTICAL_FRAMES, true); + // The getter already defaults, parses and clamps; write back what it resolves to. set(SETTING_PLUGIN_PAGES_VISIBLE_COUNT, std::to_string(get_plugin_pages_visible_count())); diff --git a/src/libslic3r/AppConfig.hpp b/src/libslic3r/AppConfig.hpp index 8e3305105a..24a4c2069b 100644 --- a/src/libslic3r/AppConfig.hpp +++ b/src/libslic3r/AppConfig.hpp @@ -33,6 +33,8 @@ using namespace nlohmann; #define SETTING_OPENGL_AA_SAMPLES "opengl_antialiasing_samples" #define SETTING_OPENGL_FXAA_ENABLED "opengl_fxaa_enabled" #define SETTING_OPENGL_FPS_CAP "opengl_fps_cap" +#define SETTING_OPENGL_SCENE_CACHE "opengl_scene_cache" +#define SETTING_OPENGL_SKIP_IDENTICAL_FRAMES "opengl_skip_identical_frames" #define SETTING_OPENGL_SHOW_FPS_OVERLAY "opengl_show_fps_overlay" #define SETTING_OPENGL_REALISTIC_MODE "opengl_realistic_mode" #define SETTING_OPENGL_REALISTIC_PHONG "opengl_realistic_phong" diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 18730b248b..e689548896 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -469,6 +469,8 @@ set(SLIC3R_GUI_SOURCES GUI/SavePresetDialog.hpp GUI/SceneRaycaster.cpp GUI/SceneRaycaster.hpp + GUI/SceneCache.cpp + GUI/SceneCache.hpp GUI/PartSkipCommon.hpp GUI/PartSkipDialog.cpp GUI/PartSkipDialog.hpp diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index c40d6dd222..3b3354d86f 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -997,13 +997,17 @@ void GCodeViewer::SequentialView::GCodeWindow::stop_mapping_file() BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": finished mapping file " << m_filename; } } -void GCodeViewer::SequentialView::render(const bool has_render_path, float legend_height, const libvgcode::Viewer* viewer, uint32_t gcode_id, int canvas_width, int canvas_height, int right_margin, const libvgcode::EViewType& view_type) +void GCodeViewer::SequentialView::render_marker(const bool has_render_path, int canvas_width, int canvas_height, const libvgcode::EViewType& view_type) { - if (has_render_path && m_show_marker) { + if (has_render_path && m_show_marker) // marker.set_world_offset(current_offset); marker.render(canvas_width, canvas_height, view_type); +} + +void GCodeViewer::SequentialView::render_overlay(const bool has_render_path, float legend_height, const libvgcode::Viewer* viewer, uint32_t gcode_id, int canvas_width, int canvas_height, int right_margin, const libvgcode::EViewType& view_type) +{ + if (has_render_path && m_show_marker) marker.render_position_window(viewer, canvas_width, canvas_height, view_type); - } //float bottom = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size().get_height(); // BBS @@ -1618,7 +1622,7 @@ void GCodeViewer::reset() } //BBS: GUI refactor: add canvas width and height -void GCodeViewer::render(int canvas_width, int canvas_height, int right_margin) +void GCodeViewer::render_scene(int canvas_width, int canvas_height) { glsafe(::glEnable(GL_DEPTH_TEST)); render_shells(canvas_width, canvas_height); @@ -1628,6 +1632,20 @@ void GCodeViewer::render(int canvas_width, int canvas_height, int right_margin) render_toolpaths(); + auto current = m_viewer.get_view_visible_range(); + auto endpoints = m_viewer.get_view_full_range(); + m_sequential_view.m_show_marker = m_sequential_view.m_show_marker || (current.back() != endpoints.back() && !m_no_render_path); + const libvgcode::PathVertex& curr_vertex = m_viewer.get_current_vertex(); + m_sequential_view.marker.set_world_position(libvgcode::convert(curr_vertex.position)); + m_sequential_view.marker.set_z_offset(m_z_offset + 0.5f); + m_sequential_view.render_marker(!m_no_render_path, canvas_width, sequential_view_height(canvas_height), m_viewer.get_view_type()); +} + +void GCodeViewer::render_overlay(int canvas_width, int canvas_height, int right_margin) +{ + if (m_viewer.get_extrusion_roles().empty()) + return; + float legend_height = 0.0f; render_legend(legend_height, canvas_width, canvas_height, right_margin); @@ -1636,16 +1654,7 @@ void GCodeViewer::render(int canvas_width, int canvas_height, int right_margin) m_user_mode = wxGetApp().get_mode(); } - //BBS fixed bottom_margin for space to render horiz slider - int bottom_margin = SLIDER_BOTTOM_MARGIN * GCODE_VIEWER_SLIDER_SCALE; - auto current = m_viewer.get_view_visible_range(); - auto endpoints = m_viewer.get_view_full_range(); - m_sequential_view.m_show_marker = m_sequential_view.m_show_marker || (current.back() != endpoints.back() && !m_no_render_path); - const libvgcode::PathVertex& curr_vertex = m_viewer.get_current_vertex(); - m_sequential_view.marker.set_world_position(libvgcode::convert(curr_vertex.position)); - m_sequential_view.marker.set_z_offset(m_z_offset + 0.5f); - // BBS fixed buttom margin. m_moves_slider.pos_y - m_sequential_view.render(!m_no_render_path, legend_height, &m_viewer, m_viewer.get_current_vertex().gcode_id, canvas_width, canvas_height - bottom_margin * m_scale, right_margin * m_scale, m_viewer.get_view_type()); + m_sequential_view.render_overlay(!m_no_render_path, legend_height, &m_viewer, m_viewer.get_current_vertex().gcode_id, canvas_width, sequential_view_height(canvas_height), right_margin * m_scale, m_viewer.get_view_type()); #if VGCODE_ENABLE_COG_AND_TOOL_MARKERS if (is_legend_shown()) { @@ -1686,6 +1695,14 @@ void GCodeViewer::render(int canvas_width, int canvas_height, int right_margin) render_slider(canvas_width, canvas_height); } +int GCodeViewer::sequential_view_height(int canvas_height) const +{ + //BBS fixed bottom_margin for space to render horiz slider + const int bottom_margin = SLIDER_BOTTOM_MARGIN * GCODE_VIEWER_SLIDER_SCALE; + // BBS fixed buttom margin. m_moves_slider.pos_y + return canvas_height - bottom_margin * m_scale; +} + #define ENABLE_CALIBRATION_THUMBNAIL_OUTPUT 0 #if ENABLE_CALIBRATION_THUMBNAIL_OUTPUT static void debug_calibration_output_thumbnail(const ThumbnailData& thumbnail_data) diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 5ba748775c..9b82a81b2f 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -153,7 +153,10 @@ public: GCodeWindow gcode_window; float m_scale = 1.0; bool m_show_marker = false; - void render(const bool has_render_path, float legend_height, const libvgcode::Viewer* viewer, uint32_t gcode_id, int canvas_width, int canvas_height, int right_margin, const libvgcode::EViewType& view_type); + // The tool marker at the current move, drawn in 3D. + void render_marker(const bool has_render_path, int canvas_width, int canvas_height, const libvgcode::EViewType& view_type); + // The marker's position window and the G-code window, both ImGui. + void render_overlay(const bool has_render_path, float legend_height, const libvgcode::Viewer* viewer, uint32_t gcode_id, int canvas_width, int canvas_height, int right_margin, const libvgcode::EViewType& view_type); }; struct ExtruderFilament { @@ -272,7 +275,10 @@ public: //BBS: add all plates filament statistics void render_all_plates_stats(const std::vector& gcode_result_list, bool show = true) const; //BBS: GUI refactor: add canvas width and height - void render(int canvas_width, int canvas_height, int right_margin); + // Shells, toolpaths and the sequential marker, drawn in 3D. + void render_scene(int canvas_width, int canvas_height); + // Legend, sliders, the marker's position window and the G-code window, all ImGui. + void render_overlay(int canvas_width, int canvas_height, int right_margin); //BBS // void _render_calibration_thumbnail_internal(ThumbnailData& thumbnail_data, const ThumbnailsParams& thumbnail_params, PartPlateList& partplate_list, OpenGLManager& opengl_manager); // void _render_calibration_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, PartPlateList& partplate_list, OpenGLManager& opengl_manager); @@ -362,6 +368,8 @@ public: private: //BBS: always load shell at preview //void load_shells(const Print& print); + // Canvas height minus the room the horizontal slider takes. + int sequential_view_height(int canvas_height) const; void render_toolpaths(); void render_shells(int canvas_width, int canvas_height); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index a5ab7bd5d8..02ef95e2c6 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -69,6 +69,7 @@ #include #include +#include #include #include @@ -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(cnv_size.get_width()), static_cast(cnv_size.get_height())); - - if (_is_fxaa_enabled()) - _render_fxaa_pass(static_cast(cnv_size.get_width()), static_cast(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 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(cnv_size.get_width()), static_cast(cnv_size.get_height())); + + if (_is_fxaa_enabled()) + _render_fxaa_pass(static_cast(cnv_size.get_width()), static_cast(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& 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() diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index ada132072b..fe6108d39f 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include "GLToolbar.hpp" #include "Event.hpp" @@ -17,6 +18,7 @@ #include "GCodeViewer.hpp" #include "Camera.hpp" #include "SceneRaycaster.hpp" +#include "SceneCache.hpp" #include "IMToolbar.hpp" #include "slic3r/GUI/3DBed.hpp" #include "libslic3r/Slicing.hpp" @@ -33,6 +35,7 @@ class wxTimerEvent; class wxPaintEvent; class wxGLCanvas; class wxGLContext; +struct ImDrawData; // Support for Retina OpenGL on Mac OS. // wxGTK3 seems to simulate OSX behavior in regard to HiDPI scaling support, enable it as well. @@ -402,16 +405,23 @@ class GLCanvas3D std::chrono::time_point m_measuring_start; int m_fps_out = -1; int m_fps_running = 0; + // Frames that redrew the 3D scene rather than reusing the cached one. + int m_scene_fps_out = 0; + int m_scene_fps_running = 0; public: void increment_fps_counter() { ++m_fps_running; } + void increment_scene_fps_counter() { ++m_scene_fps_running; } int get_fps() { return m_fps_out; } + int get_scene_fps() const { return m_scene_fps_out; } int get_fps_and_reset_if_needed() { auto cur_time = std::chrono::high_resolution_clock::now(); int elapsed_ms = std::chrono::duration_cast(cur_time-m_measuring_start).count(); if (elapsed_ms > 1000 || m_fps_out == -1) { m_measuring_start = cur_time; m_fps_out = int (1000. * m_fps_running / elapsed_ms); + m_scene_fps_out = int (1000. * m_scene_fps_running / elapsed_ms); m_fps_running = 0; + m_scene_fps_running = 0; } return m_fps_out; } @@ -533,6 +543,10 @@ private: bool m_in_render; wxTimer m_timer; wxTimer m_timer_set_color; + // Armed by each frame that draws the FPS overlay; its tick requests an overlay-only frame. + wxTimer m_fps_overlay_timer; + // True during the frame the timer requested, which is not counted. + bool m_fps_overlay_tick{ false }; LayersEditing m_layers_editing; Mouse m_mouse; GLGizmosManager m_gizmos; @@ -600,6 +614,8 @@ private: // Screen is only refreshed from the OnIdle handler if it is dirty. bool m_dirty; + // A frame is needed, and only for the overlay. + bool m_overlay_dirty{ false }; bool m_initialized; //BBS: add flag to controll rendering bool m_render_preview{ true }; @@ -755,6 +771,11 @@ public: unsigned int m_ssao_color_texture_id{ 0 }; unsigned int m_ssao_depth_texture_id{ 0 }; std::array m_ssao_texture_size{ { 0, 0 } }; + // The last scene pass, for frames that only rebuild the overlay. + SceneCache m_scene_cache; + // Signature of the overlay on screen; empty after render(), a paint request or a frame drawn but + // not shown, so the next frame is presented regardless. + std::optional m_presented_signature; GLModel m_plate_shadow_mask; std::string m_plate_shadow_mask_key; // Depth-based shadow map used to cast object shadows onto other objects and themselves. @@ -1077,6 +1098,7 @@ public: void on_timer(wxTimerEvent& evt); void on_render_timer(wxTimerEvent& evt); void on_set_color_timer(wxTimerEvent& evt); + void on_fps_overlay_timer(wxTimerEvent& evt); void on_mouse(wxMouseEvent& evt); void on_gesture(wxGestureEvent& evt); void on_paint(wxPaintEvent& evt); @@ -1274,7 +1296,7 @@ private: void _zoom_to_box(const BoundingBoxf3& box, double margin_factor = DefaultCameraZoomToBoxMarginFactor); void _update_camera_zoom(double zoom); - void _refresh_if_shown_on_screen(); + void _refresh_if_shown_on_screen(bool scene_dirty = true); void _picking_pass(); void _rectangular_selection_picking_pass(); @@ -1282,9 +1304,21 @@ private: bool _is_ssao_enabled() const; int _get_effective_fps_cap() const; bool _is_fps_overlay_enabled() const; + bool _is_scene_cache_enabled() const; + bool _is_scene_cacheable() const; + bool _is_frame_skipping_enabled() const; void _render_fps_overlay(int fps) const; void _render_fxaa_pass(unsigned int width, unsigned int height); void _render_ssao_pass(unsigned int width, unsigned int height); + // scene_dirty is false only for a frame that its requester knows to be overlay-only. + void _render_frame(bool scene_dirty, bool only_init = false); + void _render_scene(const Camera& camera, const Size& cnv_size); + // Request a frame that only rebuilds the overlay. + void _set_overlay_as_dirty() { m_overlay_dirty = true; } + // These read the hover state _picking_pass() sets. + SceneCache::Key _scene_cache_key(const Camera& camera) const; + bool _can_reuse_cached_scene(const Camera& camera) const; + void _capture_scene_cache(const Camera& camera); void _render_background(); void _render_bed(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool show_axes); // Build the light-space depth shadow map (consumed by gouraud/phong for object & self shadows) @@ -1301,6 +1335,7 @@ private: void _render_wireframe_overlay(); //BBS: GUI refactor: add canvas size as parameters void _render_gcode(int canvas_width, int canvas_height); + void _render_gcode_overlay(int canvas_width, int canvas_height); //BBS: render a plane for assemble void _render_plane() const; void _render_selection(); @@ -1310,6 +1345,8 @@ private: #endif // ENABLE_RENDER_SELECTION_CENTER void _check_and_update_toolbar_icon_scale(); void _render_overlays(); + void _render_overlay_toolbars(); + size_t _overlay_signature(const ImDrawData* draw_data) const; void _render_style_editor(); void _render_volumes_for_picking(const Camera& camera) const; void _render_current_gizmo() const; diff --git a/src/slic3r/GUI/GLTexture.cpp b/src/slic3r/GUI/GLTexture.cpp index fbdb308c56..3df368de3f 100644 --- a/src/slic3r/GUI/GLTexture.cpp +++ b/src/slic3r/GUI/GLTexture.cpp @@ -707,6 +707,29 @@ void GLTexture::render_sub_texture(unsigned int tex_id, float left, float right, glsafe(::glDisable(GL_BLEND)); } +void GLTexture::copy_from_framebuffer(unsigned int& tex_id, std::array& tex_size, unsigned int width, unsigned int height, int filter) +{ + if (tex_id == 0) { + glsafe(::glGenTextures(1, &tex_id)); + glsafe(::glBindTexture(GL_TEXTURE_2D, tex_id)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter)); + 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)); + } + else + glsafe(::glBindTexture(GL_TEXTURE_2D, tex_id)); + + if (tex_size[0] != width || tex_size[1] != height) { + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr)); + tex_size = { width, height }; + } + + // Copying from the default framebuffer resolves its multisampling. + glsafe(::glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, width, height)); + glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); +} + static bool to_squared_power_of_two(const std::string& filename, int max_size_px, int& w, int& h) { auto is_power_of_two = [](int v) { return v != 0 && (v & (v - 1)) == 0; }; diff --git a/src/slic3r/GUI/GLTexture.hpp b/src/slic3r/GUI/GLTexture.hpp index 1bb63651f0..5e3d11984d 100644 --- a/src/slic3r/GUI/GLTexture.hpp +++ b/src/slic3r/GUI/GLTexture.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -132,6 +133,9 @@ namespace GUI { static void render_texture(unsigned int tex_id, float left, float right, float bottom, float top); static void render_sub_texture(unsigned int tex_id, float left, float right, float bottom, float top, const Quad_UVs& uvs); + // Copies the bound read framebuffer into an RGBA texture, creating it on first use and + // reallocating it when the size changes. + static void copy_from_framebuffer(unsigned int& tex_id, std::array& tex_size, unsigned int width, unsigned int height, int filter); private: bool load_from_png(const std::string& filename, bool use_mipmaps, ECompressionType compression_type, bool apply_anisotropy); diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index 234340dc6b..e36385c946 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -8,6 +8,8 @@ #include "slic3r/GUI/Camera.hpp" #include "slic3r/GUI/Plater.hpp" +#include + #include #include #include @@ -199,7 +201,10 @@ void GLToolbarItem::render(unsigned int tex_id, float left, float right, float b }; GLTexture::render_sub_texture(tex_id, left, right, bottom, top, uvs(tex_width, tex_height, icon_size)); +} +void GLToolbarItem::render_window(float left, float right, float bottom, float top) const +{ if (is_pressed()) { if ((m_last_action_type == Left) && m_data.left.can_render()) @@ -215,13 +220,6 @@ void GLToolbarItem::render_image(unsigned int tex_id, float left, float right, f //GLTexture::Quad_UVs image_uvs = { { 0.0f, 1.0f }, { 1.0f, 1.0f }, { 1.0f, 0.0f }, { 0.0f, 0.0f } }; GLTexture::render_sub_texture(tex_id, left, right, bottom, top, image_uvs); - - if (is_pressed()) { - if ((m_last_action_type == Left) && m_data.left.can_render()) - m_data.left.render_callback(left, right, bottom, top); - else if ((m_last_action_type == Right) && m_data.right.can_render()) - m_data.right.render_callback(left, right, bottom, top); - } } BackgroundTexture::Metadata::Metadata() @@ -544,11 +542,35 @@ void GLToolbar::render(const GLCanvas3D& parent,GLToolbarItem::EType type) switch (m_layout.type) { default: - case Layout::Horizontal: { render_horizontal(parent,type); break; } - case Layout::Vertical: { render_vertical(parent); break; } + case Layout::Horizontal: { render_horizontal(parent, type, true); break; } + case Layout::Vertical: { render_vertical(parent, true); break; } } } +void GLToolbar::render_item_windows(const GLCanvas3D& parent) +{ + if (!m_enabled || m_items.empty()) + return; + + switch (m_layout.type) + { + default: + case Layout::Horizontal: { render_horizontal(parent, GLToolbarItem::Action, false); break; } + case Layout::Vertical: { render_vertical(parent, false); break; } + } +} + +size_t GLToolbar::get_state_hash() const +{ + size_t hash = 0; + boost::hash_combine(hash, m_enabled); + for (const GLToolbarItem* item : m_items) { + boost::hash_combine(hash, (int)item->get_state()); + boost::hash_combine(hash, item->is_visible()); + } + return hash; +} + bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent) { if (!m_enabled) @@ -1354,7 +1376,7 @@ void GLToolbar::render_arrow(const GLCanvas3D& parent, GLToolbarItem* highlighte } } -void GLToolbar::render_horizontal(const GLCanvas3D& parent,GLToolbarItem::EType type) +void GLToolbar::render_horizontal(const GLCanvas3D& parent, GLToolbarItem::EType type, bool draw_icons) { const Size cnv_size = parent.get_canvas_size(); const float cnv_w = (float)cnv_size.get_width(); @@ -1385,7 +1407,8 @@ void GLToolbar::render_horizontal(const GLCanvas3D& parent,GLToolbarItem::EType right = left + width * 0.5; const float bottom = top - height; - render_background(left, top, right, bottom, border_w, border_h); + if (draw_icons) + render_background(left, top, right, bottom, border_w, border_h); left += border_w; top -= border_h; @@ -1400,7 +1423,9 @@ void GLToolbar::render_horizontal(const GLCanvas3D& parent,GLToolbarItem::EType else { //BBS GUI refactor item->render_left_pos = left; - if (!item->is_action_with_text_image()) { + if (!draw_icons) + item->render_window(left, left + icons_size_x, top - icons_size_y, top); + else if (!item->is_action_with_text_image()) { unsigned int tex_id = m_icons_texture.get_id(); int tex_width = m_icons_texture.get_width(); int tex_height = m_icons_texture.get_height(); @@ -1412,7 +1437,8 @@ void GLToolbar::render_horizontal(const GLCanvas3D& parent,GLToolbarItem::EType if (item->is_action_with_text()) { float scaled_text_size = item->get_extra_size_ratio() * icons_size_x; - item->render_text(left + icons_size_x, left + icons_size_x + scaled_text_size, top - icons_size_y, top); + if (draw_icons) + item->render_text(left + icons_size_x, left + icons_size_x + scaled_text_size, top - icons_size_y, top); left += scaled_text_size; } left += icon_stride; @@ -1420,7 +1446,7 @@ void GLToolbar::render_horizontal(const GLCanvas3D& parent,GLToolbarItem::EType } } -void GLToolbar::render_vertical(const GLCanvas3D& parent) +void GLToolbar::render_vertical(const GLCanvas3D& parent, bool draw_icons) { const Size cnv_size = parent.get_canvas_size(); const float cnv_w = (float)cnv_size.get_width(); @@ -1449,7 +1475,8 @@ void GLToolbar::render_vertical(const GLCanvas3D& parent) const float right = left + width; const float bottom = top - height; - render_background(left, top, right, bottom, border_w, border_h); + if (draw_icons) + render_background(left, top, right, bottom, border_w, border_h); left += border_w; top -= border_h; @@ -1462,6 +1489,11 @@ void GLToolbar::render_vertical(const GLCanvas3D& parent) if (item->is_separator()) top -= separator_stride; else { + if (!draw_icons) { + item->render_window(left, left + icons_size_x, top - icons_size_y, top); + top -= icon_stride; + continue; + } unsigned int tex_id; int tex_width, tex_height; if (item->is_action_with_text_image()) { diff --git a/src/slic3r/GUI/GLToolbar.hpp b/src/slic3r/GUI/GLToolbar.hpp index c4bc88045e..638ac937d4 100644 --- a/src/slic3r/GUI/GLToolbar.hpp +++ b/src/slic3r/GUI/GLToolbar.hpp @@ -231,6 +231,8 @@ public: int generate_image_texture(); void render(unsigned int tex_id, float left, float right, float bottom, float top, unsigned int tex_width, unsigned int tex_height, unsigned int icon_size) const; + // The ImGui window a pressed item shows, given the icon's rectangle. + void render_window(float left, float right, float bottom, float top) const; void render_image(unsigned int tex_id, float left, float right, float bottom, float top, unsigned int tex_width, unsigned int tex_height, unsigned int icon_size) const; private: void set_visible(bool visible) { m_data.visible = visible; } @@ -410,6 +412,10 @@ public: bool update_items_state(); void render(const GLCanvas3D& parent,GLToolbarItem::EType type = GLToolbarItem::Action); + // The ImGui windows of pressed items, built with the same layout as render(). + void render_item_windows(const GLCanvas3D& parent); + // Hash of the state render() draws from: enabled, and each item's state and visibility. + size_t get_state_hash() const; void render_arrow(const GLCanvas3D& parent, GLToolbarItem* highlighted_item); bool on_mouse(wxMouseEvent& evt, GLCanvas3D& parent); @@ -438,8 +444,8 @@ private: int contains_mouse_vertical(const Vec2d& mouse_pos, const GLCanvas3D& parent) const; void render_background(float left, float top, float right, float bottom, float border_w, float border_h) const; - void render_horizontal(const GLCanvas3D &parent, GLToolbarItem::EType type); - void render_vertical(const GLCanvas3D& parent); + void render_horizontal(const GLCanvas3D &parent, GLToolbarItem::EType type, bool draw_icons); + void render_vertical(const GLCanvas3D& parent, bool draw_icons); bool generate_icons_texture(); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 5d4be5e9f0..ab53b773a2 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -8467,12 +8467,14 @@ void GUI_App::open_exportpresetbundledialog(size_t open_on_tab, const std::strin void GUI_App::open_preferences(size_t open_on_tab, const std::string& highlight_option) { - static constexpr const char* opengl_fxaa_setting_key = "opengl_fxaa_enabled"; - static constexpr const char* opengl_fps_cap_setting_key = "opengl_fps_cap"; - static constexpr const char* opengl_show_fps_overlay_setting_key = "opengl_show_fps_overlay"; - const std::string previous_opengl_fxaa = app_config->get(opengl_fxaa_setting_key); - const std::string previous_opengl_fps_cap = app_config->get(opengl_fps_cap_setting_key); - const std::string previous_opengl_show_fps_overlay = app_config->get(opengl_show_fps_overlay_setting_key); + // Render settings the canvas reads every frame; a change needs one redraw to show. + static constexpr const char* opengl_render_setting_keys[] = { + SETTING_OPENGL_FXAA_ENABLED, SETTING_OPENGL_FPS_CAP, SETTING_OPENGL_SHOW_FPS_OVERLAY, SETTING_OPENGL_SCENE_CACHE, + SETTING_OPENGL_SKIP_IDENTICAL_FRAMES + }; + std::vector previous_opengl_render_settings; + for (const char* key : opengl_render_setting_keys) + previous_opengl_render_settings.emplace_back(app_config->get(key)); bool need_recreate_gui = false; std::string pending_language; @@ -8512,10 +8514,10 @@ void GUI_App::open_preferences(size_t open_on_tab, const std::string& highlight_ } } - const bool opengl_fxaa_changed = app_config->get(opengl_fxaa_setting_key) != previous_opengl_fxaa; - const bool opengl_fps_cap_changed = app_config->get(opengl_fps_cap_setting_key) != previous_opengl_fps_cap; - const bool opengl_show_fps_overlay_changed = app_config->get(opengl_show_fps_overlay_setting_key) != previous_opengl_show_fps_overlay; - if ((opengl_fxaa_changed || opengl_fps_cap_changed || opengl_show_fps_overlay_changed) && !need_recreate_gui && this->plater_ != nullptr) { + bool opengl_render_settings_changed = false; + for (size_t i = 0; i < previous_opengl_render_settings.size(); ++i) + opengl_render_settings_changed |= app_config->get(opengl_render_setting_keys[i]) != previous_opengl_render_settings[i]; + if (opengl_render_settings_changed && !need_recreate_gui && this->plater_ != nullptr) { this->plater_->set_current_canvas_as_dirty(); this->plater_->get_current_canvas3D()->force_set_focus(); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoAssembly.cpp b/src/slic3r/GUI/Gizmos/GLGizmoAssembly.cpp index b1b163ed9d..e686d5bb8f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoAssembly.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoAssembly.cpp @@ -69,6 +69,7 @@ bool GLGizmoAssembly::on_is_activable() const void GLGizmoAssembly::on_render_input_window(float x, float y, float bottom_limit) { + render_dimensioning_if_scene_reused(); static std::optional last_feature; static EMode last_mode = EMode::FeatureSelection; static SelectedFeatures last_selected_features; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp index 73e0484977..92491d0d44 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp @@ -179,6 +179,8 @@ public: bool is_selectable() const { return on_is_selectable(); } CommonGizmosDataID get_requirements() const { return on_get_requirements(); } virtual bool wants_enter_leave_snapshots() const { return false; } + // True when what on_render() draws would change if the cursor moved. + virtual bool render_follows_cursor() const { return false; } virtual std::string get_gizmo_entering_text() const { assert(false); return ""; } virtual std::string get_gizmo_leaving_text() const { assert(false); return ""; } virtual std::string get_action_snapshot_name() const; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBrimEars.hpp b/src/slic3r/GUI/Gizmos/GLGizmoBrimEars.hpp index cd3bc53cbd..075fb37b21 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBrimEars.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBrimEars.hpp @@ -167,6 +167,8 @@ protected: std::string on_get_name() const override; bool on_is_activable() const override; + // The preview ear is drawn only while the cursor is on the model. + bool render_follows_cursor() const override { return render_hover_point.has_value(); } //bool on_is_selectable() const override; virtual CommonGizmosDataID on_get_requirements() const override; void on_load(cereal::BinaryInputArchive& ar) override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp index 6f7d6fed58..ba2f54d5c5 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp @@ -563,8 +563,30 @@ void GLGizmoMeasure::init_plane_glmodel(GripperType gripper_type, const Measure: } } +bool GLGizmoMeasure::render_follows_cursor() const +{ + // The two raycasts on_render() starts with, without their side effects. + if (m_editing_distance) + return false; + + const Vec2d mouse_position = m_parent.get_local_mouse_position(); + const Camera& camera = wxGetApp().plater()->get_camera(); + Vec3f hit = Vec3f::Zero(); + Vec3f normal = Vec3f::Zero(); + for (const auto& item : m_gripper_id_raycast_map) { + if (item.second->get_id() > 0 && item.second->get_raycaster()->closest_hit(mouse_position, item.second->get_transform(), camera, hit, normal)) + return true; + } + for (const auto& item : m_mesh_raycaster_map) { + if (item.second->get_raycaster()->unproject_on_mesh(mouse_position, item.second->get_transform(), camera, hit, normal)) + return true; + } + return false; +} + void GLGizmoMeasure::on_render() { + m_rendered_this_frame = true; #if ENABLE_MEASURE_GIZMO_DEBUG render_debug_dialog(); #endif // ENABLE_MEASURE_GIZMO_DEBUG @@ -701,35 +723,36 @@ void GLGizmoMeasure::on_render() reset_gripper_pick(GripperType::UNDEFINE, true); m_curr_feature = curr_feature; - if (!m_curr_feature.has_value()) - return; - m_curr_feature->volume = m_last_hit_volume; - m_curr_feature->world_tran = m_mesh_raycaster_map[m_last_hit_volume]->get_transform(); + // The selected features are drawn below whether or not one is hovered. + if (m_curr_feature.has_value()) { + m_curr_feature->volume = m_last_hit_volume; + m_curr_feature->world_tran = m_mesh_raycaster_map[m_last_hit_volume]->get_transform(); - switch (m_curr_feature->get_type()) { - default: { assert(false); break; } - case Measure::SurfaceFeatureType::Point: - { - m_gripper_id_raycast_map[GripperType::POINT] = std::make_shared(POINT_ID, *m_sphere.mesh_raycaster); - break; - } - case Measure::SurfaceFeatureType::Edge: - { - m_gripper_id_raycast_map[GripperType::EDGE] = std::make_shared(EDGE_ID, *m_cylinder.mesh_raycaster); - break; - } - case Measure::SurfaceFeatureType::Circle: { - m_curr_circle.last_circle_feature = nullptr; - m_curr_circle.inv_zoom = 0; - init_circle_glmodel(GripperType::CIRCLE, *m_curr_feature, m_curr_circle,inv_zoom); - break; - } - case Measure::SurfaceFeatureType::Plane: { - update_world_plane_features(m_curr_measuring.get(), *m_curr_feature); - m_curr_plane.plane_idx = -1; - init_plane_glmodel(GripperType::PLANE, *m_curr_feature, m_curr_plane); - break; - } + switch (m_curr_feature->get_type()) { + default: { assert(false); break; } + case Measure::SurfaceFeatureType::Point: + { + m_gripper_id_raycast_map[GripperType::POINT] = std::make_shared(POINT_ID, *m_sphere.mesh_raycaster); + break; + } + case Measure::SurfaceFeatureType::Edge: + { + m_gripper_id_raycast_map[GripperType::EDGE] = std::make_shared(EDGE_ID, *m_cylinder.mesh_raycaster); + break; + } + case Measure::SurfaceFeatureType::Circle: { + m_curr_circle.last_circle_feature = nullptr; + m_curr_circle.inv_zoom = 0; + init_circle_glmodel(GripperType::CIRCLE, *m_curr_feature, m_curr_circle,inv_zoom); + break; + } + case Measure::SurfaceFeatureType::Plane: { + update_world_plane_features(m_curr_measuring.get(), *m_curr_feature); + m_curr_plane.plane_idx = -1; + init_plane_glmodel(GripperType::PLANE, *m_curr_feature, m_curr_plane); + break; + } + } } } } @@ -2136,8 +2159,18 @@ void GLGizmoMeasure::init_render_input_window() m_same_model_object = is_two_volume_in_same_model_object(); } +void GLGizmoMeasure::render_dimensioning_if_scene_reused() +{ + // The labels are ImGui and live one frame; the lines drawn with them land under the cached + // scene, which is drawn after the overlay is built. + if (!m_rendered_this_frame) + render_dimensioning(); + m_rendered_this_frame = false; +} + void GLGizmoMeasure::on_render_input_window(float x, float y, float bottom_limit) { + render_dimensioning_if_scene_reused(); static std::optional last_feature; static EMode last_mode = EMode::FeatureSelection; static SelectedFeatures last_selected_features; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp index 3965652c05..40cb3bab1f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp @@ -228,6 +228,9 @@ protected: void restore_scene_raycasters_state(); void render_dimensioning(); + // Builds the dimension labels on a frame that reused the cached scene and so skipped on_render(). + void render_dimensioning_if_scene_reused(); + bool m_rendered_this_frame{ false }; #if ENABLE_MEASURE_GIZMO_DEBUG void render_debug_dialog(); @@ -255,6 +258,9 @@ protected: bool on_init() override; std::string on_get_name() const override; bool on_is_activable() const override; + // The hover is resolved inside on_render(), against the mesh and against the grippers of the + // selected features, which sit off the mesh. + bool render_follows_cursor() const override; void on_render() override; void on_set_state() override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index 3dcf51cbb5..d90579e589 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -131,15 +131,12 @@ void GLGizmoPainterBase::render_triangles(const Selection& selection) const } } -void GLGizmoPainterBase::render_cursor() +std::vector GLGizmoPainterBase::mesh_trafo_matrices() const { - // First check that the mouse pointer is on an object. const ModelObject* mo = m_c->selection_info()->model_object(); const Selection& selection = m_parent.get_selection(); const ModelInstance* mi = mo->instances[selection.get_instance_idx()]; - const Camera& camera = wxGetApp().plater()->get_camera(); - // Precalculate transformations of individual meshes. std::vector trafo_matrices; for (const ModelVolume* mv : mo->volumes) { if (mv->is_model_part()) @@ -154,6 +151,26 @@ void GLGizmoPainterBase::render_cursor() } } } + return trafo_matrices; +} + +bool GLGizmoPainterBase::render_follows_cursor() const +{ + // The brush is drawn only where the cursor meets the model. update_raycast_cache() keeps the + // answer for render_cursor(). + if (m_c->selection_info() == nullptr || m_c->selection_info()->model_object() == nullptr) + return false; + update_raycast_cache(m_parent.get_local_mouse_position(), wxGetApp().plater()->get_camera(), mesh_trafo_matrices()); + return m_rr.mesh_id != -1; +} + +void GLGizmoPainterBase::render_cursor() +{ + // First check that the mouse pointer is on an object. + const Camera& camera = wxGetApp().plater()->get_camera(); + + // Precalculate transformations of individual meshes. + const std::vector trafo_matrices = mesh_trafo_matrices(); // Raycast and return if there's no hit. update_raycast_cache(m_parent.get_local_mouse_position(), camera, trafo_matrices); if (m_rr.mesh_id == -1) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp index 8ec1cb1fb0..63e9d3f436 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp @@ -318,6 +318,8 @@ private: std::vector get_projected_height_range(const Vec2d& mouse_position, double resolution, const std::vector& part_volumes, const std::vector& trafo_matrices) const; bool is_mesh_point_clipped(const Vec3d& point, const Transform3d& trafo) const; + // World transforms of the model parts, in mo->volumes order. + std::vector mesh_trafo_matrices() const; void update_raycast_cache(const Vec2d& mouse_position, const Camera& camera, const std::vector& trafo_matrices) const; @@ -370,6 +372,7 @@ protected: virtual PainterGizmoType get_painter_type() const = 0; bool on_is_activable() const override; + bool render_follows_cursor() const override; bool on_is_selectable() const override; void on_load(cereal::BinaryInputArchive& ar) override; void on_save(cereal::BinaryOutputArchive& ar) const override {} diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index 64e566805e..5243c8294f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -410,6 +410,10 @@ void ObjectClipper::set_position_by_ratio(double pos, bool keep_normal, bool ver void ObjectClipper::set_range_and_pos(const Vec3d& cpl_normal, double cpl_offset, double pos) { + // Called every frame by GLGizmoCut3D::on_render(), usually with the plane already set. + if (m_clp && *m_clp == ClippingPlane(cpl_normal, cpl_offset) && m_clp_ratio == pos) + return; + m_clp.reset(new ClippingPlane(cpl_normal, cpl_offset)); m_clp_ratio = pos; get_pool()->get_canvas()->set_as_dirty(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 8f4c320a5b..17a7b769c3 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -36,6 +36,8 @@ #include "libslic3r/Model.hpp" #include "libslic3r/PresetBundle.hpp" +#include + #include namespace Slic3r { @@ -631,6 +633,7 @@ void GLGizmosManager::render_painter_assemble_view() const m_assemble_view_data->model_objects_clipper()->render_cut(); } +// The icon bar, drawn with GL. void GLGizmosManager::render_overlay() { if (!m_enabled) @@ -639,7 +642,27 @@ void GLGizmosManager::render_overlay() if (m_icons_texture_dirty) generate_icons_texture(); - do_render_overlay(); + do_render_overlay(true); +} + +// The open gizmo's settings panel, ImGui. +void GLGizmosManager::render_overlay_input_window() +{ + if (!m_enabled) + return; + + do_render_overlay(false); +} + +size_t GLGizmosManager::get_overlay_state_hash() const +{ + size_t hash = 0; + boost::hash_combine(hash, m_enabled); + boost::hash_combine(hash, (int)m_hover); + boost::hash_combine(hash, (int)m_current); + boost::hash_combine(hash, (int)m_highlight.first); + boost::hash_combine(hash, m_highlight.second); + return hash; } std::string GLGizmosManager::get_tooltip() const @@ -1222,7 +1245,9 @@ void GLGizmosManager::render_arrow(const GLCanvas3D& parent, EType highlighted_t //BBS: GUI refactor: GLToolbar&&Gizmo adjust //when rendering, {0, 0} is at the center, {-0.5, 0.5} at the left-top -void GLGizmosManager::do_render_overlay() const +// draw_icons selects the icon bar (GL) or the open gizmo's input window (ImGui), placed by the same +// layout walk. +void GLGizmosManager::do_render_overlay(bool draw_icons) const { const std::vector selectable_idxs = get_selectable_idxs(); if (selectable_idxs.empty()) @@ -1261,7 +1286,8 @@ void GLGizmosManager::do_render_overlay() const } float top_y = 1.0f; - render_background(top_x, top_y, top_x + width, top_y - height, border_w, border_h); + if (draw_icons) + render_background(top_x, top_y, top_x + width, top_y - height, border_w, border_h); top_x += border_w; top_y -= border_h; @@ -1298,7 +1324,8 @@ void GLGizmosManager::do_render_overlay() const const float v_top = v_offset + sprite_id * dv; const float v_bottom = v_top + dv - v_offset; - GLTexture::render_sub_texture(icons_texture_id, top_x, top_x + icons_size_x, top_y - icons_size_y, top_y, { { u_left, v_bottom }, { u_right, v_bottom }, { u_right, v_top }, { u_left, v_top } }); + if (draw_icons) + GLTexture::render_sub_texture(icons_texture_id, top_x, top_x + icons_size_x, top_y - icons_size_y, top_y, { { u_left, v_bottom }, { u_right, v_bottom }, { u_right, v_top }, { u_left, v_top } }); if (idx == m_current // Orca: Show Svg dialog at the same place as emboss gizmo || (m_current == Svg && idx == Emboss)) { @@ -1306,7 +1333,8 @@ void GLGizmosManager::do_render_overlay() const //render_input_window uses a different coordination(imgui) //1. no need to scale by camera zoom, set {0,0} at left-up corner for imgui //gizmo->render_input_window(width, 0.5f * cnv_h - zoomed_top_y * zoom, toolbar_top); - m_gizmos[m_current]->render_input_window(0.5 * cnv_w + 0.5f * top_x * cnv_w, get_scaled_total_height(), cnv_h); + if (!draw_icons) + m_gizmos[m_current]->render_input_window(0.5 * cnv_w + 0.5f * top_x * cnv_w, get_scaled_total_height(), cnv_h); is_render_current = true; } @@ -1314,7 +1342,7 @@ void GLGizmosManager::do_render_overlay() const } // BBS simplify gizmo is not a selected gizmo and need to render input window - if (!is_render_current && m_current != Undefined) { + if (!draw_icons && !is_render_current && m_current != Undefined) { m_gizmos[m_current]->render_input_window(0.5 * cnv_w + 0.5f * top_x * cnv_w, get_scaled_total_height(), cnv_h); } } diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp index d490a6c05f..5f249d2ef7 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp @@ -296,6 +296,9 @@ public: void render_painter_assemble_view() const; void render_overlay(); + void render_overlay_input_window(); + // Hash of the state render_overlay() draws from: enabled, hover, current and highlight. + size_t get_overlay_state_hash() const; void render_arrow(const GLCanvas3D& parent, EType highlighted_type) const; @@ -329,7 +332,7 @@ private: void render_background(float left, float top, float right, float bottom, float border_w, float border_h) const; - void do_render_overlay() const; + void do_render_overlay(bool draw_icons) const; bool generate_icons_texture(); diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index cf107565e3..3ccbc6745a 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -590,11 +590,39 @@ void ImGuiWrapper::new_frame() // BBL: end copy & paste } -void ImGuiWrapper::render() +ImDrawData* ImGuiWrapper::end_frame() { ImGui::Render(); - render_draw_data(ImGui::GetDrawData()); m_new_frame_open = false; + return ImGui::GetDrawData(); +} + +void ImGuiWrapper::render(ImDrawData* draw_data) +{ + render_draw_data(draw_data); +} + +ImGuiID ImGuiWrapper::draw_data_signature(const ImDrawData* draw_data) +{ + ImGuiID hash = 0; + if (draw_data == nullptr) + return hash; + + for (int i = 0; i < draw_data->CmdListsCount; ++i) { + const ImDrawList* list = draw_data->CmdLists[i]; + hash = ImHashData(list->VtxBuffer.Data, list->VtxBuffer.Size * sizeof(ImDrawVert), hash); + hash = ImHashData(list->IdxBuffer.Data, list->IdxBuffer.Size * sizeof(ImDrawIdx), hash); + // ImDrawCmd has padding, and a hovered ImageButton3() differs only in TextureId. + for (const ImDrawCmd& cmd : list->CmdBuffer) { + hash = ImHashData(&cmd.ClipRect, sizeof(cmd.ClipRect), hash); + hash = ImHashData(&cmd.TextureId, sizeof(cmd.TextureId), hash); + hash = ImHashData(&cmd.VtxOffset, sizeof(cmd.VtxOffset), hash); + hash = ImHashData(&cmd.IdxOffset, sizeof(cmd.IdxOffset), hash); + hash = ImHashData(&cmd.ElemCount, sizeof(cmd.ElemCount), hash); + hash = ImHashData(&cmd.UserCallback, sizeof(cmd.UserCallback), hash); + } + } + return hash; } ImVec2 ImGuiWrapper::calc_text_size(std::string_view text, diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index db94b3edcb..ad83d50d2d 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -98,7 +98,11 @@ public: const ImWchar *get_glyph_ranges() const { return m_glyph_ranges; } // language specific void new_frame(); - void render(); + // Ends the frame and returns its draw data without drawing it. + ImDrawData* end_frame(); + void render(ImDrawData* draw_data); + // Hash of every draw list's vertices, indices and commands. + static ImGuiID draw_data_signature(const ImDrawData* draw_data); float scaled(float x) const { return x * m_font_size; } ImVec2 scaled(float x, float y) const { return ImVec2(x * m_font_size, y * m_font_size); } diff --git a/src/slic3r/GUI/PartPlate.cpp b/src/slic3r/GUI/PartPlate.cpp index ba0d9580dc..428f45c845 100644 --- a/src/slic3r/GUI/PartPlate.cpp +++ b/src/slic3r/GUI/PartPlate.cpp @@ -1115,19 +1115,9 @@ void PartPlate::render_plate_name_texture() glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); } -void PartPlate::show_tooltip(const std::string tooltip) +void PartPlate::set_hover_tooltip(const std::string& tooltip) { - const auto scale = m_plater->get_current_canvas3D()->get_scale(); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, {6 * scale, 3 * scale}); - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 3 * scale); - ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND); - ImGui::PushStyleColor(ImGuiCol_Border, {0, 0, 0, 0}); - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.00f, 1.00f, 1.00f, 1.00f)); - ImGui::BeginTooltip(); - ImGui::TextUnformatted(tooltip.c_str()); - ImGui::EndTooltip(); - ImGui::PopStyleColor(3); - ImGui::PopStyleVar(2); + m_partplate_list->m_hover_tooltip = tooltip; } void PartPlate::render_icons(bool bottom, bool only_name, int hover_id) @@ -1152,21 +1142,21 @@ void PartPlate::render_icons(bool bottom, bool only_name, int hover_id) if (!only_name) { if (hover_id == 1) { render_icon_texture(m_del_icon.model, m_partplate_list->m_del_hovered_texture); - show_tooltip(_u8L("Remove current plate (if not last one)")); + set_hover_tooltip(_u8L("Remove current plate (if not last one)")); } else render_icon_texture(m_del_icon.model, m_partplate_list->m_del_texture); if (hover_id == 2) { render_icon_texture(m_orient_icon.model, m_partplate_list->m_orient_hovered_texture); - show_tooltip(_u8L("Auto orient objects on current plate")); + set_hover_tooltip(_u8L("Auto orient objects on current plate")); } else render_icon_texture(m_orient_icon.model, m_partplate_list->m_orient_texture); if (hover_id == 3) { render_icon_texture(m_arrange_icon.model, m_partplate_list->m_arrange_hovered_texture); - show_tooltip(_u8L("Arrange objects on current plate")); + set_hover_tooltip(_u8L("Arrange objects on current plate")); } else render_icon_texture(m_arrange_icon.model, m_partplate_list->m_arrange_texture); @@ -1175,12 +1165,12 @@ void PartPlate::render_icons(bool bottom, bool only_name, int hover_id) if (this->is_locked()) { render_icon_texture(m_lock_icon.model, m_partplate_list->m_locked_hovered_texture); - show_tooltip(_u8L("Unlock current plate")); + set_hover_tooltip(_u8L("Unlock current plate")); } else { render_icon_texture(m_lock_icon.model, m_partplate_list->m_lockopen_hovered_texture); - show_tooltip(_u8L("Lock current plate")); + set_hover_tooltip(_u8L("Lock current plate")); } } else { if (this->is_locked()) @@ -1194,21 +1184,21 @@ void PartPlate::render_icons(bool bottom, bool only_name, int hover_id) if (dual_bbl) { if (hover_id == PLATE_FILAMENT_MAP_ID){ render_icon_texture(m_plate_filament_map_icon.model, m_partplate_list->m_plate_set_filament_map_hovered_texture); - show_tooltip(_u8L("Filament grouping")); + set_hover_tooltip(_u8L("Filament grouping")); } else render_icon_texture(m_plate_filament_map_icon.model, m_partplate_list->m_plate_set_filament_map_texture); } if (hover_id == 6) { render_icon_texture(m_plate_name_edit_icon.model, m_partplate_list->m_plate_name_edit_hovered_texture); - show_tooltip(_u8L("Edit current plate name")); + set_hover_tooltip(_u8L("Edit current plate name")); } else render_icon_texture(m_plate_name_edit_icon.model, m_partplate_list->m_plate_name_edit_texture); if (hover_id == 7) { render_icon_texture(m_move_front_icon.model, m_partplate_list->m_move_front_hovered_texture); - show_tooltip(_u8L("Move plate to the front")); + set_hover_tooltip(_u8L("Move plate to the front")); } else render_icon_texture(m_move_front_icon.model, m_partplate_list->m_move_front_texture); @@ -1221,7 +1211,7 @@ void PartPlate::render_icons(bool bottom, bool only_name, int hover_id) else render_icon_texture(m_plate_settings_icon.model, m_partplate_list->m_plate_settings_changed_hovered_texture); - show_tooltip(_u8L("Customize current plate")); + set_hover_tooltip(_u8L("Customize current plate")); } else { if (!has_plate_settings) render_icon_texture(m_plate_settings_icon.model, m_partplate_list->m_plate_settings_texture); @@ -6002,6 +5992,24 @@ void PartPlateList::render(const Transform3d& view_matrix, const Transform3d& pr } } +void PartPlateList::render_hover_tooltip() const +{ + if (m_hover_tooltip.empty()) + return; + + const auto scale = m_plater->get_current_canvas3D()->get_scale(); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, {6 * scale, 3 * scale}); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 3 * scale); + ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND); + ImGui::PushStyleColor(ImGuiCol_Border, {0, 0, 0, 0}); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.00f, 1.00f, 1.00f, 1.00f)); + ImGui::BeginTooltip(); + ImGui::TextUnformatted(m_hover_tooltip.c_str()); + ImGui::EndTooltip(); + ImGui::PopStyleColor(3); + ImGui::PopStyleVar(2); +} + /*int PartPlateList::select_plate_by_hover_id(int hover_id) { int index = hover_id / PartPlate::GRABBER_COUNT; diff --git a/src/slic3r/GUI/PartPlate.hpp b/src/slic3r/GUI/PartPlate.hpp index c05775a963..9bee917243 100644 --- a/src/slic3r/GUI/PartPlate.hpp +++ b/src/slic3r/GUI/PartPlate.hpp @@ -197,7 +197,7 @@ private: // void render_left_arrow(const ColorRGBA render_color, bool use_lighting) const; // void render_right_arrow(const ColorRGBA render_color, bool use_lighting) const; void render_icon_texture(GLModel &buffer, GLTexture &texture); - void show_tooltip(const std::string tooltip); + void set_hover_tooltip(const std::string& tooltip); void render_icons(bool bottom, bool only_name = false, int hover_id = -1); void render_only_numbers(bool bottom); void render_plate_name_texture(); @@ -640,6 +640,8 @@ class PartPlateList : public ObjectBase bool render_bedtype_logo = true; bool render_plate_settings = true; bool render_cali_logo = true; + // Tooltip of the plate icon the last scene pass drew hovered; the canvas overlay shows it. + std::string m_hover_tooltip; bool m_is_dark = false; @@ -860,6 +862,8 @@ public: void render(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool only_current = false, bool only_body = false, int hover_id = -1, bool render_cali = false, bool show_grid = true, bool hide_chrome = false); void set_render_option(bool bedtype_texture, bool plate_settings); void set_render_cali(bool value = true) { render_cali_logo = value; } + void render_hover_tooltip() const; + void clear_hover_tooltip() { m_hover_tooltip.clear(); } void register_raycasters_for_picking(GLCanvas3D& canvas) { for (auto plate : m_plate_list) diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 628c9069ca..adda0edd74 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -1005,6 +1005,7 @@ wxBoxSizer *PreferencesDialog::create_item_checkbox(wxString title, wxString too checkbox->SetToolTip(tip); if (param == "sync_user_preset") { m_sync_user_preset_checkbox = checkbox; } + if (param == SETTING_OPENGL_SKIP_IDENTICAL_FRAMES) { m_skip_identical_frames_checkbox = checkbox; } m_sizer->Add(checkbox, 0, wxALIGN_CENTER); @@ -1031,6 +1032,9 @@ wxBoxSizer *PreferencesDialog::create_item_checkbox(wxString title, wxString too } BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " sync_user_preset: " << (sync ? "true" : "false"); } + else if (param == SETTING_OPENGL_SCENE_CACHE) { + if (m_skip_identical_frames_checkbox) m_skip_identical_frames_checkbox->Enable(checkbox->GetValue()); + } else if (param == "stealth_mode") { bool enabled = app_config->get_stealth_mode(); if (enabled) wxGetApp().on_stealth_mode_enter(); @@ -1959,9 +1963,32 @@ void PreferencesDialog::create_items() ); g_sizer->Add(item_fps_cap); + auto item_scene_cache = create_item_checkbox( + _L("Reuse the 3D scene while idle"), + _L("Skips redrawing the 3D scene when only the mouse cursor moves over the viewport,\n" + "and reuses the previous frame's scene instead. Reduces GPU load.\n" + "Disable it if the viewport shows stale or missing contents.\n\n" + "Takes effect immediately."), + SETTING_OPENGL_SCENE_CACHE + ); + g_sizer->Add(item_scene_cache); + + auto item_skip_identical_frames = create_item_checkbox( + _L("Skip unchanged frames"), + _L("Skips drawing a frame altogether when it would be identical to the one already on screen.\n" + "Only applies to frames that reuse the 3D scene, so it needs Reuse the 3D scene while idle.\n" + "Disable it if a hover highlight, tooltip or animation stops updating.\n\n" + "Takes effect immediately."), + SETTING_OPENGL_SKIP_IDENTICAL_FRAMES + ); + g_sizer->Add(item_skip_identical_frames); + if (m_skip_identical_frames_checkbox) m_skip_identical_frames_checkbox->Enable(app_config->get_bool(SETTING_OPENGL_SCENE_CACHE)); + auto item_fps_overlay = create_item_checkbox( _L("Show FPS overlay"), - _L("Displays current viewport FPS in the top-right corner."), + _L("Displays rendering counts in the top-right corner of the viewport.") + "\n" + + _L("FPS: frames presented to the screen per second.") + "\n" + + _L("3D: frames per second that redrew the 3D scene."), SETTING_OPENGL_SHOW_FPS_OVERLAY ); g_sizer->Add(item_fps_overlay); diff --git a/src/slic3r/GUI/Preferences.hpp b/src/slic3r/GUI/Preferences.hpp index 94340ce619..5ab7fb1919 100644 --- a/src/slic3r/GUI/Preferences.hpp +++ b/src/slic3r/GUI/Preferences.hpp @@ -71,6 +71,7 @@ public: ::CheckBox * m_dark_mode_ckeckbox = {nullptr}; ::CheckBox * m_sync_user_preset_checkbox = {nullptr}; ::CheckBox * m_bambu_cloud_checkbox = {nullptr}; + ::CheckBox * m_skip_identical_frames_checkbox = {nullptr}; ::TextInput *m_backup_interval_textinput = {nullptr}; ::SpinInput *m_dim_previous_layers_brightness_input = {nullptr}; ::ComboBox * m_network_version_combo = {nullptr}; diff --git a/src/slic3r/GUI/SceneCache.cpp b/src/slic3r/GUI/SceneCache.cpp new file mode 100644 index 0000000000..a72e32695f --- /dev/null +++ b/src/slic3r/GUI/SceneCache.cpp @@ -0,0 +1,62 @@ +#include "libslic3r/libslic3r.h" +#include "SceneCache.hpp" + +#include "3DScene.hpp" +#include "GLModel.hpp" +#include "GLShader.hpp" +#include "GLTexture.hpp" +#include "GUI_App.hpp" + +#include + +namespace Slic3r { +namespace GUI { + +void SceneCache::capture(Key key) +{ + m_valid = false; + + if (wxGetApp().get_shader("flat_texture") == nullptr) + return; + + GLTexture::copy_from_framebuffer(m_texture_id, m_texture_size, key.size[0], key.size[1], GL_NEAREST); + + m_key = std::move(key); + m_valid = true; +} + +void SceneCache::render(GLModel& quad) +{ + GLShaderProgram* shader = wxGetApp().get_shader("flat_texture"); + + glsafe(::glDisable(GL_DEPTH_TEST)); + glsafe(::glDisable(GL_BLEND)); + + shader->start_using(); + shader->set_uniform("view_model_matrix", Transform3d::Identity()); + shader->set_uniform("projection_matrix", Transform3d::Identity()); + shader->set_uniform("uniform_texture", 0); + + glsafe(::glActiveTexture(GL_TEXTURE0)); + glsafe(::glBindTexture(GL_TEXTURE_2D, m_texture_id)); + quad.render(); + glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); + shader->stop_using(); + + glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glEnable(GL_BLEND)); + glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); +} + +void SceneCache::reset() +{ + m_valid = false; + if (m_texture_id != 0) { + glsafe(::glDeleteTextures(1, &m_texture_id)); + m_texture_id = 0; + } + m_texture_size = { { 0, 0 } }; +} + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/SceneCache.hpp b/src/slic3r/GUI/SceneCache.hpp new file mode 100644 index 0000000000..d645d2d2a6 --- /dev/null +++ b/src/slic3r/GUI/SceneCache.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include "libslic3r/Point.hpp" + +#include +#include + +namespace Slic3r { +namespace GUI { + +class GLModel; + +// The last 3D scene pass, kept as a texture for frames that only rebuild the overlay. +class SceneCache +{ +public: + // Scene pass inputs that change without any frame request. + struct Key + { + std::array size{ { 0, 0 } }; + Transform3d view_matrix{ Transform3d::Identity() }; + Transform3d projection_matrix{ Transform3d::Identity() }; + // Hovered volumes that draw their sinking contour, hovered plate icons, hovered gizmo grabber. + std::vector sinking_hover_volume_idxs; + std::vector hover_plate_icon_idxs; + int gizmo_hover_id{ -1 }; + bool render_preview{ true }; + + bool operator == (const Key& other) const { + return size == other.size && render_preview == other.render_preview && + gizmo_hover_id == other.gizmo_hover_id && + sinking_hover_volume_idxs == other.sinking_hover_volume_idxs && + hover_plate_icon_idxs == other.hover_plate_icon_idxs && + view_matrix.isApprox(other.view_matrix) && + projection_matrix.isApprox(other.projection_matrix); + } + }; + + // Copies the bound read framebuffer, sized by key.size, and remembers key. + void capture(Key key); + bool matches(const Key& key) const { return m_valid && m_key == key; } + // Draws the last capture over the whole viewport, on the given full screen quad. + void render(GLModel& quad); + void invalidate() { m_valid = false; } + // Frees the texture. + void reset(); + +private: + unsigned int m_texture_id{ 0 }; + std::array m_texture_size{ { 0, 0 } }; + Key m_key; + bool m_valid{ false }; +}; + +} // namespace GUI +} // namespace Slic3r