diff --git a/resources/shaders/extrusions.fs b/resources/shaders/extrusions.fs
new file mode 100644
index 000000000..046dade8a
--- /dev/null
+++ b/resources/shaders/extrusions.fs
@@ -0,0 +1,45 @@
+#version 110
+
+#define INTENSITY_AMBIENT    0.3
+#define INTENSITY_CORRECTION 0.6
+
+// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31)
+const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
+#define LIGHT_TOP_DIFFUSE    (0.8 * INTENSITY_CORRECTION)
+#define LIGHT_TOP_SPECULAR   (0.125 * INTENSITY_CORRECTION)
+#define LIGHT_TOP_SHININESS  20.0
+
+// normalized values for (1./1.43, 0.2/1.43, 1./1.43)
+const vec3 LIGHT_FRONT_DIR = vec3(0.0, 0.0, 1.0);
+#define LIGHT_FRONT_DIFFUSE  (0.3 * INTENSITY_CORRECTION)
+
+uniform vec4 uniform_color;
+
+varying vec3 eye_position;
+varying vec3 eye_normal;
+//varying float world_normal_z;
+
+// x = tainted, y = specular;
+vec2 intensity;
+
+void main()
+{
+    vec3 normal = normalize(eye_normal);
+
+    // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
+    // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
+    float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0);
+
+    intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
+    intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(eye_position), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS);
+
+    // Perform the same lighting calculation for the 2nd light source (no specular applied).
+    NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0);
+    intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;    
+
+//    // darkens fragments whose normal points downward
+//    if (world_normal_z < 0.0)
+//        intensity.x *= (1.0 + world_normal_z * (1.0 - INTENSITY_AMBIENT));
+
+    gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + uniform_color.rgb * intensity.x, uniform_color.a);
+}
diff --git a/resources/shaders/extrusions.vs b/resources/shaders/extrusions.vs
new file mode 100644
index 000000000..d97adbabe
--- /dev/null
+++ b/resources/shaders/extrusions.vs
@@ -0,0 +1,15 @@
+#version 110
+
+varying vec3 eye_position;
+varying vec3 eye_normal;
+//// world z component of the normal used to darken the lower side of the toolpaths
+//varying float world_normal_z;
+
+void main()
+{
+    eye_position = (gl_ModelViewMatrix * gl_Vertex).xyz;
+    eye_normal = gl_NormalMatrix * vec3(0.0, 0.0, 1.0);
+//    eye_normal = gl_NormalMatrix * gl_Normal;
+//	world_normal_z = gl_Normal.z;
+    gl_Position = ftransform();
+}
diff --git a/resources/shaders/retractions.fs b/resources/shaders/retractions.fs
new file mode 100644
index 000000000..046dade8a
--- /dev/null
+++ b/resources/shaders/retractions.fs
@@ -0,0 +1,45 @@
+#version 110
+
+#define INTENSITY_AMBIENT    0.3
+#define INTENSITY_CORRECTION 0.6
+
+// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31)
+const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
+#define LIGHT_TOP_DIFFUSE    (0.8 * INTENSITY_CORRECTION)
+#define LIGHT_TOP_SPECULAR   (0.125 * INTENSITY_CORRECTION)
+#define LIGHT_TOP_SHININESS  20.0
+
+// normalized values for (1./1.43, 0.2/1.43, 1./1.43)
+const vec3 LIGHT_FRONT_DIR = vec3(0.0, 0.0, 1.0);
+#define LIGHT_FRONT_DIFFUSE  (0.3 * INTENSITY_CORRECTION)
+
+uniform vec4 uniform_color;
+
+varying vec3 eye_position;
+varying vec3 eye_normal;
+//varying float world_normal_z;
+
+// x = tainted, y = specular;
+vec2 intensity;
+
+void main()
+{
+    vec3 normal = normalize(eye_normal);
+
+    // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
+    // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
+    float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0);
+
+    intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
+    intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(eye_position), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS);
+
+    // Perform the same lighting calculation for the 2nd light source (no specular applied).
+    NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0);
+    intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;    
+
+//    // darkens fragments whose normal points downward
+//    if (world_normal_z < 0.0)
+//        intensity.x *= (1.0 + world_normal_z * (1.0 - INTENSITY_AMBIENT));
+
+    gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + uniform_color.rgb * intensity.x, uniform_color.a);
+}
diff --git a/resources/shaders/retractions.vs b/resources/shaders/retractions.vs
new file mode 100644
index 000000000..d97adbabe
--- /dev/null
+++ b/resources/shaders/retractions.vs
@@ -0,0 +1,15 @@
+#version 110
+
+varying vec3 eye_position;
+varying vec3 eye_normal;
+//// world z component of the normal used to darken the lower side of the toolpaths
+//varying float world_normal_z;
+
+void main()
+{
+    eye_position = (gl_ModelViewMatrix * gl_Vertex).xyz;
+    eye_normal = gl_NormalMatrix * vec3(0.0, 0.0, 1.0);
+//    eye_normal = gl_NormalMatrix * gl_Normal;
+//	world_normal_z = gl_Normal.z;
+    gl_Position = ftransform();
+}
diff --git a/resources/shaders/toolchanges.fs b/resources/shaders/toolchanges.fs
new file mode 100644
index 000000000..046dade8a
--- /dev/null
+++ b/resources/shaders/toolchanges.fs
@@ -0,0 +1,45 @@
+#version 110
+
+#define INTENSITY_AMBIENT    0.3
+#define INTENSITY_CORRECTION 0.6
+
+// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31)
+const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
+#define LIGHT_TOP_DIFFUSE    (0.8 * INTENSITY_CORRECTION)
+#define LIGHT_TOP_SPECULAR   (0.125 * INTENSITY_CORRECTION)
+#define LIGHT_TOP_SHININESS  20.0
+
+// normalized values for (1./1.43, 0.2/1.43, 1./1.43)
+const vec3 LIGHT_FRONT_DIR = vec3(0.0, 0.0, 1.0);
+#define LIGHT_FRONT_DIFFUSE  (0.3 * INTENSITY_CORRECTION)
+
+uniform vec4 uniform_color;
+
+varying vec3 eye_position;
+varying vec3 eye_normal;
+//varying float world_normal_z;
+
+// x = tainted, y = specular;
+vec2 intensity;
+
+void main()
+{
+    vec3 normal = normalize(eye_normal);
+
+    // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
+    // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
+    float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0);
+
+    intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
+    intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(eye_position), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS);
+
+    // Perform the same lighting calculation for the 2nd light source (no specular applied).
+    NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0);
+    intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;    
+
+//    // darkens fragments whose normal points downward
+//    if (world_normal_z < 0.0)
+//        intensity.x *= (1.0 + world_normal_z * (1.0 - INTENSITY_AMBIENT));
+
+    gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + uniform_color.rgb * intensity.x, uniform_color.a);
+}
diff --git a/resources/shaders/toolchanges.vs b/resources/shaders/toolchanges.vs
new file mode 100644
index 000000000..d97adbabe
--- /dev/null
+++ b/resources/shaders/toolchanges.vs
@@ -0,0 +1,15 @@
+#version 110
+
+varying vec3 eye_position;
+varying vec3 eye_normal;
+//// world z component of the normal used to darken the lower side of the toolpaths
+//varying float world_normal_z;
+
+void main()
+{
+    eye_position = (gl_ModelViewMatrix * gl_Vertex).xyz;
+    eye_normal = gl_NormalMatrix * vec3(0.0, 0.0, 1.0);
+//    eye_normal = gl_NormalMatrix * gl_Normal;
+//	world_normal_z = gl_Normal.z;
+    gl_Position = ftransform();
+}
diff --git a/resources/shaders/travels.fs b/resources/shaders/travels.fs
new file mode 100644
index 000000000..046dade8a
--- /dev/null
+++ b/resources/shaders/travels.fs
@@ -0,0 +1,45 @@
+#version 110
+
+#define INTENSITY_AMBIENT    0.3
+#define INTENSITY_CORRECTION 0.6
+
+// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31)
+const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
+#define LIGHT_TOP_DIFFUSE    (0.8 * INTENSITY_CORRECTION)
+#define LIGHT_TOP_SPECULAR   (0.125 * INTENSITY_CORRECTION)
+#define LIGHT_TOP_SHININESS  20.0
+
+// normalized values for (1./1.43, 0.2/1.43, 1./1.43)
+const vec3 LIGHT_FRONT_DIR = vec3(0.0, 0.0, 1.0);
+#define LIGHT_FRONT_DIFFUSE  (0.3 * INTENSITY_CORRECTION)
+
+uniform vec4 uniform_color;
+
+varying vec3 eye_position;
+varying vec3 eye_normal;
+//varying float world_normal_z;
+
+// x = tainted, y = specular;
+vec2 intensity;
+
+void main()
+{
+    vec3 normal = normalize(eye_normal);
+
+    // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
+    // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
+    float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0);
+
+    intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
+    intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(eye_position), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS);
+
+    // Perform the same lighting calculation for the 2nd light source (no specular applied).
+    NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0);
+    intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;    
+
+//    // darkens fragments whose normal points downward
+//    if (world_normal_z < 0.0)
+//        intensity.x *= (1.0 + world_normal_z * (1.0 - INTENSITY_AMBIENT));
+
+    gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + uniform_color.rgb * intensity.x, uniform_color.a);
+}
diff --git a/resources/shaders/travels.vs b/resources/shaders/travels.vs
new file mode 100644
index 000000000..d97adbabe
--- /dev/null
+++ b/resources/shaders/travels.vs
@@ -0,0 +1,15 @@
+#version 110
+
+varying vec3 eye_position;
+varying vec3 eye_normal;
+//// world z component of the normal used to darken the lower side of the toolpaths
+//varying float world_normal_z;
+
+void main()
+{
+    eye_position = (gl_ModelViewMatrix * gl_Vertex).xyz;
+    eye_normal = gl_NormalMatrix * vec3(0.0, 0.0, 1.0);
+//    eye_normal = gl_NormalMatrix * gl_Normal;
+//	world_normal_z = gl_Normal.z;
+    gl_Position = ftransform();
+}
diff --git a/resources/shaders/unretractions.fs b/resources/shaders/unretractions.fs
new file mode 100644
index 000000000..046dade8a
--- /dev/null
+++ b/resources/shaders/unretractions.fs
@@ -0,0 +1,45 @@
+#version 110
+
+#define INTENSITY_AMBIENT    0.3
+#define INTENSITY_CORRECTION 0.6
+
+// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31)
+const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
+#define LIGHT_TOP_DIFFUSE    (0.8 * INTENSITY_CORRECTION)
+#define LIGHT_TOP_SPECULAR   (0.125 * INTENSITY_CORRECTION)
+#define LIGHT_TOP_SHININESS  20.0
+
+// normalized values for (1./1.43, 0.2/1.43, 1./1.43)
+const vec3 LIGHT_FRONT_DIR = vec3(0.0, 0.0, 1.0);
+#define LIGHT_FRONT_DIFFUSE  (0.3 * INTENSITY_CORRECTION)
+
+uniform vec4 uniform_color;
+
+varying vec3 eye_position;
+varying vec3 eye_normal;
+//varying float world_normal_z;
+
+// x = tainted, y = specular;
+vec2 intensity;
+
+void main()
+{
+    vec3 normal = normalize(eye_normal);
+
+    // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
+    // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
+    float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0);
+
+    intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
+    intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(eye_position), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS);
+
+    // Perform the same lighting calculation for the 2nd light source (no specular applied).
+    NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0);
+    intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;    
+
+//    // darkens fragments whose normal points downward
+//    if (world_normal_z < 0.0)
+//        intensity.x *= (1.0 + world_normal_z * (1.0 - INTENSITY_AMBIENT));
+
+    gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + uniform_color.rgb * intensity.x, uniform_color.a);
+}
diff --git a/resources/shaders/unretractions.vs b/resources/shaders/unretractions.vs
new file mode 100644
index 000000000..d97adbabe
--- /dev/null
+++ b/resources/shaders/unretractions.vs
@@ -0,0 +1,15 @@
+#version 110
+
+varying vec3 eye_position;
+varying vec3 eye_normal;
+//// world z component of the normal used to darken the lower side of the toolpaths
+//varying float world_normal_z;
+
+void main()
+{
+    eye_position = (gl_ModelViewMatrix * gl_Vertex).xyz;
+    eye_normal = gl_NormalMatrix * vec3(0.0, 0.0, 1.0);
+//    eye_normal = gl_NormalMatrix * gl_Normal;
+//	world_normal_z = gl_Normal.z;
+    gl_Position = ftransform();
+}
diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp
index f5094553a..c75c24002 100644
--- a/src/libslic3r/GCode/GCodeProcessor.cpp
+++ b/src/libslic3r/GCode/GCodeProcessor.cpp
@@ -38,6 +38,8 @@ void GCodeProcessor::CpColor::reset()
     current = 0;
 }
 
