diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp
index 103c1f777..aaafd333d 100644
--- a/src/slic3r/GUI/GCodeViewer.cpp
+++ b/src/slic3r/GUI/GCodeViewer.cpp
@@ -79,6 +79,20 @@ static float round_to_nearest(float value, unsigned int decimals)
     return res;
 }
 
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+#if ENABLE_SPLITTED_VERTEX_BUFFER
+void GCodeViewer::VBuffer::reset()
+{
+    // release gpu memory
+    if (!ids.empty()) {
+        glsafe(::glDeleteBuffers(static_cast<GLsizei>(ids.size()), static_cast<const GLuint*>(ids.data())));
+        ids.clear();
+    }
+
+    count = 0;
+}
+#else
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
 void GCodeViewer::VBuffer::reset()
 {
     // release gpu memory
@@ -89,15 +103,35 @@ void GCodeViewer::VBuffer::reset()
 
     count = 0;
 }
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+#endif // ENABLE_SPLITTED_VERTEX_BUFFER
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
 
 void GCodeViewer::IBuffer::reset()
 {
     // release gpu memory
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+#if ENABLE_SPLITTED_VERTEX_BUFFER
+    // release gpu memory
+    if (ibo > 0) {
+        glsafe(::glDeleteBuffers(1, &ibo));
+        ibo = 0;
+    }
+#else
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
     if (id > 0) {
         glsafe(::glDeleteBuffers(1, &id));
         id = 0;
     }
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+#endif // ENABLE_SPLITTED_VERTEX_BUFFER
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
 
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+#if ENABLE_SPLITTED_VERTEX_BUFFER
+    vbo = 0;
+#endif // ENABLE_SPLITTED_VERTEX_BUFFER
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
     count = 0;
 }
 
