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

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