+unsigned int GCodeProcessor::Result::id = 0;
+
 void GCodeProcessor::apply_config(const PrintConfig& config)
 {
     m_parser.apply_config(config);
diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp
index 54ac546b3..1f7af9c29 100644
--- a/src/libslic3r/GCode/GCodeProcessor.hpp
+++ b/src/libslic3r/GCode/GCodeProcessor.hpp
@@ -39,17 +39,6 @@ namespace Slic3r {
             Relative
         };
 
-        enum class EMoveType : unsigned char
-        {
-            Noop,
-            Retract,
-            Unretract,
-            Tool_change,
-            Travel,
-            Extrude,
-            Num_Types
-        };
-
         struct CachedPosition
         {
             AxisCoords position; // mm
@@ -67,6 +56,17 @@ namespace Slic3r {
         };
 
     public:
+        enum class EMoveType : unsigned char
+        {
+            Noop,
+            Retract,
+            Unretract,
+            Tool_change,
+            Travel,
+            Extrude,
+            Count
+        };
+
         struct MoveVertex
         {
             EMoveType type{ EMoveType::Noop };
@@ -98,8 +98,9 @@ namespace Slic3r {
 
         struct Result
         {
+            static unsigned int id;
             std::vector<MoveVertex> moves;
-            void reset() { moves = std::vector<MoveVertex>(); }
+            void reset() { ++id; moves = std::vector<MoveVertex>(); }
         };
 
     private:
diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt
index c8f7e9f1c..f5f5f6eb6 100644
--- a/src/slic3r/CMakeLists.txt
+++ b/src/slic3r/CMakeLists.txt
@@ -56,6 +56,8 @@ set(SLIC3R_GUI_SOURCES
     GUI/GLTexture.cpp
     GUI/GLToolbar.hpp
     GUI/GLToolbar.cpp
+    GUI/GCodeViewer.hpp
+    GUI/GCodeViewer.cpp    
     GUI/Preferences.cpp
     GUI/Preferences.hpp
     GUI/Preset.cpp
diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp
new file mode 100644
index 000000000..584ef06c3
--- /dev/null
+++ b/src/slic3r/GUI/GCodeViewer.cpp
@@ -0,0 +1,242 @@
+#include "libslic3r/libslic3r.h"
+#include "GCodeViewer.hpp"
+#include "3DScene.hpp"
+
+#if ENABLE_GCODE_VIEWER
+
+#include <GL/glew.h>
+#include <boost/log/trivial.hpp>
+
+#include <array>
+
+namespace Slic3r {
+namespace GUI {
+
+static unsigned char buffer_id(GCodeProcessor::EMoveType type) {
+    return static_cast<unsigned char>(type) - static_cast<unsigned char>(GCodeProcessor::EMoveType::Retract);
+}
+
+static GCodeProcessor::EMoveType buffer_type(unsigned char id) {
+    return static_cast<GCodeProcessor::EMoveType>(static_cast<unsigned char>(GCodeProcessor::EMoveType::Retract) + id);
+}
+
+void GCodeViewer::generate(const GCodeProcessor::Result& gcode_result)
+{
+    if (m_last_result_id == gcode_result.id)
+        return;
+
+    m_last_result_id = gcode_result.id;
+
+    // release gpu memory, if used
+    reset_buffers();
+
+    // convert data
+    size_t vertices_count = gcode_result.moves.size();
+    for (size_t i = 0; i < vertices_count; ++i)
+    {
+        // skip first vertex
+        if (i == 0)
+            continue;
+
+        const GCodeProcessor::MoveVertex& prev = gcode_result.moves[i - 1];
+        const GCodeProcessor::MoveVertex& curr = gcode_result.moves[i];
+
+        Buffer& buffer = m_buffers[buffer_id(curr.type)];
+
+        switch (curr.type)
+        {
+        case GCodeProcessor::EMoveType::Tool_change:
+        case GCodeProcessor::EMoveType::Retract:
+        case GCodeProcessor::EMoveType::Unretract:
+        {
+            for (int j = 0; j < 3; ++j)
+            {
+                buffer.data.insert(buffer.data.end(), curr.position[j]);
+            }
+            break;
+        }
+        case GCodeProcessor::EMoveType::Extrude:
+        case GCodeProcessor::EMoveType::Travel:
+        {
+            for (int j = 0; j < 3; ++j)
+            {
+                buffer.data.insert(buffer.data.end(), prev.position[j]);
+            }
+            for (int j = 0; j < 3; ++j)
+            {
+                buffer.data.insert(buffer.data.end(), curr.position[j]);
+            }
+            break;
+        }
+        default:
+        {
+            continue;
+        }
+        }
+    }
+
+    // send data to gpu
+    for (Buffer& buffer : m_buffers)
+    {
+        glsafe(::glGenBuffers(1, &buffer.vbo_id));
+        glsafe(::glBindBuffer(GL_ARRAY_BUFFER, buffer.vbo_id));
+        glsafe(::glBufferData(GL_ARRAY_BUFFER, buffer.data.size() * sizeof(float), buffer.data.data(), GL_STATIC_DRAW));
+        glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
+    }
+}
+
+void GCodeViewer::render() const
+{
+    auto set_color = [](GLint current_program_id, const std::array<float, 4>& color) {
+        if (current_program_id > 0)
+        {
+            GLint color_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "uniform_color") : -1;
+            if (color_id >= 0)
+            {
+                glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)color.data()));
+                return;
+            }
+        }
+        BOOST_LOG_TRIVIAL(error) << "Unable to find uniform_color uniform";
+    };
+
+    unsigned char begin_id = buffer_id(GCodeProcessor::EMoveType::Retract);
+    unsigned char end_id = buffer_id(GCodeProcessor::EMoveType::Count);
+
+    glsafe(::glEnable(GL_DEPTH_TEST));
+
+    for (unsigned char i = begin_id; i < end_id; ++i)
+    {
+        const Buffer& buffer = m_buffers[i];
+        if (buffer.vbo_id == 0)
+            continue;
+
+        const Shader& shader = m_shaders[i];
+        if (shader.is_initialized())
+        {
+            shader.start_using();
+
+            GLint current_program_id;
+            glsafe(::glGetIntegerv(GL_CURRENT_PROGRAM, &current_program_id));
+
+            GCodeProcessor::EMoveType type = buffer_type(i);
+
+            glsafe(::glBindBuffer(GL_ARRAY_BUFFER, buffer.vbo_id));
+            glsafe(::glVertexPointer(3, GL_FLOAT, Buffer::stride(type), (const void*)0));
+            glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
+
+            switch (type)
+            {
+            case GCodeProcessor::EMoveType::Tool_change:
+            case GCodeProcessor::EMoveType::Retract:
+            case GCodeProcessor::EMoveType::Unretract:
+            {
+                std::array<float, 4> color = { 0.0f, 1.0f, 0.0f, 1.0f };
+                set_color(current_program_id, color);
+                glsafe(::glDrawArrays(GL_POINTS, 0, (GLsizei)buffer.data.size() / Buffer::record_size(type)));
+                break;
+            }
+            case GCodeProcessor::EMoveType::Extrude:
+            {
+                std::array<float, 4> color = { 1.0f, 0.0f, 0.0f, 1.0f };
+                set_color(current_program_id, color);
+                glsafe(::glDrawArrays(GL_LINES, 0, (GLsizei)buffer.data.size() / Buffer::record_size(type)));
+                break;
+            }
+            case GCodeProcessor::EMoveType::Travel:
+            {
+                std::array<float, 4> color = { 1.0f, 1.0f, 0.0f, 1.0f };
+                set_color(current_program_id, color);
+                glsafe(::glDrawArrays(GL_LINES, 0, (GLsizei)buffer.data.size() / Buffer::record_size(type)));
+                break;
+            }
+            default:
+            {
+                break;
+            }
+            }
+
+            glsafe(::glDisableClientState(GL_VERTEX_ARRAY));
+
+            glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
+            shader.stop_using();
+        }
+    }
+}
+
+bool GCodeViewer::init_shaders()
+{
+    unsigned char begin_id = buffer_id(GCodeProcessor::EMoveType::Retract);
+    unsigned char end_id = buffer_id(GCodeProcessor::EMoveType::Count);
+
+    for (unsigned char i = begin_id; i < end_id; ++i)
+    {
+        Shader& shader = m_shaders[i];
+        std::string vertex_shader_src;
+        std::string fragment_shader_src;
+        GCodeProcessor::EMoveType type = buffer_type(i);
+        switch (type)
+        {
+        case GCodeProcessor::EMoveType::Tool_change:
+        {
+            vertex_shader_src = "toolchanges.vs";
+            fragment_shader_src = "toolchanges.fs";
+            break;
+        }
+        case GCodeProcessor::EMoveType::Retract:
+        {
+            vertex_shader_src = "retractions.vs";
+            fragment_shader_src = "retractions.fs";
+            break;
+        }
+        case GCodeProcessor::EMoveType::Unretract:
+        {
+            vertex_shader_src = "unretractions.vs";
+            fragment_shader_src = "unretractions.fs";
+            break;
+        }
+        case GCodeProcessor::EMoveType::Extrude:
+        {
+            vertex_shader_src = "extrusions.vs";
+            fragment_shader_src = "extrusions.fs";
+            break;
+        }
+        case GCodeProcessor::EMoveType::Travel:
+        {
+            vertex_shader_src = "travels.vs";
+            fragment_shader_src = "travels.fs";
+            break;
+        }
+        default:
+        {
+            break;
+        }
+        }
+
+        if (!shader.init(vertex_shader_src, fragment_shader_src))
+        {
+            BOOST_LOG_TRIVIAL(error) << "Unable to initialize toolpaths shader: please, check that the files " << vertex_shader_src << " and " << fragment_shader_src << " are available";
+            return false;
+        }
+    }
+
+    return true;
+}
+
+void GCodeViewer::reset_buffers()
+{
+    for (Buffer& buffer : m_buffers)
+    {
+        // release gpu memory
+        if (buffer.vbo_id > 0)
+            glsafe(::glDeleteBuffers(1, &buffer.vbo_id));
+
+        // release cpu memory
+        buffer.data = std::vector<float>();
+    }
+}
+
+} // namespace GUI
+} // namespace Slic3r
+
+#endif // ENABLE_GCODE_VIEWER
diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp
new file mode 100644
index 000000000..95250b203
--- /dev/null
+++ b/src/slic3r/GUI/GCodeViewer.hpp
@@ -0,0 +1,63 @@
+#ifndef slic3r_GCodeViewer_hpp_
+#define slic3r_GCodeViewer_hpp_
+
+#if ENABLE_GCODE_VIEWER
+
+#include "GLShader.hpp"
+#include "libslic3r/GCode/GCodeProcessor.hpp"
+
+#include <vector>
+
+namespace Slic3r {
+namespace GUI {
+
+class GCodeViewer
+{
+    struct Buffer
+    {
+        unsigned int vbo_id{ 0 };
+        std::vector<float> data;
+
+        static size_t stride(GCodeProcessor::EMoveType type)
+        {
+            return 3 * sizeof(float);
+        }
+
+        static size_t record_size(GCodeProcessor::EMoveType type)
+        {
+            switch (type)
+            {
+            case GCodeProcessor::EMoveType::Tool_change:
+            case GCodeProcessor::EMoveType::Retract:
+            case GCodeProcessor::EMoveType::Unretract: { return 3; }
+            case GCodeProcessor::EMoveType::Extrude:
+            case GCodeProcessor::EMoveType::Travel: { return 6; }
+            default: { return 0; }
+            }
+        }
+    };
+
+    std::vector<Buffer> m_buffers{ static_cast<size_t>(GCodeProcessor::EMoveType::Extrude) };
+    std::vector<Shader> m_shaders{ static_cast<size_t>(GCodeProcessor::EMoveType::Extrude) };
+    unsigned int m_last_result_id{ 0 };
+
+public:
+    GCodeViewer() = default;
+    ~GCodeViewer() { reset_buffers(); }
+
+    bool init() { return init_shaders(); }
+    void generate(const GCodeProcessor::Result& gcode_result);
+    void render() const;
+
+private:
+    bool init_shaders();
+    void reset_buffers();
+};
+
+} // namespace GUI
+} // namespace Slic3r
+
+#endif // ENABLE_GCODE_VIEWER
+
+#endif // slic3r_GCodeViewer_hpp_
+
diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp
index 85ea44ef1..c9c4ab336 100644
--- a/src/slic3r/GUI/GLCanvas3D.cpp
+++ b/src/slic3r/GUI/GLCanvas3D.cpp
@@ -1678,6 +1678,14 @@ bool GLCanvas3D::init()
         return false;
     }
 
