diff --git a/resources/shaders/gouraud.fs b/resources/shaders/gouraud.fs
index 09003f407..dce033d7b 100644
--- a/resources/shaders/gouraud.fs
+++ b/resources/shaders/gouraud.fs
@@ -1,6 +1,21 @@
 #version 110
 
 const vec3 ZERO = vec3(0.0, 0.0, 0.0);
+const vec3 GREEN = vec3(0.0, 0.7, 0.0);
+const vec3 YELLOW = vec3(0.5, 0.7, 0.0);
+const vec3 RED = vec3(0.7, 0.0, 0.0);
+const float EPSILON = 0.0001;
+
+struct SlopeDetection
+{
+    bool active;
+	// x = yellow, y = red
+	vec2 z_range;
+    mat3 volume_world_normal_matrix;
+};
+
+uniform vec4 uniform_color;
+uniform SlopeDetection slope;
 
 varying vec3 clipping_planes_dots;
 
@@ -10,14 +25,20 @@ varying vec2 intensity;
 varying vec3 delta_box_min;
 varying vec3 delta_box_max;
 
-uniform vec4 uniform_color;
+varying float world_normal_z;
 
+vec3 slope_color()
+{
+    float gradient_range = slope.z_range.x - slope.z_range.y;
+    return (world_normal_z > slope.z_range.x - EPSILON) ? GREEN : ((gradient_range == 0.0) ? RED : mix(RED, YELLOW, clamp((world_normal_z - slope.z_range.y) / gradient_range, 0.0, 1.0)));
+}
 
 void main()
 {
     if (any(lessThan(clipping_planes_dots, ZERO)))
         discard;
+	vec3 color = slope.active ? slope_color() : uniform_color.rgb;
     // if the fragment is outside the print volume -> use darker color
-    vec3 color = (any(lessThan(delta_box_min, ZERO)) || any(greaterThan(delta_box_max, ZERO))) ? mix(uniform_color.rgb, ZERO, 0.3333) : uniform_color.rgb;
+	color = (any(lessThan(delta_box_min, ZERO)) || any(greaterThan(delta_box_max, ZERO))) ? mix(color, ZERO, 0.3333) : color;
     gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + color * intensity.x, uniform_color.a);
 }
diff --git a/resources/shaders/gouraud.vs b/resources/shaders/gouraud.vs
index cc54c1c44..72d6c18b3 100644
--- a/resources/shaders/gouraud.vs
+++ b/resources/shaders/gouraud.vs
@@ -20,13 +20,22 @@ const vec3 ZERO = vec3(0.0, 0.0, 0.0);
 
 struct PrintBoxDetection
 {
+    bool active;
     vec3 min;
     vec3 max;
-    bool volume_detection;
     mat4 volume_world_matrix;
 };
 
+struct SlopeDetection
+{
+    bool active;
+	// x = yellow, y = red
+	vec2 z_range;
+    mat3 volume_world_normal_matrix;
+};
+
 uniform PrintBoxDetection print_box;
+uniform SlopeDetection slope;
 
 // Clipping plane, x = min z, y = max z. Used by the FFF and SLA previews to clip with a top / bottom plane.
 uniform vec2 z_range;
@@ -41,6 +50,8 @@ varying vec3 delta_box_max;
 
 varying vec3 clipping_planes_dots;
 
+varying float world_normal_z;
+
 void main()
 {
     // First transform the normal into camera space and normalize the result.
@@ -61,7 +72,7 @@ void main()
     intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
 
     // compute deltas for out of print volume detection (world coordinates)
-    if (print_box.volume_detection)
+    if (print_box.active)
     {
         vec3 v = (print_box.volume_world_matrix * gl_Vertex).xyz;
         delta_box_min = v - print_box.min;
@@ -73,6 +84,9 @@ void main()
         delta_box_max = ZERO;
     }
 
+    // z component of normal vector in world coordinate used for slope shading
+	world_normal_z = slope.active ? (normalize(slope.volume_world_normal_matrix * gl_Normal)).z : 0.0;
+
     gl_Position = ftransform();
     // Point in homogenous coordinates.
     vec4 world_pos = print_box.volume_world_matrix * gl_Vertex;
diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp
index f758709b8..9270d3887 100644
--- a/src/PrusaSlicer.cpp
+++ b/src/PrusaSlicer.cpp
@@ -531,8 +531,10 @@ int CLI::run(int argc, char **argv)
                 gui->mainframe->load_config(m_extra_config);
         });
         int result = wxEntry(argc, argv);
+#if !ENABLE_NON_STATIC_CANVAS_MANAGER
         //FIXME this is a workaround for the PrusaSlicer 2.1 release.
 		_3DScene::destroy();
+#endif // !ENABLE_NON_STATIC_CANVAS_MANAGER
         return result;
 #else /* SLIC3R_GUI */
         // No GUI support. Just print out a help.
diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp
index 6806f4f54..674410388 100644
--- a/src/libslic3r/Config.cpp
+++ b/src/libslic3r/Config.cpp
@@ -464,7 +464,7 @@ bool ConfigBase::set_deserialize_nothrow(const t_config_option_key &opt_key_src,
 void ConfigBase::set_deserialize(const t_config_option_key &opt_key_src, const std::string &value_src, bool append)
 {
 	if (! this->set_deserialize_nothrow(opt_key_src, value_src, append))
-		throw BadOptionTypeException("ConfigBase::set_deserialize() failed");
+		throw BadOptionTypeException((boost::format("ConfigBase::set_deserialize() failed for parameter \"%1%\", value \"%2%\"") % opt_key_src % value_src).str());
 }
 
 void ConfigBase::set_deserialize(std::initializer_list<SetDeserializeItem> items)
diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp
index f02caf226..87e020898 100644
--- a/src/libslic3r/Config.hpp
+++ b/src/libslic3r/Config.hpp
@@ -56,10 +56,9 @@ public:
 class BadOptionTypeException : public std::runtime_error
 {
 public:
-	BadOptionTypeException() :
-		std::runtime_error("Bad option type exception") {}
-	BadOptionTypeException(const char* message) : 
-		std::runtime_error(message) {}
+	BadOptionTypeException() : std::runtime_error("Bad option type exception") {}
+	BadOptionTypeException(const std::string &message) : std::runtime_error(message) {}
+    BadOptionTypeException(const char* message) : std::runtime_error(message) {}
 };
 
 // Type of a configuration value.
diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp
index 9bdda3a4c..db398f06c 100644
--- a/src/libslic3r/GCode/ToolOrdering.cpp
+++ b/src/libslic3r/GCode/ToolOrdering.cpp
@@ -94,7 +94,7 @@ ToolOrdering::ToolOrdering(const PrintObject &object, unsigned int first_extrude
     // Reorder the extruders to minimize tool switches.
     this->reorder_extruders(first_extruder);
 
-    this->fill_wipe_tower_partitions(object.print()->config(), object.layers().front()->print_z - object.layers().front()->height);
+    this->fill_wipe_tower_partitions(object.print()->config(), object.layers().front()->print_z - object.layers().front()->height, object.config().layer_height);
 
     this->collect_extruder_statistics(prime_multi_material);
 }
@@ -107,6 +107,7 @@ ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool
 
     // Initialize the print layers for all objects and all layers.
     coordf_t object_bottom_z = 0.;
+    coordf_t max_layer_height = 0.;
     {
         std::vector<coordf_t> zs;
         for (auto object : print.objects()) {
@@ -122,6 +123,8 @@ ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool
                     object_bottom_z = layer->print_z - layer->height;
                     break;
                 }
+
+            max_layer_height = std::max(max_layer_height, object->config().layer_height.value);
         }
         this->initialize_layers(zs);
     }
@@ -144,7 +147,7 @@ ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool
     // Reorder the extruders to minimize tool switches.
     this->reorder_extruders(first_extruder);
 
-    this->fill_wipe_tower_partitions(print.config(), object_bottom_z);
+    this->fill_wipe_tower_partitions(print.config(), object_bottom_z, max_layer_height);
 
     this->collect_extruder_statistics(prime_multi_material);
 }
@@ -318,7 +321,7 @@ void ToolOrdering::reorder_extruders(unsigned int last_extruder_id)
         }    
 }
 
-void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_t object_bottom_z)
+void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_t object_bottom_z, coordf_t max_object_layer_height)
 {
     if (m_layer_tools.empty())
         return;
@@ -351,6 +354,10 @@ void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_
             mlh = 0.75 * config.nozzle_diameter.values[i];
         max_layer_height = std::min(max_layer_height, mlh);
     }
+    // The Prusa3D Fast (0.35mm layer height) print profile sets a higher layer height than what is normally allowed
+    // by the nozzle. This is a hack and it works by increasing extrusion width.
+    max_layer_height = std::max(max_layer_height, max_object_layer_height);
+
     for (size_t i = 0; i + 1 < m_layer_tools.size(); ++ i) {
         const LayerTools &lt      = m_layer_tools[i];
         const LayerTools &lt_next = m_layer_tools[i + 1];
@@ -393,21 +400,47 @@ void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_
     // and maybe other problems. We will therefore go through layer_tools and detect and fix this.
     // So, if there is a non-object layer starting with different extruder than the last one ended with (or containing more than one extruder),
     // we'll mark it with has_wipe tower.
-    for (unsigned int i=0; i+1<m_layer_tools.size(); ++i) {
-        LayerTools& lt = m_layer_tools[i];
-        LayerTools& lt_next = m_layer_tools[i+1];
-        if (lt.extruders.empty() || lt_next.extruders.empty())
-            break;
-        if (!lt_next.has_wipe_tower && (lt_next.extruders.front() != lt.extruders.back() || lt_next.extruders.size() > 1))
-            lt_next.has_wipe_tower = true;
-        // We should also check that the next wipe tower layer is no further than max_layer_height:
-        unsigned int j = i+1;
-        double last_wipe_tower_print_z = lt_next.print_z;
-        while (++j < m_layer_tools.size()-1 && !m_layer_tools[j].has_wipe_tower)
-            if (m_layer_tools[j+1].print_z - last_wipe_tower_print_z > max_layer_height) {
-                m_layer_tools[j].has_wipe_tower = true;
-                last_wipe_tower_print_z = m_layer_tools[j].print_z;
+    assert(! m_layer_tools.empty() && m_layer_tools.front().has_wipe_tower);
+    if (! m_layer_tools.empty() && m_layer_tools.front().has_wipe_tower) {
+        for (size_t i = 0; i + 1 < m_layer_tools.size();) {
+            const LayerTools &lt = m_layer_tools[i];
+            assert(lt.has_wipe_tower);
+            assert(! lt.extruders.empty());
+            // Find the next layer with wipe tower or mark a layer as such.
+            size_t j = i + 1;
+            for (; j < m_layer_tools.size() && ! m_layer_tools[j].has_wipe_tower; ++ j) {
+                LayerTools &lt_next = m_layer_tools[j];
+                if (lt_next.extruders.empty()) {
+                    //FIXME Vojtech: Lukasi, proc?
+                    j = m_layer_tools.size();
+                    break;
+                }
+                if (lt_next.extruders.front() != lt.extruders.back() || lt_next.extruders.size() > 1) {
+                    // Support only layer, soluble layers? Otherwise the layer should have been already marked as having wipe tower.
+                    assert(lt_next.has_support && ! lt_next.has_object);
+                    lt_next.has_wipe_tower = true;
+                    break;
+                }
             }
+            if (j == m_layer_tools.size())
+                // No wipe tower above layer i, therefore no need to add any wipe tower layer above i.
+                break;
+            // We should also check that the next wipe tower layer is no further than max_layer_height.
+            // This algorith may in theory create very thin wipe layer j if layer closely below j is marked as wipe tower.
+            // This may happen if printing with non-soluble break away supports.
+            // On the other side it should not hurt as there will be no wipe, just perimeter and sparse infill printed
+            // at that particular wipe tower layer without extruder change.
+            double last_wipe_tower_print_z = lt.print_z;
+            assert(m_layer_tools[j].has_wipe_tower);
+            for (size_t k = i + 1; k < j; ++k) {
+                assert(! m_layer_tools[k].has_wipe_tower);
+                if (m_layer_tools[k + 1].print_z - last_wipe_tower_print_z > max_layer_height + EPSILON) {
+                    m_layer_tools[k].has_wipe_tower = true;
+                    last_wipe_tower_print_z = m_layer_tools[k].print_z;
+                }
+            }
+            i = j;
+        }
     }
 
     // Calculate the wipe_tower_layer_height values.
diff --git a/src/libslic3r/GCode/ToolOrdering.hpp b/src/libslic3r/GCode/ToolOrdering.hpp
index a82db2d04..5fe27516d 100644
--- a/src/libslic3r/GCode/ToolOrdering.hpp
+++ b/src/libslic3r/GCode/ToolOrdering.hpp
@@ -166,7 +166,7 @@ private:
     void				initialize_layers(std::vector<coordf_t> &zs);
     void 				collect_extruders(const PrintObject &object, const std::vector<std::pair<double, unsigned int>> &per_layer_extruder_switches);
     void				reorder_extruders(unsigned int last_extruder_id);
-    void 				fill_wipe_tower_partitions(const PrintConfig &config, coordf_t object_bottom_z);
+    void 				fill_wipe_tower_partitions(const PrintConfig &config, coordf_t object_bottom_z, coordf_t max_layer_height);
     void 				collect_extruder_statistics(bool prime_multi_material);
 
     std::vector<LayerTools>    m_layer_tools;
diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp
index 3895a87a1..c66cca8ea 100644
--- a/src/libslic3r/Technologies.hpp
+++ b/src/libslic3r/Technologies.hpp
@@ -44,5 +44,21 @@
 // Enable fix for dragging mouse event handling for gizmobar
 #define ENABLE_GIZMO_TOOLBAR_DRAGGING_FIX (1 && ENABLE_2_2_0_FINAL)
 
+//============
+// 2.3.0 techs
+//============
+#define ENABLE_2_3_0 1
+
+// Enable rendering of objects colored by facets' slope
+#define ENABLE_SLOPE_RENDERING (1 && ENABLE_2_3_0)
+
+//===================
+// 2.3.0.alpha1 techs
+//===================
+#define ENABLE_2_3_0_ALPHA1 1
+
+// Moves GLCanvas3DManager from being a static member of _3DScene to be a normal member of GUI_App
+#define ENABLE_NON_STATIC_CANVAS_MANAGER (1 && ENABLE_2_3_0_ALPHA1)
+
 
 #endif // _prusaslicer_technologies_h_
diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp
index bf6f23cac..c13c1b937 100644
--- a/src/slic3r/GUI/3DScene.cpp
+++ b/src/slic3r/GUI/3DScene.cpp
@@ -394,6 +394,7 @@ void GLVolume::render() const
         glFrontFace(GL_CCW);
 }
 
+#if !ENABLE_SLOPE_RENDERING
 void GLVolume::render(int color_id, int detection_id, int worldmatrix_id) const
 {
     if (color_id >= 0)
@@ -409,6 +410,7 @@ void GLVolume::render(int color_id, int detection_id, int worldmatrix_id) const
 
     render();
 }
+#endif // !ENABLE_SLOPE_RENDERING
 
 bool GLVolume::is_sla_support() const { return this->composite_id.volume_id == -int(slaposSupportTree); }
 bool GLVolume::is_sla_pad() const { return this->composite_id.volume_id == -int(slaposPad); }
@@ -650,28 +652,64 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab
     GLint color_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "uniform_color") : -1;
     GLint z_range_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "z_range") : -1;
     GLint clipping_plane_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "clipping_plane") : -1;
+
     GLint print_box_min_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "print_box.min") : -1;
     GLint print_box_max_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "print_box.max") : -1;
-    GLint print_box_detection_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "print_box.volume_detection") : -1;
+    GLint print_box_active_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "print_box.active") : -1;
     GLint print_box_worldmatrix_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "print_box.volume_world_matrix") : -1;
+
+#if ENABLE_SLOPE_RENDERING
+    GLint slope_active_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "slope.active") : -1;
+    GLint slope_normal_matrix_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "slope.volume_world_normal_matrix") : -1;
+    GLint slope_z_range_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "slope.z_range") : -1;
+#endif // ENABLE_SLOPE_RENDERING
     glcheck();
 
     if (print_box_min_id != -1)
-        glsafe(::glUniform3fv(print_box_min_id, 1, (const GLfloat*)print_box_min));
+        glsafe(::glUniform3fv(print_box_min_id, 1, (const GLfloat*)m_print_box_min));
 
     if (print_box_max_id != -1)
-        glsafe(::glUniform3fv(print_box_max_id, 1, (const GLfloat*)print_box_max));
+        glsafe(::glUniform3fv(print_box_max_id, 1, (const GLfloat*)m_print_box_max));
 
     if (z_range_id != -1)