@@ -119,6 +153,21 @@ bool GCodeViewer::Path::matches(const GCodeProcessor::MoveVertex& move) const
     case EMoveType::Unretract:
     case EMoveType::Extrude: {
         // use rounding to reduce the number of generated paths
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+#if ENABLE_SPLITTED_VERTEX_BUFFER
+#if ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE
+        return type == move.type && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id && role == move.extrusion_role &&
+            move.position[2] <= sub_paths.front().first.position[2] && feedrate == move.feedrate && fan_speed == move.fan_speed &&
+            height == round_to_nearest(move.height, 2) && width == round_to_nearest(move.width, 2) &&
+            matches_percent(volumetric_rate, move.volumetric_rate(), 0.05f);
+#else
+        return type == move.type && move.position[2] <= sub_paths.front().position[2] && role == move.extrusion_role && height == round_to_nearest(move.height, 2) &&
+            width == round_to_nearest(move.width, 2) && feedrate == move.feedrate && fan_speed == move.fan_speed &&
+            volumetric_rate == round_to_nearest(move.volumetric_rate(), 2) && extruder_id == move.extruder_id &&
+            cp_color_id == move.cp_color_id;
+#endif // ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE
+#else
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
 #if ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE
         return type == move.type && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id && role == move.extrusion_role &&
             move.position[2] <= first.position[2] && feedrate == move.feedrate && fan_speed == move.fan_speed &&
@@ -130,6 +179,9 @@ bool GCodeViewer::Path::matches(const GCodeProcessor::MoveVertex& move) const
             volumetric_rate == round_to_nearest(move.volumetric_rate(), 2) && extruder_id == move.extruder_id &&
             cp_color_id == move.cp_color_id;
 #endif // ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+#endif // ENABLE_SPLITTED_VERTEX_BUFFER
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
     }
     case EMoveType::Travel: {
         return type == move.type && feedrate == move.feedrate && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id;
@@ -156,6 +208,19 @@ void GCodeViewer::TBuffer::add_path(const GCodeProcessor::MoveVertex& move, unsi
 {
     Path::Endpoint endpoint = { b_id, i_id, s_id, move.position };
     // use rounding to reduce the number of generated paths
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+#if ENABLE_SPLITTED_VERTEX_BUFFER
+#if ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE
+    paths.push_back({ move.type, move.extrusion_role, move.delta_extruder,
+        round_to_nearest(move.height, 2), round_to_nearest(move.width, 2), move.feedrate, move.fan_speed,
+        move.volumetric_rate(), move.extruder_id, move.cp_color_id, { { endpoint, endpoint } } });
+#else
+    paths.push_back({ move.type, move.extrusion_role, move.delta_extruder,
+        round_to_nearest(move.height, 2), round_to_nearest(move.width, 2), move.feedrate, move.fan_speed,
+        round_to_nearest(move.volumetric_rate(), 2), move.extruder_id, move.cp_color_id, { { endpoint, endpoint } } });
+#endif // ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE
+#else
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
 #if ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE
     paths.push_back({ move.type, move.extrusion_role, endpoint, endpoint, move.delta_extruder,
         round_to_nearest(move.height, 2), round_to_nearest(move.width, 2), move.feedrate, move.fan_speed,
@@ -165,6 +230,9 @@ void GCodeViewer::TBuffer::add_path(const GCodeProcessor::MoveVertex& move, unsi
         round_to_nearest(move.height, 2), round_to_nearest(move.width, 2), move.feedrate, move.fan_speed,
         round_to_nearest(move.volumetric_rate(), 2), move.extruder_id, move.cp_color_id });
 #endif // ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+#endif // ENABLE_SPLITTED_VERTEX_BUFFER
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
 }
 
 GCodeViewer::Color GCodeViewer::Extrusions::Range::get_color_at(float value) const
@@ -319,21 +387,18 @@ GCodeViewer::GCodeViewer()
         case EMoveType::Pause_Print:
         case EMoveType::Custom_GCode:
         case EMoveType::Retract:
-        case EMoveType::Unretract:
-        {
+        case EMoveType::Unretract: {
             buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Point;
             buffer.vertices.format = VBuffer::EFormat::Position;
             break;
         }
         case EMoveType::Wipe:
-        case EMoveType::Extrude:
-        {
+        case EMoveType::Extrude: {
             buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Triangle;
             buffer.vertices.format = VBuffer::EFormat::PositionNormal3;
             break;
         }
-        case EMoveType::Travel:
-        {
+        case EMoveType::Travel: {
             buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Line;
             buffer.vertices.format = VBuffer::EFormat::PositionNormal1;
             break;
@@ -357,6 +422,7 @@ void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print&
     reset();
 
     load_toolpaths(gcode_result);
+
     if (m_layers.empty())
         return;
 
@@ -526,7 +592,8 @@ void GCodeViewer::render() const
         // initializes opengl data of TBuffers
         for (size_t i = 0; i < m_buffers.size(); ++i) {
             TBuffer& buffer = m_buffers[i];
-            switch (buffer_type(i)) {
+            switch (buffer_type(i))
+            {
             default: { break; }
             case EMoveType::Tool_change:
             case EMoveType::Color_change:
@@ -590,8 +657,16 @@ void GCodeViewer::update_sequential_view_current(unsigned int first, unsigned in
         for (const TBuffer& buffer : m_buffers) {
             if (buffer.visible) {
                 for (const Path& path : buffer.paths) {
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+#if ENABLE_SPLITTED_VERTEX_BUFFER
+                    if (path.sub_paths.front().first.s_id <= id && id <= path.sub_paths.back().last.s_id)
+#else
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
                     if (path.first.s_id <= id && id <= path.last.s_id)
-                        return true;
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+#endif // ENABLE_SPLITTED_VERTEX_BUFFER
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+                    return true;
                 }
             }
         }
@@ -694,6 +769,10 @@ void GCodeViewer::set_layers_z_range(const std::array<unsigned int, 2>& layers_z
 
 void GCodeViewer::export_toolpaths_to_obj(const char* filename) const
 {
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+#if !ENABLE_SPLITTED_VERTEX_BUFFER
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+
     if (filename == nullptr)
         return;
 
@@ -820,6 +899,7 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const
         // get paths segments from buffer paths
         const IndexBuffer& ibuffer = indices[render_path.index_buffer_id];
         const Path& path = buffer.paths[render_path.path_id];
+
         float half_width = 0.5f * path.width;
         // clamp height to avoid artifacts due to z-fighting when importing the obj file into blender and similar
         float half_height = std::max(0.5f * path.height, 0.005f);
@@ -987,8 +1067,720 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const
     }
 
     fclose(fp);
+
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+#endif // !ENABLE_SPLITTED_VERTEX_BUFFER
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
 }
 
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+#if ENABLE_SPLITTED_VERTEX_BUFFER
+void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
+{
+    auto log_memory_usage = [this](const std::string& label, const std::vector<MultiVertexBuffer>& vertices, const std::vector<MultiIndexBuffer>& indices) {
+        int64_t vertices_size = 0;
+        for (const MultiVertexBuffer& v_multibuffer : vertices) {
+            for (const VertexBuffer& v_buffer : v_multibuffer) {
+                vertices_size += SLIC3R_STDVEC_MEMSIZE(v_buffer, float);
+            }
+        }
+        int64_t indices_size = 0;
+        for (const MultiIndexBuffer& i_multibuffer : indices) {
+            for (const IndexBuffer& i_buffer : i_multibuffer) {
+                indices_size += SLIC3R_STDVEC_MEMSIZE(i_buffer, unsigned int);
+            }
+        }
+        log_memory_used(label, vertices_size + indices_size);
+    };
+
+    // format data into the buffers to be rendered as points
+    auto add_vertices_as_point = [](const GCodeProcessor::MoveVertex& curr, VertexBuffer& vertices) {
+        vertices.push_back(curr.position[0]);
+        vertices.push_back(curr.position[1]);
+        vertices.push_back(curr.position[2]);
+    };
+    auto add_indices_as_point = [](const GCodeProcessor::MoveVertex& curr, TBuffer& buffer,
+        unsigned int i_buffer_id, IndexBuffer& indices, size_t move_id) {
+            buffer.add_path(curr, i_buffer_id, indices.size(), move_id);
+            indices.push_back(static_cast<unsigned int>(indices.size()));
+    };
+
+    // format data into the buffers to be rendered as lines
+    auto add_vertices_as_line = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, VertexBuffer& vertices) {
+        // x component of the normal to the current segment (the normal is parallel to the XY plane)
+        float normal_x = (curr.position - prev.position).normalized()[1];
+
+        auto add_vertex = [&vertices, normal_x](const GCodeProcessor::MoveVertex& vertex) {
+            // add position
+            vertices.push_back(vertex.position[0]);
+            vertices.push_back(vertex.position[1]);
+            vertices.push_back(vertex.position[2]);
+            // add normal x component
+            vertices.push_back(normal_x);
+        };
+
+        // add previous vertex
+        add_vertex(prev);
+        // add current vertex
+        add_vertex(curr);
+    };
+    auto add_indices_as_line = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, TBuffer& buffer,
+        unsigned int i_buffer_id, IndexBuffer& indices, size_t move_id) {
+            if (prev.type != curr.type || !buffer.paths.back().matches(curr)) {
+                // add starting index
+                indices.push_back(static_cast<unsigned int>(indices.size()));
+                buffer.add_path(curr, i_buffer_id, indices.size() - 1, move_id - 1);
+                buffer.paths.back().sub_paths.front().first.position = prev.position;
+            }
+
+            Path& last_path = buffer.paths.back();
+            if (last_path.sub_paths.front().first.i_id != last_path.sub_paths.back().last.i_id) {
+                // add previous index
+                indices.push_back(static_cast<unsigned int>(indices.size()));
+            }
+
+            // add current index
+            indices.push_back(static_cast<unsigned int>(indices.size()));
+            last_path.sub_paths.back().last = { i_buffer_id, indices.size() - 1, move_id, curr.position };
+    };
+
+    // format data into the buffers to be rendered as solid
+    auto add_vertices_as_solid = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, VertexBuffer& vertices, size_t move_id) {
+        static Vec3f prev_dir;
+        static Vec3f prev_up;
+        static float prev_length;
+        auto store_vertex = [](VertexBuffer& vertices, const Vec3f& position, const Vec3f& normal) {
+            // append position
+            vertices.push_back(position[0]);
+            vertices.push_back(position[1]);
+            vertices.push_back(position[2]);
+            // append normal
+            vertices.push_back(normal[0]);
+            vertices.push_back(normal[1]);
+            vertices.push_back(normal[2]);
+        };
+        auto extract_position_at = [](const VertexBuffer& vertices, size_t id) {
+            return Vec3f(vertices[id + 0], vertices[id + 1], vertices[id + 2]);
+        };
+        auto update_position_at = [](VertexBuffer& vertices, size_t id, const Vec3f& position) {
+            vertices[id + 0] = position[0];
+            vertices[id + 1] = position[1];
+            vertices[id + 2] = position[2];
+        };
+
+        if (prev.type != curr.type || !buffer.paths.back().matches(curr)) {
+            buffer.add_path(curr, 0, 0, move_id - 1);
+            buffer.paths.back().sub_paths.back().first.position = prev.position;
+        }
+
+        unsigned int starting_vertices_size = static_cast<unsigned int>(vertices.size() / buffer.vertices.vertex_size_floats());
+
+        Vec3f dir = (curr.position - prev.position).normalized();
+        Vec3f right = (std::abs(std::abs(dir.dot(Vec3f::UnitZ())) - 1.0f) < EPSILON) ? -Vec3f::UnitY() : Vec3f(dir[1], -dir[0], 0.0f).normalized();
+        Vec3f left = -right;
+        Vec3f up = right.cross(dir);
+        Vec3f down = -up;
+
+        Path& last_path = buffer.paths.back();
+
+        float half_width = 0.5f * last_path.width;
+        float half_height = 0.5f * last_path.height;
+
+        Vec3f prev_pos = prev.position - half_height * up;
+        Vec3f curr_pos = curr.position - half_height * up;
+
+        float length = (curr_pos - prev_pos).norm();
+        if (last_path.vertices_count() == 1 || vertices.empty()) {
+            // 1st segment or restart into a new vertex buffer
+            // ===============================================
+
+            // vertices 1st endpoint
+            store_vertex(vertices, prev_pos + half_height * up, up);
+            store_vertex(vertices, prev_pos + half_width * right, right);
+            store_vertex(vertices, prev_pos + half_height * down, down);
+            store_vertex(vertices, prev_pos + half_width * left, left);
+
+            // vertices 2nd endpoint
+            store_vertex(vertices, curr_pos + half_height * up, up);
+            store_vertex(vertices, curr_pos + half_width * right, right);
+            store_vertex(vertices, curr_pos + half_height * down, down);
+            store_vertex(vertices, curr_pos + half_width * left, left);
+        }
+        else {
+            // any other segment
+            // =================
+
+            float displacement = 0.0f;
+            float cos_dir = prev_dir.dot(dir);
+            if (cos_dir > -0.9998477f) {
+                // if the angle between adjacent segments is smaller than 179 degrees
+                Vec3f med_dir = (prev_dir + dir).normalized();
+                displacement = half_width * ::tan(::acos(std::clamp(dir.dot(med_dir), -1.0f, 1.0f)));
+            }
+
+            Vec3f displacement_vec = displacement * prev_dir;
+            bool can_displace = displacement > 0.0f && displacement < prev_length&& displacement < length;
+
+            size_t prev_right_id = (starting_vertices_size - 3) * buffer.vertices.vertex_size_floats();
+            size_t prev_left_id = (starting_vertices_size - 1) * buffer.vertices.vertex_size_floats();
+            Vec3f prev_right_pos = extract_position_at(vertices, prev_right_id);
+            Vec3f prev_left_pos = extract_position_at(vertices, prev_left_id);
+
+            bool is_right_turn = prev_up.dot(prev_dir.cross(dir)) <= 0.0f;
+            // whether the angle between adjacent segments is greater than 45 degrees
+            bool is_sharp = cos_dir < 0.7071068f;
+
+            bool right_displaced = false;
+            bool left_displaced = false;
+
+            // displace the vertex (inner with respect to the corner) of the previous segment 2nd endpoint, if possible
+            if (can_displace) {
+                if (is_right_turn) {
+                    prev_right_pos -= displacement_vec;
+                    update_position_at(vertices, prev_right_id, prev_right_pos);
+                    right_displaced = true;
+                }
+                else {
+                    prev_left_pos -= displacement_vec;
+                    update_position_at(vertices, prev_left_id, prev_left_pos);
+                    left_displaced = true;
+                }
+            }
+
+            if (!is_sharp) {
+                // displace the vertex (outer with respect to the corner) of the previous segment 2nd endpoint, if possible
+                if (can_displace) {
+                    if (is_right_turn) {
+                        prev_left_pos += displacement_vec;
+                        update_position_at(vertices, prev_left_id, prev_left_pos);
+                        left_displaced = true;
+                    }
+                    else {
+                        prev_right_pos += displacement_vec;
+                        update_position_at(vertices, prev_right_id, prev_right_pos);
+                        right_displaced = true;
+                    }
+                }
+
+                // vertices 1st endpoint (top and bottom are from previous segment 2nd endpoint)
+                // vertices position matches that of the previous segment 2nd endpoint, if displaced
+                store_vertex(vertices, right_displaced ? prev_right_pos : prev_pos + half_width * right, right);
+                store_vertex(vertices, left_displaced ? prev_left_pos : prev_pos + half_width * left, left);
+            }
+            else {
+                // vertices 1st endpoint (top and bottom are from previous segment 2nd endpoint)
+                // the inner corner vertex position matches that of the previous segment 2nd endpoint, if displaced
+                if (is_right_turn) {
+                    store_vertex(vertices, right_displaced ? prev_right_pos : prev_pos + half_width * right, right);
+                    store_vertex(vertices, prev_pos + half_width * left, left);
+                }
+                else {
+                    store_vertex(vertices, prev_pos + half_width * right, right);
+                    store_vertex(vertices, left_displaced ? prev_left_pos : prev_pos + half_width * left, left);
+                }
+            }
+
+            // vertices 2nd endpoint
+            store_vertex(vertices, curr_pos + half_height * up, up);
+            store_vertex(vertices, curr_pos + half_width * right, right);
+            store_vertex(vertices, curr_pos + half_height * down, down);
+            store_vertex(vertices, curr_pos + half_width * left, left);
+        }
+
+        last_path.sub_paths.back().last = { 0, 0, move_id, curr.position };
+        prev_dir = dir;
+        prev_up = up;
+        prev_length = length;
+    };
+    auto add_indices_as_solid = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, TBuffer& buffer,
+        size_t& vbuffer_size, unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id) {
+            static Vec3f prev_dir;
+            static Vec3f prev_up;
+            static float prev_length;
+            auto store_triangle = [](IndexBuffer& indices, unsigned int i1, unsigned int i2, unsigned int i3) {
+                indices.push_back(i1);
+                indices.push_back(i2);
+                indices.push_back(i3);
+            };
+            auto append_dummy_cap = [store_triangle](IndexBuffer& indices, unsigned int id) {
+                store_triangle(indices, id, id, id);
+                store_triangle(indices, id, id, id);
+            };
+
+            if (prev.type != curr.type || !buffer.paths.back().matches(curr)) {
+                buffer.add_path(curr, ibuffer_id, indices.size(), move_id - 1);
+                buffer.paths.back().sub_paths.back().first.position = prev.position;
+            }
+
+            Vec3f dir = (curr.position - prev.position).normalized();
+            Vec3f right = (std::abs(std::abs(dir.dot(Vec3f::UnitZ())) - 1.0f) < EPSILON) ? -Vec3f::UnitY() : Vec3f(dir[1], -dir[0], 0.0f).normalized();
+            Vec3f up = right.cross(dir);
+
+            Path& last_path = buffer.paths.back();
+
+            float half_width = 0.5f * last_path.width;
+            float half_height = 0.5f * last_path.height;
+
+            Vec3f prev_pos = prev.position - half_height * up;
+            Vec3f curr_pos = curr.position - half_height * up;
+
+            float length = (curr_pos - prev_pos).norm();
+            if (last_path.vertices_count() == 1 || vbuffer_size == 0) {
+                // 1st segment or restart into a new vertex buffer
+                // ===============================================
+
+                // triangles starting cap
+                store_triangle(indices, vbuffer_size + 0, vbuffer_size + 2, vbuffer_size + 1);
+                store_triangle(indices, vbuffer_size + 0, vbuffer_size + 3, vbuffer_size + 2);
+
+                // dummy triangles outer corner cap
+                append_dummy_cap(indices, vbuffer_size);
+
+                // triangles sides
+                store_triangle(indices, vbuffer_size + 0, vbuffer_size + 1, vbuffer_size + 4);
+                store_triangle(indices, vbuffer_size + 1, vbuffer_size + 5, vbuffer_size + 4);
+                store_triangle(indices, vbuffer_size + 1, vbuffer_size + 2, vbuffer_size + 5);
+                store_triangle(indices, vbuffer_size + 2, vbuffer_size + 6, vbuffer_size + 5);
+                store_triangle(indices, vbuffer_size + 2, vbuffer_size + 3, vbuffer_size + 6);
+                store_triangle(indices, vbuffer_size + 3, vbuffer_size + 7, vbuffer_size + 6);
+                store_triangle(indices, vbuffer_size + 3, vbuffer_size + 0, vbuffer_size + 7);
+                store_triangle(indices, vbuffer_size + 0, vbuffer_size + 4, vbuffer_size + 7);
+
+                // triangles ending cap
+                store_triangle(indices, vbuffer_size + 4, vbuffer_size + 6, vbuffer_size + 7);
+                store_triangle(indices, vbuffer_size + 4, vbuffer_size + 5, vbuffer_size + 6);
+
+                vbuffer_size += 8;
+            }
+            else {
+                // any other segment
+                // =================
+
+                float displacement = 0.0f;
+                float cos_dir = prev_dir.dot(dir);
+                if (cos_dir > -0.9998477f) {
+                    // if the angle between adjacent segments is smaller than 179 degrees
+                    Vec3f med_dir = (prev_dir + dir).normalized();
+                    displacement = half_width * ::tan(::acos(std::clamp(dir.dot(med_dir), -1.0f, 1.0f)));
+                }
+
+                Vec3f displacement_vec = displacement * prev_dir;
+                bool can_displace = displacement > 0.0f && displacement < prev_length&& displacement < length;
+
+                bool is_right_turn = prev_up.dot(prev_dir.cross(dir)) <= 0.0f;
+                // whether the angle between adjacent segments is greater than 45 degrees
+                bool is_sharp = cos_dir < 0.7071068f;
+
+                bool right_displaced = false;
+                bool left_displaced = false;
+
+                if (!is_sharp) {
+                    if (can_displace) {
+                        if (is_right_turn)
+                            left_displaced = true;
+                        else
+                            right_displaced = true;
+                    }
+                }
+
+                // triangles starting cap
+                store_triangle(indices, vbuffer_size - 4, vbuffer_size - 2, vbuffer_size + 0);
+                store_triangle(indices, vbuffer_size - 4, vbuffer_size + 1, vbuffer_size - 2);
+
+                // triangles outer corner cap
+                if (is_right_turn) {
+                    if (left_displaced)
+                        // dummy triangles
+                        append_dummy_cap(indices, vbuffer_size);
+                    else {
+                        store_triangle(indices, vbuffer_size - 4, vbuffer_size + 1, vbuffer_size - 1);
+                        store_triangle(indices, vbuffer_size + 1, vbuffer_size - 2, vbuffer_size - 1);
+                    }
+                }
+                else {
+                    if (right_displaced)
+                        // dummy triangles
+                        append_dummy_cap(indices, vbuffer_size);
+                    else {
+                        store_triangle(indices, vbuffer_size - 4, vbuffer_size - 3, vbuffer_size + 0);
+                        store_triangle(indices, vbuffer_size - 3, vbuffer_size - 2, vbuffer_size + 0);
+                    }
+                }
+
+                // triangles sides
+                store_triangle(indices, vbuffer_size - 4, vbuffer_size + 0, vbuffer_size + 2);
+                store_triangle(indices, vbuffer_size + 0, vbuffer_size + 3, vbuffer_size + 2);
+                store_triangle(indices, vbuffer_size + 0, vbuffer_size - 2, vbuffer_size + 3);
+                store_triangle(indices, vbuffer_size - 2, vbuffer_size + 4, vbuffer_size + 3);
+                store_triangle(indices, vbuffer_size - 2, vbuffer_size + 1, vbuffer_size + 4);
+                store_triangle(indices, vbuffer_size + 1, vbuffer_size + 5, vbuffer_size + 4);
+                store_triangle(indices, vbuffer_size + 1, vbuffer_size - 4, vbuffer_size + 5);
+                store_triangle(indices, vbuffer_size - 4, vbuffer_size + 2, vbuffer_size + 5);
+
+                // triangles ending cap
+                store_triangle(indices, vbuffer_size + 2, vbuffer_size + 4, vbuffer_size + 5);
+                store_triangle(indices, vbuffer_size + 2, vbuffer_size + 3, vbuffer_size + 4);
+
+                vbuffer_size += 6;
+            }
+
+            last_path.sub_paths.back().last = { ibuffer_id, indices.size() - 1, move_id, curr.position };
+            prev_dir = dir;
+            prev_up = up;
+            prev_length = length;
+    };
+
+#if ENABLE_GCODE_VIEWER_STATISTICS
+    auto start_time = std::chrono::high_resolution_clock::now();
+    m_statistics.results_size = SLIC3R_STDVEC_MEMSIZE(gcode_result.moves, GCodeProcessor::MoveVertex);
+    m_statistics.results_time = gcode_result.time;
+#endif // ENABLE_GCODE_VIEWER_STATISTICS
+
+    m_moves_count = gcode_result.moves.size();
+    if (m_moves_count == 0)
+        return;
+
+    unsigned int progress_count = 0;
+    static const unsigned int progress_threshold = 1000;
+    wxProgressDialog* progress_dialog = wxGetApp().is_gcode_viewer() ?
+        new wxProgressDialog(_L("Generating toolpaths"), "...",
+            100, wxGetApp().plater(), wxPD_AUTO_HIDE | wxPD_APP_MODAL) : nullptr;
+
+    wxBusyCursor busy;
+
+    // extract approximate paths bounding box from result
+    for (const GCodeProcessor::MoveVertex& move : gcode_result.moves) {
+        if (wxGetApp().is_gcode_viewer())
+            // for the gcode viewer we need to take in account all moves to correctly size the printbed
+            m_paths_bounding_box.merge(move.position.cast<double>());
+        else {
+            if (move.type == EMoveType::Extrude && move.width != 0.0f && move.height != 0.0f)
+                m_paths_bounding_box.merge(move.position.cast<double>());
+        }
+    }
+
+    // set approximate max bounding box (take in account also the tool marker)
+    m_max_bounding_box = m_paths_bounding_box;
+    m_max_bounding_box.merge(m_paths_bounding_box.max + m_sequential_view.marker.get_bounding_box().size()[2] * Vec3d::UnitZ());
+
+    std::vector<MultiVertexBuffer> vertices(m_buffers.size());
+    std::vector<MultiIndexBuffer> indices(m_buffers.size());
+    std::vector<float> options_zs;
+
+    // max vertex buffer size, in bytes
+    const size_t VBUFFER_THRESHOLD_BYTES = 64 * 1024 * 1024;
+
+    // toolpaths data -> extract vertices from result
+    for (size_t i = 0; i < m_moves_count; ++i) {
+        const GCodeProcessor::MoveVertex& curr = gcode_result.moves[i];
+
+        // skip first vertex
+        if (i == 0)
+            continue;
+
+        const GCodeProcessor::MoveVertex& prev = gcode_result.moves[i - 1];
+
+        // update progress dialog
+        ++progress_count;
+        if (progress_dialog != nullptr && progress_count % progress_threshold == 0) {
+            progress_dialog->Update(int(100.0f * float(i) / (2.0f * float(m_moves_count))),
+                _L("Generating vertex buffer") + ": " + wxNumberFormatter::ToString(100.0 * double(i) / double(m_moves_count), 0, wxNumberFormatter::Style_None) + "%");
+            progress_dialog->Fit();
+            progress_count = 0;
+        }
+
+        unsigned char id = buffer_id(curr.type);
+        TBuffer& t_buffer = m_buffers[id];
+        MultiVertexBuffer& v_multibuffer = vertices[id];
+
+        // ensure there is at least one vertex buffer
+        if (v_multibuffer.empty())
+            v_multibuffer.push_back(VertexBuffer());
+
+        // if adding the vertices for the current segment exceeds the threshold size of the current vertex buffer
+        // add another vertex buffer
+        if (v_multibuffer.back().size() * sizeof(float) > VBUFFER_THRESHOLD_BYTES - t_buffer.max_vertices_per_segment_size_bytes())
+            v_multibuffer.push_back(VertexBuffer());
+
+        VertexBuffer& v_buffer = v_multibuffer.back();
+
+        switch (t_buffer.render_primitive_type)
+        {
+        case TBuffer::ERenderPrimitiveType::Point:    { add_vertices_as_point(curr, v_buffer); break; }
+        case TBuffer::ERenderPrimitiveType::Line:     { add_vertices_as_line(prev, curr, v_buffer); break; }
+        case TBuffer::ERenderPrimitiveType::Triangle: { add_vertices_as_solid(prev, curr, t_buffer, v_buffer, i); break; }
+        }
+
+        // collect options zs for later use
+        if (curr.type == EMoveType::Pause_Print || curr.type == EMoveType::Custom_GCode) {
+            const float* const last_z = options_zs.empty() ? nullptr : &options_zs.back();
+            if (last_z == nullptr || curr.position[2] < *last_z - EPSILON || *last_z + EPSILON < curr.position[2])
+                options_zs.emplace_back(curr.position[2]);
+        }
+    }
+
+    for (MultiVertexBuffer& v_multibuffer : vertices) {
+        for (VertexBuffer& v_buffer : v_multibuffer) {
+            v_buffer.shrink_to_fit();
+        }
+    }
+
+    // move the wipe toolpaths half height up to render them on proper position
+    MultiVertexBuffer& wipe_vertices = vertices[buffer_id(EMoveType::Wipe)];
+    for (VertexBuffer& v_buffer : wipe_vertices) {
+        for (size_t i = 2; i < v_buffer.size(); i += 3) {
+            v_buffer[i] += 0.5f * GCodeProcessor::Wipe_Height;
+        }
+    }
+
+    // send vertices data to gpu
+    for (size_t i = 0; i < m_buffers.size(); ++i) {
+        TBuffer& t_buffer = m_buffers[i];
+
+        const MultiVertexBuffer& v_multibuffer = vertices[i];
+        for (const VertexBuffer& v_buffer : v_multibuffer) {
+            size_t size_elements = v_buffer.size();
+            size_t size_bytes = size_elements * sizeof(float);
+            size_t vertices_count = size_elements / t_buffer.vertices.vertex_size_floats();
+            t_buffer.vertices.count += vertices_count;
+
+#if ENABLE_GCODE_VIEWER_STATISTICS
+            m_statistics.total_vertices_gpu_size += static_cast<int64_t>(size_bytes);
+            m_statistics.max_vbuffer_gpu_size = std::max(m_statistics.max_vbuffer_gpu_size, static_cast<int64_t>(size_bytes));
+            ++m_statistics.vbuffers_count;
+#endif // ENABLE_GCODE_VIEWER_STATISTICS
+
+            GLuint id = 0;
+            glsafe(::glGenBuffers(1, &id));
+            t_buffer.vertices.ids.push_back(static_cast<unsigned int>(id));
+            glsafe(::glBindBuffer(GL_ARRAY_BUFFER, id));
+            glsafe(::glBufferData(GL_ARRAY_BUFFER, size_bytes, v_buffer.data(), GL_STATIC_DRAW));
+            glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
+        }
+    }
+
+    log_memory_usage("Loaded G-code generated vertex buffers ", vertices, indices);
+
+    // dismiss vertices data, no more needed
+    std::vector<MultiVertexBuffer>().swap(vertices);
+
+    // toolpaths data -> extract indices from result
+    // paths may have been filled while extracting vertices,
+    // so reset them, they will be filled again while extracting indices
+    for (TBuffer& buffer : m_buffers) {
+        buffer.paths.clear();
+    }
+
+    // max index buffer size, in bytes
+    const size_t IBUFFER_THRESHOLD_BYTES = 64 * 1024 * 1024;
+
+    // variable used to keep track of the current vertex buffers index and size
+    using CurrVertexBuffer = std::pair<unsigned int, size_t>;
+    std::vector<CurrVertexBuffer> curr_vertex_buffers(m_buffers.size(), { 0, 0 });
+
+    // variable used to keep track of the vertex buffers ids
+    using VboIndexList = std::vector<unsigned int>;
+    std::vector<VboIndexList> vbo_indices(m_buffers.size());
+
+    for (size_t i = 0; i < m_moves_count; ++i) {
+        const GCodeProcessor::MoveVertex& curr = gcode_result.moves[i];
+
+        // skip first vertex
+        if (i == 0)
+            continue;
+
+        const GCodeProcessor::MoveVertex& prev = gcode_result.moves[i - 1];
+
+        ++progress_count;
+        if (progress_dialog != nullptr && progress_count % progress_threshold == 0) {
+            progress_dialog->Update(int(100.0f * float(m_moves_count + i) / (2.0f * float(m_moves_count))),
+                _L("Generating index buffers") + ": " + wxNumberFormatter::ToString(100.0 * double(i) / double(m_moves_count), 0, wxNumberFormatter::Style_None) + "%");
+            progress_dialog->Fit();
+            progress_count = 0;
+        }
+
+        unsigned char id = buffer_id(curr.type);
+        TBuffer& t_buffer = m_buffers[id];
+        MultiIndexBuffer& i_multibuffer = indices[id];
+        CurrVertexBuffer& curr_vertex_buffer = curr_vertex_buffers[id];
+        VboIndexList& vbo_index_list = vbo_indices[id];
+
+        // ensure there is at least one index buffer
+        if (i_multibuffer.empty()) {
+            i_multibuffer.push_back(IndexBuffer());
+            vbo_index_list.push_back(t_buffer.vertices.ids[curr_vertex_buffer.first]);
+        }
+
+        // if adding the indices for the current segment exceeds the threshold size of the current index buffer
+        // create another index buffer
+        if (i_multibuffer.back().size() * sizeof(unsigned int) >= IBUFFER_THRESHOLD_BYTES - t_buffer.indices_per_segment_size_bytes()) {
+            i_multibuffer.push_back(IndexBuffer());
+            vbo_index_list.push_back(t_buffer.vertices.ids[curr_vertex_buffer.first]);
+        }
+
+        // if adding the vertices for the current segment exceeds the threshold size of the current vertex buffer
+        // create another index buffer
+        if (curr_vertex_buffer.second * t_buffer.vertices.vertex_size_bytes() > VBUFFER_THRESHOLD_BYTES - t_buffer.max_vertices_per_segment_size_bytes()) {
+            i_multibuffer.push_back(IndexBuffer());
+
+            ++curr_vertex_buffer.first;
+            curr_vertex_buffer.second = 0;
+            vbo_index_list.push_back(t_buffer.vertices.ids[curr_vertex_buffer.first]);
+
+            if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::Point) {
+                Path& last_path = t_buffer.paths.back();
+                last_path.add_sub_path(curr, static_cast<unsigned int>(i_multibuffer.size()) - 1, 0, i);
+            }
+        }
+
+        IndexBuffer& i_buffer = i_multibuffer.back();
+
+        switch (t_buffer.render_primitive_type)
+        {
+        case TBuffer::ERenderPrimitiveType::Point: {
+            add_indices_as_point(curr, t_buffer, static_cast<unsigned int>(i_multibuffer.size()) - 1, i_buffer, i);
+            curr_vertex_buffer.second += t_buffer.max_vertices_per_segment();
+            break;
+        }
+        case TBuffer::ERenderPrimitiveType::Line: {
+            add_indices_as_line(prev, curr, t_buffer, static_cast<unsigned int>(i_multibuffer.size()) - 1, i_buffer, i);
+            curr_vertex_buffer.second += t_buffer.max_vertices_per_segment();
+            break;
+        }
+        case TBuffer::ERenderPrimitiveType::Triangle: {
+            add_indices_as_solid(prev, curr, t_buffer, curr_vertex_buffer.second, static_cast<unsigned int>(i_multibuffer.size()) - 1, i_buffer, i);
+            break;
+        }
+        }
+    }
+
+    for (MultiIndexBuffer& i_multibuffer : indices) {
+        for (IndexBuffer& i_buffer : i_multibuffer) {
+            i_buffer.shrink_to_fit();
+        }
+    }
+
+    // toolpaths data -> send indices data to gpu
+    for (size_t i = 0; i < m_buffers.size(); ++i) {
+        TBuffer& t_buffer = m_buffers[i];
+        const MultiIndexBuffer& i_multibuffer = indices[i];
+        for (const IndexBuffer& i_buffer : i_multibuffer) {
+            size_t size_elements = i_buffer.size();
+            size_t size_bytes = size_elements * sizeof(unsigned int);
+
+            if (size_elements == 0) {
+                continue;
+            }
+
+            // stores index buffer informations into TBuffer
+            t_buffer.indices.push_back(IBuffer());
+            IBuffer& ibuf = t_buffer.indices.back();
+            ibuf.count = size_elements;
+            ibuf.vbo = vbo_indices[i][t_buffer.indices.size() - 1];
+
+#if ENABLE_GCODE_VIEWER_STATISTICS
+            m_statistics.total_indices_gpu_size += static_cast<int64_t>(size_bytes);
+            m_statistics.max_ibuffer_gpu_size = std::max(m_statistics.max_ibuffer_gpu_size, static_cast<int64_t>(size_bytes));
+            ++m_statistics.ibuffers_count;
+#endif // ENABLE_GCODE_VIEWER_STATISTICS
+
+            glsafe(::glGenBuffers(1, &ibuf.ibo));
+            glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibuf.ibo));
+            glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, size_bytes, i_buffer.data(), GL_STATIC_DRAW));
+            glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
+        }
+    }
+
+    if (progress_dialog != nullptr) {
+        progress_dialog->Update(100, "");
+        progress_dialog->Fit();
+    }
+
+#if ENABLE_GCODE_VIEWER_STATISTICS
+    for (const TBuffer& buffer : m_buffers) {
+        m_statistics.paths_size += SLIC3R_STDVEC_MEMSIZE(buffer.paths, Path);
+    }
+    unsigned int travel_buffer_id = buffer_id(EMoveType::Travel);
+    const MultiIndexBuffer& travel_buffers = indices[travel_buffer_id];
+    for (const IndexBuffer& buffer : travel_buffers) {
+        m_statistics.travel_segments_count += buffer.size() / m_buffers[travel_buffer_id].indices_per_segment();
+    }
+    unsigned int wipe_buffer_id = buffer_id(EMoveType::Wipe);
+    const MultiIndexBuffer& wipe_buffers = indices[wipe_buffer_id];
+    for (const IndexBuffer& buffer : wipe_buffers) {
+        m_statistics.wipe_segments_count += buffer.size() / m_buffers[wipe_buffer_id].indices_per_segment();
+    }
+    unsigned int extrude_buffer_id = buffer_id(EMoveType::Extrude);
+    const MultiIndexBuffer& extrude_buffers = indices[extrude_buffer_id];
+    for (const IndexBuffer& buffer : extrude_buffers) {
+        m_statistics.extrude_segments_count += buffer.size() / m_buffers[extrude_buffer_id].indices_per_segment();
+    }
+#endif // ENABLE_GCODE_VIEWER_STATISTICS
+
+    log_memory_usage("Loaded G-code generated indices buffers ", vertices, indices);
+
+    // dismiss indices data, no more needed
+    std::vector<MultiIndexBuffer>().swap(indices);
+
+    // layers zs / roles / extruder ids -> extract from result
+    size_t last_travel_s_id = 0;
+    for (size_t i = 0; i < m_moves_count; ++i) {
+        const GCodeProcessor::MoveVertex& move = gcode_result.moves[i];
+        if (move.type == EMoveType::Extrude) {
+            // layers zs
+            const double* const last_z = m_layers.empty() ? nullptr : &m_layers.get_zs().back();
+            double z = static_cast<double>(move.position[2]);
+            if (last_z == nullptr || z < *last_z - EPSILON || *last_z + EPSILON < z)
+                m_layers.append(z, { last_travel_s_id, i });
+            else
+                m_layers.get_endpoints().back().last = i;
+            // extruder ids
+            m_extruder_ids.emplace_back(move.extruder_id);
+            // roles
+            if (i > 0)
+                m_roles.emplace_back(move.extrusion_role);
+        }
+        else if (move.type == EMoveType::Travel) {
+            if (i - last_travel_s_id > 1 && !m_layers.empty())
+                m_layers.get_endpoints().back().last = i;
+
+            last_travel_s_id = i;
+        }
+    }
+
+    // roles -> remove duplicates
+    std::sort(m_roles.begin(), m_roles.end());
+    m_roles.erase(std::unique(m_roles.begin(), m_roles.end()), m_roles.end());
+    m_roles.shrink_to_fit();
+
+    // extruder ids -> remove duplicates
+    std::sort(m_extruder_ids.begin(), m_extruder_ids.end());
+    m_extruder_ids.erase(std::unique(m_extruder_ids.begin(), m_extruder_ids.end()), m_extruder_ids.end());
+    m_extruder_ids.shrink_to_fit();
+
+    // set layers z range
+    if (!m_layers.empty())
+        m_layers_z_range = { 0, static_cast<unsigned int>(m_layers.size() - 1) };
+
+    // change color of paths whose layer contains option points
+    if (!options_zs.empty()) {
+        TBuffer& extrude_buffer = m_buffers[buffer_id(EMoveType::Extrude)];
+        for (Path& path : extrude_buffer.paths) {
+            float z = path.sub_paths.front().first.position[2];
+            if (std::find_if(options_zs.begin(), options_zs.end(), [z](float f) { return f - EPSILON <= z && z <= f + EPSILON; }) != options_zs.end())
+                path.cp_color_id = 255 - path.cp_color_id;
+        }
+    }
+
+#if ENABLE_GCODE_VIEWER_STATISTICS
+    m_statistics.load_time = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - start_time).count();
+#endif // ENABLE_GCODE_VIEWER_STATISTICS
+
+    if (progress_dialog != nullptr)
+        progress_dialog->Destroy();
+}
+#else
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
 void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
 {
 #if ENABLE_GCODE_VIEWER_STATISTICS
@@ -1073,8 +1865,10 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
     };
     auto add_indices_as_line = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, TBuffer& buffer,
         unsigned int index_buffer_id, IndexBuffer& indices, size_t move_id) {
-            // x component of the normal to the current segment (the normal is parallel to the XY plane)
-            float normal_x = (curr.position - prev.position).normalized()[1];
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+//            // x component of the normal to the current segment (the normal is parallel to the XY plane)
+//            float normal_x = (curr.position - prev.position).normalized()[1];
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
 
             if (prev.type != curr.type || !buffer.paths.back().matches(curr)) {
                 // add starting index
@@ -1240,7 +2034,6 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
             prev_up = up;
             prev_length = length;
     };
-
     auto add_indices_as_solid = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, TBuffer& buffer,
         size_t& buffer_vertices_size, unsigned int index_buffer_id, IndexBuffer& indices, size_t move_id) {
             static Vec3f prev_dir;
@@ -1261,7 +2054,9 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
                 buffer.paths.back().first.position = prev.position;
             }
 