+#if ENABLE_GCODE_VIEWER
+    if (!m_main_toolbar.is_enabled())
+    {
+        if (!m_gcode_viewer.init())
+            return false;
+    }
+#endif // ENABLE_GCODE_VIEWER
+
     // on linux the gl context is not valid until the canvas is not shown on screen
     // we defer the geometry finalization of volumes until the first call to render()
     m_volumes.finalize_geometry(true);
@@ -2109,6 +2117,9 @@ void GLCanvas3D::render()
     _render_background();
 
     _render_objects();
+#if ENABLE_GCODE_VIEWER
+    _render_gcode();
+#endif // ENABLE_GCODE_VIEWER
     _render_sla_slices();
     _render_selection();
 #if ENABLE_NON_STATIC_CANVAS_MANAGER
@@ -2783,6 +2794,8 @@ void GLCanvas3D::load_gcode_preview_2(const GCodeProcessor::Result& gcode_result
         out << v.to_string() << "\n";
     }
     out.close();
+
+    m_gcode_viewer.generate(gcode_result);
 #endif // ENABLE_GCODE_VIEWER_DEBUG_OUTPUT
 }
 #endif // ENABLE_GCODE_VIEWER
@@ -5440,6 +5453,13 @@ void GLCanvas3D::_render_objects() const
     m_camera_clipping_plane = ClippingPlane::ClipsNothing();
 }
 