-        glsafe(::glUniform2fv(z_range_id, 1, (const GLfloat*)z_range));
+        glsafe(::glUniform2fv(z_range_id, 1, (const GLfloat*)m_z_range));
 
     if (clipping_plane_id != -1)
-        glsafe(::glUniform4fv(clipping_plane_id, 1, (const GLfloat*)clipping_plane));
+        glsafe(::glUniform4fv(clipping_plane_id, 1, (const GLfloat*)m_clipping_plane));
+
+#if ENABLE_SLOPE_RENDERING
+    if (slope_z_range_id != -1)
+        glsafe(::glUniform2fv(slope_z_range_id, 1, (const GLfloat*)m_slope.z_range.data()));
+#endif // ENABLE_SLOPE_RENDERING
 
     GLVolumeWithIdAndZList to_render = volumes_to_render(this->volumes, type, view_matrix, filter_func);
     for (GLVolumeWithIdAndZ& volume : to_render) {
         volume.first->set_render_color();
+#if ENABLE_SLOPE_RENDERING
+        if (color_id >= 0)
+            glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)volume.first->render_color));
+        else
+            glsafe(::glColor4fv(volume.first->render_color));
+
+        if (print_box_active_id != -1)
+            glsafe(::glUniform1i(print_box_active_id, volume.first->shader_outside_printer_detection_enabled ? 1 : 0));
+
+        if (print_box_worldmatrix_id != -1)
+            glsafe(::glUniformMatrix4fv(print_box_worldmatrix_id, 1, GL_FALSE, (const GLfloat*)volume.first->world_matrix().cast<float>().data()));
+
+        if (slope_active_id != -1)
+            glsafe(::glUniform1i(slope_active_id, m_slope.active && !volume.first->is_modifier && !volume.first->is_wipe_tower ? 1 : 0));
+
+        if (slope_normal_matrix_id != -1)
+        {
+            Matrix3f normal_matrix = volume.first->world_matrix().matrix().block(0, 0, 3, 3).inverse().transpose().cast<float>();
+            glsafe(::glUniformMatrix3fv(slope_normal_matrix_id, 1, GL_FALSE, (const GLfloat*)normal_matrix.data()));
+        }
+
+        volume.first->render();
+#else
         volume.first->render(color_id, print_box_detection_id, print_box_worldmatrix_id);
+#endif // ENABLE_SLOPE_RENDERING
     }
 
     glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
@@ -1816,7 +1854,9 @@ void _3DScene::point3_to_verts(const Vec3crd& point, double width, double height
     thick_point_to_verts(point, width, height, volume);
 }
 
+#if !ENABLE_NON_STATIC_CANVAS_MANAGER
 GUI::GLCanvas3DManager _3DScene::s_canvas_mgr;
+#endif // !ENABLE_NON_STATIC_CANVAS_MANAGER
 
 GLModel::GLModel()
     : m_filename("")
@@ -1885,7 +1925,16 @@ void GLModel::render() const
     GLint color_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "uniform_color") : -1;
     glcheck();
 
+#if ENABLE_SLOPE_RENDERING
+    if (color_id >= 0)
+        glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)m_volume.render_color));
+    else
+        glsafe(::glColor4fv(m_volume.render_color));
+
+    m_volume.render();
+#else
     m_volume.render(color_id, -1, -1);
+#endif // ENABLE_SLOPE_RENDERING
 
     glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
     glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
@@ -2099,6 +2148,7 @@ bool GLBed::on_init_from_file(const std::string& filename)
     return true;
 }
 
+#if !ENABLE_NON_STATIC_CANVAS_MANAGER
 std::string _3DScene::get_gl_info(bool format_as_html, bool extensions)
 {
     return Slic3r::GUI::GLCanvas3DManager::get_gl_info().to_string(format_as_html, extensions);
@@ -2133,5 +2183,6 @@ GUI::GLCanvas3D* _3DScene::get_canvas(wxGLCanvas* canvas)
 {
     return s_canvas_mgr.get_canvas(canvas);
 }
+#endif // !ENABLE_NON_STATIC_CANVAS_MANAGER
 
 } // namespace Slic3r
diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp
index 1fc8308f7..70d6fb016 100644
--- a/src/slic3r/GUI/3DScene.hpp
+++ b/src/slic3r/GUI/3DScene.hpp
@@ -7,7 +7,9 @@
 #include "libslic3r/TriangleMesh.hpp"
 #include "libslic3r/Utils.hpp"
 #include "libslic3r/Model.hpp"
+#if !ENABLE_NON_STATIC_CANVAS_MANAGER
 #include "slic3r/GUI/GLCanvas3DManager.hpp"
+#endif // !ENABLE_NON_STATIC_CANVAS_MANAGER
 
 #include <functional>
 
@@ -443,7 +445,9 @@ public:
     void                set_range(double low, double high);
 
     void                render() const;
+#if !ENABLE_SLOPE_RENDERING
     void                render(int color_id, int detection_id, int worldmatrix_id) const;
+#endif // !ENABLE_SLOPE_RENDERING
 
     void                finalize_geometry(bool opengl_initialized) { this->indexed_vertex_array.finalize_geometry(opengl_initialized); }
     void                release_geometry() { this->indexed_vertex_array.release_geometry(); }
@@ -479,20 +483,36 @@ public:
 
 private:
     // min and max vertex of the print box volume
-    float print_box_min[3];
-    float print_box_max[3];
+    float m_print_box_min[3];
+    float m_print_box_max[3];
 
     // z range for clipping in shaders
-    float z_range[2];
+    float m_z_range[2];
 
     // plane coeffs for clipping in shaders
-    float clipping_plane[4];
+    float m_clipping_plane[4];
+
+#if ENABLE_SLOPE_RENDERING
+    struct Slope
+    {
+        // toggle for slope rendering 
+        bool active{ false };
+        // [0] = yellow, [1] = red
+        std::array<float, 2> z_range;
+    };
+
+    Slope m_slope;
+#endif // ENABLE_SLOPE_RENDERING
 
 public:
     GLVolumePtrs volumes;
 
-    GLVolumeCollection() {};
-    ~GLVolumeCollection() { clear(); };
+#if ENABLE_SLOPE_RENDERING
+    GLVolumeCollection() { set_default_slope_z_range(); }
+#else
+    GLVolumeCollection() = default;
+#endif // ENABLE_SLOPE_RENDERING
+    ~GLVolumeCollection() { clear(); }
 
     std::vector<int> load_object(
         const ModelObject 		*model_object,
@@ -543,12 +563,21 @@ public:
     void set_range(double low, double high) { for (GLVolume *vol : this->volumes) vol->set_range(low, high); }
 
     void set_print_box(float min_x, float min_y, float min_z, float max_x, float max_y, float max_z) {
-        print_box_min[0] = min_x; print_box_min[1] = min_y; print_box_min[2] = min_z;
-        print_box_max[0] = max_x; print_box_max[1] = max_y; print_box_max[2] = max_z;
+        m_print_box_min[0] = min_x; m_print_box_min[1] = min_y; m_print_box_min[2] = min_z;
+        m_print_box_max[0] = max_x; m_print_box_max[1] = max_y; m_print_box_max[2] = max_z;
     }
 
-    void set_z_range(float min_z, float max_z) { z_range[0] = min_z; z_range[1] = max_z; }
-    void set_clipping_plane(const double* coeffs) { clipping_plane[0] = coeffs[0]; clipping_plane[1] = coeffs[1]; clipping_plane[2] = coeffs[2]; clipping_plane[3] = coeffs[3]; }
+    void set_z_range(float min_z, float max_z) { m_z_range[0] = min_z; m_z_range[1] = max_z; }
+    void set_clipping_plane(const double* coeffs) { m_clipping_plane[0] = coeffs[0]; m_clipping_plane[1] = coeffs[1]; m_clipping_plane[2] = coeffs[2]; m_clipping_plane[3] = coeffs[3]; }
+
+#if ENABLE_SLOPE_RENDERING
+    bool is_slope_active() const { return m_slope.active; }
+    void set_slope_active(bool active) { m_slope.active = active; }
+
+    const std::array<float, 2>& get_slope_z_range() const { return m_slope.z_range; }
+    void set_slope_z_range(const std::array<float, 2>& range) { m_slope.z_range = range; }
+    void set_default_slope_z_range() { m_slope.z_range = { -::cos(Geometry::deg2rad(90.0f - 45.0f)), -::cos(Geometry::deg2rad(90.0f - 70.0f)) }; }
+#endif // ENABLE_SLOPE_RENDERING
 
     // returns true if all the volumes are completely contained in the print volume
     // returns the containment state in the given out_state, if non-null
@@ -639,10 +668,17 @@ protected:
     bool on_init_from_file(const std::string& filename) override;
 };
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+struct _3DScene
+#else
 class _3DScene
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 {
+#if !ENABLE_NON_STATIC_CANVAS_MANAGER
     static GUI::GLCanvas3DManager s_canvas_mgr;
+#endif // !ENABLE_NON_STATIC_CANVAS_MANAGER
 
+#if !ENABLE_NON_STATIC_CANVAS_MANAGER
 public:
     static std::string get_gl_info(bool format_as_html, bool extensions);
 
@@ -654,6 +690,7 @@ public:
     static void destroy();
 
     static GUI::GLCanvas3D* get_canvas(wxGLCanvas* canvas);
+#endif // !ENABLE_NON_STATIC_CANVAS_MANAGER
 
     static void thick_lines_to_verts(const Lines& lines, const std::vector<double>& widths, const std::vector<double>& heights, bool closed, double top_z, GLVolume& volume);
     static void thick_lines_to_verts(const Lines3& lines, const std::vector<double>& widths, const std::vector<double>& heights, bool closed, GLVolume& volume);
diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp
index 3cd1319b5..f67fea7a5 100644
--- a/src/slic3r/GUI/GLCanvas3D.cpp
+++ b/src/slic3r/GUI/GLCanvas3D.cpp
@@ -20,6 +20,9 @@
 #include "slic3r/GUI/PresetBundle.hpp"
 #include "slic3r/GUI/Tab.hpp"
 #include "slic3r/GUI/GUI_Preview.hpp"
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+#include "slic3r/GUI/GLCanvas3DManager.hpp"
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 #include "slic3r/GUI/3DBed.hpp"
 #include "slic3r/GUI/Camera.hpp"
 
@@ -359,7 +362,11 @@ Rect GLCanvas3D::LayersEditing::get_bar_rect_viewport(const GLCanvas3D& canvas)
     float half_w = 0.5f * (float)cnv_size.get_width();
     float half_h = 0.5f * (float)cnv_size.get_height();
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom();
+#else
     float inv_zoom = (float)canvas.get_camera().get_inv_zoom();
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 
     return Rect((half_w - thickness_bar_width(canvas)) * inv_zoom, half_h * inv_zoom, half_w * inv_zoom, -half_h * inv_zoom);
 }
@@ -774,6 +781,13 @@ bool GLCanvas3D::WarningTexture::generate(const std::string& msg_utf8, const GLC
 #else
     // select default font
     const float scale = canvas.get_canvas_size().get_scale_factor();
+#if ENABLE_RETINA_GL
+    // For non-visible or non-created window getBackingScaleFactor function return 0.0 value.
+    // And using of the zero scale causes a crash, when we trying to draw text to the (0,0) rectangle
+    // https://github.com/prusa3d/PrusaSlicer/issues/3916
+    if (scale <= 0.0f)
+        return false;
+#endif
     wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Scale(scale);
 #endif
 
@@ -851,7 +865,11 @@ void GLCanvas3D::WarningTexture::render(const GLCanvas3D& canvas) const
     if ((m_id > 0) && (m_original_width > 0) && (m_original_height > 0) && (m_width > 0) && (m_height > 0))
     {
         const Size& cnv_size = canvas.get_canvas_size();
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+        float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom();
+#else
         float inv_zoom = (float)canvas.get_camera().get_inv_zoom();
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
         float left = (-0.5f * (float)m_original_width) * inv_zoom;
         float top = (-0.5f * (float)cnv_size.get_height() + (float)m_original_height + 2.0f) * inv_zoom;
         float right = left + (float)m_original_width * inv_zoom;
@@ -1218,7 +1236,11 @@ void GLCanvas3D::LegendTexture::render(const GLCanvas3D& canvas) const
     if ((m_id > 0) && (m_original_width > 0) && (m_original_height > 0) && (m_width > 0) && (m_height > 0))
     {
         const Size& cnv_size = canvas.get_canvas_size();
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+        float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom();
+#else
         float inv_zoom = (float)canvas.get_camera().get_inv_zoom();
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
         float left = (-0.5f * (float)cnv_size.get_width()) * inv_zoom;
         float top = (0.5f * (float)cnv_size.get_height()) * inv_zoom;
         float right = left + (float)m_original_width * inv_zoom;
@@ -1244,7 +1266,11 @@ void GLCanvas3D::Labels::render(const std::vector<const ModelInstance*>& sorted_
     if (!m_enabled || !is_shown())
         return;
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    const Camera& camera = wxGetApp().plater()->get_camera();
+#else
     const Camera& camera = m_canvas.get_camera();
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
     const Model* model = m_canvas.get_model();
     if (model == nullptr)
         return;
@@ -1426,6 +1452,62 @@ void GLCanvas3D::Tooltip::render(const Vec2d& mouse_position, GLCanvas3D& canvas
 }
 #endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI
 
+#if ENABLE_SLOPE_RENDERING
+void GLCanvas3D::Slope::render() const
+{
+    if (is_shown())
+    {
+        const std::array<float, 2>& z_range = m_volumes.get_slope_z_range();
+        std::array<float, 2> angle_range = { Geometry::rad2deg(::acos(z_range[0])) - 90.0f, Geometry::rad2deg(::acos(z_range[1])) - 90.0f };
+        bool modified = false;
+
+        ImGuiWrapper& imgui = *wxGetApp().imgui();
+        const Size& cnv_size = m_canvas.get_canvas_size();
+        imgui.set_next_window_pos((float)cnv_size.get_width(), (float)cnv_size.get_height(), ImGuiCond_Always, 1.0f, 1.0f);
+        imgui.begin(_(L("Slope visualization")), nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
+
+        imgui.text(_(L("Facets' normal angle range (degrees)")) + ":");
+
+        ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.75f, 0.75f, 0.0f, 0.5f));
+        ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(1.0f, 1.0f, 0.0f, 0.5f));
+        ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(0.85f, 0.85f, 0.0f, 0.5f));
+        ImGui::PushStyleColor(ImGuiCol_SliderGrab, ImVec4(0.25f, 0.25f, 0.0f, 1.0f));
+        if (ImGui::SliderFloat("##yellow", &angle_range[0], 0.0f, 90.0f, "%.1f"))
+        {
+            modified = true;
+            if (angle_range[1] < angle_range[0])
+                angle_range[1] = angle_range[0];
+        }
+        ImGui::PopStyleColor(4);
+        ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.75f, 0.0f, 0.0f, 0.5f));
+        ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(1.0f, 0.0f, 0.0f, 0.5f));
+        ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(0.85f, 0.0f, 0.0f, 0.5f));
+        ImGui::PushStyleColor(ImGuiCol_SliderGrab, ImVec4(0.25f, 0.0f, 0.0f, 1.0f));
+        if (ImGui::SliderFloat("##red", &angle_range[1], 0.0f, 90.0f, "%.1f"))
+        {
+            modified = true;
+            if (angle_range[0] > angle_range[1])
+                angle_range[0] = angle_range[1];
+        }
+        ImGui::PopStyleColor(4);
+
+        ImGui::Separator();
+
+        if (imgui.button(_(L("Default"))))
+            m_volumes.set_default_slope_z_range();
+
+        // to let the dialog immediately showup without waiting for a mouse move
+        if (ImGui::GetWindowContentRegionWidth() + 2.0f * ImGui::GetStyle().WindowPadding.x != ImGui::CalcWindowExpectedSize(ImGui::GetCurrentWindow()).x)
+            m_canvas.request_extra_frame();
+
+        imgui.end();
+
+        if (modified)
+            m_volumes.set_slope_z_range({ -::cos(Geometry::deg2rad(90.0f - angle_range[0])), -::cos(Geometry::deg2rad(90.0f - angle_range[1])) });
+    }
+    }
+#endif // ENABLE_SLOPE_RENDERING
+
 wxDEFINE_EVENT(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent);
 wxDEFINE_EVENT(EVT_GLCANVAS_OBJECT_SELECT, SimpleEvent);
 wxDEFINE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, RBtnEvent);