-            unsigned int starting_vertices_size = static_cast<unsigned int>(buffer_vertices_size);
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+//            unsigned int starting_vertices_size = static_cast<unsigned int>(buffer_vertices_size);
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
 
             Vec3f dir = (curr.position - prev.position).normalized();
             Vec3f right = (std::abs(std::abs(dir.dot(Vec3f::UnitZ())) - 1.0f) < EPSILON) ? -Vec3f::UnitY() : Vec3f(dir[1], -dir[0], 0.0f).normalized();
@@ -1277,29 +2072,56 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
 
             float length = (curr_pos - prev_pos).norm();
             if (last_path.vertices_count() == 1) {
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
                 // 1st segment
-                buffer_vertices_size += 8;
 
                 // triangles starting cap
-                store_triangle(indices, starting_vertices_size + 0, starting_vertices_size + 2, starting_vertices_size + 1);
-                store_triangle(indices, starting_vertices_size + 0, starting_vertices_size + 3, starting_vertices_size + 2);
+                store_triangle(indices, buffer_vertices_size + 0, buffer_vertices_size + 2, buffer_vertices_size + 1);
+                store_triangle(indices, buffer_vertices_size + 0, buffer_vertices_size + 3, buffer_vertices_size + 2);
 
                 // dummy triangles outer corner cap
-                append_dummy_cap(indices, starting_vertices_size);
+                append_dummy_cap(indices, buffer_vertices_size);
 
                 // triangles sides
-                store_triangle(indices, starting_vertices_size + 0, starting_vertices_size + 1, starting_vertices_size + 4);
-                store_triangle(indices, starting_vertices_size + 1, starting_vertices_size + 5, starting_vertices_size + 4);
-                store_triangle(indices, starting_vertices_size + 1, starting_vertices_size + 2, starting_vertices_size + 5);
-                store_triangle(indices, starting_vertices_size + 2, starting_vertices_size + 6, starting_vertices_size + 5);
-                store_triangle(indices, starting_vertices_size + 2, starting_vertices_size + 3, starting_vertices_size + 6);
-                store_triangle(indices, starting_vertices_size + 3, starting_vertices_size + 7, starting_vertices_size + 6);
-                store_triangle(indices, starting_vertices_size + 3, starting_vertices_size + 0, starting_vertices_size + 7);
-                store_triangle(indices, starting_vertices_size + 0, starting_vertices_size + 4, starting_vertices_size + 7);
+                store_triangle(indices, buffer_vertices_size + 0, buffer_vertices_size + 1, buffer_vertices_size + 4);
+                store_triangle(indices, buffer_vertices_size + 1, buffer_vertices_size + 5, buffer_vertices_size + 4);
+                store_triangle(indices, buffer_vertices_size + 1, buffer_vertices_size + 2, buffer_vertices_size + 5);
+                store_triangle(indices, buffer_vertices_size + 2, buffer_vertices_size + 6, buffer_vertices_size + 5);
+                store_triangle(indices, buffer_vertices_size + 2, buffer_vertices_size + 3, buffer_vertices_size + 6);
+                store_triangle(indices, buffer_vertices_size + 3, buffer_vertices_size + 7, buffer_vertices_size + 6);
+                store_triangle(indices, buffer_vertices_size + 3, buffer_vertices_size + 0, buffer_vertices_size + 7);
+                store_triangle(indices, buffer_vertices_size + 0, buffer_vertices_size + 4, buffer_vertices_size + 7);
 
                 // triangles ending cap
-                store_triangle(indices, starting_vertices_size + 4, starting_vertices_size + 6, starting_vertices_size + 7);
-                store_triangle(indices, starting_vertices_size + 4, starting_vertices_size + 5, starting_vertices_size + 6);
+                store_triangle(indices, buffer_vertices_size + 4, buffer_vertices_size + 6, buffer_vertices_size + 7);
+                store_triangle(indices, buffer_vertices_size + 4, buffer_vertices_size + 5, buffer_vertices_size + 6);
+
+                buffer_vertices_size += 8;
+
+//                // 1st segment
+//                buffer_vertices_size += 8;
+//
+//                // triangles starting cap
+//                store_triangle(indices, starting_vertices_size + 0, starting_vertices_size + 2, starting_vertices_size + 1);
+//                store_triangle(indices, starting_vertices_size + 0, starting_vertices_size + 3, starting_vertices_size + 2);
+//
+//                // dummy triangles outer corner cap
+//                append_dummy_cap(indices, starting_vertices_size);
+//
+//                // triangles sides
+//                store_triangle(indices, starting_vertices_size + 0, starting_vertices_size + 1, starting_vertices_size + 4);
+//                store_triangle(indices, starting_vertices_size + 1, starting_vertices_size + 5, starting_vertices_size + 4);
+//                store_triangle(indices, starting_vertices_size + 1, starting_vertices_size + 2, starting_vertices_size + 5);
+//                store_triangle(indices, starting_vertices_size + 2, starting_vertices_size + 6, starting_vertices_size + 5);
+//                store_triangle(indices, starting_vertices_size + 2, starting_vertices_size + 3, starting_vertices_size + 6);
+//                store_triangle(indices, starting_vertices_size + 3, starting_vertices_size + 7, starting_vertices_size + 6);
+//                store_triangle(indices, starting_vertices_size + 3, starting_vertices_size + 0, starting_vertices_size + 7);
+//                store_triangle(indices, starting_vertices_size + 0, starting_vertices_size + 4, starting_vertices_size + 7);
+//
+//                // triangles ending cap
+//                store_triangle(indices, starting_vertices_size + 4, starting_vertices_size + 6, starting_vertices_size + 7);
+//                store_triangle(indices, starting_vertices_size + 4, starting_vertices_size + 5, starting_vertices_size + 6);
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
             }
             else {
                 // any other segment
@@ -1330,45 +2152,87 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
                     }
                 }
 