+#if ENABLE_GCODE_VIEWER
+void GLCanvas3D::_render_gcode() const
+{
+    m_gcode_viewer.render();
+}
+#endif // ENABLE_GCODE_VIEWER
+
 void GLCanvas3D::_render_selection() const
 {
     float scale_factor = 1.0;
diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp
index 0c82f058f..8c6b3c3f0 100644
--- a/src/slic3r/GUI/GLCanvas3D.hpp
+++ b/src/slic3r/GUI/GLCanvas3D.hpp
@@ -21,6 +21,7 @@
 #endif // !ENABLE_NON_STATIC_CANVAS_MANAGER
 #if ENABLE_GCODE_VIEWER
 #include "libslic3r/GCode/GCodeProcessor.hpp"
+#include "GCodeViewer.hpp"
 #endif // ENABLE_GCODE_VIEWER
 
 #include <float.h>
@@ -468,6 +469,10 @@ private:
     bool m_extra_frame_requested;
 
     mutable GLVolumeCollection m_volumes;
+#if ENABLE_GCODE_VIEWER
+    GCodeViewer m_gcode_viewer;
+#endif // ENABLE_GCODE_VIEWER
+
     Selection m_selection;
     const DynamicPrintConfig* m_config;
     Model* m_model;
@@ -764,6 +769,9 @@ private:
     void _render_background() const;
     void _render_bed(float theta, bool show_axes) const;
     void _render_objects() const;
+#if ENABLE_GCODE_VIEWER
+    void _render_gcode() const;
+#endif // ENABLE_GCODE_VIEWER
     void _render_selection() const;
 #if ENABLE_RENDER_SELECTION_CENTER
     void _render_selection_center() const;
diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp
index 979509562..0171dd597 100644
--- a/src/slic3r/GUI/GUI_Preview.cpp
+++ b/src/slic3r/GUI/GUI_Preview.cpp
@@ -946,9 +946,10 @@ void Preview::load_print_as_fff(bool keep_z_range)
         m_canvas->set_selected_extruder(0);
         if (gcode_preview_data_valid) {
             // Load the real G-code preview.
-            m_canvas->load_gcode_preview(*m_gcode_preview_data, colors);
 #if ENABLE_GCODE_VIEWER
             m_canvas->load_gcode_preview_2(*m_gcode_result);
+#else
+            m_canvas->load_gcode_preview(*m_gcode_preview_data, colors);
 #endif // ENABLE_GCODE_VIEWER
             m_loaded = true;
         } else {