@@ -1458,16 +1540,22 @@ wxDEFINE_EVENT(EVT_GLCANVAS_RELOAD_FROM_DISK, SimpleEvent);
 
 const double GLCanvas3D::DefaultCameraZoomToBoxMarginFactor = 1.25;
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas)
+#else
 GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar)
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
     : m_canvas(canvas)
     , m_context(nullptr)
 #if ENABLE_RETINA_GL
     , m_retina_helper(nullptr)
 #endif
     , m_in_render(false)
+#if !ENABLE_NON_STATIC_CANVAS_MANAGER
     , m_bed(bed)
     , m_camera(camera)
     , m_view_toolbar(view_toolbar)
+#endif // !ENABLE_NON_STATIC_CANVAS_MANAGER
     , m_main_toolbar(GLToolbar::Normal, "Top")
     , m_undoredo_toolbar(GLToolbar::Normal, "Top")
     , m_gizmos(*this)
@@ -1495,14 +1583,19 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar
 #endif // ENABLE_RENDER_PICKING_PASS
     , m_render_sla_auxiliaries(true)
     , m_labels(*this)
+#if ENABLE_SLOPE_RENDERING
+    , m_slope(*this, m_volumes)
+#endif // ENABLE_SLOPE_RENDERING
 {
     if (m_canvas != nullptr) {
         m_timer.SetOwner(m_canvas);
 #if ENABLE_RETINA_GL
         m_retina_helper.reset(new RetinaHelper(canvas));
+#if !ENABLE_NON_STATIC_CANVAS_MANAGER
         // set default view_toolbar icons size equal to GLGizmosManager::Default_Icons_Size
         m_view_toolbar.set_icons_size(GLGizmosManager::Default_Icons_Size);
-#endif
+#endif // !ENABLE_NON_STATIC_CANVAS_MANAGER
+#endif // ENABLE_RETINA_GL
     }
 
     m_selection.set_volumes(&m_volumes.volumes);
@@ -1615,6 +1708,18 @@ void GLCanvas3D::reset_volumes()
     if (!m_initialized)
         return;
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    if (m_volumes.empty())
+        return;
+
+    _set_current();
+
+    m_selection.clear();
+    m_volumes.clear();
+    m_dirty = true;
+
+    _set_warning_texture(WarningTexture::ObjectOutside, false);
+#else
     _set_current();
 
     if (!m_volumes.empty())
@@ -1625,6 +1730,7 @@ void GLCanvas3D::reset_volumes()
     }
 
     _set_warning_texture(WarningTexture::ObjectOutside, false);
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 }
 
 int GLCanvas3D::check_volumes_outside_state() const
@@ -1706,7 +1812,11 @@ void GLCanvas3D::set_model(Model* model)
 void GLCanvas3D::bed_shape_changed()
 {
     refresh_camera_scene_box();
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    wxGetApp().plater()->get_camera().requires_zoom_to_bed = true;
+#else
     m_camera.requires_zoom_to_bed = true;
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
     m_dirty = true;
 }
 
@@ -1715,6 +1825,15 @@ void GLCanvas3D::set_color_by(const std::string& value)
     m_color_by = value;
 }
 
+void GLCanvas3D::refresh_camera_scene_box()
+{
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    wxGetApp().plater()->get_camera().set_scene_box(scene_bounding_box());
+#else
+    m_camera.set_scene_box(scene_bounding_box());
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
+}
+
 BoundingBoxf3 GLCanvas3D::volumes_bounding_box() const
 {
     BoundingBoxf3 bb;
@@ -1729,7 +1848,11 @@ BoundingBoxf3 GLCanvas3D::volumes_bounding_box() const
 BoundingBoxf3 GLCanvas3D::scene_bounding_box() const
 {
     BoundingBoxf3 bb = volumes_bounding_box();
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    bb.merge(wxGetApp().plater()->get_bed().get_bounding_box(true));
+#else
     bb.merge(m_bed.get_bounding_box(true));
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 
     if (m_config != nullptr)
     {
@@ -1782,6 +1905,11 @@ bool GLCanvas3D::is_reload_delayed() const
 
 void GLCanvas3D::enable_layers_editing(bool enable)
 {
+#if ENABLE_SLOPE_RENDERING
+    if (enable && m_slope.is_shown())
+        m_slope.show(false);
+#endif // ENABLE_SLOPE_RENDERING
+
     m_layers_editing.set_enabled(enable);
     const Selection::IndicesList& idxs = m_selection.get_volume_idxs();
     for (unsigned int idx : idxs)
@@ -1842,7 +1970,11 @@ void GLCanvas3D::allow_multisample(bool allow)
 
 void GLCanvas3D::zoom_to_bed()
 {
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    _zoom_to_box(wxGetApp().plater()->get_bed().get_bounding_box(false));
+#else
     _zoom_to_box(m_bed.get_bounding_box(false));
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 }
 
 void GLCanvas3D::zoom_to_volumes()
@@ -1860,7 +1992,11 @@ void GLCanvas3D::zoom_to_selection()
 
 void GLCanvas3D::select_view(const std::string& direction)
 {
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    wxGetApp().plater()->get_camera().select_view(direction);
+#else
     m_camera.select_view(direction);
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
     if (m_canvas != nullptr)
         m_canvas->Refresh();
 }
@@ -1888,14 +2024,26 @@ void GLCanvas3D::render()
         return;
 
     // ensures this canvas is current and initialized
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    if (!_is_shown_on_screen() || !_set_current() || !wxGetApp().init_opengl())
+        return;
+
+    if (!is_initialized() && !init())
+        return;
+#else
     if (! _is_shown_on_screen() || !_set_current() || !_3DScene::init(m_canvas))
         return;
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 
 #if ENABLE_RENDER_STATISTICS
     auto start_time = std::chrono::high_resolution_clock::now();
 #endif // ENABLE_RENDER_STATISTICS
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    if (wxGetApp().plater()->get_bed().get_shape().empty())
+#else
     if (m_bed.get_shape().empty())
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
     {
         // this happens at startup when no data is still saved under <>\AppData\Roaming\Slic3rPE
         post_event(SimpleEvent(EVT_GLCANVAS_UPDATE_BED_SHAPE));
@@ -1907,6 +2055,20 @@ void GLCanvas3D::render()
     // to preview, this was called before canvas had its final size. It reported zero width
     // and the viewport was set incorrectly, leading to tripping glAsserts further down
     // the road (in apply_projection). That's why the minimum size is forced to 10.
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    Camera& camera = wxGetApp().plater()->get_camera();
+    camera.apply_viewport(0, 0, std::max(10u, (unsigned int)cnv_size.get_width()), std::max(10u, (unsigned int)cnv_size.get_height()));
+
+    if (camera.requires_zoom_to_bed)
+    {
+        zoom_to_bed();
+        _resize((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height());
+        camera.requires_zoom_to_bed = false;
+    }
+
+    camera.apply_view_matrix();
+    camera.apply_projection(_max_bounding_box(true, true));
+#else
     m_camera.apply_viewport(0, 0, std::max(10u, (unsigned int)cnv_size.get_width()), std::max(10u, (unsigned int)cnv_size.get_height()));
 
     if (m_camera.requires_zoom_to_bed)
@@ -1918,6 +2080,7 @@ void GLCanvas3D::render()
 
     m_camera.apply_view_matrix();
     m_camera.apply_projection(_max_bounding_box(true, true));
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 
     GLfloat position_cam[4] = { 1.0f, 0.0f, 1.0f, 0.0f };
     glsafe(::glLightfv(GL_LIGHT1, GL_POSITION, position_cam));
@@ -1947,7 +2110,11 @@ void GLCanvas3D::render()
     _render_objects();
     _render_sla_slices();
     _render_selection();
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    _render_bed(!camera.is_looking_downward(), true);
+#else
     _render_bed(!m_camera.is_looking_downward(), true);
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 
 #if ENABLE_RENDER_SELECTION_CENTER
     _render_selection_center();
@@ -2016,8 +2183,12 @@ void GLCanvas3D::render()
 	        tooltip = m_undoredo_toolbar.get_tooltip();
 
 	    if (tooltip.empty())
-	        tooltip = m_view_toolbar.get_tooltip();
-	}
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+            tooltip = wxGetApp().plater()->get_view_toolbar().get_tooltip();
+#else
+            tooltip = m_view_toolbar.get_tooltip();
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
+    }
 
     set_tooltip(tooltip);
 
@@ -2061,8 +2232,13 @@ void GLCanvas3D::render_thumbnail(ThumbnailData& thumbnail_data, unsigned int w,
 {
     switch (GLCanvas3DManager::get_framebuffers_type())
     {
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    case GLCanvas3DManager::EFramebufferType::Arb: { _render_thumbnail_framebuffer(thumbnail_data, w, h, printable_only, parts_only, show_bed, transparent_background); break; }
+    case GLCanvas3DManager::EFramebufferType::Ext: { _render_thumbnail_framebuffer_ext(thumbnail_data, w, h, printable_only, parts_only, show_bed, transparent_background); break; }
+#else
     case GLCanvas3DManager::FB_Arb: { _render_thumbnail_framebuffer(thumbnail_data, w, h, printable_only, parts_only, show_bed, transparent_background); break; }
     case GLCanvas3DManager::FB_Ext: { _render_thumbnail_framebuffer_ext(thumbnail_data, w, h, printable_only, parts_only, show_bed, transparent_background); break; }
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
     default: { _render_thumbnail_legacy(thumbnail_data, w, h, printable_only, parts_only, show_bed, transparent_background); break; }
     }
 }
@@ -2167,8 +2343,15 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
     if ((m_canvas == nullptr) || (m_config == nullptr) || (m_model == nullptr))
         return;
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    if (!m_initialized)
+        return;
+    
+    _set_current();
+#else
     if (m_initialized)
         _set_current();
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 
     struct ModelVolumeState {
         ModelVolumeState(const GLVolume* volume) :
@@ -2764,8 +2947,13 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt)
 
     m_dirty |= m_main_toolbar.update_items_state();
     m_dirty |= m_undoredo_toolbar.update_items_state();
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    m_dirty |= wxGetApp().plater()->get_view_toolbar().update_items_state();
+    bool mouse3d_controller_applied = wxGetApp().plater()->get_mouse3d_controller().apply(wxGetApp().plater()->get_camera());
+#else
     m_dirty |= m_view_toolbar.update_items_state();
     bool mouse3d_controller_applied = wxGetApp().plater()->get_mouse3d_controller().apply(m_camera);
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
     m_dirty |= mouse3d_controller_applied;
 
     if (!m_dirty)
@@ -2906,12 +3094,27 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
         case 'a': { post_event(SimpleEvent(EVT_GLCANVAS_ARRANGE)); break; }
         case 'B':
         case 'b': { zoom_to_bed(); break; }
+#if ENABLE_SLOPE_RENDERING
+        case 'D':
+        case 'd': {
+                    if (!is_layers_editing_enabled())
+                    {
+                        m_slope.show(!m_slope.is_shown());
+                        m_dirty = true;
+                    }
+                    break;
+                  }
+#endif // ENABLE_SLOPE_RENDERING
         case 'E':
         case 'e': { m_labels.show(!m_labels.is_shown()); m_dirty = true; break; }
         case 'I':
         case 'i': { _update_camera_zoom(1.0); break; }
         case 'K':
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+        case 'k': { wxGetApp().plater()->get_camera().select_next_type(); m_dirty = true; break; }
+#else
         case 'k': { m_camera.select_next_type(); m_dirty = true; break; }
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
         case 'O':
         case 'o': { _update_camera_zoom(-1.0); break; }
 #if ENABLE_RENDER_PICKING_PASS
@@ -3038,7 +3241,11 @@ void GLCanvas3D::on_key(wxKeyEvent& evt)
             Vec3d displacement;
             if (camera_space)
             {
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+                Eigen::Matrix<double, 3, 3, Eigen::DontAlign> inv_view_3x3 = wxGetApp().plater()->get_camera().get_view_matrix().inverse().matrix().block(0, 0, 3, 3);
+#else
                 Eigen::Matrix<double, 3, 3, Eigen::DontAlign> inv_view_3x3 = m_camera.get_view_matrix().inverse().matrix().block(0, 0, 3, 3);
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
                 displacement = multiplier * (inv_view_3x3 * direction);
                 displacement(2) = 0.0;
             }
@@ -3364,7 +3571,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
         return;
     }
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    if (wxGetApp().plater()->get_view_toolbar().on_mouse(evt, *this))
+#else
     if (m_view_toolbar.on_mouse(evt, *this))
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
     {
         if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp())
             mouse_up_cleanup();
@@ -3527,7 +3738,12 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
             // we do not want to translate objects if the user just clicked on an object while pressing shift to remove it from the selection and then drag
             if (m_selection.contains_volume(get_first_hover_volume_idx()))
             {
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+                const Camera& camera = wxGetApp().plater()->get_camera();
+                if (std::abs(camera.get_dir_forward()(2)) < EPSILON)
+#else
                 if (std::abs(m_camera.get_dir_forward()(2)) < EPSILON)
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
                 {
                     // side view -> move selected volumes orthogonally to camera view direction
                     Linef3 ray = mouse_ray(pos);
@@ -3540,8 +3756,13 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
                     // vector from the starting position to the found intersection
                     Vec3d inters_vec = inters - m_mouse.drag.start_position_3D;
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+                    Vec3d camera_right = camera.get_dir_right();
+                    Vec3d camera_up = camera.get_dir_up();
+#else
                     Vec3d camera_right = m_camera.get_dir_right();
                     Vec3d camera_up = m_camera.get_dir_up();
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 
                     // finds projection of the vector along the camera axes
                     double projection_x = inters_vec.dot(camera_right);
@@ -3591,15 +3812,25 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
                 const Vec3d rot = (Vec3d(pos.x(), pos.y(), 0.) - m_mouse.drag.start_position_3D) * (PI * TRACKBALLSIZE / 180.);
                 if (wxGetApp().app_config->get("use_free_camera") == "1")
                     // Virtual track ball (similar to the 3DConnexion mouse).
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+                    wxGetApp().plater()->get_camera().rotate_local_around_target(Vec3d(rot.y(), rot.x(), 0.));
+#else
                     m_camera.rotate_local_around_target(Vec3d(rot.y(), rot.x(), 0.));
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
                 else
                 {
-                	// Forces camera right vector to be parallel to XY plane in case it has been misaligned using the 3D mouse free rotation.
-                	// It is cheaper to call this function right away instead of testing wxGetApp().plater()->get_mouse3d_controller().connected(),
-                	// which checks an atomics (flushes CPU caches).
-                	// See GH issue #3816.
+                    // Forces camera right vector to be parallel to XY plane in case it has been misaligned using the 3D mouse free rotation.
+                    // It is cheaper to call this function right away instead of testing wxGetApp().plater()->get_mouse3d_controller().connected(),
+                    // which checks an atomics (flushes CPU caches).
+                    // See GH issue #3816.
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+                    Camera& camera = wxGetApp().plater()->get_camera();
+                    camera.recover_from_free_camera();
+                    camera.rotate_on_sphere(rot.x(), rot.y(), wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA);
+#else
                     m_camera.recover_from_free_camera();
                     m_camera.rotate_on_sphere(rot.x(), rot.y(), wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA);
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
                 }
 
                 m_dirty = true;
@@ -3615,17 +3846,26 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
                 float z = 0.0f;
                 const Vec3d& cur_pos = _mouse_to_3d(pos, &z);
                 Vec3d orig = _mouse_to_3d(m_mouse.drag.start_position_2D, &z);
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+                Camera& camera = wxGetApp().plater()->get_camera();
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER                
                 if (wxGetApp().app_config->get("use_free_camera") != "1")
-                	// Forces camera right vector to be parallel to XY plane in case it has been misaligned using the 3D mouse free rotation.
-                	// It is cheaper to call this function right away instead of testing wxGetApp().plater()->get_mouse3d_controller().connected(),
-                	// which checks an atomics (flushes CPU caches).
-                	// See GH issue #3816.
+                    // Forces camera right vector to be parallel to XY plane in case it has been misaligned using the 3D mouse free rotation.
+                    // It is cheaper to call this function right away instead of testing wxGetApp().plater()->get_mouse3d_controller().connected(),
+                    // which checks an atomics (flushes CPU caches).
+                    // See GH issue #3816.
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+                    camera.recover_from_free_camera();
+
+                camera.set_target(camera.get_target() + orig - cur_pos);
+#else
                     m_camera.recover_from_free_camera();
 
                 m_camera.set_target(m_camera.get_target() + orig - cur_pos);
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER                
                 m_dirty = true;
             }
-            
+
             m_mouse.drag.start_position_2D = pos;
         }
     }
@@ -4071,7 +4311,12 @@ void GLCanvas3D::update_ui_from_settings()
     if (new_scaling != orig_scaling) {
         BOOST_LOG_TRIVIAL(debug) << "GLCanvas3D: Scaling factor: " << new_scaling;
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+        Camera& camera = wxGetApp().plater()->get_camera();
+        camera.set_zoom(camera.get_zoom() * new_scaling / orig_scaling);
+#else
         m_camera.set_zoom(m_camera.get_zoom() * new_scaling / orig_scaling);
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
         _refresh_if_shown_on_screen();
     }
 #endif
@@ -4104,16 +4349,13 @@ Linef3 GLCanvas3D::mouse_ray(const Point& mouse_pos)
     return Linef3(_mouse_to_3d(mouse_pos, &z0), _mouse_to_3d(mouse_pos, &z1));
 }
 
-
-void GLCanvas3D::refresh_camera_scene_box()
-{
-    m_camera.set_scene_box(scene_bounding_box());
-}
-
-
 double GLCanvas3D::get_size_proportional_to_max_bed_size(double factor) const
 {
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    return factor * wxGetApp().plater()->get_bed().get_bounding_box(false).max_size();
+#else
     return factor * m_bed.get_bounding_box(false).max_size();
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 }
 
 void GLCanvas3D::set_cursor(ECursorType type)
@@ -4176,7 +4418,11 @@ bool GLCanvas3D::_render_undo_redo_stack(const bool is_undo, float pos_x) const
 
     ImGuiWrapper* imgui = wxGetApp().imgui();
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    const float x = pos_x * (float)wxGetApp().plater()->get_camera().get_zoom() + 0.5f * (float)get_canvas_size().get_width();
+#else
     const float x = pos_x * (float)get_camera().get_zoom() + 0.5f * (float)get_canvas_size().get_width();
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
     imgui->set_next_window_pos(x, m_undoredo_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f);
     std::string title = is_undo ? L("Undo History") : L("Redo History");
     imgui->begin(_(title), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
@@ -4325,7 +4571,11 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, bool
         // extends the near and far z of the frustrum to avoid the bed being clipped
 
         // box in eye space
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+        BoundingBoxf3 t_bed_box = wxGetApp().plater()->get_bed().get_bounding_box(true).transformed(camera.get_view_matrix());
+#else
         BoundingBoxf3 t_bed_box = m_bed.get_bounding_box(true).transformed(camera.get_view_matrix());
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
         near_z = -t_bed_box.max(2);
         far_z = -t_bed_box.min(2);
     }
@@ -4602,7 +4852,11 @@ void GLCanvas3D::_render_thumbnail_legacy(ThumbnailData& thumbnail_data, unsigne
 #endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
 
     // restore the default framebuffer size to avoid flickering on the 3D scene
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    wxGetApp().plater()->get_camera().apply_viewport(0, 0, cnv_size.get_width(), cnv_size.get_height());
+#else
     m_camera.apply_viewport(0, 0, cnv_size.get_width(), cnv_size.get_height());
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 }
 
 bool GLCanvas3D::_init_toolbars()
@@ -4956,19 +5210,31 @@ BoundingBoxf3 GLCanvas3D::_max_bounding_box(bool include_gizmos, bool include_be
         bb.merge(BoundingBoxf3(sel_bb_center - extend_by, sel_bb_center + extend_by));
     }
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    bb.merge(wxGetApp().plater()->get_bed().get_bounding_box(include_bed_model));
+#else
     bb.merge(m_bed.get_bounding_box(include_bed_model));
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
     return bb;
 }
 
 void GLCanvas3D::_zoom_to_box(const BoundingBoxf3& box, double margin_factor)
 {
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    wxGetApp().plater()->get_camera().zoom_to_box(box, margin_factor);
+#else
     m_camera.zoom_to_box(box, margin_factor);
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
     m_dirty = true;
 }
 
 void GLCanvas3D::_update_camera_zoom(double zoom)
 {
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    wxGetApp().plater()->get_camera().update_zoom(zoom);
+#else
     m_camera.update_zoom(zoom);
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
     m_dirty = true;
 }
 
@@ -5159,7 +5425,11 @@ void GLCanvas3D::_render_bed(float theta, bool show_axes) const
 #if ENABLE_RETINA_GL
     scale_factor = m_retina_helper->get_scale_factor();
 #endif // ENABLE_RETINA_GL
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    wxGetApp().plater()->get_bed().render(const_cast<GLCanvas3D&>(*this), theta, scale_factor, show_axes);
+#else
     m_bed.render(const_cast<GLCanvas3D&>(*this), theta, scale_factor, show_axes);
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 }
 
 void GLCanvas3D::_render_objects() const