-                buffer_vertices_size += 6;
-
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
                 // triangles starting cap
-                store_triangle(indices, starting_vertices_size - 4, starting_vertices_size - 2, starting_vertices_size + 0);
-                store_triangle(indices, starting_vertices_size - 4, starting_vertices_size + 1, starting_vertices_size - 2);
+                store_triangle(indices, buffer_vertices_size - 4, buffer_vertices_size - 2, buffer_vertices_size + 0);
+                store_triangle(indices, buffer_vertices_size - 4, buffer_vertices_size + 1, buffer_vertices_size - 2);
 
                 // triangles outer corner cap
                 if (is_right_turn) {
                     if (left_displaced)
                         // dummy triangles
-                        append_dummy_cap(indices, starting_vertices_size);
+                        append_dummy_cap(indices, buffer_vertices_size);
                     else {
-                        store_triangle(indices, starting_vertices_size - 4, starting_vertices_size + 1, starting_vertices_size - 1);
-                        store_triangle(indices, starting_vertices_size + 1, starting_vertices_size - 2, starting_vertices_size - 1);
+                        store_triangle(indices, buffer_vertices_size - 4, buffer_vertices_size + 1, buffer_vertices_size - 1);
+                        store_triangle(indices, buffer_vertices_size + 1, buffer_vertices_size - 2, buffer_vertices_size - 1);
                     }
                 }
                 else {
                     if (right_displaced)
                         // dummy triangles
-                        append_dummy_cap(indices, starting_vertices_size);
+                        append_dummy_cap(indices, buffer_vertices_size);
                     else {
-                        store_triangle(indices, starting_vertices_size - 4, starting_vertices_size - 3, starting_vertices_size + 0);
-                        store_triangle(indices, starting_vertices_size - 3, starting_vertices_size - 2, starting_vertices_size + 0);
+                        store_triangle(indices, buffer_vertices_size - 4, buffer_vertices_size - 3, buffer_vertices_size + 0);
+                        store_triangle(indices, buffer_vertices_size - 3, buffer_vertices_size - 2, buffer_vertices_size + 0);
                     }
                 }
 
                 // triangles sides
