diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index e42e1a4888..073429fc73 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -212,6 +212,10 @@ void AppConfig::set_defaults() set("preview_reduced_detail_mode", "no_infill"); } + // ORCA: keep the drawn preview scene while nothing in it changes + if (get("preview_cache_static_scene").empty()) + set_bool("preview_cache_static_scene", false); + { const std::string mode = get("preview_rest_detail_mode"); if (mode != "full" && mode != "no_infill" && mode != "shell") diff --git a/src/libvgcode/include/Viewer.hpp b/src/libvgcode/include/Viewer.hpp index 77de1c972d..0f8fec680c 100644 --- a/src/libvgcode/include/Viewer.hpp +++ b/src/libvgcode/include/Viewer.hpp @@ -131,6 +131,13 @@ public: // bool is_rest_view_from_above() const; void set_rest_view_from_above(bool value); + // + // ORCA: a counter that changes whenever what render() draws changes, and whether an update is + // still pending that the next render() will apply. Together they tell a caller whether a frame + // it has kept from an earlier render() can be shown again. + // + uint64_t get_state_version() const; + bool has_pending_updates() const; float get_dim_previous_layers_brightness() const; void set_dim_previous_layers_brightness(float value); // diff --git a/src/libvgcode/src/Viewer.cpp b/src/libvgcode/src/Viewer.cpp index 0c30a7baad..8a1545ab63 100644 --- a/src/libvgcode/src/Viewer.cpp +++ b/src/libvgcode/src/Viewer.cpp @@ -137,6 +137,16 @@ void Viewer::set_rest_view_from_above(bool value) m_impl->set_rest_view_from_above(value); } +uint64_t Viewer::get_state_version() const +{ + return m_impl->get_state_version(); +} + +bool Viewer::has_pending_updates() const +{ + return m_impl->has_pending_updates(); +} + void Viewer::set_dim_previous_layers(bool value) { m_impl->set_dim_previous_layers(value); diff --git a/src/libvgcode/src/ViewerImpl.cpp b/src/libvgcode/src/ViewerImpl.cpp index e16c2d1022..8c1681202b 100644 --- a/src/libvgcode/src/ViewerImpl.cpp +++ b/src/libvgcode/src/ViewerImpl.cpp @@ -899,6 +899,7 @@ void ViewerImpl::reset() m_enabled_segments_reduced_count = 0; m_enabled_options_reduced_count = 0; m_enabled_segments_rest_count = 0; + ++m_state_version; m_shell_bitset = BitSet<>(); m_near_shell_bitset = BitSet<>(); m_top_visible_bitset = BitSet<>(); @@ -1844,6 +1845,7 @@ void ViewerImpl::update_enabled_entities() #endif // ENABLE_OPENGL_ES m_settings.update_enabled_entities = false; + ++m_state_version; } static float encode_color(const Color& color) { @@ -1953,6 +1955,7 @@ void ViewerImpl::update_colors() update_colors_texture(); m_settings.update_colors = false; + ++m_state_version; } void ViewerImpl::render(const Mat4x4& view_matrix, const Mat4x4& projection_matrix) @@ -2562,6 +2565,7 @@ void ViewerImpl::update_view_full_range() } m_settings.update_view_full_range = false; + ++m_state_version; } void ViewerImpl::update_color_ranges() diff --git a/src/libvgcode/src/ViewerImpl.hpp b/src/libvgcode/src/ViewerImpl.hpp index aee96ea7b7..c49c25d01b 100644 --- a/src/libvgcode/src/ViewerImpl.hpp +++ b/src/libvgcode/src/ViewerImpl.hpp @@ -95,7 +95,12 @@ public: // Draw the preview from the reduced set of entities. Meant to be held only while the user is // dragging; the sets are already built, so this is just a buffer binding and never rebuilds. // - void set_reduced_detail(bool value) { m_settings.reduced_detail = value; } + void set_reduced_detail(bool value) { + if (m_settings.reduced_detail != value) { + m_settings.reduced_detail = value; + ++m_state_version; + } + } bool is_reduced_detail() const { return m_settings.reduced_detail; } EReducedDetailMode get_reduced_detail_mode() const { return m_settings.reduced_detail_mode; } void set_reduced_detail_mode(EReducedDetailMode mode); @@ -107,6 +112,10 @@ public: void set_rest_layer_stride(uint32_t value); bool is_rest_view_from_above() const { return m_settings.rest_view_from_above; } void set_rest_view_from_above(bool value); + uint64_t get_state_version() const { return m_state_version; } + bool has_pending_updates() const { + return m_settings.update_view_full_range || m_settings.update_enabled_entities || m_settings.update_colors; + } float get_dim_previous_layers_brightness() const { return m_settings.dim_previous_layers_brightness; } void set_dim_previous_layers_brightness(float value); @@ -339,6 +348,11 @@ private: // fills the step of a sloped surface between one layer's outer wall and the next, too narrow // for the grid to see BitSet<> m_near_shell_bitset; + // + // ORCA: bumped whenever what render() draws changes: on every applied update, on a change of + // the bound set, on load and reset + // + uint64_t m_state_version{ 0 }; #endif // ENABLE_OPENGL_ES // // Variables used for toolpaths coloring diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 5f08d77e56..be1bb47683 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -1178,6 +1178,7 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const m_reduced_detail_layer_stride = static_cast(std::max(1, std::stoi(get_app_config()->get("preview_reduced_detail_layer_stride")))); m_rest_detail_mode = reduced_detail_mode_from_string(get_app_config()->get("preview_rest_detail_mode")); apply_reduced_detail_settings(); + ++m_scene_version; // the median z step between layers, robust to the first layer and to variable layer height { std::vector steps; @@ -1594,11 +1595,21 @@ void GCodeViewer::load_as_preview(libvgcode::GCodeInputData&& data) void GCodeViewer::update_shells_color_by_extruder(const DynamicPrintConfig *config) { + ++m_scene_version; if (config != nullptr) m_shells.volumes.update_colors_by_extruder(config, false); } -void GCodeViewer::set_shell_transparency(float alpha) { m_shells.volumes.set_transparency(alpha); } +void GCodeViewer::set_shell_transparency(float alpha) +{ + m_shells.volumes.set_transparency(alpha); + ++m_scene_version; +} + +std::array GCodeViewer::scene_version() const +{ + return { (m_scene_version << 2) | (m_shells.visible ? 1u : 0u) | (m_no_render_path ? 2u : 0u), m_viewer.get_state_version() }; +} //BBS: always load shell at preview void GCodeViewer::reset_shell() @@ -1610,6 +1621,7 @@ void GCodeViewer::reset_shell() void GCodeViewer::reset() { + ++m_scene_version; //BBS: should also reset the result id BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": current result id %1% ")%m_last_result_id; m_last_result_id = -1; @@ -1638,17 +1650,19 @@ 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(int canvas_width, int canvas_height, int right_margin, bool draw_scene) { glsafe(::glEnable(GL_DEPTH_TEST)); - render_shells(canvas_width, canvas_height); + if (draw_scene) + render_shells(canvas_width, canvas_height); if (m_viewer.get_extrusion_roles_count() == 0) return; update_rest_layer_stride(); - render_toolpaths(); + if (draw_scene) + render_toolpaths(); float legend_height = 0.0f; render_legend(legend_height, canvas_width, canvas_height, right_margin); @@ -2336,6 +2350,7 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const void GCodeViewer::load_shells(const Print& print, bool initialized, bool force_previewing) { + ++m_scene_version; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": initialized=%1%, force_previewing=%2%")%initialized %force_previewing; if ((print.id().id == m_shells.print_id)&&(print.get_modified_count() == m_shells.print_modify_count)) { //BBS: update force previewing logic diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index cc55435ea4..d92e4a104a 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -251,6 +251,8 @@ private: bool m_contained_in_bed{ true }; mutable bool m_no_render_path { false }; + // ORCA: bumped on every change to what the scene pass draws that libvgcode does not track + uint64_t m_scene_version{ 0 }; bool m_is_dark = false; libvgcode::Viewer m_viewer; @@ -283,7 +285,12 @@ 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); + // draw_scene = false records the legend, the sliders and the marker only, for a frame whose + // toolpaths are shown again from a kept image + void render(int canvas_width, int canvas_height, int right_margin, bool draw_scene = true); + // ORCA: what the scene pass draws, as two counters that change whenever it would look different + std::array scene_version() const; + bool scene_update_pending() const { return m_viewer.has_pending_updates(); } //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); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 8bd1e66397..a76368a809 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1224,6 +1224,7 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed) GLCanvas3D::~GLCanvas3D() { + _scene_cache_release(); if (_set_current()) { if (m_fxaa_texture_id != 0) { glsafe(::glDeleteTextures(1, &m_fxaa_texture_id)); @@ -1328,6 +1329,7 @@ bool GLCanvas3D::init() void GLCanvas3D::on_change_color_mode(bool is_dark, bool reinit) { m_is_dark = is_dark; + ++m_scene_version; // Bed color m_bed.on_change_color_mode(is_dark); // GcodeViewer color @@ -2050,6 +2052,46 @@ void GLCanvas3D::render(bool only_init) } // draw scene + int hover_id = (m_hover_plate_idxs.size() > 0)?m_hover_plate_idxs.front():-1; + + // ORCA: while the preference is on and nothing the scene pass draws has changed since the + // last full frame, that frame is shown again and only the overlays are drawn on top of it + const bool preview_scene = m_canvas_type == ECanvasType::CanvasPreview && m_render_preview && m_gcode_viewer.has_data(); + bool scene_from_cache = false; + bool scene_to_cache = false; + if (preview_scene && _scene_cache_enabled() && !m_scene_cache.broken) { + const int current_plate = wxGetApp().plater()->get_partplate_list().get_curr_plate_index(); + const std::array viewer_version = m_gcode_viewer.scene_version(); + const bool unchanged = m_scene_cache.valid && + m_scene_cache.width == cnv_size.get_width() && m_scene_cache.height == cnv_size.get_height() && + m_scene_cache.view == camera.get_view_matrix().matrix() && m_scene_cache.projection == camera.get_projection_matrix().matrix() && + m_scene_cache.hover_plate == hover_id && m_scene_cache.current_plate == current_plate && + m_scene_cache.world_axes == m_show_world_axes && m_scene_cache.canvas_version == m_scene_version && + m_scene_cache.viewer_version == viewer_version && !m_gcode_viewer.scene_update_pending(); + if (unchanged) { + _scene_cache_blit(false); + scene_from_cache = !m_scene_cache.broken; + } + if (!scene_from_cache && _scene_cache_prepare(cnv_size.get_width(), cnv_size.get_height())) { + m_scene_cache.valid = false; + m_scene_cache.view = camera.get_view_matrix().matrix(); + m_scene_cache.projection = camera.get_projection_matrix().matrix(); + m_scene_cache.hover_plate = hover_id; + m_scene_cache.current_plate = current_plate; + m_scene_cache.world_axes = m_show_world_axes; + m_scene_cache.canvas_version = m_scene_version; + m_scene_cache.viewer_version = viewer_version; + scene_to_cache = true; + } + } + else + m_scene_cache.valid = false; + + if (scene_from_cache) { + // the preview's panels and its marker are drawn on top of the frame shown again + _render_gcode(cnv_size.get_width(), cnv_size.get_height(), false); + } + else { 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. @@ -2070,7 +2112,6 @@ void GLCanvas3D::render(bool only_init) 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) { if (!no_partplate) _render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), m_show_world_axes); @@ -2147,6 +2188,13 @@ void GLCanvas3D::render(bool only_init) if (_is_fxaa_enabled()) _render_fxaa_pass(static_cast(cnv_size.get_width()), static_cast(cnv_size.get_height())); + // ORCA: keep the finished scene, with its depth, for the frames that follow + if (scene_to_cache) { + _scene_cache_blit(true); + m_scene_cache.valid = !m_scene_cache.broken; + } + } + // draw overlays _render_overlays(); @@ -8522,7 +8570,99 @@ void GLCanvas3D::_render_wireframe_overlay() } //BBS: GUI refactor: add canvas size as parameters -void GLCanvas3D::_render_gcode(int canvas_width, int canvas_height) +bool GLCanvas3D::_scene_cache_enabled() const +{ + return wxGetApp().app_config != nullptr && wxGetApp().app_config->get_bool("preview_cache_static_scene"); +} + +// Makes the cache's framebuffer match the window: same size, same sample count, a depth buffer of +// the same format, so that colour and depth can be blitted both ways. Returns false when it cannot. +bool GLCanvas3D::_scene_cache_prepare(int width, int height) +{ + SceneCache& cache = m_scene_cache; + glsafe(::glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &cache.default_fbo)); + int samples = 0; + glsafe(::glGetIntegerv(GL_SAMPLES, &samples)); + int depth_bits = 0; + int stencil_bits = 0; + const GLenum depth_query = (cache.default_fbo == 0) ? GL_DEPTH : GL_DEPTH_ATTACHMENT; + const GLenum stencil_query = (cache.default_fbo == 0) ? GL_STENCIL : GL_DEPTH_ATTACHMENT; + glsafe(::glGetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER, depth_query, GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE, &depth_bits)); + glsafe(::glGetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER, stencil_query, GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE, &stencil_bits)); + const bool stencil = stencil_bits > 0; + if (cache.fbo != 0 && cache.width == width && cache.height == height && cache.samples == samples && cache.stencil == stencil) + return true; + + _scene_cache_release(); + const GLenum depth_format = stencil ? GL_DEPTH24_STENCIL8 : (depth_bits <= 16) ? GL_DEPTH_COMPONENT16 : (depth_bits >= 32) ? GL_DEPTH_COMPONENT32 : GL_DEPTH_COMPONENT24; + glsafe(::glGenRenderbuffers(1, &cache.color)); + glsafe(::glBindRenderbuffer(GL_RENDERBUFFER, cache.color)); + if (samples > 0) + glsafe(::glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, GL_RGBA8, width, height)); + else + glsafe(::glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, width, height)); + glsafe(::glGenRenderbuffers(1, &cache.depth)); + glsafe(::glBindRenderbuffer(GL_RENDERBUFFER, cache.depth)); + if (samples > 0) + glsafe(::glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, depth_format, width, height)); + else + glsafe(::glRenderbufferStorage(GL_RENDERBUFFER, depth_format, width, height)); + glsafe(::glBindRenderbuffer(GL_RENDERBUFFER, 0)); + glsafe(::glGenFramebuffers(1, &cache.fbo)); + glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, cache.fbo)); + glsafe(::glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, cache.color)); + glsafe(::glFramebufferRenderbuffer(GL_FRAMEBUFFER, stencil ? GL_DEPTH_STENCIL_ATTACHMENT : GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, cache.depth)); + const GLenum status = ::glCheckFramebufferStatus(GL_FRAMEBUFFER); + glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, static_cast(cache.default_fbo))); + if (status != GL_FRAMEBUFFER_COMPLETE) { + BOOST_LOG_TRIVIAL(warning) << "Preview scene cache disabled: framebuffer incomplete, status " << status; + _scene_cache_release(); + cache.broken = true; + return false; + } + cache.width = width; + cache.height = height; + cache.samples = samples; + cache.stencil = stencil; + return true; +} + +// Copies colour and depth from the window into the cache (store) or back (restore). A driver that +// refuses the blit marks the cache broken, and the preview falls back to drawing every frame. +void GLCanvas3D::_scene_cache_blit(bool store) +{ + SceneCache& cache = m_scene_cache; + if (cache.fbo == 0) + return; + const GLuint window = static_cast(cache.default_fbo); + glsafe(::glBindFramebuffer(GL_READ_FRAMEBUFFER, store ? window : cache.fbo)); + glsafe(::glBindFramebuffer(GL_DRAW_FRAMEBUFFER, store ? cache.fbo : window)); + while (::glGetError() != GL_NO_ERROR) {} + ::glBlitFramebuffer(0, 0, cache.width, cache.height, 0, 0, cache.width, cache.height, GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, GL_NEAREST); + const GLenum error = ::glGetError(); + glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, window)); + if (error != GL_NO_ERROR) { + BOOST_LOG_TRIVIAL(warning) << "Preview scene cache disabled: framebuffer blit failed with GL error " << error; + cache.broken = true; + _scene_cache_release(); + } +} + +void GLCanvas3D::_scene_cache_release() +{ + SceneCache& cache = m_scene_cache; + if (cache.fbo != 0) + glsafe(::glDeleteFramebuffers(1, &cache.fbo)); + if (cache.color != 0) + glsafe(::glDeleteRenderbuffers(1, &cache.color)); + if (cache.depth != 0) + glsafe(::glDeleteRenderbuffers(1, &cache.depth)); + cache.fbo = cache.color = cache.depth = 0; + cache.width = cache.height = 0; + cache.valid = false; +} + +void GLCanvas3D::_render_gcode(int canvas_width, int canvas_height, bool draw_scene) { IMSlider *layers_slider = m_gcode_viewer.get_layers_slider(); IMSlider *moves_slider = m_gcode_viewer.get_moves_slider(); @@ -8541,7 +8681,7 @@ void GLCanvas3D::_render_gcode(int canvas_width, int canvas_height) schedule_extra_frame(static_cast(std::chrono::duration_cast(m_preview_interaction_until - now).count()) + 1); } - m_gcode_viewer.render(canvas_width, canvas_height, SLIDER_RIGHT_MARGIN * GCODE_VIEWER_SLIDER_SCALE); + m_gcode_viewer.render(canvas_width, canvas_height, SLIDER_RIGHT_MARGIN * GCODE_VIEWER_SLIDER_SCALE, draw_scene); if (layers_slider->is_need_post_tick_event()) { auto evt = new wxCommandEvent(EVT_CUSTOMEVT_TICKSCHANGED, m_canvas->GetId()); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index b427fb634e..04890aac07 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -736,6 +736,33 @@ public: std::array m_fxaa_texture_size{ 0, 0 }; unsigned int m_ssao_color_texture_id{ 0 }; unsigned int m_ssao_depth_texture_id{ 0 }; + + // ORCA: the last fully drawn preview scene, shown again while nothing it draws has changed + struct SceneCache + { + unsigned int fbo{ 0 }; + unsigned int color{ 0 }; + unsigned int depth{ 0 }; + int default_fbo{ 0 }; + int width{ 0 }; + int height{ 0 }; + int samples{ 0 }; + bool stencil{ false }; + // the kept image matches the key below + bool valid{ false }; + // a blit failed on this driver: never try again this session + bool broken{ false }; + Matrix4d view{ Matrix4d::Zero() }; + Matrix4d projection{ Matrix4d::Zero() }; + int hover_plate{ -1 }; + int current_plate{ -1 }; + bool world_axes{ false }; + uint64_t canvas_version{ 0 }; + std::array viewer_version{ 0, 0 }; + }; + SceneCache m_scene_cache; + // bumped on changes to what the scene pass draws that neither the camera nor the viewer track + uint64_t m_scene_version{ 0 }; std::array m_ssao_texture_size{ { 0, 0 } }; GLModel m_plate_shadow_mask; std::string m_plate_shadow_mask_key; @@ -1280,7 +1307,12 @@ private: void _render_objects(GLVolumeCollection::ERenderType type, bool with_outline = true); void _render_wireframe_overlay(); //BBS: GUI refactor: add canvas size as parameters - void _render_gcode(int canvas_width, int canvas_height); + void _render_gcode(int canvas_width, int canvas_height, bool draw_scene = true); + // ORCA: scene cache, see SceneCache + bool _scene_cache_enabled() const; + bool _scene_cache_prepare(int width, int height); + void _scene_cache_blit(bool store); + void _scene_cache_release(); //BBS: render a plane for assemble void _render_plane() const; void _render_selection(); diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 3ea675dd30..6005e3c44f 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -2040,6 +2040,14 @@ void PreferencesDialog::create_items() ); g_sizer->Add(item_rest_detail_mode); + auto item_cache_static_scene = create_item_checkbox( + _L("Keep the drawn preview while nothing moves"), + _L("Keep the last drawn preview and show it again for frames in which neither the camera nor the toolpaths changed, " + "so that hovering, tooltips and notifications no longer redraw a large print. The scene is redrawn as soon as anything in it changes."), + "preview_cache_static_scene" + ); + g_sizer->Add(item_cache_static_scene); + auto item_dim_previous_layers = create_item_checkbox( _L("Dim lower layers"), _L("When scrubbing the layer slider in the sliced preview, render the layers below the current one darkened so that only the layer being viewed is shown at full brightness."),