@@ -5178,7 +5448,11 @@ void GLCanvas3D::_render_objects() const
 
         if (m_config != nullptr)
         {
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+            const BoundingBoxf3& bed_bb = wxGetApp().plater()->get_bed().get_bounding_box(false);
+#else
             const BoundingBoxf3& bed_bb = m_bed.get_bounding_box(false);
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
             m_volumes.set_print_box((float)bed_bb.min(0), (float)bed_bb.min(1), 0.0f, (float)bed_bb.max(0), (float)bed_bb.max(1), (float)m_config->opt_float("max_print_height"));
             m_volumes.check_outside_state(m_config, nullptr);
         }
@@ -5194,19 +5468,37 @@ void GLCanvas3D::_render_objects() const
     m_shader.start_using();
     if (m_picking_enabled && !m_gizmos.is_dragging() && m_layers_editing.is_enabled() && (m_layers_editing.last_object_id != -1) && (m_layers_editing.object_max_z() > 0.0f)) {
         int object_id = m_layers_editing.last_object_id;
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+        m_volumes.render(GLVolumeCollection::Opaque, false, wxGetApp().plater()->get_camera().get_view_matrix(), [object_id](const GLVolume& volume) {
+            // Which volume to paint without the layer height profile shader?
+            return volume.is_active && (volume.is_modifier || volume.composite_id.object_id != object_id);
+            });
+#else
         m_volumes.render(GLVolumeCollection::Opaque, false, m_camera.get_view_matrix(), [object_id](const GLVolume& volume) {
             // Which volume to paint without the layer height profile shader?
             return volume.is_active && (volume.is_modifier || volume.composite_id.object_id != object_id);
         });
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
         // Let LayersEditing handle rendering of the active object using the layer height profile shader.
         m_layers_editing.render_volumes(*this, this->m_volumes);
     } else {
         // do not cull backfaces to show broken geometry, if any
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+        m_volumes.render(GLVolumeCollection::Opaque, m_picking_enabled, wxGetApp().plater()->get_camera().get_view_matrix(), [this](const GLVolume& volume) {
+            return (m_render_sla_auxiliaries || volume.composite_id.volume_id >= 0);
+            });
+#else
         m_volumes.render(GLVolumeCollection::Opaque, m_picking_enabled, m_camera.get_view_matrix(), [this](const GLVolume& volume) {
             return (m_render_sla_auxiliaries || volume.composite_id.volume_id >= 0);
         });
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
     }
+
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    m_volumes.render(GLVolumeCollection::Transparent, false, wxGetApp().plater()->get_camera().get_view_matrix());
+#else
     m_volumes.render(GLVolumeCollection::Transparent, false, m_camera.get_view_matrix());
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
     m_shader.stop_using();
 
     m_camera_clipping_plane = ClippingPlane::ClipsNothing();
@@ -5236,9 +5528,16 @@ void GLCanvas3D::_render_overlays() const
     glsafe(::glPushMatrix());
     glsafe(::glLoadIdentity());
     // ensure that the textures are renderered inside the frustrum
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    const Camera& camera = wxGetApp().plater()->get_camera();
+    glsafe(::glTranslated(0.0, 0.0, -(camera.get_near_z() + 0.005)));
+    // ensure that the overlay fits the frustrum near z plane
+    double gui_scale = camera.get_gui_scale();
+#else
     glsafe(::glTranslated(0.0, 0.0, -(m_camera.get_near_z() + 0.005)));
     // ensure that the overlay fits the frustrum near z plane
     double gui_scale = m_camera.get_gui_scale();
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
     glsafe(::glScaled(gui_scale, gui_scale, 1.0));
 
     _render_gizmos_overlay();
@@ -5275,6 +5574,10 @@ void GLCanvas3D::_render_overlays() const
     }
     m_labels.render(sorted_instances);
 
+#if ENABLE_SLOPE_RENDERING
+    m_slope.render();
+#endif // ENABLE_SLOPE_RENDERING
+
     glsafe(::glPopMatrix());
 }
 
@@ -5301,7 +5604,11 @@ void GLCanvas3D::_render_volumes_for_picking() const
     glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
     glsafe(::glEnableClientState(GL_NORMAL_ARRAY));
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    const Transform3d& view_matrix = wxGetApp().plater()->get_camera().get_view_matrix();
+#else
     const Transform3d& view_matrix = m_camera.get_view_matrix();
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
     for (size_t type = 0; type < 2; ++ type) {
 	    GLVolumeWithIdAndZList to_render = volumes_to_render(m_volumes.volumes, (type == 0) ? GLVolumeCollection::Opaque : GLVolumeCollection::Transparent, view_matrix);
 	    for (const GLVolumeWithIdAndZ& volume : to_render)
@@ -5350,7 +5657,11 @@ void GLCanvas3D::_render_main_toolbar() const
         return;
 
     Size cnv_size = get_canvas_size();
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom();
+#else
     float inv_zoom = (float)m_camera.get_inv_zoom();
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 
     float top = 0.5f * (float)cnv_size.get_height() * inv_zoom;
     float left = -0.5f * (m_main_toolbar.get_width() + m_undoredo_toolbar.get_width()) * inv_zoom;
@@ -5365,7 +5676,11 @@ void GLCanvas3D::_render_undoredo_toolbar() const
         return;
 
     Size cnv_size = get_canvas_size();
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom();
+#else
     float inv_zoom = (float)m_camera.get_inv_zoom();
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 
     float top = 0.5f * (float)cnv_size.get_height() * inv_zoom;
     float left = (m_main_toolbar.get_width() - 0.5f * (m_main_toolbar.get_width() + m_undoredo_toolbar.get_width())) * inv_zoom;
@@ -5375,25 +5690,50 @@ void GLCanvas3D::_render_undoredo_toolbar() const
 
 void GLCanvas3D::_render_view_toolbar() const
 {
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    GLToolbar& view_toolbar = wxGetApp().plater()->get_view_toolbar();
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
+
 #if ENABLE_RETINA_GL
 //     m_view_toolbar.set_scale(m_retina_helper->get_scale_factor());
     const float scale = m_retina_helper->get_scale_factor() * wxGetApp().toolbar_icon_scale();
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    view_toolbar.set_scale(scale); //! #ys_FIXME_experiment
+#else
     m_view_toolbar.set_scale(scale); //! #ys_FIXME_experiment
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 #else
 //     m_view_toolbar.set_scale(m_canvas->GetContentScaleFactor());
 //     m_view_toolbar.set_scale(wxGetApp().em_unit()*0.1f);
     const float size = int(GLGizmosManager::Default_Icons_Size * wxGetApp().toolbar_icon_scale());
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    view_toolbar.set_icons_size(size); //! #ys_FIXME_experiment
+#else
     m_view_toolbar.set_icons_size(size); //! #ys_FIXME_experiment
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 #endif // ENABLE_RETINA_GL
 
     Size cnv_size = get_canvas_size();
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom();
+#else
     float inv_zoom = (float)m_camera.get_inv_zoom();
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 
     // places the toolbar on the bottom-left corner of the 3d scene
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    float top = (-0.5f * (float)cnv_size.get_height() + view_toolbar.get_height()) * inv_zoom;
+#else
     float top = (-0.5f * (float)cnv_size.get_height() + m_view_toolbar.get_height()) * inv_zoom;
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
     float left = -0.5f * (float)cnv_size.get_width() * inv_zoom;
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    view_toolbar.set_position(top, left);
+    view_toolbar.render(*this);
+#else
     m_view_toolbar.set_position(top, left);
     m_view_toolbar.render(*this);
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 }
 
 #if ENABLE_SHOW_CAMERA_TARGET
@@ -5672,10 +6012,16 @@ Vec3d GLCanvas3D::_mouse_to_3d(const Point& mouse_pos, float* z)
     if (m_canvas == nullptr)
         return Vec3d(DBL_MAX, DBL_MAX, DBL_MAX);
 
-
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    const Camera& camera = wxGetApp().plater()->get_camera();
+    const std::array<int, 4>& viewport = camera.get_viewport();
+    const Transform3d& modelview_matrix = camera.get_view_matrix();
+    const Transform3d& projection_matrix = camera.get_projection_matrix();
+#else
     const std::array<int, 4>& viewport = m_camera.get_viewport();
     const Transform3d& modelview_matrix = m_camera.get_view_matrix();
     const Transform3d& projection_matrix = m_camera.get_projection_matrix();
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 
     GLint y = viewport[3] - (GLint)mouse_pos(1);
     GLfloat mouse_z;
diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp
index 15249b1f2..228095a06 100644
--- a/src/slic3r/GUI/GLCanvas3D.hpp
+++ b/src/slic3r/GUI/GLCanvas3D.hpp
@@ -28,6 +28,9 @@ class wxMouseEvent;
 class wxTimerEvent;
 class wxPaintEvent;
 class wxGLCanvas;
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+class wxGLContext;
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 
 // Support for Retina OpenGL on Mac OS
 #define ENABLE_RETINA_GL __APPLE__
@@ -404,6 +407,24 @@ private:
     };
 #endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI
 
+#if ENABLE_SLOPE_RENDERING
+    class Slope
+    {
+        bool m_enabled{ false };
+        GLCanvas3D& m_canvas;
+        GLVolumeCollection& m_volumes;
+
+    public:
+        Slope(GLCanvas3D& canvas, GLVolumeCollection& volumes) : m_canvas(canvas), m_volumes(volumes) {}
+
+        void enable(bool enable) { m_enabled = enable; }
+        bool is_enabled() const { return m_enabled; }
+        void show(bool show) { m_volumes.set_slope_active(m_enabled ? show : false); }
+        bool is_shown() const { return m_volumes.is_slope_active(); }
+        void render() const;
+    };
+#endif // ENABLE_SLOPE_RENDERING
+
 public:
     enum ECursorType : unsigned char
     {
@@ -421,9 +442,11 @@ private:
     LegendTexture m_legend_texture;
     WarningTexture m_warning_texture;
     wxTimer m_timer;
+#if !ENABLE_NON_STATIC_CANVAS_MANAGER
     Bed3D& m_bed;
     Camera& m_camera;
     GLToolbar& m_view_toolbar;
+#endif // !ENABLE_NON_STATIC_CANVAS_MANAGER
     LayersEditing m_layers_editing;
     Shader m_shader;
     Mouse m_mouse;
@@ -485,11 +508,22 @@ private:
 #if ENABLE_CANVAS_TOOLTIP_USING_IMGUI
     mutable Tooltip m_tooltip;
 #endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI
+#if ENABLE_SLOPE_RENDERING
+    Slope m_slope;
+#endif // ENABLE_SLOPE_RENDERING
 
 public:
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    explicit GLCanvas3D(wxGLCanvas* canvas);
+#else
     GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar);
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
     ~GLCanvas3D();
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    bool is_initialized() const { return m_initialized; }
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
+
     void set_context(wxGLContext* context) { m_context = context; }
 
     wxGLCanvas* get_wxglcanvas() { return m_canvas; }
@@ -536,9 +570,14 @@ public:
 
     void set_color_by(const std::string& value);
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    void refresh_camera_scene_box();
+#else
+    void refresh_camera_scene_box() { m_camera.set_scene_box(scene_bounding_box()); }
     const Camera& get_camera() const { return m_camera; }
-    const Shader& get_shader() const { return m_shader; }
     Camera& get_camera() { return m_camera; }
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
+    const Shader& get_shader() const { return m_shader; }
 
     BoundingBoxf3 volumes_bounding_box() const;
     BoundingBoxf3 scene_bounding_box() const;
@@ -562,6 +601,9 @@ public:
     void enable_undoredo_toolbar(bool enable);
     void enable_dynamic_background(bool enable);
     void enable_labels(bool enable) { m_labels.enable(enable); }
+#if ENABLE_SLOPE_RENDERING
+    void enable_slope(bool enable) { m_slope.enable(enable); }
+#endif // ENABLE_SLOPE_RENDERING
     void allow_multisample(bool allow);
 
     void zoom_to_bed();
@@ -630,7 +672,9 @@ public:
 
     void update_ui_from_settings();
 
+#if !ENABLE_NON_STATIC_CANVAS_MANAGER
     float get_view_toolbar_height() const { return m_view_toolbar.get_height(); }
+#endif // !ENABLE_NON_STATIC_CANVAS_MANAGER
 
     int get_move_volume_id() const { return m_mouse.drag.move_volume_idx; }
     int get_first_hover_volume_idx() const { return m_hover_volume_idxs.empty() ? -1 : m_hover_volume_idxs.front(); }