-                store_triangle(indices, starting_vertices_size - 4, starting_vertices_size + 0, starting_vertices_size + 2);
-                store_triangle(indices, starting_vertices_size + 0, starting_vertices_size + 3, starting_vertices_size + 2);
-                store_triangle(indices, starting_vertices_size + 0, starting_vertices_size - 2, starting_vertices_size + 3);
-                store_triangle(indices, starting_vertices_size - 2, starting_vertices_size + 4, starting_vertices_size + 3);
-                store_triangle(indices, starting_vertices_size - 2, starting_vertices_size + 1, starting_vertices_size + 4);
-                store_triangle(indices, starting_vertices_size + 1, starting_vertices_size + 5, starting_vertices_size + 4);
-                store_triangle(indices, starting_vertices_size + 1, starting_vertices_size - 4, starting_vertices_size + 5);
-                store_triangle(indices, starting_vertices_size - 4, starting_vertices_size + 2, starting_vertices_size + 5);
+                store_triangle(indices, buffer_vertices_size - 4, buffer_vertices_size + 0, buffer_vertices_size + 2);
+                store_triangle(indices, buffer_vertices_size + 0, buffer_vertices_size + 3, buffer_vertices_size + 2);
+                store_triangle(indices, buffer_vertices_size + 0, buffer_vertices_size - 2, buffer_vertices_size + 3);
+                store_triangle(indices, buffer_vertices_size - 2, buffer_vertices_size + 4, buffer_vertices_size + 3);
+                store_triangle(indices, buffer_vertices_size - 2, buffer_vertices_size + 1, buffer_vertices_size + 4);
+                store_triangle(indices, buffer_vertices_size + 1, buffer_vertices_size + 5, buffer_vertices_size + 4);
+                store_triangle(indices, buffer_vertices_size + 1, buffer_vertices_size - 4, buffer_vertices_size + 5);
+                store_triangle(indices, buffer_vertices_size - 4, buffer_vertices_size + 2, buffer_vertices_size + 5);
 
                 // triangles ending cap
-                store_triangle(indices, starting_vertices_size + 2, starting_vertices_size + 4, starting_vertices_size + 5);
-                store_triangle(indices, starting_vertices_size + 2, starting_vertices_size + 3, starting_vertices_size + 4);
+                store_triangle(indices, buffer_vertices_size + 2, buffer_vertices_size + 4, buffer_vertices_size + 5);
+                store_triangle(indices, buffer_vertices_size + 2, buffer_vertices_size + 3, buffer_vertices_size + 4);
+
+                buffer_vertices_size += 6;
+
+//                buffer_vertices_size += 6;
+//
+//                // triangles starting cap
+//                store_triangle(indices, starting_vertices_size - 4, starting_vertices_size - 2, starting_vertices_size + 0);
+//                store_triangle(indices, starting_vertices_size - 4, starting_vertices_size + 1, starting_vertices_size - 2);
+//
+//                // triangles outer corner cap
+//                if (is_right_turn) {
+//                    if (left_displaced)
+//                        // dummy triangles
+//                        append_dummy_cap(indices, starting_vertices_size);
+//                    else {
+//                        store_triangle(indices, starting_vertices_size - 4, starting_vertices_size + 1, starting_vertices_size - 1);
+//                        store_triangle(indices, starting_vertices_size + 1, starting_vertices_size - 2, starting_vertices_size - 1);
+//                    }
+//                }
+//                else {
+//                    if (right_displaced)
+//                        // dummy triangles
+//                        append_dummy_cap(indices, starting_vertices_size);
+//                    else {
+//                        store_triangle(indices, starting_vertices_size - 4, starting_vertices_size - 3, starting_vertices_size + 0);
+//                        store_triangle(indices, starting_vertices_size - 3, starting_vertices_size - 2, starting_vertices_size + 0);
+//                    }
+//                }
+//
+//                // triangles sides
+//                store_triangle(indices, starting_vertices_size - 4, starting_vertices_size + 0, starting_vertices_size + 2);
+//                store_triangle(indices, starting_vertices_size + 0, starting_vertices_size + 3, starting_vertices_size + 2);
+//                store_triangle(indices, starting_vertices_size + 0, starting_vertices_size - 2, starting_vertices_size + 3);
+//                store_triangle(indices, starting_vertices_size - 2, starting_vertices_size + 4, starting_vertices_size + 3);
+//                store_triangle(indices, starting_vertices_size - 2, starting_vertices_size + 1, starting_vertices_size + 4);
+//                store_triangle(indices, starting_vertices_size + 1, starting_vertices_size + 5, starting_vertices_size + 4);
+//                store_triangle(indices, starting_vertices_size + 1, starting_vertices_size - 4, starting_vertices_size + 5);
+//                store_triangle(indices, starting_vertices_size - 4, starting_vertices_size + 2, starting_vertices_size + 5);
+//
+//                // triangles ending cap
+//                store_triangle(indices, starting_vertices_size + 2, starting_vertices_size + 4, starting_vertices_size + 5);
+//                store_triangle(indices, starting_vertices_size + 2, starting_vertices_size + 3, starting_vertices_size + 4);
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
             }
 
             last_path.last = { index_buffer_id, indices.size() - 1, move_id, curr.position };
@@ -1446,7 +2310,9 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
 #if ENABLE_GCODE_VIEWER_STATISTICS
         m_statistics.total_vertices_gpu_size += buffer_vertices.size() * sizeof(float);
         m_statistics.max_vbuffer_gpu_size = std::max(m_statistics.max_vbuffer_gpu_size, static_cast<int64_t>(buffer_vertices.size() * sizeof(float)));
