From c9a4c6c73cd3149b754210555e9eeb28d97cc86a Mon Sep 17 00:00:00 2001
From: bubnikv <bubnikv@gmail.com>
Date: Fri, 16 Nov 2018 18:28:50 +0100
Subject: [PATCH] Partial update of 3D scene (GLVolumes derived from Model and
 from the SLAPrintObject). Updated PrintBase to generate a unique timestamp at
 each step invalidation.

---
 src/libslic3r/Model.cpp                     |  48 ++++
 src/libslic3r/Model.hpp                     |   6 +
 src/libslic3r/Print.cpp                     |  78 +-----
 src/libslic3r/Print.hpp                     |   4 -
 src/libslic3r/PrintBase.cpp                 |  11 +-
 src/libslic3r/PrintBase.hpp                 | 187 ++++++++-----
 src/libslic3r/PrintObject.cpp               |  21 +-
 src/libslic3r/SLAPrint.cpp                  |   8 +-
 src/libslic3r/SLAPrint.hpp                  |   5 -
 src/slic3r/GUI/3DScene.cpp                  | 258 +++++++++---------
 src/slic3r/GUI/3DScene.hpp                  |  52 ++--
 src/slic3r/GUI/BackgroundSlicingProcess.cpp |  72 ++---
 src/slic3r/GUI/BackgroundSlicingProcess.hpp |   7 +-
 src/slic3r/GUI/GLCanvas3D.cpp               | 279 +++++++++++++++++---
 src/slic3r/GUI/GLCanvas3D.hpp               |   7 +-
 15 files changed, 670 insertions(+), 373 deletions(-)

diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp
index c9a097aea..0800b94fc 100644
--- a/src/libslic3r/Model.cpp
+++ b/src/libslic3r/Model.cpp
@@ -1467,4 +1467,52 @@ Transform3d ModelInstance::get_matrix(bool dont_translate, bool dont_rotate, boo
 }
 #endif // !ENABLE_MODELVOLUME_TRANSFORM
 
+#ifdef _DEBUG
+// Verify whether the IDs of Model / ModelObject / ModelVolume / ModelInstance / ModelMaterial are valid and unique.
+void check_model_ids_validity(const Model &model)
+{
+    std::set<ModelID> ids;
+    auto check = [&ids](ModelID id) { 
+        assert(id.id > 0);
+        assert(ids.find(id) == ids.end());
+        ids.insert(id);
+    };
+    for (const ModelObject *model_object : model.objects) {
+        check(model_object->id());
+        for (const ModelVolume *model_volume : model_object->volumes)
+            check(model_volume->id());
+        for (const ModelInstance *model_instance : model_object->instances)
+            check(model_instance->id());
+    }
+    for (const auto mm : model.materials)
+        check(mm.second->id());
+}
+
+void check_model_ids_equal(const Model &model1, const Model &model2)
+{
+    // Verify whether the IDs of model1 and model match.
+    assert(model1.objects.size() == model2.objects.size());
+    for (size_t idx_model = 0; idx_model < model2.objects.size(); ++ idx_model) {
+        const ModelObject &model_object1 = *model1.objects[idx_model];
+        const ModelObject &model_object2 = *  model2.objects[idx_model];
+        assert(model_object1.id() == model_object2.id());
+        assert(model_object1.volumes.size() == model_object2.volumes.size());
+        assert(model_object1.instances.size() == model_object2.instances.size());
+        for (size_t i = 0; i < model_object1.volumes.size(); ++ i)
+            assert(model_object1.volumes[i]->id() == model_object2.volumes[i]->id());
+        for (size_t i = 0; i < model_object1.instances.size(); ++ i)
+            assert(model_object1.instances[i]->id() == model_object2.instances[i]->id());
+    }
+    assert(model1.materials.size() == model2.materials.size());
+    {
+        auto it1 = model1.materials.begin();
+        auto it2 = model2.materials.begin();
+        for (; it1 != model1.materials.end(); ++ it1, ++ it2) {
+            assert(it1->first == it2->first); // compare keys
+            assert(it1->second->id() == it2->second->id());
+        }
+    }
+}
+#endif /* _DEBUG */
+
 }
diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp
index 354d71dc4..10abe7dd5 100644
--- a/src/libslic3r/Model.hpp
+++ b/src/libslic3r/Model.hpp
@@ -652,6 +652,12 @@ private:
 #undef MODELBASE_DERIVED_COPY_MOVE_CLONE
 #undef MODELBASE_DERIVED_PRIVATE_COPY_MOVE
 
+#ifdef _DEBUG
+// Verify whether the IDs of Model / ModelObject / ModelVolume / ModelInstance / ModelMaterial are valid and unique.
+void check_model_ids_validity(const Model &model);
+void check_model_ids_equal(const Model &model1, const Model &model2);
+#endif /* _DEBUG */
+
 }
 
 #endif
diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp
index c661da5ca..4e1685e45 100644
--- a/src/libslic3r/Print.cpp
+++ b/src/libslic3r/Print.cpp
@@ -27,7 +27,7 @@ template class PrintState<PrintObjectStep, posCount>;
 
 void Print::clear() 
 {
-	tbb::mutex::scoped_lock lock(this->cancel_mutex());
+	tbb::mutex::scoped_lock lock(this->state_mutex());
     // The following call should stop background processing if it is running.
     this->invalidate_all_steps();
 	for (PrintObject *object : m_objects)
@@ -43,7 +43,7 @@ void Print::reload_object(size_t /* idx */)
 {
 	ModelObjectPtrs model_objects;
 	{
-		tbb::mutex::scoped_lock lock(this->cancel_mutex());
+		tbb::mutex::scoped_lock lock(this->state_mutex());
         // The following call should stop background processing if it is running.
         this->invalidate_all_steps();
 		/* TODO: this method should check whether the per-object config and per-material configs
@@ -271,8 +271,9 @@ bool Print::is_step_done(PrintObjectStep step) const
 {
     if (m_objects.empty())
         return false;
+	tbb::mutex::scoped_lock lock(this->state_mutex());
     for (const PrintObject *object : m_objects)
-        if (!object->m_state.is_done(step))
+        if (! object->m_state.is_done_unguarded(step))
             return false;
     return true;
 }
@@ -374,7 +375,7 @@ static PrintRegionConfig region_config_from_model_volume(const PrintRegionConfig
 // and have explicit instance positions.
 void Print::add_model_object(ModelObject* model_object, int idx)
 {
-	tbb::mutex::scoped_lock lock(this->cancel_mutex());
+	tbb::mutex::scoped_lock lock(this->state_mutex());
     // Initialize a new print object and store it at the given position.
     PrintObject *object = new PrintObject(this, model_object);
     if (idx != -1) {
@@ -435,7 +436,7 @@ void Print::add_model_object(ModelObject* model_object, int idx)
 
 bool Print::apply_config(DynamicPrintConfig config)
 {
-	tbb::mutex::scoped_lock lock(this->cancel_mutex());
+	tbb::mutex::scoped_lock lock(this->state_mutex());
 
     // we get a copy of the config object so we can modify it safely
     config.normalize();
@@ -734,54 +735,6 @@ static std::vector<PrintInstances> print_objects_from_model_object(const ModelOb
     return std::vector<PrintInstances>(trafos.begin(), trafos.end());
 }
 
-#ifdef _DEBUG
-// Verify whether the IDs of Model / ModelObject / ModelVolume / ModelInstance / ModelMaterial are valid and unique.
-static inline void check_model_ids_validity(const Model &model)
-{
-    std::set<ModelID> ids;
-    auto check = [&ids](ModelID id) { 
-        assert(id.id > 0);
-        assert(ids.find(id) == ids.end());
-        ids.insert(id);
-    };
-    for (const ModelObject *model_object : model.objects) {
-        check(model_object->id());
-        for (const ModelVolume *model_volume : model_object->volumes)
-            check(model_volume->id());
-        for (const ModelInstance *model_instance : model_object->instances)
-            check(model_instance->id());
-    }
-    for (const auto mm : model.materials)
-        check(mm.second->id());
-}
-
-static inline void check_model_ids_equal(const Model &model1, const Model &model2)
-{
-    // Verify whether the IDs of model1 and model match.
-    assert(model1.objects.size() == model2.objects.size());
-    for (size_t idx_model = 0; idx_model < model2.objects.size(); ++ idx_model) {
-        const ModelObject &model_object1 = *model1.objects[idx_model];
-        const ModelObject &model_object2 = *  model2.objects[idx_model];
-        assert(model_object1.id() == model_object2.id());
-        assert(model_object1.volumes.size() == model_object2.volumes.size());
-        assert(model_object1.instances.size() == model_object2.instances.size());
-        for (size_t i = 0; i < model_object1.volumes.size(); ++ i)
-			assert(model_object1.volumes[i]->id() == model_object2.volumes[i]->id());
-        for (size_t i = 0; i < model_object1.instances.size(); ++ i)
-			assert(model_object1.instances[i]->id() == model_object2.instances[i]->id());
-    }
-    assert(model1.materials.size() == model2.materials.size());
-    {
-        auto it1 = model1.materials.begin();
-        auto it2 = model2.materials.begin();
-        for (; it1 != model1.materials.end(); ++ it1, ++ it2) {
-            assert(it1->first == it2->first); // compare keys
-            assert(it1->second->id() == it2->second->id());
-        }
-    }
-}
-#endif /* _DEBUG */
-
 Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &config_in)
 {
 #ifdef _DEBUG
@@ -804,7 +757,7 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co
         update_apply_status(false);
 
     // Grab the lock for the Print / PrintObject milestones.
-	tbb::mutex::scoped_lock lock(this->cancel_mutex());
+	tbb::mutex::scoped_lock lock(this->state_mutex());
 
     // The following call may stop the background processing.
     update_apply_status(this->invalidate_state_by_config_options(print_diff));
@@ -1579,16 +1532,12 @@ void Print::process()
     BOOST_LOG_TRIVIAL(info) << "Staring the slicing process.";
     for (PrintObject *obj : m_objects)
         obj->make_perimeters();
-    this->throw_if_canceled();
     this->set_status(70, "Infilling layers");
     for (PrintObject *obj : m_objects)
         obj->infill();
-    this->throw_if_canceled();
     for (PrintObject *obj : m_objects)
         obj->generate_support_material();
-    this->throw_if_canceled();
-    if (! this->is_step_done(psSkirt)) {
-        this->set_started(psSkirt);
+    if (this->set_started(psSkirt)) {
         m_skirt.clear();
         if (this->has_skirt()) {
             this->set_status(88, "Generating skirt");
@@ -1596,9 +1545,7 @@ void Print::process()
         }
         this->set_done(psSkirt);
     }
-    this->throw_if_canceled();
-	if (! this->is_step_done(psBrim)) {
-        this->set_started(psBrim);
+	if (this->set_started(psBrim)) {
         m_brim.clear();
         if (m_config.brim_width > 0) {
             this->set_status(88, "Generating brim");
@@ -1606,9 +1553,7 @@ void Print::process()
         }
        this->set_done(psBrim);
     }
-    this->throw_if_canceled();
-    if (! this->is_step_done(psWipeTower)) {
-        this->set_started(psWipeTower);
+    if (this->set_started(psWipeTower)) {
         m_wipe_tower_data.clear();
         if (this->has_wipe_tower()) {
             //this->set_status(95, "Generating wipe tower");
@@ -1625,9 +1570,6 @@ void Print::process()
 // It is up to the caller to show an error message.
 void Print::export_gcode(const std::string &path_template, GCodePreviewData *preview_data)
 {
-    // prerequisites
-    this->process();
-    
     // output everything to a G-code file
     // The following call may die if the output_filename_format template substitution fails.
     std::string path = this->output_filepath(path_template);
diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp
index b2f9f9501..0f6c0afbd 100644
--- a/src/libslic3r/Print.hpp
+++ b/src/libslic3r/Print.hpp
@@ -97,8 +97,6 @@ public:
 
     Vec3crd                 size;           // XYZ in scaled coordinates
 
-    const ModelObject*      model_object() const    { return m_model_object; }
-    ModelObject*            model_object()          { return m_model_object; }
     const PrintObjectConfig& config() const         { return m_config; }    
     const LayerPtrs&        layers() const          { return m_layers; }
     const SupportLayerPtrs& support_layers() const  { return m_support_layers; }
@@ -197,7 +195,6 @@ private:
     void combine_infill();
     void _generate_support_material();
 
-    ModelObject                            *m_model_object;
     PrintObjectConfig                       m_config;
     // Translation in Z + Rotation + Scaling / Mirroring.
     Transform3d                             m_trafo = Transform3d::Identity();
@@ -381,7 +378,6 @@ private:
     // Declared here to have access to Model / ModelObject / ModelInstance
     static void         model_volume_list_update_supports(ModelObject &model_object_dst, const ModelObject &model_object_src);
 
-    Model                                   m_model;
     PrintConfig                             m_config;
     PrintObjectConfig                       m_default_object_config;
     PrintRegionConfig                       m_default_region_config;
diff --git a/src/libslic3r/PrintBase.cpp b/src/libslic3r/PrintBase.cpp
index d4fffca94..7ae5b1f9f 100644
--- a/src/libslic3r/PrintBase.cpp
+++ b/src/libslic3r/PrintBase.cpp
@@ -3,9 +3,11 @@
 namespace Slic3r
 {
 
-tbb::mutex& PrintObjectBase::cancel_mutex(PrintBase *print)
+size_t PrintStateBase::g_last_timestamp = 0;
+
+tbb::mutex& PrintObjectBase::state_mutex(PrintBase *print)
 { 
-	return print->cancel_mutex();
+	return print->state_mutex();
 }
 
 std::function<void()> PrintObjectBase::cancel_callback(PrintBase *print)
@@ -13,4 +15,9 @@ std::function<void()> PrintObjectBase::cancel_callback(PrintBase *print)
 	return print->cancel_callback();
 }
 
+void PrintObjectBase::throw_if_canceled(PrintBase *print)
+{ 
+	print->throw_if_canceled();
+}
+
 } // namespace Slic3r
diff --git a/src/libslic3r/PrintBase.hpp b/src/libslic3r/PrintBase.hpp
index 8e9a6693a..67f8c9e86 100644
--- a/src/libslic3r/PrintBase.hpp
+++ b/src/libslic3r/PrintBase.hpp
@@ -2,13 +2,11 @@
 #define slic3r_PrintBase_hpp_
 
 #include "libslic3r.h"
-#include <atomic>
 #include <set>
 #include <vector>
 #include <string>
 #include <functional>
 
-#include "tbb/atomic.h"
 // tbb/mutex.h includes Windows, which in turn defines min/max macros. Convince Windows.h to not define these min/max macros.
 #ifndef NOMINMAX
     #define NOMINMAX
@@ -25,68 +23,117 @@ public:
    const char* what() const throw() { return "Background processing has been canceled"; }
 };
 
-// To be instantiated over PrintStep or PrintObjectStep enums.
-template <class StepType, size_t COUNT>
-class PrintState
-{
+class PrintStateBase {
 public:
-    PrintState() { for (size_t i = 0; i < COUNT; ++ i) m_state[i].store(INVALID, std::memory_order_relaxed); }
-
     enum State {
         INVALID,
         STARTED,
         DONE,
     };
-    
-    // With full memory barrier.
-    bool is_done(StepType step) const { return m_state[step] == DONE; }
+
+    // A new unique timestamp is being assigned to the step every time the step changes its state.
+    struct StateWithTimeStamp
+    {
+        StateWithTimeStamp() : state(INVALID), timestamp(0) {}
+        State   state;
+        size_t  timestamp;
+    };
+
+protected:
+    //FIXME last timestamp is shared between Print & SLAPrint,
+    // and if multiple Print or SLAPrint instances are executed in parallel, modification of g_last_timestamp
+    // is not synchronized!
+    static size_t g_last_timestamp;
+};
+
+// To be instantiated over PrintStep or PrintObjectStep enums.
+template <class StepType, size_t COUNT>
+class PrintState : public PrintStateBase
+{
+public:
+    PrintState() {}
+
+    StateWithTimeStamp state_with_timestamp(StepType step, tbb::mutex &mtx) const { 
+        tbb::mutex::scoped_lock lock(mtx);
+        StateWithTimeStamp state = m_state[step];
+        return state;
+    }
+
+    bool is_done(StepType step, tbb::mutex &mtx) const {
+        return this->state_with_timestamp(step, mtx).state == DONE;
+    }
+
+    StateWithTimeStamp state_with_timestamp_unguarded(StepType step) const { 
+        return m_state[step];
+    }
+
+    bool is_done_unguarded(StepType step) const {
+        return this->state_with_timestamp_unguarded(step).state == DONE;
+    }
 
     // Set the step as started. Block on mutex while the Print / PrintObject / PrintRegion objects are being
     // modified by the UI thread.
     // This is necessary to block until the Print::apply_config() updates its state, which may
     // influence the processing step being entered.
-    void set_started(StepType step, tbb::mutex &mtx) {
-        mtx.lock();
-        m_state[step].store(STARTED, std::memory_order_relaxed);
-        mtx.unlock();
+    template<typename ThrowIfCanceled>
+    bool set_started(StepType step, tbb::mutex &mtx, ThrowIfCanceled throw_if_canceled) {
+        tbb::mutex::scoped_lock lock(mtx);
+        // If canceled, throw before changing the step state.
+        throw_if_canceled();
+        if (m_state[step].state == DONE)
+            return false;
+        m_state[step].state = STARTED;
+        m_state[step].timestamp = ++ g_last_timestamp;
+        return true;
     }
 
     // Set the step as done. Block on mutex while the Print / PrintObject / PrintRegion objects are being
     // modified by the UI thread.
-    void set_done(StepType step, tbb::mutex &mtx) { 
-        mtx.lock();
-        m_state[step].store(DONE, std::memory_order_relaxed);
-        mtx.unlock();
+	template<typename ThrowIfCanceled>
+	void set_done(StepType step, tbb::mutex &mtx, ThrowIfCanceled throw_if_canceled) {
+        tbb::mutex::scoped_lock lock(mtx);
+        // If canceled, throw before changing the step state.
+        throw_if_canceled();
+        assert(m_state[step].state != DONE);
+        m_state[step].state = DONE;
+        m_state[step].timestamp = ++ g_last_timestamp;
     }
 
     // Make the step invalid.
-    // The provided mutex should be locked at this point, guarding access to m_state.
+    // PrintBase::m_state_mutex should be locked at this point, guarding access to m_state.
     // In case the step has already been entered or finished, cancel the background
     // processing by calling the cancel callback.
     template<typename CancelationCallback>
-    bool invalidate(StepType step, tbb::mutex &mtx, CancelationCallback cancel) {
-        bool invalidated = m_state[step].load(std::memory_order_relaxed) != INVALID;
+    bool invalidate(StepType step, CancelationCallback cancel) {
+        bool invalidated = m_state[step].state != INVALID;
         if (invalidated) {
 #if 0
             if (mtx.state != mtx.HELD) {
                 printf("Not held!\n");
             }
 #endif
+            m_state[step].state = INVALID;
+            m_state[step].timestamp = ++ g_last_timestamp;
             // Raise the mutex, so that the following cancel() callback could cancel
             // the background processing.
-            mtx.unlock();
+            // Internally the cancel() callback shall unlock the PrintBase::m_status_mutex to let
+            // the working thread to proceed.
             cancel();
-            m_state[step] = INVALID;
-            mtx.lock();
         }
         return invalidated;
     }
 
     template<typename CancelationCallback, typename StepTypeIterator>
-    bool invalidate_multiple(StepTypeIterator step_begin, StepTypeIterator step_end, tbb::mutex &mtx, CancelationCallback cancel) {
+    bool invalidate_multiple(StepTypeIterator step_begin, StepTypeIterator step_end, CancelationCallback cancel) {
         bool invalidated = false;
-        for (StepTypeIterator it = step_begin; ! invalidated && it != step_end; ++ it)
-            invalidated = m_state[*it].load(std::memory_order_relaxed) != INVALID;
+        for (StepTypeIterator it = step_begin; it != step_end; ++ it) {
+            StateWithTimeStamp &state = m_state[*it];
+            if (state.state != INVALID) {
+                invalidated = true;
+                state.state = INVALID;
+                state.timestamp = ++ g_last_timestamp;
+            }
+        }
         if (invalidated) {
 #if 0
             if (mtx.state != mtx.HELD) {
@@ -95,50 +142,56 @@ public:
 #endif
             // Raise the mutex, so that the following cancel() callback could cancel
             // the background processing.
-            mtx.unlock();
+            // Internally the cancel() callback shall unlock the PrintBase::m_status_mutex to let
+            // the working thread to proceed.
             cancel();
-            for (StepTypeIterator it = step_begin; it != step_end; ++ it)
-                m_state[*it] = INVALID;
-            mtx.lock();
         }
         return invalidated;
     }
 
     // Make all steps invalid.
-    // The provided mutex should be locked at this point, guarding access to m_state.
+    // PrintBase::m_state_mutex should be locked at this point, guarding access to m_state.
     // In case any step has already been entered or finished, cancel the background
     // processing by calling the cancel callback.
     template<typename CancelationCallback>
-    bool invalidate_all(tbb::mutex &mtx, CancelationCallback cancel) {
+    bool invalidate_all(CancelationCallback cancel) {
         bool invalidated = false;
-        for (size_t i = 0; i < COUNT; ++ i)
-            if (m_state[i].load(std::memory_order_relaxed) != INVALID) {
+        for (size_t i = 0; i < COUNT; ++ i) {
+            StateWithTimeStamp &state = m_state[i];
+            if (state.state != INVALID) {
                 invalidated = true;
-                break;
+                state.state = INVALID;
+                state.timestamp = ++ g_last_timestamp;
             }
-        if (invalidated) {
-            mtx.unlock();
-            cancel();
-            for (size_t i = 0; i < COUNT; ++ i)
-                m_state[i].store(INVALID, std::memory_order_relaxed);
-            mtx.lock();
         }
+        if (invalidated)
+            cancel();
         return invalidated;
     }
 
 private:
-    std::atomic<State>          m_state[COUNT];
+    StateWithTimeStamp m_state[COUNT];
 };
 
 class PrintBase;
 
 class PrintObjectBase
 {
+public:
+    const ModelObject*      model_object() const    { return m_model_object; }
+    ModelObject*            model_object()          { return m_model_object; }
+
 protected:
+    PrintObjectBase(ModelObject *model_object) : m_model_object(model_object) {}
     virtual ~PrintObjectBase() {}
     // Declared here to allow access from PrintBase through friendship.
-	static tbb::mutex&            cancel_mutex(PrintBase *print);
+	static tbb::mutex&            state_mutex(PrintBase *print);
 	static std::function<void()>  cancel_callback(PrintBase *print);
+	// If the background processing stop was requested, throw CanceledException.
+	// To be called by the worker thread and its sub-threads (mostly launched on the TBB thread pool) regularly.
+	static void					  throw_if_canceled(PrintBase *print);
+
+    ModelObject                  *m_model_object;
 };
 
 /**
@@ -179,6 +232,7 @@ public:
         APPLY_STATUS_INVALIDATED,
     };
     virtual ApplyStatus     apply(const Model &model, const DynamicPrintConfig &config) = 0;
+    const Model&            model() const { return m_model; }
 
     virtual void            process() = 0;
 
@@ -220,8 +274,9 @@ public:
 
 protected:
 	friend class PrintObjectBase;
+    friend class BackgroundSlicingProcess;
 
-    tbb::mutex&            cancel_mutex() { return m_cancel_mutex; }
+    tbb::mutex&            state_mutex() const { return m_state_mutex; }
     std::function<void()>  cancel_callback() { return m_cancel_callback; }
 	void				   call_cancell_callback() { m_cancel_callback(); }
 
@@ -229,6 +284,8 @@ protected:
     // To be called by the worker thread and its sub-threads (mostly launched on the TBB thread pool) regularly.
     void                   throw_if_canceled() const { if (m_cancel_status) throw CanceledException(); }
 
+	Model                                   m_model;
+
 private:
     tbb::atomic<CancelStatus>               m_cancel_status;
     // Callback to be evoked regularly to update state of the UI thread.
@@ -240,27 +297,28 @@ private:
     // Mutex used for synchronization of the worker thread with the UI thread:
     // The mutex will be used to guard the worker thread against entering a stage
     // while the data influencing the stage is modified.
-    mutable tbb::mutex                      m_cancel_mutex;
+    mutable tbb::mutex                      m_state_mutex;
 };
 
 template<typename PrintStepEnum, const size_t COUNT>
 class PrintBaseWithState : public PrintBase
 {
 public:
-    bool            is_step_done(PrintStepEnum step) const { return m_state.is_done(step); }
+    bool            is_step_done(PrintStepEnum step) const { return m_state.is_done(step, this->state_mutex()); }
+	PrintStateBase::StateWithTimeStamp step_state_with_timestamp(PrintStepEnum step) const { return m_state.state_with_timestamp(step, this->state_mutex()); }
 
 protected:
-    void            set_started(PrintStepEnum step) { m_state.set_started(step, this->cancel_mutex()); throw_if_canceled(); }
-    void            set_done(PrintStepEnum step) { m_state.set_done(step, this->cancel_mutex()); throw_if_canceled(); }
+    bool            set_started(PrintStepEnum step) { return m_state.set_started(step, this->state_mutex(), [this](){ this->throw_if_canceled(); }); }
+	void            set_done(PrintStepEnum step) { m_state.set_done(step, this->state_mutex(), [this](){ this->throw_if_canceled(); }); }
     bool            invalidate_step(PrintStepEnum step)
-		{ return m_state.invalidate(step, this->cancel_mutex(), this->cancel_callback()); }
+		{ return m_state.invalidate(step, this->cancel_callback()); }
     template<typename StepTypeIterator>
     bool            invalidate_steps(StepTypeIterator step_begin, StepTypeIterator step_end) 
-        { return m_state.invalidate_multiple(step_begin, step_end, this->cancel_mutex(), this->cancel_callback()); }
+        { return m_state.invalidate_multiple(step_begin, step_end, this->cancel_callback()); }
     bool            invalidate_steps(std::initializer_list<PrintStepEnum> il) 
-        { return m_state.invalidate_multiple(il.begin(), il.end(), this->cancel_mutex(), this->cancel_callback()); }
+        { return m_state.invalidate_multiple(il.begin(), il.end(), this->cancel_callback()); }
     bool            invalidate_all_steps() 
-        { return m_state.invalidate_all(this->cancel_mutex(), this->cancel_callback()); }
+        { return m_state.invalidate_all(this->cancel_callback()); }
 
 private:
     PrintState<PrintStepEnum, COUNT> m_state;
@@ -273,22 +331,27 @@ public:
     PrintType*       print()         { return m_print; }
     const PrintType* print() const   { return m_print; }
 
-    bool            is_step_done(PrintObjectStepEnum step) const { return m_state.is_done(step); }
+    typedef PrintState<PrintObjectStepEnum, COUNT> PrintObjectState;
+    bool            is_step_done(PrintObjectStepEnum step) const { return m_state.is_done(step, PrintObjectBase::state_mutex(m_print)); }
+    PrintStateBase::StateWithTimeStamp step_state_with_timestamp(PrintObjectStepEnum step) const { return m_state.state_with_timestamp(step, PrintObjectBase::state_mutex(m_print)); }
 
 protected:
-	PrintObjectBaseWithState(PrintType *print) : m_print(print) {}
+	PrintObjectBaseWithState(PrintType *print, ModelObject *model_object) : PrintObjectBase(model_object), m_print(print) {}
 
-    void            set_started(PrintObjectStepEnum step) { m_state.set_started(step, PrintObjectBase::cancel_mutex(m_print)); }
-    void            set_done(PrintObjectStepEnum step) { m_state.set_done(step, PrintObjectBase::cancel_mutex(m_print)); }
+    bool            set_started(PrintObjectStepEnum step) 
+        { return m_state.set_started(step, PrintObjectBase::state_mutex(m_print), [this](){ PrintObjectBase::throw_if_canceled(this->m_print); }); }
+	void            set_done(PrintObjectStepEnum step) 
+        { m_state.set_done(step, PrintObjectBase::state_mutex(m_print), [this](){ PrintObjectBase::throw_if_canceled(this->m_print); }); }
 
     bool            invalidate_step(PrintObjectStepEnum step)
-        { return m_state.invalidate(step, PrintObjectBase::cancel_mutex(m_print), PrintObjectBase::cancel_callback(m_print)); }
+        { return m_state.invalidate(step, PrintObjectBase::cancel_callback(m_print)); }
     template<typename StepTypeIterator>
     bool            invalidate_steps(StepTypeIterator step_begin, StepTypeIterator step_end) 
-        { return m_state.invalidate_multiple(step_begin, step_end, PrintObjectBase::cancel_mutex(m_print), PrintObjectBase::cancel_callback(m_print)); }
+        { return m_state.invalidate_multiple(step_begin, step_end, PrintObjectBase::cancel_callback(m_print)); }
     bool            invalidate_steps(std::initializer_list<PrintObjectStepEnum> il) 
-        { return m_state.invalidate_multiple(il.begin(), il.end(), PrintObjectBase::cancel_mutex(m_print), PrintObjectBase::cancel_callback(m_print)); }
-    bool            invalidate_all_steps() { return m_state.invalidate_all(PrintObjectBase::cancel_mutex(m_print), PrintObjectBase::cancel_callback(m_print)); }
+        { return m_state.invalidate_multiple(il.begin(), il.end(), PrintObjectBase::cancel_callback(m_print)); }
+    bool            invalidate_all_steps() 
+        { return m_state.invalidate_all(PrintObjectBase::cancel_callback(m_print)); }
 
 protected:
     friend PrintType;
diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp
index a71fa194f..371e62ff9 100644
--- a/src/libslic3r/PrintObject.cpp
+++ b/src/libslic3r/PrintObject.cpp
@@ -35,9 +35,8 @@
 namespace Slic3r {
 
 PrintObject::PrintObject(Print* print, ModelObject* model_object) :
-    PrintObjectBaseWithState(print),
+    PrintObjectBaseWithState(print, model_object),
     typed_slices(false),
-    m_model_object(model_object),
     size(Vec3crd::Zero()),
     layer_height_profile_valid(false)
 {
@@ -103,9 +102,8 @@ bool PrintObject::set_copies(const Points &points)
 // this should be idempotent
 void PrintObject::slice()
 {
-    if (this->is_step_done(posSlice))
+    if (! this->set_started(posSlice))
         return;
-    this->set_started(posSlice);
     m_print->set_status(10, "Processing triangulated mesh");
     this->_slice();
     m_print->throw_if_canceled();
@@ -131,10 +129,9 @@ void PrintObject::make_perimeters()
     // prerequisites
     this->slice();
 
-    if (this->is_step_done(posPerimeters))
+    if (! this->set_started(posPerimeters))
         return;
 
-    this->set_started(posPerimeters);
     m_print->set_status(20, "Generating perimeters");
     BOOST_LOG_TRIVIAL(info) << "Generating perimeters...";
     
@@ -242,10 +239,9 @@ void PrintObject::make_perimeters()
 
 void PrintObject::prepare_infill()
 {
-    if (this->is_step_done(posPrepareInfill))
+    if (! this->set_started(posPrepareInfill))
         return;
 
-    this->set_started(posPrepareInfill);
     m_print->set_status(30, "Preparing infill");
 
     // This will assign a type (top/bottom/internal) to $layerm->slices.
@@ -361,8 +357,7 @@ void PrintObject::infill()
     // prerequisites
     this->prepare_infill();
 
-    if (! this->is_step_done(posInfill)) {
-        this->set_started(posInfill);        
+    if (this->set_started(posInfill)) {
         BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - start";
         tbb::parallel_for(
             tbb::blocked_range<size_t>(0, m_layers.size()),
@@ -384,8 +379,7 @@ void PrintObject::infill()
 
 void PrintObject::generate_support_material()
 {
-    if (! this->is_step_done(posSupportMaterial)) {
-        this->set_started(posSupportMaterial);
+    if (this->set_started(posSupportMaterial)) {
         this->clear_support_layers();
         if ((m_config.support_material || m_config.raft_layers > 0) && m_layers.size() > 1) {
             m_print->set_status(85, "Generating support material");    
@@ -1706,9 +1700,8 @@ void PrintObject::_simplify_slices(double distance)
 
 void PrintObject::_make_perimeters()
 {
-    if (this->is_step_done(posPerimeters))
+    if (! this->set_started(posPerimeters))
         return;
-    this->set_started(posPerimeters);
 
     BOOST_LOG_TRIVIAL(info) << "Generating perimeters...";
     
diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp
index 6b6455e18..5a57679fb 100644
--- a/src/libslic3r/SLAPrint.cpp
+++ b/src/libslic3r/SLAPrint.cpp
@@ -61,7 +61,7 @@ const std::array<std::string, slapsCount> PRINT_STEP_LABELS =
 
 void SLAPrint::clear()
 {
-	tbb::mutex::scoped_lock lock(this->cancel_mutex());
+	tbb::mutex::scoped_lock lock(this->state_mutex());
     // The following call should stop background processing if it is running.
     this->invalidate_all_steps();
 
@@ -76,7 +76,7 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model,
 //		return APPLY_STATUS_UNCHANGED;
 
     // Grab the lock for the Print / PrintObject milestones.
-	tbb::mutex::scoped_lock lock(this->cancel_mutex());
+	tbb::mutex::scoped_lock lock(this->state_mutex());
     if(m_objects.empty() && model.objects.empty())
         return APPLY_STATUS_UNCHANGED;
 
@@ -409,11 +409,9 @@ void SLAPrint::process()
 }
 
 SLAPrintObject::SLAPrintObject(SLAPrint *print, ModelObject *model_object):
-    Inherited(print),
-    m_model_object(model_object),
+    Inherited(print, model_object),
     m_stepmask(slaposCount, true)
 {
-
 }
 
 SLAPrintObject::~SLAPrintObject() {}
diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp
index a1e317abc..e27fac7d4 100644
--- a/src/libslic3r/SLAPrint.hpp
+++ b/src/libslic3r/SLAPrint.hpp
@@ -35,8 +35,6 @@ private: // Prevents erroneous use by other classes.
     using Inherited = _SLAPrintObjectBase;
 
 public:
-    const ModelObject*      model_object() const    { return m_model_object; }
-    ModelObject*            model_object()          { return m_model_object; }
     const Transform3d&      trafo()        const    { return m_trafo; }
 
     struct Instance {
@@ -78,8 +76,6 @@ protected:
     bool                    invalidate_step(SLAPrintObjectStep step);
 
 private:
-	// Points to the instance owned by a Model stored at the parent SLAPrint instance.
-    ModelObject                            *m_model_object;
     // Object specific configuration, pulled from the configuration layer.
     SLAPrintObjectConfig                    m_config;
     // Translation in Z + Rotation by Y and Z + Scaling / Mirroring.
@@ -138,7 +134,6 @@ private:
     using SLAPrinter = FilePrinter<FilePrinterFormat::SLA_PNGZIP>;
     using SLAPrinterPtr = std::unique_ptr<SLAPrinter>;
 
-    Model                           m_model;
     SLAPrinterConfig                m_printer_config;
     SLAMaterialConfig               m_material_config;
     PrintObjects                    m_objects;
diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp
index 45c700482..16b7e022b 100644
--- a/src/slic3r/GUI/3DScene.cpp
+++ b/src/slic3r/GUI/3DScene.cpp
@@ -98,12 +98,18 @@ void GLIndexedVertexArray::finalize_geometry(bool use_VBOs)
 
 void GLIndexedVertexArray::release_geometry()
 {
-    if (this->vertices_and_normals_interleaved_VBO_id)
+    if (this->vertices_and_normals_interleaved_VBO_id) {
         glDeleteBuffers(1, &this->vertices_and_normals_interleaved_VBO_id);
-    if (this->triangle_indices_VBO_id)
+        this->vertices_and_normals_interleaved_VBO_id = 0;
+    }
+    if (this->triangle_indices_VBO_id) {
         glDeleteBuffers(1, &this->triangle_indices_VBO_id);
-    if (this->quad_indices_VBO_id)
+        this->triangle_indices_VBO_id = 0;
+    }
+    if (this->quad_indices_VBO_id) {
         glDeleteBuffers(1, &this->quad_indices_VBO_id);
+        this->quad_indices_VBO_id = 0;
+    }
     this->clear();
     this->shrink_to_fit();
 }
@@ -210,9 +216,8 @@ GLVolume::GLVolume(float r, float g, float b, float a)
 #endif // ENABLE_MODELVOLUME_TRANSFORM
     , m_transformed_convex_hull_bounding_box_dirty(true)
     , m_convex_hull(nullptr)
-    , object_id(-1)
-    , volume_id(-1)
-    , instance_id(-1)
+    // geometry_id == 0 -> invalid
+    , geometry_id(std::pair<size_t, size_t>(0, 0))
     , extruder_id(0)
     , selected(false)
     , disabled(false)
@@ -705,6 +710,25 @@ std::vector<int> GLVolumeCollection::load_object(
     const std::vector<int>  &instance_idxs,
     const std::string       &color_by,
     bool                     use_VBOs)
+{
+    // Object will share a single common layer height texture between all printable volumes.
+    std::shared_ptr<LayersTexture> layer_height_texture = std::make_shared<LayersTexture>();
+    std::vector<int> volumes_idx;
+    for (int volume_idx = 0; volume_idx < int(model_object->volumes.size()); ++ volume_idx)
+        for (int instance_idx : instance_idxs)
+			volumes_idx.emplace_back(this->GLVolumeCollection::load_object_volume(model_object, layer_height_texture, obj_idx, volume_idx, instance_idx, color_by, use_VBOs));
+    return volumes_idx; 
+}
+
+int GLVolumeCollection::load_object_volume(
+	const ModelObject              *model_object,
+    // Layer height texture is shared between all printable volumes of a single ModelObject.
+	std::shared_ptr<LayersTexture> &layer_height_texture,
+    int                             obj_idx,
+    int                             volume_idx,
+    int                             instance_idx,
+    const std::string              &color_by,
+    bool                            use_VBOs)
 {
     static float colors[4][4] = {
         { 1.0f, 1.0f, 0.0f, 1.f }, 
@@ -713,132 +737,112 @@ std::vector<int> GLVolumeCollection::load_object(
         { 0.5f, 0.5f, 1.0f, 1.f }
     };
 
-    // Object will have a single common layer height texture for all volumes.
-    std::shared_ptr<LayersTexture> layer_height_texture = std::make_shared<LayersTexture>();
+    const ModelVolume *model_volume = model_object->volumes[volume_idx];
 
-    std::vector<int> volumes_idx;
-    for (int volume_idx = 0; volume_idx < int(model_object->volumes.size()); ++ volume_idx) {
-        const ModelVolume *model_volume = model_object->volumes[volume_idx];
-
-        int extruder_id = -1;
-        if (model_volume->is_model_part())
-        {
-            extruder_id = model_volume->config.has("extruder") ? model_volume->config.option("extruder")->getInt() : 0;
-            if (extruder_id == 0)
-                extruder_id = model_object->config.has("extruder") ? model_object->config.option("extruder")->getInt() : 0;
-        }
-
-        for (int instance_idx : instance_idxs) {
-            const ModelInstance *instance = model_object->instances[instance_idx];
-#if ENABLE_MODELVOLUME_TRANSFORM
-            const TriangleMesh& mesh = model_volume->mesh;
-#else
-            TriangleMesh mesh = model_volume->mesh;
-#endif // ENABLE_MODELVOLUME_TRANSFORM
-            volumes_idx.push_back(int(this->volumes.size()));
-            float color[4];
-            memcpy(color, colors[((color_by == "volume") ? volume_idx : obj_idx) % 4], sizeof(float) * 3);
-            if (model_volume->is_support_blocker()) {
-                color[0] = 1.0f;
-                color[1] = 0.2f;
-                color[2] = 0.2f;
-            } else if (model_volume->is_support_enforcer()) {
-                color[0] = 0.2f;
-                color[1] = 0.2f;
-                color[2] = 1.0f;
-            }
-            color[3] = model_volume->is_model_part() ? 1.f : 0.5f;
-            this->volumes.emplace_back(new GLVolume(color));
-            GLVolume &v = *this->volumes.back();
-            if (use_VBOs)
-                v.indexed_vertex_array.load_mesh_full_shading(mesh);
-            else
-                v.indexed_vertex_array.load_mesh_flat_shading(mesh);
-
-            // finalize_geometry() clears the vertex arrays, therefore the bounding box has to be computed before finalize_geometry().
-            v.bounding_box = v.indexed_vertex_array.bounding_box();
-            v.indexed_vertex_array.finalize_geometry(use_VBOs);
-            v.object_id    = obj_idx;
-            v.volume_id    = volume_idx;
-            v.instance_id  = instance_idx;
-            if (model_volume->is_model_part())
-            {
-                v.set_convex_hull(model_volume->get_convex_hull());
-                v.layer_height_texture = layer_height_texture;
-                if (extruder_id != -1)
-                    v.extruder_id = extruder_id;
-            }
-            v.is_modifier = ! model_volume->is_model_part();
-            v.shader_outside_printer_detection_enabled = model_volume->is_model_part();
-#if ENABLE_MODELVOLUME_TRANSFORM
-            v.set_instance_transformation(instance->get_transformation());
-            v.set_volume_transformation(model_volume->get_transformation());
-#else
-            v.set_offset(instance->get_offset());
-            v.set_rotation(instance->get_rotation());
-            v.set_scaling_factor(instance->get_scaling_factor());
-            v.set_mirror(instance->get_mirror());
-#endif // ENABLE_MODELVOLUME_TRANSFORM
-        }
+    int extruder_id = -1;
+    if (model_volume->is_model_part())
+    {
+        const ConfigOption *opt = model_volume->config.option("extruder");
+        if (opt == nullptr)
+            opt = model_object->config.option("extruder");
+        extruder_id = (opt == nullptr) ? 0 : opt->getInt();
     }
-    
-    return volumes_idx; 
+
+    const ModelInstance *instance = model_object->instances[instance_idx];
+#if ENABLE_MODELVOLUME_TRANSFORM
+    const TriangleMesh& mesh = model_volume->mesh;
+#else
+    TriangleMesh mesh = model_volume->mesh;
+#endif // ENABLE_MODELVOLUME_TRANSFORM
+    float color[4];
+    memcpy(color, colors[((color_by == "volume") ? volume_idx : obj_idx) % 4], sizeof(float) * 3);
+    if (model_volume->is_support_blocker()) {
+        color[0] = 1.0f;
+        color[1] = 0.2f;
+        color[2] = 0.2f;
+    } else if (model_volume->is_support_enforcer()) {
+        color[0] = 0.2f;
+        color[1] = 0.2f;
+        color[2] = 1.0f;
+    }
+    color[3] = model_volume->is_model_part() ? 1.f : 0.5f;
+    this->volumes.emplace_back(new GLVolume(color));
+    GLVolume &v = *this->volumes.back();
+    if (use_VBOs)
+        v.indexed_vertex_array.load_mesh_full_shading(mesh);
+    else
+        v.indexed_vertex_array.load_mesh_flat_shading(mesh);
+
+    // finalize_geometry() clears the vertex arrays, therefore the bounding box has to be computed before finalize_geometry().
+    v.bounding_box = v.indexed_vertex_array.bounding_box();
+    v.indexed_vertex_array.finalize_geometry(use_VBOs);
+	v.composite_id = GLVolume::CompositeID(obj_idx, volume_idx, instance_idx);
+    if (model_volume->is_model_part())
+    {
+        v.set_convex_hull(model_volume->get_convex_hull());
+        if (extruder_id != -1)
+            v.extruder_id = extruder_id;
+        v.layer_height_texture = layer_height_texture;
+    }
+    v.is_modifier = ! model_volume->is_model_part();
+    v.shader_outside_printer_detection_enabled = model_volume->is_model_part();
+#if ENABLE_MODELVOLUME_TRANSFORM
+    v.set_instance_transformation(instance->get_transformation());
+    v.set_volume_transformation(model_volume->get_transformation());
+#else
+    v.set_offset(instance->get_offset());
+    v.set_rotation(instance->get_rotation());
+    v.set_scaling_factor(instance->get_scaling_factor());
+    v.set_mirror(instance->get_mirror());
+#endif // ENABLE_MODELVOLUME_TRANSFORM
+
+    return int(this->volumes.size() - 1); 
 }
 
 // Load SLA auxiliary GLVolumes (for support trees or pad).
-std::vector<int> GLVolumeCollection::load_object_auxiliary(
-    const ModelObject       *model_object,
-    const SLAPrintObject    *print_object,
-    int                      obj_idx,
-    SLAPrintObjectStep       milestone,
-    bool                     use_VBOs)
+// This function produces volumes for multiple instances in a single shot,
+// as some object specific mesh conversions may be expensive.
+void GLVolumeCollection::load_object_auxiliary(
+    const SLAPrintObject           *print_object,
+    int                             obj_idx,
+    // pairs of <instance_idx, print_instance_idx>
+    const std::vector<std::pair<size_t, size_t>> &instances,
+    SLAPrintObjectStep              milestone,
+    bool                            use_VBOs)
 {
-    std::vector<int> volumes_idx;
-    // Find the SLAPrintObject's instance to it.
-    if (print_object->is_step_done(milestone)) {
-        // Get the support mesh.
-        TriangleMesh mesh;
-        switch (milestone) {
-        case slaposSupportTree: mesh = print_object->support_mesh(); break;
-        case slaposBasePool:    mesh = print_object->pad_mesh();     break;
-        default:
-            assert(false);
-        }
-		// Convex hull is required for out of print bed detection.
-		TriangleMesh convex_hull = mesh.convex_hull_3d();
-        const std::vector<SLAPrintObject::Instance> &instances = print_object->instances();
-        std::map<ModelID, int> map_instances;
-        for (int i = 0; i < (int)model_object->instances.size(); ++ i)
-            map_instances[model_object->instances[i]->id()] = i;
-        for (const SLAPrintObject::Instance &instance : instances) {
-            auto model_instance_it = map_instances.find(instance.instance_id);
-            assert(model_instance_it != map_instances.end());
-            const int instance_idx = model_instance_it->second;
-            const ModelInstance *model_instance = model_object->instances[instance_idx];
-            volumes_idx.push_back(int(this->volumes.size()));
-            float color[4] { 0.f, 0.f, 1.f, 1.f };
-            this->volumes.emplace_back(new GLVolume(color));
-            GLVolume &v = *this->volumes.back();
-            if (use_VBOs)
-                v.indexed_vertex_array.load_mesh_full_shading(mesh);
-            else
-                v.indexed_vertex_array.load_mesh_flat_shading(mesh);
-            // finalize_geometry() clears the vertex arrays, therefore the bounding box has to be computed before finalize_geometry().
-            v.bounding_box = v.indexed_vertex_array.bounding_box();
-            v.indexed_vertex_array.finalize_geometry(use_VBOs);
-            v.object_id    = obj_idx;
-            v.volume_id    = -1; // SLA supports
-            v.instance_id  = instance_idx;
-			v.set_convex_hull(convex_hull);
-            v.is_modifier  = false;
-            v.shader_outside_printer_detection_enabled = true;
-			v.set_instance_transformation(model_instance->get_transformation());
-			// Leave the volume transformation at identity.
-            // v.set_volume_transformation(model_volume->get_transformation());
-        }
+    assert(print_object->is_step_done(milestone));
+    // Get the support mesh.
+    TriangleMesh mesh;
+    switch (milestone) {
+    case slaposSupportTree: mesh = print_object->support_mesh(); break;
+    case slaposBasePool:    mesh = print_object->pad_mesh();     break;
+    default:
+        assert(false);
+    }
+	// Convex hull is required for out of print bed detection.
+	TriangleMesh convex_hull = mesh.convex_hull_3d();
+    for (const std::pair<size_t, size_t> &instance_idx : instances) {
+        const ModelInstance            &model_instance = *print_object->model_object()->instances[instance_idx.first];
+        const SLAPrintObject::Instance &print_instance = print_object->instances()[instance_idx.second];
+        float color[4] { 0.f, 0.f, 1.f, 1.f };
+        this->volumes.emplace_back(new GLVolume(color));
+        GLVolume &v = *this->volumes.back();
+        if (use_VBOs)
+            v.indexed_vertex_array.load_mesh_full_shading(mesh);
+        else
+            v.indexed_vertex_array.load_mesh_flat_shading(mesh);
+        // finalize_geometry() clears the vertex arrays, therefore the bounding box has to be computed before finalize_geometry().
+        v.bounding_box = v.indexed_vertex_array.bounding_box();
+        v.indexed_vertex_array.finalize_geometry(use_VBOs);
+		v.composite_id = GLVolume::CompositeID(obj_idx, -1, (int)instance_idx.first);
+		v.set_convex_hull(convex_hull);
+        v.is_modifier  = false;
+        v.shader_outside_printer_detection_enabled = true;
+        //FIXME adjust with print_instance?
+		v.set_instance_transformation(model_instance.get_transformation());
+		// Leave the volume transformation at identity.
+        // v.set_volume_transformation(model_volume->get_transformation());
     }
-
-    return volumes_idx; 
 }
 
 int GLVolumeCollection::load_wipe_tower_preview(
@@ -911,9 +915,7 @@ int GLVolumeCollection::load_wipe_tower_preview(
     // finalize_geometry() clears the vertex arrays, therefore the bounding box has to be computed before finalize_geometry().
     v.bounding_box = v.indexed_vertex_array.bounding_box();
     v.indexed_vertex_array.finalize_geometry(use_VBOs);
-    v.object_id    = obj_idx;
-    v.volume_id    = 0;
-    v.instance_id  = 0;
+	v.composite_id = GLVolume::CompositeID(obj_idx, 0, 0);
     v.is_wipe_tower = true;
     v.shader_outside_printer_detection_enabled = ! size_unknown;
     return int(this->volumes.size() - 1);
diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp
index 5d81b57bb..dfeed31a1 100644
--- a/src/slic3r/GUI/3DScene.hpp
+++ b/src/slic3r/GUI/3DScene.hpp
@@ -293,15 +293,25 @@ public:
     float               color[4];
     // Color used to render this volume.
     float               render_color[4];
-    // Object ID, which is equal to the index of the respective ModelObject in Model.objects array.
-    int                 object_id;
-    // Volume ID, which is equal to the index of the respective ModelVolume in ModelObject.volumes array.
-    // If negative, it is an index of a geometry produced by the PrintObject for the respective ModelObject,
-    // and which has no associated ModelVolume in ModelObject.volumes. For example, SLA supports.
-    // Volume with a negative volume_id cannot be picked independently, it will pick the associated instance.
-    int                 volume_id;
-    // Instance ID, which is equal to the index of the respective ModelInstance in ModelObject.instances array.
-    int                 instance_id;
+    struct CompositeID {
+        CompositeID(int object_id, int volume_id, int instance_id) : object_id(object_id), volume_id(volume_id), instance_id(instance_id) {}
+        CompositeID() : object_id(-1), volume_id(-1), instance_id(-1) {}
+        // Object ID, which is equal to the index of the respective ModelObject in Model.objects array.
+        int             object_id;
+        // Volume ID, which is equal to the index of the respective ModelVolume in ModelObject.volumes array.
+        // If negative, it is an index of a geometry produced by the PrintObject for the respective ModelObject,
+        // and which has no associated ModelVolume in ModelObject.volumes. For example, SLA supports.
+        // Volume with a negative volume_id cannot be picked independently, it will pick the associated instance.
+        int             volume_id;
+        // Instance ID, which is equal to the index of the respective ModelInstance in ModelObject.instances array.
+        int             instance_id;
+    };
+    CompositeID         composite_id;
+    // Fingerprint of the source geometry. For ModelVolumes, it is the ModelVolume::ID and ModelInstanceID, 
+    // for generated volumes it is the timestamp generated by PrintState::invalidate() or PrintState::set_done(),
+    // and the associated ModelInstanceID.
+    // Valid geometry_id should always be positive.
+    std::pair<size_t, size_t> geometry_id;
     // An ID containing the extruder ID (used to select color).
     int                 extruder_id;
     // Is this object selected?
@@ -414,9 +424,9 @@ public:
 
     void set_convex_hull(const TriangleMesh& convex_hull);
 
-    int                 object_idx() const { return this->object_id; }
-    int                 volume_idx() const { return this->volume_id; }
-    int                 instance_idx() const { return this->instance_id; }
+    int                 object_idx() const { return this->composite_id.object_id; }
+    int                 volume_idx() const { return this->composite_id.volume_id; }
+    int                 instance_idx() const { return this->composite_id.instance_id; }
 
 #if ENABLE_MODELVOLUME_TRANSFORM
     Transform3d world_matrix() const { return m_instance_transformation.get_matrix() * m_volume_transformation.get_matrix(); }
@@ -499,14 +509,24 @@ public:
         const std::string       &color_by,
         bool                     use_VBOs);
 
-    // Load SLA auxiliary GLVolumes (for support trees or pad).
-    std::vector<int> load_object_auxiliary(
+    int load_object_volume(
         const ModelObject       *model_object,
-        const SLAPrintObject    *print_object,
+        std::shared_ptr<LayersTexture> &layer_height_texture,
         int                      obj_idx,
-        SLAPrintObjectStep       milestone,
+        int                      volume_idx,
+        int                      instance_idx,
+        const std::string       &color_by,
         bool                     use_VBOs);
 
+    // Load SLA auxiliary GLVolumes (for support trees or pad).
+    void GLVolumeCollection::load_object_auxiliary(
+        const SLAPrintObject           *print_object,
+        int                             obj_idx,
+        // pairs of <instance_idx, print_instance_idx>
+        const std::vector<std::pair<size_t, size_t>> &instances,
+        SLAPrintObjectStep              milestone,
+        bool                            use_VBOs);
+
     int load_wipe_tower_preview(
         int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs, bool size_unknown, float brim_width);
 
diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp
index 5dcef44a1..5c3c24201 100644
--- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp
+++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp
@@ -60,24 +60,21 @@ void BackgroundSlicingProcess::process_fff()
 {
 	assert(m_print == m_fff_print);
     m_print->process();
-    if (! m_print->canceled()) {
-        wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_sliced_id));
-	    m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data);
-	    if (! m_print->canceled() && ! this->is_step_done(bspsGCodeFinalize)) {
-	    	this->set_step_started(bspsGCodeFinalize);
-	    	if (! m_export_path.empty()) {
-	    		//FIXME localize the messages
-		    	if (copy_file(m_temp_output_path, m_export_path) != 0)
-	    			throw std::runtime_error("Copying of the temporary G-code to the output G-code failed");
-	    		m_print->set_status(95, "Running post-processing scripts");
-	    		run_post_process_scripts(m_export_path, m_fff_print->config());
-	    		m_print->set_status(100, "G-code file exported to " + m_export_path);
-	    	} else {
-	    		m_print->set_status(100, "Slicing complete");
-	    	}
-			this->set_step_done(bspsGCodeFinalize);
+	wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_sliced_id));
+	m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data);
+	if (this->set_step_started(bspsGCodeFinalize)) {
+	    if (! m_export_path.empty()) {
+	    	//FIXME localize the messages
+		    if (copy_file(m_temp_output_path, m_export_path) != 0)
+	    		throw std::runtime_error("Copying of the temporary G-code to the output G-code failed");
+	    	m_print->set_status(95, "Running post-processing scripts");
+	    	run_post_process_scripts(m_export_path, m_fff_print->config());
+	    	m_print->set_status(100, "G-code file exported to " + m_export_path);
+	    } else {
+	    	m_print->set_status(100, "Slicing complete");
 	    }
-    }
+		this->set_step_done(bspsGCodeFinalize);
+	}
 }
 
 // Pseudo type for specializing LayerWriter trait class
@@ -120,11 +117,11 @@ public:
     }
 };
 
-void BackgroundSlicingProcess::process_sla() {
+void BackgroundSlicingProcess::process_sla()
+{
     assert(m_print == m_sla_print);
     m_print->process();
-    if(!m_print->canceled() && ! this->is_step_done(bspsGCodeFinalize)) {
-        this->set_step_started(bspsGCodeFinalize);
+    if (this->set_step_started(bspsGCodeFinalize)) {
         if (! m_export_path.empty()) {
             m_sla_print->export_raster<SLAZipFmt>(m_export_path);
             m_print->set_status(100, "Zip file exported to " + m_export_path);
@@ -246,6 +243,7 @@ bool BackgroundSlicingProcess::start()
 
 bool BackgroundSlicingProcess::stop()
 {
+	// m_print->state_mutex() shall NOT be held. Unfortunately there is no interface to test for it.
 	std::unique_lock<std::mutex> lck(m_mutex);
 	if (m_state == STATE_INITIAL) {
 //		this->m_export_path.clear();
@@ -282,12 +280,23 @@ bool BackgroundSlicingProcess::reset()
 // This function shall not trigger any UI update through the wxWidgets event.
 void BackgroundSlicingProcess::stop_internal()
 {
+	// m_print->state_mutex() shall be held. Unfortunately there is no interface to test for it.
+	if (m_state == STATE_IDLE)
+		// The worker thread is waiting on m_mutex/m_condition for wake up. The following lock of the mutex would block.
+		return;
 	std::unique_lock<std::mutex> lck(m_mutex);
 	assert(m_state == STATE_STARTED || m_state == STATE_RUNNING || m_state == STATE_FINISHED || m_state == STATE_CANCELED);
 	if (m_state == STATE_STARTED || m_state == STATE_RUNNING) {
+		// At this point of time the worker thread may be blocking on m_print->state_mutex().
+		// Set the print state to canceled before unlocking the state_mutex(), so when the worker thread wakes up,
+		// it throws the CanceledException().
 		m_print->cancel_internal();
+		// Allow the worker thread to wake up if blocking on a milestone.
+		m_print->state_mutex().unlock();
 		// Wait until the background processing stops by being canceled.
 		m_condition.wait(lck, [this](){ return m_state == STATE_CANCELED; });
+		// Lock it back to be in a consistent state.
+		m_print->state_mutex().lock();
 	}
 	// In the "Canceled" state. Reset the state to "Idle".
 	m_state = STATE_IDLE;
@@ -324,7 +333,7 @@ void BackgroundSlicingProcess::schedule_export(const std::string &path)
 		return;
 
 	// Guard against entering the export step before changing the export path.
-	tbb::mutex::scoped_lock lock(m_step_state_mutex);
+	tbb::mutex::scoped_lock lock(m_print->state_mutex());
 	this->invalidate_step(bspsGCodeFinalize);
 	m_export_path = path;
 }
@@ -335,34 +344,35 @@ void BackgroundSlicingProcess::reset_export()
 	if (! this->running()) {
 		m_export_path.clear();
 		// invalidate_step expects the mutex to be locked.
-		tbb::mutex::scoped_lock lock(m_step_state_mutex);
+		tbb::mutex::scoped_lock lock(m_print->state_mutex());
 		this->invalidate_step(bspsGCodeFinalize);
 	}
 }
 
-void BackgroundSlicingProcess::set_step_started(BackgroundSlicingProcessStep step)
+bool BackgroundSlicingProcess::set_step_started(BackgroundSlicingProcessStep step)
 { 
-	m_step_state.set_started(step, m_step_state_mutex);
-	if (m_print->canceled())
-		throw CanceledException();
+	return m_step_state.set_started(step, m_print->state_mutex(), [this](){ this->throw_if_canceled(); });
 }
 
 void BackgroundSlicingProcess::set_step_done(BackgroundSlicingProcessStep step)
 { 
-	m_step_state.set_done(step, m_step_state_mutex);
-	if (m_print->canceled())
-		throw CanceledException();
+	m_step_state.set_done(step, m_print->state_mutex(), [this](){ this->throw_if_canceled(); });
+}
+
+bool BackgroundSlicingProcess::is_step_done(BackgroundSlicingProcessStep step) const
+{ 
+	return m_step_state.is_done(step, m_print->state_mutex());
 }
 
 bool BackgroundSlicingProcess::invalidate_step(BackgroundSlicingProcessStep step)
 {
-    bool invalidated = m_step_state.invalidate(step, m_step_state_mutex, [this](){ this->stop(); });
+    bool invalidated = m_step_state.invalidate(step, [this](){ this->stop_internal(); });
     return invalidated;
 }
 
 bool BackgroundSlicingProcess::invalidate_all_steps()
 { 
-	return m_step_state.invalidate_all(m_step_state_mutex, [this](){ this->stop(); });
+	return m_step_state.invalidate_all([this](){ this->stop_internal(); });
 }
 
 }; // namespace Slic3r
diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/src/slic3r/GUI/BackgroundSlicingProcess.hpp
index 92bc512d7..ea24b0eda 100644
--- a/src/slic3r/GUI/BackgroundSlicingProcess.hpp
+++ b/src/slic3r/GUI/BackgroundSlicingProcess.hpp
@@ -125,11 +125,14 @@ private:
 
     PrintState<BackgroundSlicingProcessStep, bspsCount>   	m_step_state;
     mutable tbb::mutex                      				m_step_state_mutex;
-	void                set_step_started(BackgroundSlicingProcessStep step);
+	bool                set_step_started(BackgroundSlicingProcessStep step);
 	void                set_step_done(BackgroundSlicingProcessStep step);
-    bool 				is_step_done(BackgroundSlicingProcessStep step) const { return m_step_state.is_done(step); }
+	bool 				is_step_done(BackgroundSlicingProcessStep step) const;
 	bool                invalidate_step(BackgroundSlicingProcessStep step);
     bool                invalidate_all_steps();
+    // If the background processing stop was requested, throw CanceledException.
+    void                throw_if_canceled() const { if (m_print->canceled()) throw CanceledException(); }
+
 
 	// wxWidgets command ID to be sent to the platter to inform that the slicing is finished, and the G-code export will continue.
 	int 						m_event_sliced_id 	 = 0;
diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp
index c7a1bad5b..a0a2c108b 100644
--- a/src/slic3r/GUI/GLCanvas3D.cpp
+++ b/src/slic3r/GUI/GLCanvas3D.cpp
@@ -1390,6 +1390,47 @@ void GLCanvas3D::Selection::clear()
     m_bounding_box_dirty = true;
 }
 
+// Update the selection based on the map from old indices to new indices after m_volumes changed.
+// If the current selection is by instance, this call may select newly added volumes, if they belong to already selected instances.
+void GLCanvas3D::Selection::volumes_changed(const std::vector<size_t> &map_volume_old_to_new)
+{
+    assert(m_valid);
+
+    // 1) Update the selection set.
+    IndicesList list_new;
+    std::vector<std::pair<unsigned int, unsigned int>> model_instances;
+    for (unsigned int idx : m_list) {
+		if (map_volume_old_to_new[idx] != size_t(-1)) {
+			unsigned int new_idx = (unsigned int)map_volume_old_to_new[idx];
+			list_new.insert(new_idx);
+			if (m_mode == Instance) {
+                // Save the object_idx / instance_idx pair of selected old volumes,
+                // so we may add the newly added volumes of the same object_idx / instance_idx pair
+                // to the selection.
+				const GLVolume *volume = (*m_volumes)[new_idx];
+				model_instances.emplace_back(volume->object_idx(), volume->instance_idx());
+			}
+        }
+    }
+	m_list = std::move(list_new);
+
+    if (! model_instances.empty()) {
+        // Instance selection mode. Add the newly added volumes of the same object_idx / instance_idx pair
+        // to the selection.
+        assert(m_mode == Instance);
+        sort_remove_duplicates(model_instances);
+        for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++ i) {
+			const GLVolume* volume = (*m_volumes)[i];
+            for (const std::pair<int, int> &model_instance : model_instances)
+				if (volume->object_idx() == model_instance.first && volume->instance_idx() == model_instance.second)
+                    this->_add_volume(i);
+        }
+    }
+
+    _update_type();
+    m_bounding_box_dirty = true;
+}
+
 bool GLCanvas3D::Selection::is_single_full_instance() const
 {
     if (m_type == SingleFullInstance)
@@ -2042,10 +2083,6 @@ void GLCanvas3D::Selection::_set_caches()
 
 void GLCanvas3D::Selection::_add_volume(unsigned int volume_idx)
 {
-    // check if the given idx is already selected
-    if (m_list.find(volume_idx) != m_list.end())
-        return;
-
     m_list.insert(volume_idx);
     (*m_volumes)[volume_idx]->selected = true;
 }
@@ -3656,13 +3693,6 @@ std::vector<int> GLCanvas3D::load_object(const Model& model, int obj_idx)
     return std::vector<int>();
 }
 
-std::vector<int> GLCanvas3D::load_support_meshes(const Model& model, int obj_idx)
-{
-    std::vector<int> volumes = m_volumes.load_object_auxiliary(model.objects[obj_idx], m_sla_print->objects()[obj_idx], obj_idx, slaposSupportTree, m_use_VBOs && m_initialized);
-	append(volumes, m_volumes.load_object_auxiliary(model.objects[obj_idx], m_sla_print->objects()[obj_idx], obj_idx, slaposBasePool, m_use_VBOs && m_initialized));
-    return volumes;
-}
-
 void GLCanvas3D::mirror_selection(Axis axis)
 {
     m_selection.mirror(axis);
@@ -3670,6 +3700,12 @@ void GLCanvas3D::mirror_selection(Axis axis)
     wxGetApp().obj_manipul()->update_settings_value(m_selection);
 }
 
+// Reload the 3D scene of 
+// 1) Model / ModelObjects / ModelInstances / ModelVolumes
+// 2) Print bed
+// 3) SLA support meshes for their respective ModelObjects / ModelInstances
+// 4) Wipe tower preview
+// 5) Out of bed collision status & message overlay (texture)
 void GLCanvas3D::reload_scene(bool force)
 {
     if ((m_canvas == nullptr) || (m_config == nullptr) || (m_model == nullptr))
@@ -3680,40 +3716,208 @@ void GLCanvas3D::reload_scene(bool force)
         return;
 #endif // !ENABLE_USE_UNIQUE_GLCONTEXT
 
-    if (m_regenerate_volumes)
-    {
-        reset_volumes();
+    struct ModelVolumeState {
+        ModelVolumeState(const GLVolume *volume) : 
+			geometry_id(volume->geometry_id), volume_idx(-1) {}
+		ModelVolumeState(const ModelID &volume_id, const ModelID &instance_id, const GLVolume::CompositeID &composite_id) :
+			geometry_id(std::make_pair(volume_id.id, instance_id.id)), composite_id(composite_id), volume_idx(-1) {}
+		ModelVolumeState(const ModelID &volume_id, const ModelID &instance_id) :
+			geometry_id(std::make_pair(volume_id.id, instance_id.id)), volume_idx(-1) {}
+		bool new_geometry() const { return this->volume_idx == size_t(-1); }
+        // ModelID of ModelVolume + ModelID of ModelInstance
+        // or timestamp of an SLAPrintObjectStep + ModelID of ModelInstance
+        std::pair<size_t, size_t>   geometry_id;
+        GLVolume::CompositeID       composite_id;
+        // Volume index in the new GLVolume vector.
+		size_t                      volume_idx;
+    };
+    std::vector<ModelVolumeState> model_volume_state;
+	std::vector<ModelVolumeState> aux_volume_state;
 
-        // to update the toolbar
-        post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT));
-    }
+    // SLA steps to pull the preview meshes for.
+	typedef std::array<SLAPrintObjectStep, 2> SLASteps;
+	SLASteps sla_steps = { slaposSupportTree, slaposBasePool };
+    struct SLASupportState {
+		std::array<PrintStateBase::StateWithTimeStamp, std::tuple_size<SLASteps>::value> step;
+    };
+    // State of the sla_steps for all SLAPrintObjects.
+    std::vector<SLASupportState>   sla_support_state;
 
-    set_bed_shape(dynamic_cast<const ConfigOptionPoints*>(m_config->option("bed_shape"))->values);
+    std::vector<size_t> map_glvolume_old_to_new(m_volumes.volumes.size(), size_t(-1));
+    std::vector<GLVolume*> glvolumes_new;
+    glvolumes_new.reserve(m_volumes.volumes.size());
+    auto model_volume_state_lower = [](const ModelVolumeState &m1, const ModelVolumeState &m2) { return m1.geometry_id < m2.geometry_id; };
 
-    if (!m_canvas->IsShown() && !force)
-    {
-        m_reload_delayed = true;
-        return;
-    }
-
-    m_reload_delayed = false;
+    m_reload_delayed = ! m_canvas->IsShown() && ! force;
 
     PrinterTechnology printer_technology = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology();
+
     if (m_regenerate_volumes)
     {
-        for (unsigned int obj_idx = 0; obj_idx < (unsigned int)m_model->objects.size(); ++obj_idx)
-        {
-            load_object(*m_model, obj_idx);
-            if (printer_technology == ptSLA)
-                load_support_meshes(*m_model, obj_idx);
+        // Release invalidated volumes to conserve GPU memory in case of delayed refresh (see m_reload_delayed).
+        // First initialize model_volumes_new_sorted & model_instances_new_sorted.
+        for (int object_idx = 0; object_idx < (int)m_model->objects.size(); ++ object_idx) {
+            const ModelObject *model_object = m_model->objects[object_idx];
+            for (int instance_idx = 0; instance_idx < (int)model_object->instances.size(); ++ instance_idx) {
+                const ModelInstance *model_instance = model_object->instances[instance_idx];
+                for (int volume_idx = 0; volume_idx < (int)model_object->volumes.size(); ++ volume_idx) {
+                    const ModelVolume *model_volume = model_object->volumes[volume_idx];
+					model_volume_state.emplace_back(model_volume->id(), model_instance->id(), GLVolume::CompositeID(object_idx, volume_idx, instance_idx));
+                }
+            }
+        }
+        if (printer_technology == ptSLA) {
+        #ifdef _DEBUG
+            // Verify that the SLAPrint object is synchronized with m_model.
+            check_model_ids_equal(*m_model, m_sla_print->model());
+        #endif /* _DEBUG */
+            sla_support_state.reserve(m_sla_print->objects().size());
+            for (const SLAPrintObject *print_object : m_sla_print->objects()) {
+                SLASupportState state;
+				for (size_t istep = 0; istep < sla_steps.size(); ++ istep) {
+					state.step[istep] = print_object->step_state_with_timestamp(sla_steps[istep]);
+					if (state.step[istep].state == PrintStateBase::DONE)
+						for (const ModelInstance *model_instance : print_object->model_object()->instances)
+                            aux_volume_state.emplace_back(state.step[istep].timestamp, model_instance->id());
+				}
+				sla_support_state.emplace_back(state);
+            }
+        }
+        std::sort(model_volume_state.begin(), model_volume_state.end(), model_volume_state_lower);
+        std::sort(aux_volume_state  .begin(), aux_volume_state  .end(), model_volume_state_lower);
+        // Release all ModelVolume based GLVolumes not found in the current Model.
+        for (size_t volume_id = 0; volume_id < m_volumes.volumes.size(); ++ volume_id) {
+            GLVolume         *volume = m_volumes.volumes[volume_id];
+            ModelVolumeState  key(volume);
+            ModelVolumeState *mvs = nullptr;
+            if (volume->volume_idx() < 0) {
+				auto it = std::lower_bound(aux_volume_state.begin(), aux_volume_state.end(), key, model_volume_state_lower);
+                if (it != aux_volume_state.end() && it->geometry_id == key.geometry_id)
+                    mvs = &(*it);
+            } else {
+				auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower);
+                if (it != model_volume_state.end() && it->geometry_id == key.geometry_id)
+					mvs = &(*it);
+            }
+            if (mvs == nullptr) {
+                // This GLVolume will be released.
+                volume->release_geometry();
+                if (! m_reload_delayed)
+                    delete volume;
+            } else {
+                // This GLVolume will be reused.
+                map_glvolume_old_to_new[volume_id] = glvolumes_new.size();
+                mvs->volume_idx = glvolumes_new.size();
+                glvolumes_new.emplace_back(volume);
+            }
         }
     }
 
-    _update_gizmos_data();
+    if (m_reload_delayed)
+        return;
+
+    set_bed_shape(dynamic_cast<const ConfigOptionPoints*>(m_config->option("bed_shape"))->values);
 
     if (m_regenerate_volumes)
     {
-        PrinterTechnology printer_technology = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology();
+        m_volumes.volumes = std::move(glvolumes_new);
+        for (unsigned int obj_idx = 0; obj_idx < (unsigned int)m_model->objects.size(); ++ obj_idx) {
+            const ModelObject &model_object = *m_model->objects[obj_idx];
+            // Object will share a single common layer height texture between all printable volumes.
+            std::shared_ptr<LayersTexture> layer_height_texture;
+            for (int volume_idx = 0; volume_idx < (int)model_object.volumes.size(); ++ volume_idx) {
+				const ModelVolume &model_volume = *model_object.volumes[volume_idx];
+                for (int instance_idx = 0; instance_idx < (int)model_object.instances.size(); ++ instance_idx) {
+					const ModelInstance &model_instance = *model_object.instances[instance_idx];
+					ModelVolumeState key(model_volume.id(), model_instance.id());
+					auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower);
+					assert(it != model_volume_state.end() && it->geometry_id == key.geometry_id);
+                    if (it->new_geometry()) {
+                        // New volume.
+						if (model_volume.is_model_part() && ! layer_height_texture) {
+                            // New object part needs to have the layer height texture assigned, which is shared with the other volumes of the same part.
+                            // Search for the layer height texture in the other volumes.
+                            for (int iv = volume_idx; iv < (int)model_object.volumes.size(); ++ iv) {
+								const ModelVolume &mv = *model_object.volumes[iv];
+								if (mv.is_model_part())
+									for (int ii = instance_idx; ii < (int)model_object.instances.size(); ++ ii) {
+										const ModelInstance &mi = *model_object.instances[ii];
+										ModelVolumeState key(mv.id(), mi.id());
+										auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower);
+										assert(it != model_volume_state.end() && it->geometry_id == key.geometry_id);
+										if (! it->new_geometry()) {
+											// Found an old printable GLVolume (existing before this function was called).
+                                            assert(m_volumes.volumes[it->volume_idx]->geometry_id == key.geometry_id);
+											// Reuse the layer height texture.
+											const GLVolume *volume = m_volumes.volumes[it->volume_idx];
+											assert(volume->layer_height_texture);
+											layer_height_texture = volume->layer_height_texture;
+											goto iv_end;
+										}
+									}
+							}
+                        iv_end:
+                            if (! layer_height_texture)
+                                layer_height_texture = std::make_shared<LayersTexture>();
+                        }
+                        m_volumes.load_object_volume(&model_object, layer_height_texture, obj_idx, volume_idx, instance_idx, m_color_by, m_use_VBOs && m_initialized);
+						m_volumes.volumes.back()->geometry_id = key.geometry_id;
+                    } else {
+						// Recycling an old GLVolume.
+						GLVolume &existing_volume = *m_volumes.volumes[it->volume_idx];
+                        assert(existing_volume.geometry_id == key.geometry_id);
+						// Update the Object/Volume/Instance indices into the current Model.
+                        existing_volume.composite_id = it->composite_id;
+						if (model_volume.is_model_part() && ! layer_height_texture) {
+                            assert(existing_volume.layer_height_texture);
+                            // cache its layer height texture
+                            layer_height_texture = existing_volume.layer_height_texture;
+                        }
+                    }
+                }
+            }
+        }
+        if (printer_technology == ptSLA) {
+            size_t idx = 0;
+            for (const SLAPrintObject *print_object : m_sla_print->objects()) {
+                SLASupportState   &state        = sla_support_state[idx ++];
+                const ModelObject *model_object = print_object->model_object();
+                // Find an index of the ModelObject
+                int object_idx;
+				if (! std::all_of(state.step.begin(), state.step.end(), [](const PrintStateBase::StateWithTimeStamp &state){ return state.state != PrintStateBase::DONE; }))
+					continue;
+                // There may be new SLA volumes added to the scene for this print_object.
+                // Find the object index of this print_object in the Model::objects list.
+                auto it = std::find(m_sla_print->model().objects.begin(), m_sla_print->model().objects.end(), model_object);
+                assert(it != m_sla_print->model().objects.end());
+				object_idx = it - m_sla_print->model().objects.begin();
+                // Collect indices of this print_object's instances, for which the SLA support meshes are to be added to the scene.
+                // pairs of <instance_idx, print_instance_idx>
+				std::vector<std::pair<size_t, size_t>> instances[std::tuple_size<SLASteps>::value];
+                for (size_t print_instance_idx = 0; print_instance_idx < print_object->instances().size(); ++ print_instance_idx) {
+                    const SLAPrintObject::Instance &instance = print_object->instances()[print_instance_idx];
+                    // Find index of ModelInstance corresponding to this SLAPrintObject::Instance.
+					auto it = std::find_if(model_object->instances.begin(), model_object->instances.end(), 
+                        [&instance](const ModelInstance *mi) { return mi->id() == instance.instance_id; });
+                    assert(it != model_object->instances.end());
+                    int instance_idx = it - model_object->instances.begin();
+                    for (size_t istep = 0; istep < sla_steps.size(); ++ istep)
+                        if (state.step[istep].state == PrintStateBase::DONE) {
+                            ModelVolumeState key(state.step[istep].timestamp, instance.instance_id.id);
+                            auto it = std::lower_bound(aux_volume_state.begin(), aux_volume_state.end(), key, model_volume_state_lower);
+                            assert(it != aux_volume_state.end() && it->geometry_id == key.geometry_id);
+                            if (it->new_geometry())
+                                instances[istep].emplace_back(std::pair<size_t, size_t>(instance_idx, print_instance_idx));
+                            else
+								// Recycling an old GLVolume. Update the Object/Instance indices into the current Model.
+                                m_volumes.volumes[it->volume_idx]->composite_id = GLVolume::CompositeID(object_idx, -1, instance_idx);
+                        }
+                }
+                for (size_t istep = 0; istep < sla_steps.size(); ++ istep)
+					m_volumes.load_object_auxiliary(print_object, object_idx, instances[istep], sla_steps[istep], m_use_VBOs && m_initialized);
+            }
+        }
+
         if (printer_technology == ptFFF && m_config->has("nozzle_diameter"))
         {
             // Should the wipe tower be visualized ?
@@ -3742,7 +3946,14 @@ void GLCanvas3D::reload_scene(bool force)
         }
 
         update_volumes_colors_by_extruder();
-    }
+		// Update selection indices based on the old/new GLVolumeCollection.
+		m_selection.volumes_changed(map_glvolume_old_to_new);
+	}
+
+    _update_gizmos_data();
+
+    // Update the toolbar
+    post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT));
 
     // checks for geometry outside the print volume to render it accordingly
     if (!m_volumes.empty())
@@ -3773,6 +3984,8 @@ void GLCanvas3D::reload_scene(bool force)
 
     // restore to default value
     m_regenerate_volumes = true;
+    // and force this canvas to be redrawn.
+    m_dirty = true;
 }
 
 void GLCanvas3D::load_gcode_preview(const GCodePreviewData& preview_data, const std::vector<std::string>& str_tool_colors)
diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp
index fd4fa1953..bb09d636f 100644
--- a/src/slic3r/GUI/GLCanvas3D.hpp
+++ b/src/slic3r/GUI/GLCanvas3D.hpp
@@ -493,6 +493,9 @@ public:
         void add_volume(unsigned int object_idx, unsigned int volume_idx, int instance_idx, bool as_single_selection = true);
         void remove_volume(unsigned int object_idx, unsigned int volume_idx);
 
+        // Update the selection based on the map from old indices to new indices after m_volumes changed.
+        // If the current selection is by instance, this call may select newly added volumes, if they belong to already selected instances.
+        void volumes_changed(const std::vector<size_t> &map_volume_old_to_new);
         void clear();
 
         bool is_empty() const { return m_type == Empty; }
@@ -706,6 +709,7 @@ private:
     SLAPrint* m_sla_print;
     Model* m_model;
 
+    // Screen is only refreshed from the OnIdle handler if it is dirty.
     bool m_dirty;
     bool m_initialized;
     bool m_use_VBOs;
@@ -820,9 +824,6 @@ public:
     std::vector<int> load_object(const ModelObject& model_object, int obj_idx, std::vector<int> instance_idxs);
     std::vector<int> load_object(const Model& model, int obj_idx);
 
-    // Load SLA support tree and SLA pad meshes into the scene, if available at the respective SLAPrintObject instances.
-    std::vector<int> load_support_meshes(const Model& model, int obj_idx);
-
     void mirror_selection(Axis axis);
 
     void reload_scene(bool force);