@@ -662,7 +706,6 @@ public:
     Linef3 mouse_ray(const Point& mouse_pos);
 
     void set_mouse_as_dragging() { m_mouse.dragging = true; }
-    void refresh_camera_scene_box();
     bool is_mouse_dragging() const { return m_mouse.dragging; }
 
     double get_size_proportional_to_max_bed_size(double factor) const;
@@ -684,6 +727,11 @@ public:
     bool are_labels_shown() const { return m_labels.is_shown(); }
     void show_labels(bool show) { m_labels.show(show); }
 
+#if ENABLE_SLOPE_RENDERING
+    bool is_slope_shown() const { return m_slope.is_shown(); }
+    void show_slope(bool show) { m_slope.show(show); }
+#endif // ENABLE_SLOPE_RENDERING
+
 private:
     bool _is_shown_on_screen() const;
 
diff --git a/src/slic3r/GUI/GLCanvas3DManager.cpp b/src/slic3r/GUI/GLCanvas3DManager.cpp
index 02585c5f7..d4522bb07 100644
--- a/src/slic3r/GUI/GLCanvas3DManager.cpp
+++ b/src/slic3r/GUI/GLCanvas3DManager.cpp
@@ -1,3 +1,4 @@
+#include "libslic3r/libslic3r.h"
 #include "GLCanvas3DManager.hpp"
 #include "../../slic3r/GUI/GUI.hpp"
 #include "../../slic3r/GUI/AppConfig.hpp"
@@ -7,7 +8,9 @@
 
 #include <boost/algorithm/string/split.hpp>
 #include <boost/algorithm/string/classification.hpp>
-
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+#include <boost/log/trivial.hpp>
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 #include <wx/glcanvas.h>
 #include <wx/timer.h>
 #include <wx/msgdlg.h>
@@ -17,8 +20,10 @@
 #include <iostream>
 
 #if ENABLE_HACK_CLOSING_ON_OSX_10_9_5
-// Part of temporary hack to remove crash when closing on OSX 10.9.5
+#ifdef __APPLE__
+// Part of hack to remove crash when closing the application on OSX 10.9.5 when building against newer wxWidgets
 #include <wx/platinfo.h>
+#endif // __APPLE__
 #endif // ENABLE_HACK_CLOSING_ON_OSX_10_9_5
 
 #ifdef __APPLE__