-        m_statistics.max_vertices_in_vertex_buffer = std::max(m_statistics.max_vertices_in_vertex_buffer, static_cast<int64_t>(buffer.vertices.count));
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+//        m_statistics.max_vertices_in_vertex_buffer = std::max(m_statistics.max_vertices_in_vertex_buffer, static_cast<int64_t>(buffer.vertices.count));
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
 #endif // ENABLE_GCODE_VIEWER_STATISTICS
 
         if (buffer.vertices.count > 0) {
@@ -1557,7 +2423,9 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
 #if ENABLE_GCODE_VIEWER_STATISTICS
             m_statistics.total_indices_gpu_size += ibuffer.count * sizeof(unsigned int);
             m_statistics.max_ibuffer_gpu_size = std::max(m_statistics.max_ibuffer_gpu_size, static_cast<int64_t>(ibuffer.count * sizeof(unsigned int)));
-            m_statistics.max_indices_in_index_buffer = std::max(m_statistics.max_indices_in_index_buffer, static_cast<int64_t>(ibuffer.count));
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+//            m_statistics.max_indices_in_index_buffer = std::max(m_statistics.max_indices_in_index_buffer, static_cast<int64_t>(ibuffer.count));
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
 #endif // ENABLE_GCODE_VIEWER_STATISTICS
 
             if (ibuffer.count > 0) {
@@ -1579,17 +2447,26 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
     unsigned int travel_buffer_id = buffer_id(EMoveType::Travel);
     const MultiIndexBuffer& travel_buffer_indices = indices[travel_buffer_id];
     for (size_t i = 0; i < travel_buffer_indices.size(); ++i) {
-        m_statistics.travel_segments_count = travel_buffer_indices[i].size() / m_buffers[travel_buffer_id].indices_per_segment();
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+        m_statistics.travel_segments_count += travel_buffer_indices[i].size() / m_buffers[travel_buffer_id].indices_per_segment();
+//        m_statistics.travel_segments_count = travel_buffer_indices[i].size() / m_buffers[travel_buffer_id].indices_per_segment();
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
     }
     unsigned int wipe_buffer_id = buffer_id(EMoveType::Wipe);
     const MultiIndexBuffer& wipe_buffer_indices = indices[wipe_buffer_id];
     for (size_t i = 0; i < wipe_buffer_indices.size(); ++i) {
-        m_statistics.wipe_segments_count = wipe_buffer_indices[i].size() / m_buffers[wipe_buffer_id].indices_per_segment();
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+        m_statistics.wipe_segments_count += wipe_buffer_indices[i].size() / m_buffers[wipe_buffer_id].indices_per_segment();
+//        m_statistics.wipe_segments_count = wipe_buffer_indices[i].size() / m_buffers[wipe_buffer_id].indices_per_segment();
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
     }
     unsigned int extrude_buffer_id = buffer_id(EMoveType::Extrude);
     const MultiIndexBuffer& extrude_buffer_indices = indices[extrude_buffer_id];
     for (size_t i = 0; i < extrude_buffer_indices.size(); ++i) {
-        m_statistics.extrude_segments_count = extrude_buffer_indices[i].size() / m_buffers[extrude_buffer_id].indices_per_segment();
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+        m_statistics.extrude_segments_count += extrude_buffer_indices[i].size() / m_buffers[extrude_buffer_id].indices_per_segment();
+//        m_statistics.extrude_segments_count = extrude_buffer_indices[i].size() / m_buffers[extrude_buffer_id].indices_per_segment();
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
     }
 #endif // ENABLE_GCODE_VIEWER_STATISTICS
 
@@ -1655,6 +2532,9 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
     if (progress_dialog != nullptr)
         progress_dialog->Destroy();
 }
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+#endif // ENABLE_SPLITTED_VERTEX_BUFFER
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
 
 void GCodeViewer::load_shells(const Print& print, bool initialized)
 {
@@ -1714,6 +2594,264 @@ void GCodeViewer::load_shells(const Print& print, bool initialized)
     }
 }
 
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+#if ENABLE_SPLITTED_VERTEX_BUFFER
+void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const
+{
+#if ENABLE_GCODE_VIEWER_STATISTICS
+    auto start_time = std::chrono::high_resolution_clock::now();
+#endif // ENABLE_GCODE_VIEWER_STATISTICS
+
+    auto extrusion_color = [this](const Path& path) {
+        Color color;
+        switch (m_view_type)
+        {
+        case EViewType::FeatureType:    { color = Extrusion_Role_Colors[static_cast<unsigned int>(path.role)]; break; }
+        case EViewType::Height:         { color = m_extrusions.ranges.height.get_color_at(path.height); break; }
+        case EViewType::Width:          { color = m_extrusions.ranges.width.get_color_at(path.width); break; }
+        case EViewType::Feedrate:       { color = m_extrusions.ranges.feedrate.get_color_at(path.feedrate); break; }
+        case EViewType::FanSpeed:       { color = m_extrusions.ranges.fan_speed.get_color_at(path.fan_speed); break; }
+        case EViewType::VolumetricRate: { color = m_extrusions.ranges.volumetric_rate.get_color_at(path.volumetric_rate); break; }
+        case EViewType::Tool:           { color = m_tool_colors[path.extruder_id]; break; }
+        case EViewType::ColorPrint:     {
+            if (path.cp_color_id >= static_cast<unsigned char>(m_tool_colors.size())) {
+                color = { 0.5f, 0.5f, 0.5f };
+//                // complementary color
+//                color = m_tool_colors[255 - path.cp_color_id];
+//                color = { 1.0f - color[0], 1.0f - color[1], 1.0f - color[2] };
+            }
+            else
+                color = m_tool_colors[path.cp_color_id];
+
+            break;
+        }
+        default: { color = { 1.0f, 1.0f, 1.0f }; break; }
+        }
+
+        return color;
+    };
+
+    auto travel_color = [this](const Path& path) {
+        return (path.delta_extruder < 0.0f) ? Travel_Colors[2] /* Retract */ :
+            ((path.delta_extruder > 0.0f) ? Travel_Colors[1] /* Extrude */ :
+                Travel_Colors[0] /* Move */);
+    };
+
+    auto is_in_layers_range = [this](const Path& path, size_t min_id, size_t max_id) {
+        auto in_layers_range = [this, min_id, max_id](size_t id) {
+            return m_layers.get_endpoints_at(min_id).first <= id && id <= m_layers.get_endpoints_at(max_id).last;
+        };
+
+        return in_layers_range(path.sub_paths.front().first.s_id) || in_layers_range(path.sub_paths.back().last.s_id);
+    };
+
+    auto is_travel_in_layers_range = [this](size_t path_id, size_t min_id, size_t max_id) {
+        auto is_in_z_range = [](const Path& path, double min_z, double max_z) {
+            auto in_z_range = [min_z, max_z](double z) {
+                return min_z - EPSILON < z && z < max_z + EPSILON;
+            };
+
+            return in_z_range(path.sub_paths.front().first.position[2]) || in_z_range(path.sub_paths.back().last.position[2]);
+        };
+
+        const TBuffer& buffer = m_buffers[buffer_id(EMoveType::Travel)];
+        if (path_id >= buffer.paths.size())
+            return false;
+
+        Path path = buffer.paths[path_id];
+        size_t first = path_id;
+        size_t last = path_id;
+
+        // check adjacent paths
+        while (first > 0 && path.sub_paths.front().first.position.isApprox(buffer.paths[first - 1].sub_paths.back().last.position)) {
+            --first;
+            path.sub_paths.front().first = buffer.paths[first].sub_paths.front().first;
+        }
+        while (last < buffer.paths.size() - 1 && path.sub_paths.back().last.position.isApprox(buffer.paths[last + 1].sub_paths.front().first.position)) {
+            ++last;
+            path.sub_paths.back().last = buffer.paths[last].sub_paths.back().last;
+        }
+
+        size_t min_s_id = m_layers.get_endpoints_at(min_id).first;
+        size_t max_s_id = m_layers.get_endpoints_at(max_id).last;
+
+        return (min_s_id <= path.sub_paths.front().first.s_id && path.sub_paths.front().first.s_id <= max_s_id) ||
+            (min_s_id <= path.sub_paths.back().last.s_id && path.sub_paths.back().last.s_id <= max_s_id);
+    };
+
+#if ENABLE_GCODE_VIEWER_STATISTICS
+    m_statistics.render_paths_size = 0;
+#endif // ENABLE_GCODE_VIEWER_STATISTICS
+
+    bool top_layer_only = get_app_config()->get("seq_top_layer_only") == "1";
+
+    SequentialView::Endpoints global_endpoints = { m_moves_count , 0 };
+    SequentialView::Endpoints top_layer_endpoints = global_endpoints;
+    if (top_layer_only || !keep_sequential_current_first) m_sequential_view.current.first = 0;
+    if (!keep_sequential_current_last) m_sequential_view.current.last = m_moves_count;
+
+    // first pass: collect visible paths and update sequential view data
+    std::vector<std::tuple<TBuffer*, unsigned int, unsigned int, unsigned int>> paths;
+    for (TBuffer& buffer : m_buffers) {
+        // reset render paths
+        buffer.render_paths.clear();
+
+        if (!buffer.visible)
+            continue;
+
+        for (size_t i = 0; i < buffer.paths.size(); ++i) {
+            const Path& path = buffer.paths[i];
+            if (path.type == EMoveType::Travel) {
+                if (!is_travel_in_layers_range(i, m_layers_z_range[0], m_layers_z_range[1]))
+                    continue;
+            }
+            else if (!is_in_layers_range(path, m_layers_z_range[0], m_layers_z_range[1]))
+                continue;
+
+            if (path.type == EMoveType::Extrude && !is_visible(path))
+                continue;
+
+            // store valid path
+            for (size_t j = 0; j < path.sub_paths.size(); ++j) {
+                paths.push_back({ &buffer, path.sub_paths[j].first.b_id, static_cast<unsigned int>(i), static_cast<unsigned int>(j) });
+            }
+
+            global_endpoints.first = std::min(global_endpoints.first, path.sub_paths.front().first.s_id);
+            global_endpoints.last = std::max(global_endpoints.last, path.sub_paths.back().last.s_id);
+
+            if (top_layer_only) {
+                if (path.type == EMoveType::Travel) {
+                    if (is_travel_in_layers_range(i, m_layers_z_range[1], m_layers_z_range[1])) {
+                        top_layer_endpoints.first = std::min(top_layer_endpoints.first, path.sub_paths.front().first.s_id);
+                        top_layer_endpoints.last = std::max(top_layer_endpoints.last, path.sub_paths.back().last.s_id);
+                    }
+                }
+                else if (is_in_layers_range(path, m_layers_z_range[1], m_layers_z_range[1])) {
+                    top_layer_endpoints.first = std::min(top_layer_endpoints.first, path.sub_paths.front().first.s_id);
+                    top_layer_endpoints.last = std::max(top_layer_endpoints.last, path.sub_paths.back().last.s_id);
+                }
+            }
+        }
+    }
+
+    // update current sequential position
+    m_sequential_view.current.first = !top_layer_only && keep_sequential_current_first ? std::clamp(m_sequential_view.current.first, global_endpoints.first, global_endpoints.last) : global_endpoints.first;
+    m_sequential_view.current.last = keep_sequential_current_last ? std::clamp(m_sequential_view.current.last, global_endpoints.first, global_endpoints.last) : global_endpoints.last;
+
+    // get the world position from gpu
+    bool found = false;
+    for (const TBuffer& buffer : m_buffers) {
+        // searches the path containing the current position
+        for (const Path& path : buffer.paths) {
+            int sub_path_id = path.get_id_of_sub_path_containing(m_sequential_view.current.last);
+            if (sub_path_id != -1) {
+                const Path::Sub_Path& sub_path = path.sub_paths[sub_path_id];
+                unsigned int offset = static_cast<unsigned int>(m_sequential_view.current.last - sub_path.first.s_id);
+                if (offset > 0) {
+                    if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Line)
+                        offset = 2 * offset - 1;
+                    else if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) {
+                        unsigned int indices_count = buffer.indices_per_segment();
+                        offset = indices_count * (offset - 1) + (indices_count - 6);
+                    }
+                }
+                offset += static_cast<unsigned int>(sub_path.first.i_id);
+
+                // gets the index from the index buffer on gpu
+                const IBuffer& i_buffer = buffer.indices[sub_path.first.b_id];
+                unsigned int index = 0;
+                glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo));
+                glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast<GLintptr>(offset * sizeof(unsigned int)), static_cast<GLsizeiptr>(sizeof(unsigned int)), static_cast<void*>(&index)));
+                glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
+
+                // gets the position from the vertices buffer on gpu
+                glsafe(::glBindBuffer(GL_ARRAY_BUFFER, i_buffer.vbo));
+                glsafe(::glGetBufferSubData(GL_ARRAY_BUFFER, static_cast<GLintptr>(index * buffer.vertices.vertex_size_bytes()), static_cast<GLsizeiptr>(3 * sizeof(float)), static_cast<void*>(m_sequential_view.current_position.data())));
+                glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
+                found = true;
+                break;
+            }
+        }
+
+        if (found)
+            break;
+    }
+
+    // second pass: filter paths by sequential data and collect them by color
+    RenderPath* render_path = nullptr;
+    for (const auto& [buffer, index_buffer_id, path_id, sub_path_id] : paths) {
+        const Path& path = buffer->paths[path_id];
+        const Path::Sub_Path& sub_path = path.sub_paths[sub_path_id];
+        if (m_sequential_view.current.last <= sub_path.first.s_id || sub_path.last.s_id <= m_sequential_view.current.first)
+            continue;
+
+        Color color;
+        switch (path.type)
+        {
+        case EMoveType::Extrude: {
+            if (!top_layer_only ||
+                m_sequential_view.current.last == global_endpoints.last ||
+                is_in_layers_range(path, m_layers_z_range[1], m_layers_z_range[1]))
+                color = extrusion_color(path);
+            else
+                color = { 0.25f, 0.25f, 0.25f };
+
+            break;
+        }
+        case EMoveType::Travel: {
+            if (!top_layer_only || m_sequential_view.current.last == global_endpoints.last || is_travel_in_layers_range(path_id, m_layers_z_range[1], m_layers_z_range[1]))
+                color = (m_view_type == EViewType::Feedrate || m_view_type == EViewType::Tool || m_view_type == EViewType::ColorPrint) ? extrusion_color(path) : travel_color(path);
+            else
+                color = { 0.25f, 0.25f, 0.25f };
+
+            break;
+        }
+        case EMoveType::Wipe: { color = Wipe_Color; break; }
+        default: { color = { 0.0f, 0.0f, 0.0f }; break; }
+        }
+
+        RenderPath key{ color, static_cast<unsigned int>(index_buffer_id), path_id };
+        if (render_path == nullptr || !RenderPathPropertyEqual()(*render_path, key))
+            render_path = const_cast<RenderPath*>(&(*buffer->render_paths.emplace(key).first));
+        unsigned int segments_count = std::min(m_sequential_view.current.last, sub_path.last.s_id) - std::max(m_sequential_view.current.first, sub_path.first.s_id) + 1;
+        unsigned int size_in_indices = 0;
+        switch (buffer->render_primitive_type)
+        {
+        case TBuffer::ERenderPrimitiveType::Point:    { size_in_indices = segments_count; break; }
+        case TBuffer::ERenderPrimitiveType::Line:
+        case TBuffer::ERenderPrimitiveType::Triangle: { size_in_indices = buffer->indices_per_segment() * (segments_count - 1); break; }
+        }
+        render_path->sizes.push_back(size_in_indices);
+
+        unsigned int delta_1st = 0;
+        if (sub_path.first.s_id < m_sequential_view.current.first && m_sequential_view.current.first <= sub_path.last.s_id)
+            delta_1st = m_sequential_view.current.first - sub_path.first.s_id;
+
+        if (buffer->render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle)
+            delta_1st *= buffer->indices_per_segment();
+
+        render_path->offsets.push_back(static_cast<size_t>((sub_path.first.i_id + delta_1st) * sizeof(unsigned int)));
+    }
+
+    // set sequential data to their final value
+    m_sequential_view.endpoints = top_layer_only ? top_layer_endpoints : global_endpoints;
+    m_sequential_view.current.first = !top_layer_only && keep_sequential_current_first ? std::clamp(m_sequential_view.current.first, m_sequential_view.endpoints.first, m_sequential_view.endpoints.last) : m_sequential_view.endpoints.first;
+
+    wxGetApp().plater()->enable_preview_moves_slider(!paths.empty());
+
+#if ENABLE_GCODE_VIEWER_STATISTICS
+    for (const TBuffer& buffer : m_buffers) {
+        m_statistics.render_paths_size += SLIC3R_STDUNORDEREDSET_MEMSIZE(buffer.render_paths, RenderPath);
+        for (const RenderPath& path : buffer.render_paths) {
+            m_statistics.render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.sizes, unsigned int);
+            m_statistics.render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.offsets, size_t);
+        }
+    }
+    m_statistics.refresh_paths_time = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - start_time).count();
+#endif // ENABLE_GCODE_VIEWER_STATISTICS
+}
+#else
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
 void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const
 {
 #if ENABLE_GCODE_VIEWER_STATISTICS
@@ -1961,7 +3099,159 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool
     m_statistics.refresh_paths_time = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - start_time).count();
 #endif // ENABLE_GCODE_VIEWER_STATISTICS
 }
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+#endif // ENABLE_SPLITTED_VERTEX_BUFFER
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
 
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+#if ENABLE_SPLITTED_VERTEX_BUFFER
+void GCodeViewer::render_toolpaths() const
+{
+#if ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS
+    float point_size = 20.0f;
+#else
+    float point_size = 0.8f;
+#endif // ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS
+    std::array<float, 4> light_intensity = { 0.25f, 0.70f, 0.75f, 0.75f };
+    const Camera& camera = wxGetApp().plater()->get_camera();
+    double zoom = camera.get_zoom();
+    const std::array<int, 4>& viewport = camera.get_viewport();
+    float near_plane_height = camera.get_type() == Camera::Perspective ? static_cast<float>(viewport[3]) / (2.0f * static_cast<float>(2.0 * std::tan(0.5 * Geometry::deg2rad(camera.get_fov())))) :
+        static_cast<float>(viewport[3]) * 0.0005;
+
+    auto set_uniform_color = [](const std::array<float, 3>& color, GLShaderProgram& shader) {
+        std::array<float, 4> color4 = { color[0], color[1], color[2], 1.0f };
+        shader.set_uniform("uniform_color", color4);
+    };
+
+    auto render_as_points = [this, zoom, point_size, near_plane_height, set_uniform_color]
+    (const TBuffer& buffer, unsigned int i_buffer_id, EOptionsColors color_id, GLShaderProgram& shader) {
+        set_uniform_color(Options_Colors[static_cast<unsigned int>(color_id)], shader);
+#if ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS
+        shader.set_uniform("use_fixed_screen_size", 1);
+#else
+        shader.set_uniform("use_fixed_screen_size", 0);
+#endif // ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS
+        shader.set_uniform("zoom", zoom);
+        shader.set_uniform("percent_outline_radius", 0.0f);
+        shader.set_uniform("percent_center_radius", 0.33f);
+        shader.set_uniform("point_size", point_size);
+        shader.set_uniform("near_plane_height", near_plane_height);
+
+        glsafe(::glEnable(GL_VERTEX_PROGRAM_POINT_SIZE));
+        glsafe(::glEnable(GL_POINT_SPRITE));
+
+        for (const RenderPath& path : buffer.render_paths) {
+            if (path.index_buffer_id == i_buffer_id) {
+                glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size()));
+#if ENABLE_GCODE_VIEWER_STATISTICS
+                ++m_statistics.gl_multi_points_calls_count;
+#endif // ENABLE_GCODE_VIEWER_STATISTICS
+            }
+        }
+
+        glsafe(::glDisable(GL_POINT_SPRITE));
+        glsafe(::glDisable(GL_VERTEX_PROGRAM_POINT_SIZE));
+    };
+
+    auto render_as_lines = [this, light_intensity, set_uniform_color](const TBuffer& buffer, unsigned int index_buffer_id, GLShaderProgram& shader) {
+        shader.set_uniform("light_intensity", light_intensity);
+        for (const RenderPath& path : buffer.render_paths) {
+            if (path.index_buffer_id == index_buffer_id) {
+                set_uniform_color(path.color, shader);
+                glsafe(::glMultiDrawElements(GL_LINES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size()));
+#if ENABLE_GCODE_VIEWER_STATISTICS
+                ++m_statistics.gl_multi_lines_calls_count;
+#endif // ENABLE_GCODE_VIEWER_STATISTICS
+            }
+        }
+    };
+
+    auto render_as_triangles = [this, set_uniform_color](const TBuffer& buffer, unsigned int index_buffer_id, GLShaderProgram& shader) {
+        for (const RenderPath& path : buffer.render_paths) {
+            if (path.index_buffer_id == index_buffer_id) {
+                set_uniform_color(path.color, shader);
+                glsafe(::glMultiDrawElements(GL_TRIANGLES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size()));
+#if ENABLE_GCODE_VIEWER_STATISTICS
+                ++m_statistics.gl_multi_triangles_calls_count;
+#endif // ENABLE_GCODE_VIEWER_STATISTICS
+            }
+        }
+    };
+
+    auto line_width = [](double zoom) {
+        return (zoom < 5.0) ? 1.0 : (1.0 + 5.0 * (zoom - 5.0) / (100.0 - 5.0));
+    };
+
+    glsafe(::glLineWidth(static_cast<GLfloat>(line_width(zoom))));
+
+    unsigned char begin_id = buffer_id(EMoveType::Retract);
+    unsigned char end_id = buffer_id(EMoveType::Count);
+
+    for (unsigned char i = begin_id; i < end_id; ++i) {
+        const TBuffer& buffer = m_buffers[i];
+        if (!buffer.visible || !buffer.has_data())
+            continue;
+
+        GLShaderProgram* shader = wxGetApp().get_shader(buffer.shader.c_str());
+        if (shader != nullptr) {
+            shader->start_using();
+
+            for (size_t j = 0; j < buffer.indices.size(); ++j) {
+                const IBuffer& i_buffer = buffer.indices[j];
+
+                glsafe(::glBindBuffer(GL_ARRAY_BUFFER, i_buffer.vbo));
+                glsafe(::glVertexPointer(buffer.vertices.position_size_floats(), GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_size()));
+                glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
+                bool has_normals = buffer.vertices.normal_size_floats() > 0;
+                if (has_normals) {
+                    glsafe(::glNormalPointer(GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_size()));
+                    glsafe(::glEnableClientState(GL_NORMAL_ARRAY));
+                }
+
+                glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo));
+
+                switch (buffer.render_primitive_type)
+                {
+                case TBuffer::ERenderPrimitiveType::Point: {
+                    EOptionsColors color;
+                    switch (buffer_type(i))
+                    {
+                    case EMoveType::Tool_change:  { color = EOptionsColors::ToolChanges; break; }
+                    case EMoveType::Color_change: { color = EOptionsColors::ColorChanges; break; }
+                    case EMoveType::Pause_Print:  { color = EOptionsColors::PausePrints; break; }
+                    case EMoveType::Custom_GCode: { color = EOptionsColors::CustomGCodes; break; }
+                    case EMoveType::Retract:      { color = EOptionsColors::Retractions; break; }
+                    case EMoveType::Unretract:    { color = EOptionsColors::Unretractions; break; }
+                    }
+                    render_as_points(buffer, static_cast<unsigned int>(j), color, *shader);
+                    break;
+                }
+                case TBuffer::ERenderPrimitiveType::Line: {
+                    render_as_lines(buffer, static_cast<unsigned int>(j), *shader);
+                    break;
+                }
+                case TBuffer::ERenderPrimitiveType::Triangle: {
+                    render_as_triangles(buffer, static_cast<unsigned int>(j), *shader);
+                    break;
+                }
+                }
+
+                glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
+
+                if (has_normals)
+                    glsafe(::glDisableClientState(GL_NORMAL_ARRAY));
+
+                glsafe(::glDisableClientState(GL_VERTEX_ARRAY));
+                glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
+            }
+
+            shader->stop_using();
+        }
+    }
+}
+#else
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
 void GCodeViewer::render_toolpaths() const
 {
 #if ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS
@@ -2108,6 +3398,9 @@ void GCodeViewer::render_toolpaths() const
         }
     }
 }
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+#endif // ENABLE_SPLITTED_VERTEX_BUFFER
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
 
 void GCodeViewer::render_shells() const
 {
@@ -2908,10 +4201,12 @@ void GCodeViewer::render_statistics() const
         add_counter(std::string("Extrude segments count:"), m_statistics.extrude_segments_count);
         ImGui::Separator();
         add_counter(std::string("VBuffers count:"), m_statistics.vbuffers_count);
-        add_counter(std::string("IBuffers  count:"), m_statistics.ibuffers_count);
-        ImGui::Separator();
-        add_counter(std::string("Max vertices in VBuffer:"), m_statistics.max_vertices_in_vertex_buffer);
-        add_counter(std::string("Max indices in IBuffer:"), m_statistics.max_indices_in_index_buffer);
+        add_counter(std::string("IBuffers count:"), m_statistics.ibuffers_count);
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+//        ImGui::Separator();
+//        add_counter(std::string("Max vertices in VBuffer:"), m_statistics.max_vertices_in_vertex_buffer);
+//        add_counter(std::string("Max indices in IBuffer:"), m_statistics.max_indices_in_index_buffer);
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
         wxGetApp().plater()->get_current_canvas3D()->set_as_dirty();
         wxGetApp().plater()->get_current_canvas3D()->request_extra_frame();
     }
@@ -2935,9 +4230,19 @@ void GCodeViewer::log_memory_used(const std::string& label, int64_t additional)
         }
         int64_t layers_size = SLIC3R_STDVEC_MEMSIZE(m_layers.get_zs(), double);
         layers_size += SLIC3R_STDVEC_MEMSIZE(m_layers.get_endpoints(), Layers::Endpoints);
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+#if ENABLE_SPLITTED_VERTEX_BUFFER
+        BOOST_LOG_TRIVIAL(trace) << label
+            << "(" << format_memsize_MB(additional + paths_size + render_paths_size + layers_size) << ");"
+            << log_memory_info();
+#else
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
         BOOST_LOG_TRIVIAL(trace) << label
             << format_memsize_MB(additional + paths_size + render_paths_size + layers_size)
             << log_memory_info();
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+#endif // ENABLE_SPLITTED_VERTEX_BUFFER
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
     }
 }
 
diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp
index 5f276b09f..51fd560a3 100644
--- a/src/slic3r/GUI/GCodeViewer.hpp
+++ b/src/slic3r/GUI/GCodeViewer.hpp
@@ -21,6 +21,11 @@ class GCodeViewer
 {
     using Color = std::array<float, 3>;
     using VertexBuffer = std::vector<float>;
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+#if ENABLE_SPLITTED_VERTEX_BUFFER
+    using MultiVertexBuffer = std::vector<VertexBuffer>;
+#endif // ENABLE_SPLITTED_VERTEX_BUFFER
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
     using IndexBuffer = std::vector<unsigned int>;
     using MultiIndexBuffer = std::vector<IndexBuffer>;
 
@@ -40,7 +45,7 @@ class GCodeViewer
         CustomGCodes
     };
 