@@ -28,6 +33,7 @@
 namespace Slic3r {
 namespace GUI {
 
+#if !ENABLE_NON_STATIC_CANVAS_MANAGER
 GLCanvas3DManager::GLInfo::GLInfo()
     : m_detected(false)
     , m_version("")
@@ -38,6 +44,7 @@ GLCanvas3DManager::GLInfo::GLInfo()
     , m_max_anisotropy(0.0f)
 {
 }
+#endif // !ENABLE_NON_STATIC_CANVAS_MANAGER
 
 const std::string& GLCanvas3DManager::GLInfo::get_version() const
 {
@@ -196,27 +203,57 @@ std::string GLCanvas3DManager::GLInfo::to_string(bool format_as_html, bool exten
     return out.str();
 }
 
-GLCanvas3DManager::EMultisampleState GLCanvas3DManager::s_multisample = GLCanvas3DManager::MS_Unknown;
-bool GLCanvas3DManager::s_compressed_textures_supported = false;
-GLCanvas3DManager::EFramebufferType GLCanvas3DManager::s_framebuffers_type = GLCanvas3DManager::FB_None;
 GLCanvas3DManager::GLInfo GLCanvas3DManager::s_gl_info;
+bool GLCanvas3DManager::s_compressed_textures_supported = false;
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+GLCanvas3DManager::EMultisampleState GLCanvas3DManager::s_multisample = GLCanvas3DManager::EMultisampleState::Unknown;
+GLCanvas3DManager::EFramebufferType GLCanvas3DManager::s_framebuffers_type = GLCanvas3DManager::EFramebufferType::Unknown;
+#else
+GLCanvas3DManager::EMultisampleState GLCanvas3DManager::s_multisample = GLCanvas3DManager::MS_Unknown;
+GLCanvas3DManager::EFramebufferType GLCanvas3DManager::s_framebuffers_type = GLCanvas3DManager::FB_None;
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
+
 #if ENABLE_HACK_CLOSING_ON_OSX_10_9_5
 #ifdef __APPLE__ 
+// Part of hack to remove crash when closing the application on OSX 10.9.5 when building against newer wxWidgets
 GLCanvas3DManager::OSInfo GLCanvas3DManager::s_os_info;
 #endif // __APPLE__ 
 #endif // ENABLE_HACK_CLOSING_ON_OSX_10_9_5
 
+#if !ENABLE_NON_STATIC_CANVAS_MANAGER
 GLCanvas3DManager::GLCanvas3DManager()
     : m_context(nullptr)
     , m_gl_initialized(false)
 {
 }
+#endif // !ENABLE_NON_STATIC_CANVAS_MANAGER
 
 GLCanvas3DManager::~GLCanvas3DManager()
 {
-	this->destroy();
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+#if ENABLE_HACK_CLOSING_ON_OSX_10_9_5
+#ifdef __APPLE__ 
+    // This is an ugly hack needed to solve the crash happening when closing the application on OSX 10.9.5 with newer wxWidgets
+    // The crash is triggered inside wxGLContext destructor
+    if (s_os_info.major != 10 || s_os_info.minor != 9 || s_os_info.micro != 5)
+    {
+#endif //__APPLE__
+#endif // ENABLE_HACK_CLOSING_ON_OSX_10_9_5
+
+        if (m_context != nullptr)
+            delete m_context;
+
+#if ENABLE_HACK_CLOSING_ON_OSX_10_9_5
+#ifdef __APPLE__ 
+    }
+#endif //__APPLE__
+#endif // ENABLE_HACK_CLOSING_ON_OSX_10_9_5
+#else
+    this->destroy();
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 }
 
+#if !ENABLE_NON_STATIC_CANVAS_MANAGER
 bool GLCanvas3DManager::add(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar)
 {
     if (canvas == nullptr)
@@ -239,7 +276,7 @@ bool GLCanvas3DManager::add(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLTo
 
 #if ENABLE_HACK_CLOSING_ON_OSX_10_9_5
 #ifdef __APPLE__ 
-        // Part of temporary hack to remove crash when closing on OSX 10.9.5
+        // Part of hack to remove crash when closing the application on OSX 10.9.5 when building against newer wxWidgets
         s_os_info.major = wxPlatformInfo::Get().GetOSMajorVersion();
         s_os_info.minor = wxPlatformInfo::Get().GetOSMinorVersion();
         s_os_info.micro = wxPlatformInfo::Get().GetOSMicroVersion();
@@ -277,28 +314,50 @@ void GLCanvas3DManager::remove_all()
     m_canvases.clear();
 }
 
-unsigned int GLCanvas3DManager::count() const
+size_t GLCanvas3DManager::count() const
 {
-    return (unsigned int)m_canvases.size();
+    return m_canvases.size();
 }
+#endif // !ENABLE_NON_STATIC_CANVAS_MANAGER
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+bool GLCanvas3DManager::init_gl()
+#else
 void GLCanvas3DManager::init_gl()
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 {
     if (!m_gl_initialized)
     {
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+        if (glewInit() != GLEW_OK)
+        {
+            BOOST_LOG_TRIVIAL(error) << "Unable to init glew library";
+            return false;
+        }
+#else
         glewInit();
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
         m_gl_initialized = true;
         if (GLEW_EXT_texture_compression_s3tc)
             s_compressed_textures_supported = true;
         else
             s_compressed_textures_supported = false;
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+        if (GLEW_ARB_framebuffer_object)
+            s_framebuffers_type = EFramebufferType::Arb;
+        else if (GLEW_EXT_framebuffer_object)
+            s_framebuffers_type = EFramebufferType::Ext;
+        else
+            s_framebuffers_type = EFramebufferType::Unknown;
+#else
         if (GLEW_ARB_framebuffer_object)
             s_framebuffers_type = FB_Arb;
         else if (GLEW_EXT_framebuffer_object)
             s_framebuffers_type = FB_Ext;
         else
             s_framebuffers_type = FB_None;
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 
         if (! s_gl_info.is_version_greater_or_equal_to(2, 0)) {
         	// Complain about the OpenGL version.
@@ -314,8 +373,31 @@ void GLCanvas3DManager::init_gl()
         	wxMessageBox(message, wxString("PrusaSlicer - ") + _(L("Unsupported OpenGL version")), wxOK | wxICON_ERROR);
         }
     }
+
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    return true;
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 }
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+wxGLContext* GLCanvas3DManager::init_glcontext(wxGLCanvas& canvas)
+{
+    if (m_context == nullptr)
+    {
+        m_context = new wxGLContext(&canvas);
+
+#if ENABLE_HACK_CLOSING_ON_OSX_10_9_5
+#ifdef __APPLE__ 
+        // Part of hack to remove crash when closing the application on OSX 10.9.5 when building against newer wxWidgets
+        s_os_info.major = wxPlatformInfo::Get().GetOSMajorVersion();
+        s_os_info.minor = wxPlatformInfo::Get().GetOSMinorVersion();
+        s_os_info.micro = wxPlatformInfo::Get().GetOSMicroVersion();
+#endif //__APPLE__
+#endif // ENABLE_HACK_CLOSING_ON_OSX_10_9_5
+    }
+    return m_context;
+}
+#else
 bool GLCanvas3DManager::init(wxGLCanvas* canvas)
 {
     CanvasesMap::const_iterator it = do_get_canvas(canvas);
@@ -331,7 +413,7 @@ void GLCanvas3DManager::destroy()
     {
 #if ENABLE_HACK_CLOSING_ON_OSX_10_9_5
 #ifdef __APPLE__ 
-        // this is a temporary ugly hack to solve the crash happening when closing the application on OSX 10.9.5
+        // this is an ugly hack needed to solve the crash happening when closing the application on OSX 10.9.5
         // the crash is inside wxGLContext destructor
         if (s_os_info.major == 10 && s_os_info.minor == 9 && s_os_info.micro == 5)
             return;
@@ -342,14 +424,21 @@ void GLCanvas3DManager::destroy()
         m_context = nullptr;
     }
 }
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 
+#if !ENABLE_NON_STATIC_CANVAS_MANAGER
 GLCanvas3D* GLCanvas3DManager::get_canvas(wxGLCanvas* canvas)
 {
     CanvasesMap::const_iterator it = do_get_canvas(canvas);
     return (it != m_canvases.end()) ? it->second : nullptr;
 }
+#endif // !ENABLE_NON_STATIC_CANVAS_MANAGER
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+wxGLCanvas* GLCanvas3DManager::create_wxglcanvas(wxWindow& parent)
+#else
 wxGLCanvas* GLCanvas3DManager::create_wxglcanvas(wxWindow *parent)
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 {
     int attribList[] = { 
     	WX_GL_RGBA,
@@ -367,7 +456,11 @@ wxGLCanvas* GLCanvas3DManager::create_wxglcanvas(wxWindow *parent)
     	0
     };
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    if (s_multisample == EMultisampleState::Unknown)
+#else
     if (s_multisample == MS_Unknown)
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
     {
         detect_multisample(attribList);
 //        // debug output
@@ -377,9 +470,14 @@ wxGLCanvas* GLCanvas3DManager::create_wxglcanvas(wxWindow *parent)
     if (! can_multisample())
         attribList[12] = 0;
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    return new wxGLCanvas(&parent, wxID_ANY, attribList, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS);
+#else
     return new wxGLCanvas(parent, wxID_ANY, attribList, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS);
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 }
 
+#if !ENABLE_NON_STATIC_CANVAS_MANAGER
 GLCanvas3DManager::CanvasesMap::iterator GLCanvas3DManager::do_get_canvas(wxGLCanvas* canvas)
 {
     return (canvas == nullptr) ? m_canvases.end() : m_canvases.find(canvas);
@@ -397,12 +495,17 @@ bool GLCanvas3DManager::init(GLCanvas3D& canvas)
 
     return canvas.init();
 }
+#endif // !ENABLE_NON_STATIC_CANVAS_MANAGER
 
 void GLCanvas3DManager::detect_multisample(int* attribList)
 {
     int wxVersion = wxMAJOR_VERSION * 10000 + wxMINOR_VERSION * 100 + wxRELEASE_NUMBER;
     bool enable_multisample = wxVersion >= 30003;
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    s_multisample = (enable_multisample && wxGLCanvas::IsDisplaySupported(attribList)) ? EMultisampleState::Enabled : EMultisampleState::Disabled;
+#else
     s_multisample = (enable_multisample && wxGLCanvas::IsDisplaySupported(attribList)) ? MS_Enabled : MS_Disabled;
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
     // Alternative method: it was working on previous version of wxWidgets but not with the latest, at least on Windows
     // s_multisample = enable_multisample && wxGLCanvas::IsExtensionSupported("WGL_ARB_multisample");
 }
diff --git a/src/slic3r/GUI/GLCanvas3DManager.hpp b/src/slic3r/GUI/GLCanvas3DManager.hpp
index c7301f2b4..428ccd96a 100644
--- a/src/slic3r/GUI/GLCanvas3DManager.hpp
+++ b/src/slic3r/GUI/GLCanvas3DManager.hpp
@@ -23,22 +23,43 @@ class PrintObject;
 namespace GUI {
 
 class GLCanvas3D;
+#if !ENABLE_NON_STATIC_CANVAS_MANAGER
 class Bed3D;
 class GLToolbar;
 struct Camera;
+#endif // !ENABLE_NON_STATIC_CANVAS_MANAGER
 
 class GLCanvas3DManager
 {
 public:
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    enum class EFramebufferType : unsigned char
+    {
+        Unknown,
+        Arb,
+        Ext
+    };
+#else
     enum EFramebufferType : unsigned char
     {
         FB_None,
         FB_Arb,
         FB_Ext
     };
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 
     class GLInfo
     {
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+        mutable bool m_detected{ false };
+        mutable int m_max_tex_size{ 0 };
+        mutable float m_max_anisotropy{ 0.0f };
+
+        mutable std::string m_version;
+        mutable std::string m_glsl_version;
+        mutable std::string m_vendor;
+        mutable std::string m_renderer;
+#else
         mutable bool m_detected;
 
         mutable std::string m_version;
@@ -48,9 +69,14 @@ public:
 
         mutable int m_max_tex_size;
         mutable float m_max_anisotropy;
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 
     public:
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+        GLInfo() = default;
+#else
         GLInfo();
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 
         const std::string& get_version() const;
         const std::string& get_glsl_version() const;
@@ -70,6 +96,7 @@ public:
 
 #if ENABLE_HACK_CLOSING_ON_OSX_10_9_5
 #ifdef __APPLE__ 
+    // Part of hack to remove crash when closing the application on OSX 10.9.5 when building against newer wxWidgets
     struct OSInfo
     {
         int major{ 0 };
@@ -80,6 +107,14 @@ public:
 #endif // ENABLE_HACK_CLOSING_ON_OSX_10_9_5
 
 private:
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    enum class EMultisampleState : unsigned char
+    {
+        Unknown,
+        Enabled,
+        Disabled
+    };
+#else
     enum EMultisampleState : unsigned char
     {
         MS_Unknown,
@@ -88,51 +123,85 @@ private:
     };
 
     typedef std::map<wxGLCanvas*, GLCanvas3D*> CanvasesMap;
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 
-    CanvasesMap m_canvases;
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    bool m_gl_initialized{ false };
+    wxGLContext* m_context{ nullptr };
+#else
     wxGLContext* m_context;
+    bool m_gl_initialized;
+    CanvasesMap m_canvases;
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
     static GLInfo s_gl_info;
 #if ENABLE_HACK_CLOSING_ON_OSX_10_9_5
 #ifdef __APPLE__ 
+    // Part of hack to remove crash when closing the application on OSX 10.9.5 when building against newer wxWidgets
     static OSInfo s_os_info;
 #endif //__APPLE__
 #endif // ENABLE_HACK_CLOSING_ON_OSX_10_9_5
-    bool m_gl_initialized;
-    static EMultisampleState s_multisample;
     static bool s_compressed_textures_supported;
+    static EMultisampleState s_multisample;
     static EFramebufferType s_framebuffers_type;
 
 public:
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    GLCanvas3DManager() = default;
+#else
     GLCanvas3DManager();
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
     ~GLCanvas3DManager();
 
+#if !ENABLE_NON_STATIC_CANVAS_MANAGER
     bool add(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar);
     bool remove(wxGLCanvas* canvas);
     void remove_all();
 
-    unsigned int count() const;
+    size_t count() const;
+#endif // !ENABLE_NON_STATIC_CANVAS_MANAGER
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    bool init_gl();
+#else
     void init_gl();
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    wxGLContext* init_glcontext(wxGLCanvas& canvas);
+#else
     bool init(wxGLCanvas* canvas);
     void destroy();
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 
+#if !ENABLE_NON_STATIC_CANVAS_MANAGER
     GLCanvas3D* get_canvas(wxGLCanvas* canvas);
+#endif // !ENABLE_NON_STATIC_CANVAS_MANAGER
 
-    static bool can_multisample() { return s_multisample == MS_Enabled; }
     static bool are_compressed_textures_supported() { return s_compressed_textures_supported; }
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    static bool can_multisample() { return s_multisample == EMultisampleState::Enabled; }
+    static bool are_framebuffers_supported() { return (s_framebuffers_type != EFramebufferType::Unknown); }
+#else
+    static bool can_multisample() { return s_multisample == MS_Enabled; }
     static bool are_framebuffers_supported() { return (s_framebuffers_type != FB_None); }
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
     static EFramebufferType get_framebuffers_type() { return s_framebuffers_type; }
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    static wxGLCanvas* create_wxglcanvas(wxWindow& parent);
+#else
     static wxGLCanvas* create_wxglcanvas(wxWindow *parent);
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 
     static const GLInfo& get_gl_info() { return s_gl_info; }
 
 private:
+#if !ENABLE_NON_STATIC_CANVAS_MANAGER
     CanvasesMap::iterator do_get_canvas(wxGLCanvas* canvas);
     CanvasesMap::const_iterator do_get_canvas(wxGLCanvas* canvas) const;
 
     bool init(GLCanvas3D& canvas);
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
     static void detect_multisample(int* attribList);
 };
 
diff --git a/src/slic3r/GUI/GLSelectionRectangle.cpp b/src/slic3r/GUI/GLSelectionRectangle.cpp
index 10238c41b..eacf7a153 100644
--- a/src/slic3r/GUI/GLSelectionRectangle.cpp
+++ b/src/slic3r/GUI/GLSelectionRectangle.cpp
@@ -2,6 +2,9 @@
 #include "Camera.hpp"
 #include "3DScene.hpp"
 #include "GLCanvas3D.hpp"
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+#include "GUI_App.hpp"
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 
 #include <GL/glew.h>
 
@@ -35,7 +38,11 @@ namespace GUI {
 
         m_state = Off;
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+        const Camera& camera = wxGetApp().plater()->get_camera();
+#else
         const Camera& camera = canvas.get_camera();
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
         const std::array<int, 4>& viewport = camera.get_viewport();
         const Transform3d& modelview_matrix = camera.get_view_matrix();
         const Transform3d& projection_matrix = camera.get_projection_matrix();
@@ -68,7 +75,11 @@ namespace GUI {
         if (!is_dragging())
             return;
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+        const Camera& camera = wxGetApp().plater()->get_camera();
+#else
         const Camera& camera = canvas.get_camera();
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
         float inv_zoom = (float)camera.get_inv_zoom();
 
         Size cnv_size = canvas.get_canvas_size();
diff --git a/src/slic3r/GUI/GLTexture.cpp b/src/slic3r/GUI/GLTexture.cpp
index f178ddc73..545b066bb 100644
--- a/src/slic3r/GUI/GLTexture.cpp
+++ b/src/slic3r/GUI/GLTexture.cpp
@@ -2,6 +2,9 @@
 #include "GLTexture.hpp"
 
 #include "3DScene.hpp"
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+#include "GLCanvas3DManager.hpp"
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 
 #include <GL/glew.h>
 
diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp
index be3d5495e..4219fe482 100644
--- a/src/slic3r/GUI/GLToolbar.cpp
+++ b/src/slic3r/GUI/GLToolbar.cpp
@@ -3,8 +3,14 @@
 
 #include "GLToolbar.hpp"
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+#include "slic3r/GUI/GLCanvas3D.hpp"
+#include "slic3r/GUI/GUI_App.hpp"
+#include "slic3r/GUI/Camera.hpp"
+#else
 #include "../../slic3r/GUI/GLCanvas3D.hpp"
 #include "../../slic3r/GUI/Camera.hpp"
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 
 #include <wx/event.h>
 #include <wx/bitmap.h>
@@ -15,7 +21,6 @@
 namespace Slic3r {
 namespace GUI {
 
-
 wxDEFINE_EVENT(EVT_GLTOOLBAR_ADD, SimpleEvent);
 wxDEFINE_EVENT(EVT_GLTOOLBAR_DELETE, SimpleEvent);
 wxDEFINE_EVENT(EVT_GLTOOLBAR_DELETE_ALL, SimpleEvent);
@@ -718,7 +723,11 @@ std::string GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos, GLC
 {
     // NB: mouse_pos is already scaled appropriately
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom();
+#else
     float inv_zoom = (float)parent.get_camera().get_inv_zoom();
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
     float factor = m_layout.scale * inv_zoom;
 
     Size cnv_size = parent.get_canvas_size();
@@ -859,7 +868,11 @@ std::string GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos, GLCan
 {
     // NB: mouse_pos is already scaled appropriately
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom();
+#else
     float inv_zoom = (float)parent.get_camera().get_inv_zoom();
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
     float factor = m_layout.scale * inv_zoom;
 
     Size cnv_size = parent.get_canvas_size();
@@ -1008,7 +1021,11 @@ int GLToolbar::contains_mouse_horizontal(const Vec2d& mouse_pos, const GLCanvas3
 {
     // NB: mouse_pos is already scaled appropriately
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom();
+#else
     float inv_zoom = (float)parent.get_camera().get_inv_zoom();
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
     float factor = m_layout.scale * inv_zoom;
 
     Size cnv_size = parent.get_canvas_size();
@@ -1081,7 +1098,11 @@ int GLToolbar::contains_mouse_vertical(const Vec2d& mouse_pos, const GLCanvas3D&
 {
     // NB: mouse_pos is already scaled appropriately
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom();
+#else
     float inv_zoom = (float)parent.get_camera().get_inv_zoom();
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
     float factor = m_layout.scale * inv_zoom;
 
     Size cnv_size = parent.get_canvas_size();
@@ -1233,7 +1254,11 @@ void GLToolbar::render_horizontal(const GLCanvas3D& parent) const
     int tex_width = m_icons_texture.get_width();
     int tex_height = m_icons_texture.get_height();
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom();
+#else
     float inv_zoom = (float)parent.get_camera().get_inv_zoom();
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
     float factor = inv_zoom * m_layout.scale;
 
     float scaled_icons_size = m_layout.icons_size * factor;
@@ -1281,7 +1306,11 @@ void GLToolbar::render_vertical(const GLCanvas3D& parent) const
     int tex_width = m_icons_texture.get_width();
     int tex_height = m_icons_texture.get_height();
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom();
+#else
     float inv_zoom = (float)parent.get_camera().get_inv_zoom();
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
     float factor = inv_zoom * m_layout.scale;
 
     float scaled_icons_size = m_layout.icons_size * factor;
diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp
index f44efa130..f5a4a3d92 100644
--- a/src/slic3r/GUI/GUI_App.cpp
+++ b/src/slic3r/GUI/GUI_App.cpp
@@ -50,8 +50,8 @@
 #include "RemovableDriveManager.hpp"
 
 #ifdef __WXMSW__
-#include <Shlobj.h>
 #include <dbt.h>
+#include <shlobj.h>
 #endif // __WXMSW__
 
 #if ENABLE_THUMBNAIL_GENERATOR_DEBUG
@@ -158,6 +158,41 @@ static void register_win32_device_notification_event()
         }
         return true;
     });
+
+    wxWindow::MSWRegisterMessageHandler(MainFrame::WM_USER_MEDIACHANGED, [](wxWindow *win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) {
+        // Some messages are sent to top level windows by default, some messages are sent to only registered windows, and we explictely register on MainFrame only.
+        auto main_frame = dynamic_cast<MainFrame*>(win);
+        auto plater = (main_frame == nullptr) ? nullptr : main_frame->plater();
+        if (plater == nullptr)
+            // Maybe some other top level window like a dialog or maybe a pop-up menu?
+            return true;
+        wchar_t sPath[MAX_PATH];
+        if (lParam == SHCNE_MEDIAINSERTED || lParam == SHCNE_MEDIAREMOVED) {
+            struct _ITEMIDLIST* pidl = *reinterpret_cast<struct _ITEMIDLIST**>(wParam);
+            if (! SHGetPathFromIDList(pidl, sPath)) {
+                BOOST_LOG_TRIVIAL(error) << "MediaInserted: SHGetPathFromIDList failed";
+                return false;
+            }
+        }
+        switch (lParam) {
+        case SHCNE_MEDIAINSERTED:
+        {
+            //printf("SHCNE_MEDIAINSERTED %S\n", sPath);
+            plater->GetEventHandler()->AddPendingEvent(VolumeAttachedEvent(EVT_VOLUME_ATTACHED));
+            break;
+        }
+        case SHCNE_MEDIAREMOVED:
+        {
+            //printf("SHCNE_MEDIAREMOVED %S\n", sPath);
+            plater->GetEventHandler()->AddPendingEvent(VolumeDetachedEvent(EVT_VOLUME_DETACHED));
+            break;
+        }
+	    default:
+//          printf("Unknown\n");
+            break;
+	    }
+        return true;
+    });
 }
 #endif // WIN32
 
@@ -216,6 +251,23 @@ GUI_App::~GUI_App()
         delete preset_updater;
 }
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+std::string GUI_App::get_gl_info(bool format_as_html, bool extensions)
+{
+    return GLCanvas3DManager::get_gl_info().to_string(format_as_html, extensions);
+}
+
+wxGLContext* GUI_App::init_glcontext(wxGLCanvas& canvas)
+{
+    return m_canvas_mgr.init_glcontext(canvas);
+}
+
+bool GUI_App::init_opengl()
+{
+    return m_canvas_mgr.init_gl();
+}
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
+
 bool GUI_App::OnInit()
 {
     try {
@@ -667,15 +719,16 @@ bool GUI_App::select_language()
 	// Try to load a new language.
     if (index != -1 && (init_selection == -1 || init_selection != index)) {
     	const wxLanguageInfo *new_language_info = language_infos[index];
-        if (new_language_info == m_language_info_best || new_language_info == m_language_info_system) {
-        	// The newly selected profile matches user's default profile exactly. That's great.
-        } else if (m_language_info_best != nullptr && new_language_info->CanonicalName.BeforeFirst('_') == m_language_info_best->CanonicalName.BeforeFirst('_'))
-    		new_language_info = m_language_info_best;
-    	else if (m_language_info_system != nullptr && new_language_info->CanonicalName.BeforeFirst('_') == m_language_info_system->CanonicalName.BeforeFirst('_'))
-            new_language_info = m_language_info_system;
     	if (this->load_language(new_language_info->CanonicalName, false)) {
 			// Save language at application config.
-			app_config->set("translation_language", m_wxLocale->GetCanonicalName().ToUTF8().data());
+            // Which language to save as the selected dictionary language?
+            // 1) Hopefully the language set to wxTranslations by this->load_language(), but that API is weird and we don't want to rely on its
+            //    stability in the future:
+            //    wxTranslations::Get()->GetBestTranslation(SLIC3R_APP_KEY, wxLANGUAGE_ENGLISH);
+            // 2) Current locale language may not match the dictionary name, see GH issue #3901
+            //    m_wxLocale->GetCanonicalName()
+            // 3) new_language_info->CanonicalName is a safe bet. It points to a valid dictionary name.
+			app_config->set("translation_language", new_language_info->CanonicalName.ToUTF8().data());            
 			app_config->save();
     		return true;
     	}
@@ -704,7 +757,6 @@ bool GUI_App::load_language(wxString language, bool initial)
 	        	BOOST_LOG_TRIVIAL(trace) << boost::format("System language detected (user locales and such): %1%") % m_language_info_system->CanonicalName.ToUTF8().data();
 	        }
 		}
-#if defined(__WXMSW__) || defined(__WXOSX__)
         {
 	    	// Allocating a temporary locale will switch the default wxTranslations to its internal wxTranslations instance.
 	    	wxLocale temp_locale;
@@ -721,7 +773,6 @@ bool GUI_App::load_language(wxString language, bool initial)
 	        	BOOST_LOG_TRIVIAL(trace) << boost::format("Best translation language detected (may be different from user locales): %1%") % m_language_info_best->CanonicalName.ToUTF8().data();
 			}
 		}
-#endif
     }
 
 	const wxLanguageInfo *language_info = language.empty() ? nullptr : wxLocale::FindLanguageInfo(language);
@@ -737,6 +788,7 @@ bool GUI_App::load_language(wxString language, bool initial)
 	}
 
     if (language_info == nullptr) {
+        // PrusaSlicer does not support the Right to Left languages yet.
         if (m_language_info_system != nullptr && m_language_info_system->LayoutDirection != wxLayout_RightToLeft)
             language_info = m_language_info_system;
         if (m_language_info_best != nullptr && m_language_info_best->LayoutDirection != wxLayout_RightToLeft)
@@ -755,6 +807,16 @@ bool GUI_App::load_language(wxString language, bool initial)
 		BOOST_LOG_TRIVIAL(trace) << "Using Czech dictionaries for Slovak language";
     }
 
+    // Select language for locales. This language may be different from the language of the dictionary.
+    if (language_info == m_language_info_best || language_info == m_language_info_system) {
+        // The current language matches user's default profile exactly. That's great.
+    } else if (m_language_info_best != nullptr && language_info->CanonicalName.BeforeFirst('_') == m_language_info_best->CanonicalName.BeforeFirst('_')) {
+        // Use whatever the operating system recommends, if it the language code of the dictionary matches the recommended language.
+        // This allows a Swiss guy to use a German dictionary without forcing him to German locales.
+        language_info = m_language_info_best;
+    } else if (m_language_info_system != nullptr && language_info->CanonicalName.BeforeFirst('_') == m_language_info_system->CanonicalName.BeforeFirst('_'))
+        language_info = m_language_info_system;
+
     if (! wxLocale::IsAvailable(language_info->Language)) {
     	// Loading the language dictionary failed.
     	wxString message = "Switching PrusaSlicer to language " + language_info->CanonicalName + " failed.";
@@ -781,7 +843,7 @@ bool GUI_App::load_language(wxString language, bool initial)
     wxTranslations::Get()->SetLanguage(language_dict);
     m_wxLocale->AddCatalog(SLIC3R_APP_KEY);
     m_imgui->set_language(into_u8(language_info->CanonicalName));
-	//FIXME This is a temporary workaround, the correct solution is to switch to "C" locale during file import / export only.
+    //FIXME This is a temporary workaround, the correct solution is to switch to "C" locale during file import / export only.
     wxSetlocale(LC_NUMERIC, "C");
     Preset::update_suffix_modified();
 	return true;
diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp
index d02a60ba9..bf75eaaa6 100644
--- a/src/slic3r/GUI/GUI_App.hpp
+++ b/src/slic3r/GUI/GUI_App.hpp
@@ -7,6 +7,9 @@
 #include "MainFrame.hpp"
 #include "ImGuiWrapper.hpp"
 #include "ConfigWizard.hpp"
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+#include "GLCanvas3DManager.hpp"
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 
 #include <wx/app.h>
 #include <wx/colour.h>
@@ -96,7 +99,11 @@ class GUI_App : public wxApp
     // Best translation language, provided by Windows or OSX, owned by wxWidgets.
     const wxLanguageInfo		 *m_language_info_best   = nullptr;
 
-	std::unique_ptr<RemovableDriveManager> m_removable_drive_manager;
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    GLCanvas3DManager m_canvas_mgr;
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
+
+    std::unique_ptr<RemovableDriveManager> m_removable_drive_manager;
 
     std::unique_ptr<ImGuiWrapper> m_imgui;
     std::unique_ptr<PrintHostJobQueue> m_printhost_job_queue;
@@ -109,6 +116,12 @@ public:
     GUI_App();
     ~GUI_App() override;
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    static std::string get_gl_info(bool format_as_html, bool extensions);
+    wxGLContext* init_glcontext(wxGLCanvas& canvas);
+    bool init_opengl();
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
+
     static unsigned get_colour_approx_luma(const wxColour &colour);
     static bool     dark_mode();
     void            init_label_colours();
diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp
index 1ae73a192..29ece9b31 100644
--- a/src/slic3r/GUI/GUI_Preview.cpp
+++ b/src/slic3r/GUI/GUI_Preview.cpp
@@ -27,31 +27,63 @@
 namespace Slic3r {
 namespace GUI {
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+View3D::View3D(wxWindow* parent, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process)
+    : m_canvas_widget(nullptr)
+    , m_canvas(nullptr)
+{
+    init(parent, model, config, process);
+}
+#else
 View3D::View3D(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process)
     : m_canvas_widget(nullptr)
     , m_canvas(nullptr)
 {
     init(parent, bed, camera, view_toolbar, model, config, process);
 }
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 
 View3D::~View3D()
 {
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    if (m_canvas != nullptr)
+        delete m_canvas;
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
+
     if (m_canvas_widget != nullptr)
     {
+#if !ENABLE_NON_STATIC_CANVAS_MANAGER
         _3DScene::remove_canvas(m_canvas_widget);
+#endif // !ENABLE_NON_STATIC_CANVAS_MANAGER
         delete m_canvas_widget;
+#if !ENABLE_NON_STATIC_CANVAS_MANAGER
         m_canvas = nullptr;
+#endif // !ENABLE_NON_STATIC_CANVAS_MANAGER
     }
 }
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+bool View3D::init(wxWindow* parent, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process)
+#else
 bool View3D::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process)
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 {
     if (!Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 /* disable wxTAB_TRAVERSAL */))
         return false;
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    m_canvas_widget = GLCanvas3DManager::create_wxglcanvas(*this);
+    if (m_canvas_widget == nullptr)
+        return false;
+
+    m_canvas = new GLCanvas3D(m_canvas_widget);
+    m_canvas->set_context(wxGetApp().init_glcontext(*m_canvas_widget));
+    m_canvas->bind_event_handlers();
+#else
     m_canvas_widget = GLCanvas3DManager::create_wxglcanvas(this);
     _3DScene::add_canvas(m_canvas_widget, bed, camera, view_toolbar);
     m_canvas = _3DScene::get_canvas(this->m_canvas_widget);
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 
     m_canvas->allow_multisample(GLCanvas3DManager::can_multisample());
     // XXX: If have OpenGL
@@ -66,6 +98,9 @@ bool View3D::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_
     m_canvas->enable_main_toolbar(true);
     m_canvas->enable_undoredo_toolbar(true);
     m_canvas->enable_labels(true);
+#if ENABLE_SLOPE_RENDERING
+    m_canvas->enable_slope(true);
+#endif // ENABLE_SLOPE_RENDERING
 
     wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
     main_sizer->Add(m_canvas_widget, 1, wxALL | wxEXPAND, 0);
@@ -163,9 +198,15 @@ void View3D::render()
         m_canvas->set_as_dirty();
 }
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+Preview::Preview(
+    wxWindow* parent, Model* model, DynamicPrintConfig* config,
+    BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data, std::function<void()> schedule_background_process_func)
+#else
 Preview::Preview(
     wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model, DynamicPrintConfig* config, 
     BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data, std::function<void()> schedule_background_process_func)
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
     : m_canvas_widget(nullptr)
     , m_canvas(nullptr)
     , m_double_slider_sizer(nullptr)
@@ -190,21 +231,39 @@ Preview::Preview(
     , m_volumes_cleanup_required(false)
 #endif // __linux__
 {
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    if (init(parent, model))
+#else
     if (init(parent, bed, camera, view_toolbar, model))
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
     {
         show_hide_ui_elements("none");
         load_print();
     }
 }
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+bool Preview::init(wxWindow* parent, Model* model)
+#else
 bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model)
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 {
     if (!Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 /* disable wxTAB_TRAVERSAL */))
         return false;
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    m_canvas_widget = GLCanvas3DManager::create_wxglcanvas(*this);
+    if (m_canvas_widget == nullptr)
+        return false;
+
+    m_canvas = new GLCanvas3D(m_canvas_widget);
+    m_canvas->set_context(wxGetApp().init_glcontext(*m_canvas_widget));
+    m_canvas->bind_event_handlers();
+#else
     m_canvas_widget = GLCanvas3DManager::create_wxglcanvas(this);
     _3DScene::add_canvas(m_canvas_widget, bed, camera, view_toolbar);
     m_canvas = _3DScene::get_canvas(this->m_canvas_widget);
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
     m_canvas->allow_multisample(GLCanvas3DManager::can_multisample());
     m_canvas->set_config(m_config);
     m_canvas->set_model(model);
@@ -313,9 +372,16 @@ Preview::~Preview()
 {
     unbind_event_handlers();
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    if (m_canvas != nullptr)
+        delete m_canvas;
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
+
     if (m_canvas_widget != nullptr)
     {
-		_3DScene::remove_canvas(m_canvas_widget);
+#if !ENABLE_NON_STATIC_CANVAS_MANAGER
+        _3DScene::remove_canvas(m_canvas_widget);
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
         delete m_canvas_widget;
         m_canvas = nullptr;
     }
diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp
index cc8f15325..2fe26ede9 100644
--- a/src/slic3r/GUI/GUI_Preview.hpp
+++ b/src/slic3r/GUI/GUI_Preview.hpp
@@ -34,6 +34,9 @@ class GLCanvas3D;
 class GLToolbar;
 class Bed3D;
 struct Camera;
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+class Plater;
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 
 class View3D : public wxPanel
 {
@@ -41,7 +44,11 @@ class View3D : public wxPanel
     GLCanvas3D* m_canvas;
 
 public:
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    View3D(wxWindow* parent, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process);
+#else
     View3D(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process);
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
     virtual ~View3D();
 
     wxGLCanvas* get_wxglcanvas() { return m_canvas_widget; }
@@ -69,7 +76,11 @@ public:
     void render();
 
 private:
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    bool init(wxWindow* parent, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process);
+#else
     bool init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process);
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 };
 
 class Preview : public wxPanel
@@ -109,8 +120,13 @@ class Preview : public wxPanel
     DoubleSlider::Control*       m_slider {nullptr};
 
 public:
-    Preview(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model, DynamicPrintConfig* config, 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    Preview(wxWindow* parent, Model* model, DynamicPrintConfig* config,
+        BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data, std::function<void()> schedule_background_process = []() {});
+#else
+    Preview(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model, DynamicPrintConfig* config,
         BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data, std::function<void()> schedule_background_process = [](){});
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
     virtual ~Preview();
 
     wxGLCanvas* get_wxglcanvas() { return m_canvas_widget; }
@@ -137,7 +153,11 @@ public:
     bool is_loaded() const { return m_loaded; }
 
 private:
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    bool init(wxWindow* parent, Model* model);
+#else
     bool init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model);
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 
     void bind_event_handlers();
     void unbind_event_handlers();
@@ -170,7 +190,6 @@ private:
     void load_print_as_sla();
 
     void on_sliders_scroll_changed(wxCommandEvent& event);
-
 };
 
 } // namespace GUI
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp
index 0a3a6f898..51e6d7458 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp
@@ -10,6 +10,9 @@
 #include "slic3r/GUI/GUI_ObjectList.hpp"
 #include "slic3r/GUI/MeshUtils.hpp"
 #include "slic3r/GUI/Plater.hpp"
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+#include "slic3r/GUI/Camera.hpp"
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 #include "slic3r/GUI/PresetBundle.hpp"
 #include "libslic3r/SLAPrint.hpp"
 #include "libslic3r/TriangleMesh.hpp"
@@ -311,7 +314,11 @@ bool GLGizmoHollow::unproject_on_mesh(const Vec2d& mouse_pos, std::pair<Vec3f, V
     if (! m_c->m_mesh_raycaster)
         return false;
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    const Camera& camera = wxGetApp().plater()->get_camera();
+#else
     const Camera& camera = m_parent.get_camera();
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
     const Selection& selection = m_parent.get_selection();
     const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
     Geometry::Transformation trafo = volume->get_instance_transformation();
@@ -426,7 +433,11 @@ bool GLGizmoHollow::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_pos
             points_inside.push_back(points[idx].cast<float>());
 
         // Only select/deselect points that are actually visible
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+        for (size_t idx : m_c->m_mesh_raycaster->get_unobscured_idxs(trafo, wxGetApp().plater()->get_camera(), points_inside, m_c->m_clipping_plane.get()))
+#else
         for (size_t idx :  m_c->m_mesh_raycaster->get_unobscured_idxs(trafo, m_parent.get_camera(), points_inside, m_c->m_clipping_plane.get()))
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
         {
             if (rectangle_status == GLSelectionRectangle::Deselect)
                 unselect_point(points_idxs[idx]);
@@ -1026,8 +1037,13 @@ void GLGizmoHollow::update_clipping_plane(bool keep_normal) const
 {
     if (! m_c->m_model_object)
         return;
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    Vec3d normal = (keep_normal && m_c->m_clipping_plane->get_normal() != Vec3d::Zero() ?
+        m_c->m_clipping_plane->get_normal() : -wxGetApp().plater()->get_camera().get_dir_forward());
+#else
     Vec3d normal = (keep_normal && m_c->m_clipping_plane->get_normal() != Vec3d::Zero() ?
                         m_c->m_clipping_plane->get_normal() : -m_parent.get_camera().get_dir_forward());
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 
     const Vec3d& center = m_c->m_model_object->instances[m_c->m_active_instance]->get_offset() + Vec3d(0., 0., m_z_shift);
     float dist = normal.dot(center);
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp
index ab0b9b5d6..064302c02 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp
@@ -14,6 +14,9 @@
 #include "slic3r/GUI/GUI.hpp"
 #include "slic3r/GUI/GUI_ObjectSettings.hpp"
 #include "slic3r/GUI/GUI_ObjectList.hpp"
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+#include "slic3r/GUI/Camera.hpp"
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 #include "slic3r/GUI/MeshUtils.hpp"
 #include "slic3r/GUI/Plater.hpp"
 #include "slic3r/GUI/PresetBundle.hpp"
@@ -379,7 +382,11 @@ bool GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos, std::pair<Vec
     if (! m_c->m_mesh_raycaster)
         return false;
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    const Camera& camera = wxGetApp().plater()->get_camera();
+#else
     const Camera& camera = m_parent.get_camera();
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
     const Selection& selection = m_parent.get_selection();
     const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
     Geometry::Transformation trafo = volume->get_instance_transformation();
@@ -489,7 +496,11 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
                 points_inside.push_back(points[idx].cast<float>());
 
             // Only select/deselect points that are actually visible
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+            for (size_t idx : m_c->m_mesh_raycaster->get_unobscured_idxs(trafo, wxGetApp().plater()->get_camera(), points_inside, m_c->m_clipping_plane.get()))
+#else
             for (size_t idx :  m_c->m_mesh_raycaster->get_unobscured_idxs(trafo, m_parent.get_camera(), points_inside, m_c->m_clipping_plane.get()))
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
             {
                 if (rectangle_status == GLSelectionRectangle::Deselect)
                     unselect_point(points_idxs[idx]);
@@ -1282,8 +1293,14 @@ void GLGizmoSlaSupports::update_clipping_plane(bool keep_normal) const
 {
     if (! m_c->m_model_object)
         return;
+
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
     Vec3d normal = (keep_normal && m_c->m_clipping_plane->get_normal() != Vec3d::Zero() ?
-                        m_c->m_clipping_plane->get_normal() : -m_parent.get_camera().get_dir_forward());
+        m_c->m_clipping_plane->get_normal() : -wxGetApp().plater()->get_camera().get_dir_forward());
+#else
+    Vec3d normal = (keep_normal && m_c->m_clipping_plane->get_normal() != Vec3d::Zero() ?
+        m_c->m_clipping_plane->get_normal() : -m_parent.get_camera().get_dir_forward());
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 
     const Vec3d& center = m_c->m_model_object->instances[m_c->m_active_instance]->get_offset() + Vec3d(0., 0., m_z_shift);
     float dist = normal.dot(center);
diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp
index 18d603507..379bd48d1 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp
@@ -2,6 +2,9 @@
 #include "GLGizmosManager.hpp"
 #include "slic3r/GUI/GLCanvas3D.hpp"
 #include "slic3r/GUI/3DScene.hpp"
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+#include "slic3r/GUI/Camera.hpp"
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 #include "slic3r/GUI/GUI_App.hpp"
 #include "slic3r/GUI/GUI_ObjectManipulation.hpp"
 #include "slic3r/GUI/PresetBundle.hpp"
@@ -1029,8 +1032,13 @@ void GLGizmosManager::do_render_overlay() const
 
     float cnv_w = (float)m_parent.get_canvas_size().get_width();
     float cnv_h = (float)m_parent.get_canvas_size().get_height();
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    float zoom = (float)wxGetApp().plater()->get_camera().get_zoom();
+    float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom();
+#else
     float zoom = (float)m_parent.get_camera().get_zoom();
     float inv_zoom = (float)m_parent.get_camera().get_inv_zoom();
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 
     float height = get_scaled_total_height();
     float width = get_scaled_total_width();
@@ -1081,7 +1089,11 @@ void GLGizmosManager::do_render_overlay() const
 
         GLTexture::render_sub_texture(icons_texture_id, zoomed_top_x, zoomed_top_x + zoomed_icons_size, zoomed_top_y - zoomed_icons_size, zoomed_top_y, { { u_left, v_bottom }, { u_right, v_bottom }, { u_right, v_top }, { u_left, v_top } });
         if (idx == m_current) {
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+            float toolbar_top = cnv_h - wxGetApp().plater()->get_view_toolbar().get_height();
+#else
             float toolbar_top = cnv_h - m_parent.get_view_toolbar_height();
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
             gizmo->render_input_window(width, 0.5f * cnv_h - zoomed_top_y * zoom, toolbar_top);
         }
         zoomed_top_y -= zoomed_stride_y;
diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp
index 842cec5e2..d05ecbcd8 100644
--- a/src/slic3r/GUI/KBShortcutsDialog.cpp
+++ b/src/slic3r/GUI/KBShortcutsDialog.cpp
@@ -145,6 +145,9 @@ void KBShortcutsDialog::fill_shortcuts()
         // View
         { "0-6", L("Camera view") },
         { "E", L("Show/Hide object/instance labels") },
+#if ENABLE_SLOPE_RENDERING
+        { "D", L("Turn On/Off facets' slope rendering") },
+#endif // ENABLE_SLOPE_RENDERING
         // Configuration
         { ctrl + "P", L("Preferences") },
         // Help
diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp
index e1fb72c15..26ceda7bf 100644
--- a/src/slic3r/GUI/MainFrame.cpp
+++ b/src/slic3r/GUI/MainFrame.cpp
@@ -33,6 +33,7 @@
 
 #ifdef _WIN32
 #include <dbt.h>
+#include <shlobj.h>
 #endif // _WIN32
 
 namespace Slic3r {
@@ -127,6 +128,30 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S
 //		DEV_BROADCAST_HANDLE NotificationFilter = { 0 };
 //		NotificationFilter.dbch_size = sizeof(DEV_BROADCAST_HANDLE);
 //		NotificationFilter.dbch_devicetype = DBT_DEVTYP_HANDLE;
+
+		// Using Win32 Shell API to register for media insert / removal events.
+		LPITEMIDLIST ppidl;
+		if (SHGetSpecialFolderLocation(this->GetHWND(), CSIDL_DESKTOP, &ppidl) == NOERROR) {
+			SHChangeNotifyEntry shCNE;
+			shCNE.pidl       = ppidl;
+			shCNE.fRecursive = TRUE;
+			// Returns a positive integer registration identifier (ID).
+			// Returns zero if out of memory or in response to invalid parameters.
+			m_ulSHChangeNotifyRegister = SHChangeNotifyRegister(this->GetHWND(),		// Hwnd to receive notification
+				SHCNE_DISKEVENTS,														// Event types of interest (sources)
+				SHCNE_MEDIAINSERTED | SHCNE_MEDIAREMOVED,
+				//SHCNE_UPDATEITEM,														// Events of interest - use SHCNE_ALLEVENTS for all events
+				WM_USER_MEDIACHANGED,													// Notification message to be sent upon the event
+				1,																		// Number of entries in the pfsne array
+				&shCNE);																// Array of SHChangeNotifyEntry structures that 
+																						// contain the notifications. This array should 
+																						// always be set to one when calling SHChnageNotifyRegister
+																						// or SHChangeNotifyDeregister will not work properly.
+			assert(m_ulSHChangeNotifyRegister != 0);    // Shell notification failed
+		} else {
+			// Failed to get desktop location
+			assert(false); 
+		}
 #endif // _WIN32
 
         // propagate event
@@ -161,13 +186,26 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S
 void MainFrame::shutdown()
 {
 #ifdef _WIN32
-	::UnregisterDeviceNotification(HDEVNOTIFY(m_hDeviceNotify));
-	m_hDeviceNotify = nullptr;
+	if (m_hDeviceNotify) {
+		::UnregisterDeviceNotification(HDEVNOTIFY(m_hDeviceNotify));
+		m_hDeviceNotify = nullptr;
+	}
+ 	if (m_ulSHChangeNotifyRegister) {
+        SHChangeNotifyDeregister(m_ulSHChangeNotifyRegister);
+        m_ulSHChangeNotifyRegister = 0;
+ 	}
 #endif // _WIN32
 
     if (m_plater)
     	m_plater->stop_jobs();
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    // Unbinding of wxWidgets event handling in canvases needs to be done here because on MAC,
+    // when closing the application using Command+Q, a mouse event is triggered after this lambda is completed,
+    // causing a crash
+    if (m_plater) m_plater->unbind_canvas_event_handlers();
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
+
     // Weird things happen as the Paint messages are floating around the windows being destructed.
     // Avoid the Paint messages by hiding the main window.
     // Also the application closes much faster without these unnecessary screen refreshes.
@@ -188,7 +226,9 @@ void MainFrame::shutdown()
     wxGetApp().app_config->save();
 //         if (m_plater)
 //             m_plater->print = undef;
+#if !ENABLE_NON_STATIC_CANVAS_MANAGER
     _3DScene::remove_all_canvases();
+#endif // !ENABLE_NON_STATIC_CANVAS_MANAGER
 //         Slic3r::GUI::deregister_on_request_update_callback();
 
     // set to null tabs and a plater
@@ -755,9 +795,20 @@ void MainFrame::init_menubar()
         append_menu_item(viewMenu, wxID_ANY, _(L("Right")) + sep + "&6", _(L("Right View")), [this](wxCommandEvent&) { select_view("right"); },
             "", nullptr, [this](){return can_change_view(); }, this);
         viewMenu->AppendSeparator();
+#if ENABLE_SLOPE_RENDERING
+        wxMenu* options_menu = new wxMenu();
+        append_menu_check_item(options_menu, wxID_ANY, _(L("Show &labels")) + sep + "E", _(L("Show object/instance labels in 3D scene")),
+            [this](wxCommandEvent&) { m_plater->show_view3D_labels(!m_plater->are_view3D_labels_shown()); }, this,
+            [this]() { return m_plater->is_view3D_shown(); }, [this]() { return m_plater->are_view3D_labels_shown(); }, this);
+        append_menu_check_item(options_menu, wxID_ANY, _(L("Show &slope")) + sep + "D", _(L("Objects coloring using faces' slope")),
+            [this](wxCommandEvent&) { m_plater->show_view3D_slope(!m_plater->is_view3D_slope_shown()); }, this,
+            [this]() { return m_plater->is_view3D_shown() && !m_plater->is_view3D_layers_editing_enabled(); }, [this]() { return m_plater->is_view3D_slope_shown(); }, this);
+        append_submenu(viewMenu, options_menu, wxID_ANY, _(L("&Options")), "");
+#else
         append_menu_check_item(viewMenu, wxID_ANY, _(L("Show &labels")) + sep + "E", _(L("Show object/instance labels in 3D scene")),
             [this](wxCommandEvent&) { m_plater->show_view3D_labels(!m_plater->are_view3D_labels_shown()); }, this,
             [this]() { return m_plater->is_view3D_shown(); }, [this]() { return m_plater->are_view3D_labels_shown(); }, this);
+#endif // ENABLE_SLOPE_RENDERING
         append_menu_check_item(viewMenu, wxID_ANY, _(L("&Collapse sidebar")), _(L("Collapse sidebar")),
             [this](wxCommandEvent&) { m_plater->collapse_sidebur(!m_plater->is_sidebar_collapsed()); }, this,
             [this]() { return true; }, [this]() { return m_plater->is_sidebar_collapsed(); }, this);
diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp
index 8c8b98090..6038e6d2f 100644
--- a/src/slic3r/GUI/MainFrame.hpp
+++ b/src/slic3r/GUI/MainFrame.hpp
@@ -144,6 +144,8 @@ public:
 
 #ifdef _WIN32
     void*				m_hDeviceNotify { nullptr };
+    uint32_t  			m_ulSHChangeNotifyRegister { 0 };
+	static constexpr int WM_USER_MEDIACHANGED { 0x7FFF }; // WM_USER from 0x0400 to 0x7FFF, picking the last one to not interfere with wxWidgets allocation
 #endif // _WIN32
 };
 
diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp
index 74a51d9f0..a04698a47 100644
--- a/src/slic3r/GUI/Plater.cpp
+++ b/src/slic3r/GUI/Plater.cpp
@@ -81,6 +81,12 @@
 #include "RemovableDriveManager.hpp"
 #include "SearchComboBox.hpp"
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+#ifdef __APPLE__
+#include "Gizmos/GLGizmosManager.hpp"
+#endif // __APPLE__
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
+
 #include <wx/glcanvas.h>    // Needs to be last because reasons :-/
 #include "WipeTowerDialog.hpp"
 #include "libslic3r/CustomGCode.hpp"
@@ -1886,8 +1892,18 @@ struct Plater::priv
     bool is_sidebar_collapsed() const   { return sidebar->is_collapsed(); }
     void collapse_sidebur(bool show)    { sidebar->collapse(show); }
 
+#if ENABLE_SLOPE_RENDERING
+    bool is_view3D_slope_shown() const { return (current_panel == view3D) && view3D->get_canvas3d()->is_slope_shown(); }
+    void show_view3D_slope(bool show) { if (current_panel == view3D) view3D->get_canvas3d()->show_slope(show); }
+
+    bool is_view3D_layers_editing_enabled() const { return (current_panel == view3D) && view3D->get_canvas3d()->is_layers_editing_enabled(); }
+#endif // ENABLE_SLOPE_RENDERING
+
     void set_current_canvas_as_dirty();
     GLCanvas3D* get_current_canvas3D();
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    void unbind_canvas_event_handlers();
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 
     bool init_view_toolbar();
 
@@ -2116,8 +2132,18 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
     sla_print.set_status_callback(statuscb);
     this->q->Bind(EVT_SLICING_UPDATE, &priv::on_slicing_update, this);
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    view3D = new View3D(q, &model, config, &background_process);
+    preview = new Preview(q, &model, config, &background_process, &gcode_preview_data, [this]() { schedule_background_process(); });
+
+#ifdef __APPLE__
+    // set default view_toolbar icons size equal to GLGizmosManager::Default_Icons_Size
+    view_toolbar.set_icons_size(GLGizmosManager::Default_Icons_Size);
+#endif // __APPLE__
+#else
     view3D = new View3D(q, bed, camera, view_toolbar, &model, config, &background_process);
     preview = new Preview(q, bed, camera, view_toolbar, &model, config, &background_process, &gcode_preview_data, [this](){ schedule_background_process(); });
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 
     panels.push_back(view3D);
     panels.push_back(preview);
@@ -2218,7 +2244,9 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
     // Drop target:
     q->SetDropTarget(new PlaterDropTarget(q));   // if my understanding is right, wxWindow takes the owenership
 
+#if !ENABLE_NON_STATIC_CANVAS_MANAGER
     update_ui_from_settings();
+#endif // !ENABLE_NON_STATIC_CANVAS_MANAGER
     q->Layout();
 
     set_current_panel(view3D);
@@ -4124,6 +4152,17 @@ GLCanvas3D* Plater::priv::get_current_canvas3D()
     return (current_panel == view3D) ? view3D->get_canvas3d() : ((current_panel == preview) ? preview->get_canvas3d() : nullptr);
 }
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+void Plater::priv::unbind_canvas_event_handlers()
+{
+    if (view3D != nullptr)
+        view3D->get_canvas3d()->unbind_event_handlers();
+
+    if (preview != nullptr)
+        preview->get_canvas3d()->unbind_event_handlers();
+}
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
+
 bool Plater::priv::init_view_toolbar()
 {
     if (view_toolbar.get_items_count() > 0)
@@ -4594,7 +4633,8 @@ void Sidebar::set_btn_label(const ActionButtonType btn_type, const wxString& lab
 // Plater / Public
 
 Plater::Plater(wxWindow *parent, MainFrame *main_frame)
-    : wxPanel(parent), p(new priv(this, main_frame))
+    : wxPanel(parent)
+    , p(new priv(this, main_frame))
 {
     // Initialization performed in the private c-tor
 }
@@ -4721,6 +4761,13 @@ void Plater::show_view3D_labels(bool show) { p->show_view3D_labels(show); }
 bool Plater::is_sidebar_collapsed() const { return p->is_sidebar_collapsed(); }
 void Plater::collapse_sidebur(bool show) { p->collapse_sidebur(show); }
 
+#if ENABLE_SLOPE_RENDERING
+bool Plater::is_view3D_slope_shown() const { return p->is_view3D_slope_shown(); }
+void Plater::show_view3D_slope(bool show) { p->show_view3D_slope(show); }
+
+bool Plater::is_view3D_layers_editing_enabled() const { return p->is_view3D_layers_editing_enabled(); }
+#endif // ENABLE_SLOPE_RENDERING
+
 void Plater::select_all() { p->select_all(); }
 void Plater::deselect_all() { p->deselect_all(); }
 
@@ -5540,6 +5587,13 @@ void Plater::set_current_canvas_as_dirty()
     p->set_current_canvas_as_dirty();
 }
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+void Plater::unbind_canvas_event_handlers()
+{
+    p->unbind_canvas_event_handlers();
+}
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
+
 PrinterTechnology Plater::printer_technology() const
 {
     return p->printer_technology;
@@ -5673,6 +5727,28 @@ Camera& Plater::get_camera()
     return p->camera;
 }
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+const Bed3D& Plater::get_bed() const
+{
+    return p->bed;
+}
+
+Bed3D& Plater::get_bed()
+{
+    return p->bed;
+}
+
+const GLToolbar& Plater::get_view_toolbar() const
+{
+    return p->view_toolbar;
+}
+
+GLToolbar& Plater::get_view_toolbar()
+{
+    return p->view_toolbar;
+}
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
+
 const Mouse3DController& Plater::get_mouse3d_controller() const
 {
     return p->mouse3d_controller;
diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp
index 5a24ac5eb..cf1c10681 100644
--- a/src/slic3r/GUI/Plater.hpp
+++ b/src/slic3r/GUI/Plater.hpp
@@ -43,6 +43,10 @@ class ObjectList;
 class GLCanvas3D;
 class Mouse3DController;
 struct Camera;
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+class Bed3D;
+class GLToolbar;
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 
 using t_optgroups = std::vector <std::shared_ptr<ConfigOptionsGroup>>;
 
@@ -183,6 +187,13 @@ public:
     bool is_sidebar_collapsed() const;
     void collapse_sidebur(bool show);
 
+#if ENABLE_SLOPE_RENDERING
+    bool is_view3D_slope_shown() const;
+    void show_view3D_slope(bool show);
+
+    bool is_view3D_layers_editing_enabled() const;
+#endif // ENABLE_SLOPE_RENDERING
+
     // Called after the Preferences dialog is closed and the program settings are saved.
     // Update the UI based on the current preferences.
     void update_ui_from_settings();
@@ -262,6 +273,9 @@ public:
     BoundingBoxf bed_shape_bb() const;
 
     void set_current_canvas_as_dirty();
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    void unbind_canvas_event_handlers();
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
 
     PrinterTechnology   printer_technology() const;
     void                set_printer_technology(PrinterTechnology printer_technology);
@@ -291,12 +305,21 @@ public:
 
     const Camera& get_camera() const;
     Camera& get_camera();
+
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    const Bed3D& get_bed() const;
+    Bed3D& get_bed();
+
+    const GLToolbar& get_view_toolbar() const;
+    GLToolbar& get_view_toolbar();
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
+
     const Mouse3DController& get_mouse3d_controller() const;
     Mouse3DController& get_mouse3d_controller();
 
 	void set_bed_shape() const;
 
-	// ROII wrapper for suppressing the Undo / Redo snapshot to be taken.
+    // ROII wrapper for suppressing the Undo / Redo snapshot to be taken.
 	class SuppressSnapshots
 	{
 	public:
diff --git a/src/slic3r/GUI/RemovableDriveManager.cpp b/src/slic3r/GUI/RemovableDriveManager.cpp
index cdb1c1d45..d67ac4a22 100644
--- a/src/slic3r/GUI/RemovableDriveManager.cpp
+++ b/src/slic3r/GUI/RemovableDriveManager.cpp
@@ -33,17 +33,19 @@ wxDEFINE_EVENT(EVT_REMOVABLE_DRIVES_CHANGED, RemovableDrivesChangedEvent);
 #if _WIN32
 std::vector<DriveData> RemovableDriveManager::search_for_removable_drives() const
 {
-	//get logical drives flags by letter in alphabetical order
+	// Get logical drives flags by letter in alphabetical order.
 	DWORD drives_mask = ::GetLogicalDrives();
 
 	// Allocate the buffers before the loop.
 	std::wstring volume_name;
 	std::wstring file_system_name;
-	// Iterate the Windows drives from 'A' to 'Z'
+	// Iterate the Windows drives from 'C' to 'Z'
 	std::vector<DriveData> current_drives;
-	for (size_t i = 0; i < 26; ++ i)
-		if (drives_mask & (1 << i)) {
-			std::string path { char('A' + i), ':' };
+	// Skip A and B drives.
+	drives_mask >>= 2;
+	for (char drive = 'C'; drive <= 'Z'; ++ drive, drives_mask >>= 1)
+		if (drives_mask & 1) {
+			std::string path { drive, ':' };
 			UINT drive_type = ::GetDriveTypeA(path.c_str());
 			// DRIVE_REMOVABLE on W are sd cards and usb thumbnails (not usb harddrives)
 			if (drive_type ==  DRIVE_REMOVABLE) {
@@ -450,14 +452,8 @@ void RemovableDriveManager::thread_proc()
 		{
 			std::unique_lock<std::mutex> lck(m_thread_stop_mutex);
 #ifdef _WIN32
-			// Windows do not send an update on insert / eject of an SD card into an external SD card reader.
-			// Windows also do not send an update on software eject of a FLASH drive.
-			// We can likely use the Windows WMI API, but it will be quite time consuming to implement.
-			// https://www.codeproject.com/Articles/10539/Making-WMI-Queries-In-C
-			// https://docs.microsoft.com/en-us/windows/win32/wmisdk/wmi-start-page
-			// https://docs.microsoft.com/en-us/windows/win32/wmisdk/com-api-for-wmi
-			// https://docs.microsoft.com/en-us/windows/win32/wmisdk/example--receiving-event-notifications-through-wmi-
-			m_thread_stop_condition.wait_for(lck, std::chrono::seconds(2), [this]{ return m_stop || m_wakeup; });
+			// Reacting to updates by WM_DEVICECHANGE and WM_USER_MEDIACHANGED
+			m_thread_stop_condition.wait(lck, [this]{ return m_stop || m_wakeup; });
 #else
 			m_thread_stop_condition.wait_for(lck, std::chrono::seconds(2), [this]{ return m_stop; });
 #endif
diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp
index 146111f49..b07463857 100644
--- a/src/slic3r/GUI/Selection.cpp
+++ b/src/slic3r/GUI/Selection.cpp
@@ -440,6 +440,12 @@ void Selection::clear()
     update_type();
     this->set_bounding_boxes_dirty();
 
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    // this happens while the application is closing
+    if (wxGetApp().obj_manipul() == nullptr)
+        return;
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
+
     // resets the cache in the sidebar
     wxGetApp().obj_manipul()->reset_cache();
 
diff --git a/src/slic3r/GUI/SysInfoDialog.cpp b/src/slic3r/GUI/SysInfoDialog.cpp
index a1bae8742..f26fe7033 100644
--- a/src/slic3r/GUI/SysInfoDialog.cpp
+++ b/src/slic3r/GUI/SysInfoDialog.cpp
@@ -145,7 +145,11 @@ SysInfoDialog::SysInfoDialog()
             "</font>"
             "</body>"
             "</html>", bgr_clr_str, text_clr_str, text_clr_str,
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+            get_mem_info(true) + "<br>" + wxGetApp().get_gl_info(true, true));
+#else
             get_mem_info(true) + "<br>" + _3DScene::get_gl_info(true, true));
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
         m_opengl_info_html->SetPage(text);
         main_sizer->Add(m_opengl_info_html, 1, wxEXPAND | wxBOTTOM, 15);
     }
@@ -198,7 +202,11 @@ void SysInfoDialog::on_dpi_changed(const wxRect &suggested_rect)
 void SysInfoDialog::onCopyToClipboard(wxEvent &)
 {
     wxTheClipboard->Open();
+#if ENABLE_NON_STATIC_CANVAS_MANAGER
+    const auto text = get_main_info(false) + "\n" + wxGetApp().get_gl_info(false, true);
+#else
     const auto text = get_main_info(false)+"\n"+_3DScene::get_gl_info(false, true);
+#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
     wxTheClipboard->SetData(new wxTextDataObject(text));
     wxTheClipboard->Close();
 }