-    // vbo buffer containing vertices data used to rendder a specific toolpath type
+    // vbo buffer containing vertices data used to render a specific toolpath type
     struct VBuffer
     {
         enum class EFormat : unsigned char
@@ -54,8 +59,17 @@ class GCodeViewer
         };
 
         EFormat format{ EFormat::Position };
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+#if ENABLE_SPLITTED_VERTEX_BUFFER
+        // vbos id
+        std::vector<unsigned int> ids;
+#else
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
         // vbo id
         unsigned int id{ 0 };
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+#endif // ENABLE_SPLITTED_VERTEX_BUFFER
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
         // count of vertices, updated after data are sent to gpu
         size_t count{ 0 };
 
@@ -66,26 +80,24 @@ class GCodeViewer
 
         size_t position_offset_floats() const { return 0; }
         size_t position_offset_size() const { return position_offset_floats() * sizeof(float); }
-        size_t position_size_floats() const
-        {
+        size_t position_size_floats() const {
             switch (format)
             {
             case EFormat::Position:
             case EFormat::PositionNormal3: { return 3; }
             case EFormat::PositionNormal1: { return 4; }
-            default: { return 0; }
+            default:                       { return 0; }
             }
         }
         size_t position_size_bytes() const { return position_size_floats() * sizeof(float); }
 
-        size_t normal_offset_floats() const
-        {
+        size_t normal_offset_floats() const {
             switch (format)
             {
             case EFormat::Position:
             case EFormat::PositionNormal1: { return 0; }
             case EFormat::PositionNormal3: { return 3; }
-            default: { return 0; }
+            default:                       { return 0; }
             }
         }
         size_t normal_offset_size() const { return normal_offset_floats() * sizeof(float); }
@@ -103,11 +115,22 @@ class GCodeViewer
         void reset();
     };
 
-    // ibo buffer containing indices data (lines/triangles) used to render a specific toolpath type
+    // ibo buffer containing indices data (for lines/triangles) used to render a specific toolpath type
     struct IBuffer
     {
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+#if ENABLE_SPLITTED_VERTEX_BUFFER
+        // id of the associated vertex buffer
+        unsigned int vbo{ 0 };
+        // ibo id
+        unsigned int ibo{ 0 };
+#else
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
         // ibo id
         unsigned int id{ 0 };
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+#endif // ENABLE_SPLITTED_VERTEX_BUFFER
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
         // count of indices, updated after data are sent to gpu
         size_t count{ 0 };
 
@@ -128,10 +151,30 @@ class GCodeViewer
             Vec3f position{ Vec3f::Zero() };
         };
 
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+#if ENABLE_SPLITTED_VERTEX_BUFFER
+        struct Sub_Path
+        {
+            Endpoint first;
+            Endpoint last;
+
+            bool contains(size_t s_id) const {
+                return first.s_id <= s_id && s_id <= last.s_id;
+            }
+        };
+#endif // ENABLE_SPLITTED_VERTEX_BUFFER
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+
         EMoveType type{ EMoveType::Noop };
         ExtrusionRole role{ erNone };
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+#if !ENABLE_SPLITTED_VERTEX_BUFFER
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
         Endpoint first;
         Endpoint last;
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+#endif // !ENABLE_SPLITTED_VERTEX_BUFFER
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
         float delta_extruder{ 0.0f };
         float height{ 0.0f };
         float width{ 0.0f };
@@ -140,13 +183,46 @@ class GCodeViewer
         float volumetric_rate{ 0.0f };
         unsigned char extruder_id{ 0 };
         unsigned char cp_color_id{ 0 };
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+#if ENABLE_SPLITTED_VERTEX_BUFFER
+        std::vector<Sub_Path> sub_paths;
+#endif // ENABLE_SPLITTED_VERTEX_BUFFER
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
 
         bool matches(const GCodeProcessor::MoveVertex& move) const;
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+#if ENABLE_SPLITTED_VERTEX_BUFFER
+        size_t vertices_count() const {
+            return sub_paths.empty() ? 0 : sub_paths.back().last.s_id - sub_paths.front().first.s_id + 1;
+        }
+        bool contains(size_t s_id) const {
+            return sub_paths.empty() ? false : sub_paths.front().first.s_id <= s_id && s_id <= sub_paths.back().last.s_id;
+        }
+        int get_id_of_sub_path_containing(size_t s_id) const {
+            if (sub_paths.empty())
+                return -1;
+            else {
+                for (int i = 0; i < static_cast<int>(sub_paths.size()); ++i) {
+                    if (sub_paths[i].contains(s_id))
+                        return i;
+                }
+                return -1;
+            }
+        }
+        void add_sub_path(const GCodeProcessor::MoveVertex& move, unsigned int b_id, size_t i_id, size_t s_id) {
+            Endpoint endpoint = { b_id, i_id, s_id, move.position };
+            sub_paths.push_back({ endpoint , endpoint });
+        }
+#else
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
         size_t vertices_count() const { return last.s_id - first.s_id + 1; }
         bool contains(size_t id) const { return first.s_id <= id && id <= last.s_id; }
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+#endif // ENABLE_SPLITTED_VERTEX_BUFFER
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
     };
 
-    // Used to batch the indices needed to render paths
+    // Used to batch the indices needed to render the paths
     struct RenderPath
     {
         // Render path property
@@ -202,10 +278,28 @@ class GCodeViewer
         bool visible{ false };
 
         void reset();
+
         // b_id index of buffer contained in this->indices
         // i_id index of first index contained in this->indices[b_id]
         // s_id index of first vertex contained in this->vertices
         void add_path(const GCodeProcessor::MoveVertex& move, unsigned int b_id, size_t i_id, size_t s_id);
+
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+#if ENABLE_SPLITTED_VERTEX_BUFFER
+        unsigned int max_vertices_per_segment() const {
+            switch (render_primitive_type)
+            {
+            case ERenderPrimitiveType::Point:    { return 1; }
+            case ERenderPrimitiveType::Line:     { return 2; }
+            case ERenderPrimitiveType::Triangle: { return 8; }
+            default:                             { return 0; }
+            }
+        }
+
+        size_t max_vertices_per_segment_size_floats() const { return vertices.vertex_size_floats() * static_cast<size_t>(max_vertices_per_segment()); }
+        size_t max_vertices_per_segment_size_bytes() const { return max_vertices_per_segment_size_floats() * sizeof(float); }
+#endif // ENABLE_SPLITTED_VERTEX_BUFFER
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
         unsigned int indices_per_segment() const {
             switch (render_primitive_type)
             {
@@ -215,14 +309,22 @@ class GCodeViewer
             default:                             { return 0; }
             }
         }
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+#if ENABLE_SPLITTED_VERTEX_BUFFER
+        size_t indices_per_segment_size_bytes() const { return static_cast<size_t>(indices_per_segment() * sizeof(unsigned int)); }
+#endif // ENABLE_SPLITTED_VERTEX_BUFFER
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
         unsigned int start_segment_vertex_offset() const {
-            switch (render_primitive_type)
-            {
-            case ERenderPrimitiveType::Point:
-            case ERenderPrimitiveType::Line: 
-            case ERenderPrimitiveType::Triangle:
-            default: { return 0; }
-            }
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+            return 0;
+//            switch (render_primitive_type)
+//            {
+//            case ERenderPrimitiveType::Point:
+//            case ERenderPrimitiveType::Line: 
+//            case ERenderPrimitiveType::Triangle:
+//            default: { return 0; }
+//            }
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
         }
         unsigned int end_segment_vertex_offset() const {
             switch (render_primitive_type)
@@ -234,7 +336,17 @@ class GCodeViewer
             }
         }
 
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+#if ENABLE_SPLITTED_VERTEX_BUFFER
+        bool has_data() const {
+            return !vertices.ids.empty() && vertices.ids.front() != 0 && !indices.empty() && indices.front().ibo != 0;
+        }
+#else
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
         bool has_data() const { return vertices.id != 0 && !indices.empty() && indices.front().id != 0; }
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+#endif // ENABLE_SPLITTED_VERTEX_BUFFER
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
     };
 
     // helper to render shells
@@ -309,6 +421,13 @@ class GCodeViewer
         {
             size_t first{ 0 };
             size_t last{ 0 };
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+#if ENABLE_SPLITTED_VERTEX_BUFFER
+            bool operator == (const Endpoints& other) const {
+                return first == other.first && last == other.last;
+            }
+#endif // ENABLE_SPLITTED_VERTEX_BUFFER
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
         };
 
     private:
@@ -316,14 +435,12 @@ class GCodeViewer
         std::vector<Endpoints> m_endpoints;
 
     public:
-        void append(double z, Endpoints endpoints)
-        {
+        void append(double z, Endpoints endpoints) {
             m_zs.emplace_back(z);
             m_endpoints.emplace_back(endpoints);
         }
 
-        void reset()
-        {
+        void reset() {
             m_zs = std::vector<double>();
             m_endpoints = std::vector<Endpoints>();
         }
@@ -335,6 +452,19 @@ class GCodeViewer
         std::vector<Endpoints>& get_endpoints() { return m_endpoints; }
         double get_z_at(unsigned int id) const { return (id < m_zs.size()) ? m_zs[id] : 0.0; }
         Endpoints get_endpoints_at(unsigned int id) const { return (id < m_endpoints.size()) ? m_endpoints[id] : Endpoints(); }
+
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+#if ENABLE_SPLITTED_VERTEX_BUFFER
+        bool operator != (const Layers& other) const {
+            if (m_zs != other.m_zs)
+                return true;
+            if (!(m_endpoints == other.m_endpoints))
+                return true;
+
+            return false;
+        }
+#endif // ENABLE_SPLITTED_VERTEX_BUFFER
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
     };
 
 #if ENABLE_GCODE_VIEWER_STATISTICS
@@ -363,8 +493,10 @@ class GCodeViewer
         int64_t extrude_segments_count{ 0 };
         int64_t vbuffers_count{ 0 };
         int64_t ibuffers_count{ 0 };
-        int64_t max_vertices_in_vertex_buffer{ 0 };
-        int64_t max_indices_in_index_buffer{ 0 };
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+//        int64_t max_vertices_in_vertex_buffer{ 0 };
+//        int64_t max_indices_in_index_buffer{ 0 };
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
 
         void reset_all() {
             reset_times();
@@ -402,8 +534,10 @@ class GCodeViewer
             extrude_segments_count =  0;
             vbuffers_count = 0;
             ibuffers_count = 0;
-            max_vertices_in_vertex_buffer = 0;
-            max_indices_in_index_buffer = 0;
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+//            max_vertices_in_vertex_buffer = 0;
+//            max_indices_in_index_buffer = 0;
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
         }
     };
 #endif // ENABLE_GCODE_VIEWER_STATISTICS