diff --git a/Cargo.toml b/Cargo.toml
index e7e116a4b689200bbf6afec9ab449b06c733be45..82e2d60f907351c6af1d597e12beee71218b90e3 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -30,6 +30,7 @@ default-features = false
 default = ["acpi"]
 acpi = []
 doc = []
+graphical_debug = []
 live = []
 multi_core = []
 pti = []
diff --git a/res/unifont.font b/res/unifont.font
new file mode 100644
index 0000000000000000000000000000000000000000..a00366d5f559bc6fb3c20b9ee9cf8320219013b5
Binary files /dev/null and b/res/unifont.font differ
diff --git a/src/arch/x86_64/debug.rs b/src/arch/x86_64/debug.rs
new file mode 100644
index 0000000000000000000000000000000000000000..198ca29aad7d58df885b4a624b0a720e84a89d54
--- /dev/null
+++ b/src/arch/x86_64/debug.rs
@@ -0,0 +1,41 @@
+use core::fmt;
+use spin::MutexGuard;
+
+use devices::uart_16550::SerialPort;
+use syscall::io::Pio;
+
+use super::device::serial::COM1;
+#[cfg(feature = "graphical_debug")]
+use super::graphical_debug::{DEBUG_DISPLAY, DebugDisplay};
+
+pub struct Writer<'a> {
+    serial: MutexGuard<'a, SerialPort<Pio<u8>>>,
+    #[cfg(feature = "graphical_debug")]
+    display: MutexGuard<'a, Option<DebugDisplay>>
+}
+
+impl<'a> Writer<'a> {
+    pub fn new() -> Writer<'a> {
+        Writer {
+            serial: COM1.lock(),
+            #[cfg(feature = "graphical_debug")]
+            display: DEBUG_DISPLAY.lock(),
+        }
+    }
+}
+
+impl<'a> fmt::Write for Writer<'a> {
+    #[cfg(not(feature = "graphical_debug"))]
+    fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
+        self.serial.write_str(s)
+    }
+
+    #[cfg(feature = "graphical_debug")]
+    fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
+        if let Some(ref mut display) = *self.display {
+            display.write_str(s)
+        } else {
+            self.serial.write_str(s)
+        }
+    }
+}
diff --git a/src/arch/x86_64/graphical_debug/debug.rs b/src/arch/x86_64/graphical_debug/debug.rs
new file mode 100644
index 0000000000000000000000000000000000000000..338b9a1e188d4ead7a9cf568e5c299dc5bfb57e5
--- /dev/null
+++ b/src/arch/x86_64/graphical_debug/debug.rs
@@ -0,0 +1,83 @@
+use core::fmt;
+
+use super::Display;
+
+pub struct DebugDisplay {
+    display: Display,
+    x: usize,
+    y: usize,
+    w: usize,
+    h: usize,
+}
+
+impl DebugDisplay {
+    pub fn new(display: Display) -> DebugDisplay {
+        let w = display.width/8;
+        let h = display.height/16;
+        DebugDisplay {
+            display,
+            x: 0,
+            y: 0,
+            w: w,
+            h: h,
+        }
+    }
+
+    pub fn write(&mut self, c: char) {
+        if self.x >= self.w || c == '\n' {
+            self.x = 0;
+            self.y += 1;
+        }
+
+        if self.y >= self.h {
+            let new_y = self.h - 1;
+            let d_y = self.y - new_y;
+
+            self.display.scroll(d_y * 16);
+
+            self.display.rect(
+                0, (self.h - d_y) * 16,
+                self.w * 8, d_y * 16,
+                0x000000
+            );
+
+            self.display.sync(
+                0, 0,
+                self.w * 8, self.h * 16
+            );
+
+            self.y = new_y;
+        }
+
+        if c != '\n' {
+            self.display.rect(
+                self.x * 8, self.y * 16,
+                8, 16,
+                0x000000
+            );
+
+            self.display.char(
+                self.x * 8, self.y * 16,
+                c,
+                0xFFFFFF
+            );
+
+            self.display.sync(
+                self.x, self.y,
+                8, 16
+            );
+
+            self.x += 1;
+        }
+    }
+}
+
+impl fmt::Write for DebugDisplay {
+    fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
+        for c in s.chars() {
+            self.write(c);
+        }
+
+        Ok(())
+    }
+}
diff --git a/src/arch/x86_64/graphical_debug/display.rs b/src/arch/x86_64/graphical_debug/display.rs
new file mode 100644
index 0000000000000000000000000000000000000000..b2f79f6fb539a15a505b26e4b6ed1ce690e7d9fb
--- /dev/null
+++ b/src/arch/x86_64/graphical_debug/display.rs
@@ -0,0 +1,150 @@
+use alloc::allocator::{Alloc, Layout};
+use alloc::heap::Heap;
+use core::{cmp, slice};
+
+use super::FONT;
+use super::primitive::{fast_set32, fast_set64, fast_copy};
+
+/// A display
+pub struct Display {
+    pub width: usize,
+    pub height: usize,
+    pub onscreen: &'static mut [u32],
+    pub offscreen: &'static mut [u32],
+}
+
+impl Display {
+    pub fn new(width: usize, height: usize, onscreen: usize) -> Display {
+        let size = width * height;
+        let offscreen = unsafe { Heap.alloc(Layout::from_size_align_unchecked(size * 4, 4096)).unwrap() };
+        unsafe { fast_set64(offscreen as *mut u64, 0, size/2) };
+        Display {
+            width: width,
+            height: height,
+            onscreen: unsafe { slice::from_raw_parts_mut(onscreen as *mut u32, size) },
+            offscreen: unsafe { slice::from_raw_parts_mut(offscreen as *mut u32, size) }
+        }
+    }
+
+    /// Draw a rectangle
+    pub fn rect(&mut self, x: usize, y: usize, w: usize, h: usize, color: u32) {
+        let start_y = cmp::min(self.height, y);
+        let end_y = cmp::min(self.height, y + h);
+
+        let start_x = cmp::min(self.width, x);
+        let len = cmp::min(self.width, x + w) - start_x;
+
+        let mut offscreen_ptr = self.offscreen.as_mut_ptr() as usize;
+
+        let stride = self.width * 4;
+
+        let offset = y * stride + start_x * 4;
+        offscreen_ptr += offset;
+
+        let mut rows = end_y - start_y;
+        while rows > 0 {
+            unsafe {
+                fast_set32(offscreen_ptr as *mut u32, color, len);
+            }
+            offscreen_ptr += stride;
+            rows -= 1;
+        }
+    }
+
+    /// Invert a rectangle
+    pub fn invert(&mut self, x: usize, y: usize, w: usize, h: usize) {
+        let start_y = cmp::min(self.height, y);
+        let end_y = cmp::min(self.height, y + h);
+
+        let start_x = cmp::min(self.width, x);
+        let len = cmp::min(self.width, x + w) - start_x;
+
+        let mut offscreen_ptr = self.offscreen.as_mut_ptr() as usize;
+
+        let stride = self.width * 4;
+
+        let offset = y * stride + start_x * 4;
+        offscreen_ptr += offset;
+
+        let mut rows = end_y - start_y;
+        while rows > 0 {
+            let mut row_ptr = offscreen_ptr;
+            let mut cols = len;
+            while cols > 0 {
+                unsafe {
+                    let color = *(row_ptr as *mut u32);
+                    *(row_ptr as *mut u32) = !color;
+                }
+                row_ptr += 4;
+                cols -= 1;
+            }
+            offscreen_ptr += stride;
+            rows -= 1;
+        }
+    }
+
+    /// Draw a character
+    pub fn char(&mut self, x: usize, y: usize, character: char, color: u32) {
+        if x + 8 <= self.width && y + 16 <= self.height {
+            let mut dst = self.offscreen.as_mut_ptr() as usize + (y * self.width + x) * 4;
+
+            let font_i = 16 * (character as usize);
+            if font_i + 16 <= FONT.len() {
+                for row in 0..16 {
+                    let row_data = FONT[font_i + row];
+                    for col in 0..8 {
+                        if (row_data >> (7 - col)) & 1 == 1 {
+                            unsafe { *((dst + col * 4) as *mut u32)  = color; }
+                        }
+                    }
+                    dst += self.width * 4;
+                }
+            }
+        }
+    }
+
+    // Scroll the screen
+    pub fn scroll(&mut self, lines: usize) {
+        let offset = cmp::min(self.height, lines) * self.width;
+        let size = self.offscreen.len() - offset;
+        unsafe {
+            let to = self.offscreen.as_mut_ptr();
+            let from = to.offset(offset as isize);
+            fast_copy(to as *mut u8, from as *const u8, size * 4);
+        }
+    }
+
+    /// Copy from offscreen to onscreen
+    pub fn sync(&mut self, x: usize, y: usize, w: usize, h: usize) {
+        let start_y = cmp::min(self.height, y);
+        let end_y = cmp::min(self.height, y + h);
+
+        let start_x = cmp::min(self.width, x);
+        let len = (cmp::min(self.width, x + w) - start_x) * 4;
+
+        let mut offscreen_ptr = self.offscreen.as_mut_ptr() as usize;
+        let mut onscreen_ptr = self.onscreen.as_mut_ptr() as usize;
+
+        let stride = self.width * 4;
+
+        let offset = y * stride + start_x * 4;
+        offscreen_ptr += offset;
+        onscreen_ptr += offset;
+
+        let mut rows = end_y - start_y;
+        while rows > 0 {
+            unsafe {
+                fast_copy(onscreen_ptr as *mut u8, offscreen_ptr as *const u8, len);
+            }
+            offscreen_ptr += stride;
+            onscreen_ptr += stride;
+            rows -= 1;
+        }
+    }
+}
+
+impl Drop for Display {
+    fn drop(&mut self) {
+        unsafe { Heap.dealloc(self.offscreen.as_mut_ptr() as *mut u8, Layout::from_size_align_unchecked(self.offscreen.len() * 4, 4096)) };
+    }
+}
diff --git a/src/arch/x86_64/graphical_debug/mod.rs b/src/arch/x86_64/graphical_debug/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..a8b2a7014ea7ebe17d47806ddae7571a54b7cfc1
--- /dev/null
+++ b/src/arch/x86_64/graphical_debug/mod.rs
@@ -0,0 +1,71 @@
+use spin::Mutex;
+
+use memory::Frame;
+use paging::{ActivePageTable, Page, PhysicalAddress, VirtualAddress};
+use paging::entry::EntryFlags;
+
+pub use self::debug::DebugDisplay;
+use self::display::Display;
+use self::mode_info::VBEModeInfo;
+use self::primitive::fast_set64;
+
+pub mod debug;
+pub mod display;
+pub mod mode_info;
+pub mod primitive;
+
+pub static FONT: &'static [u8] = include_bytes!("../../../../res/unifont.font");
+
+pub static DEBUG_DISPLAY: Mutex<Option<DebugDisplay>> = Mutex::new(None);
+
+pub fn init(active_table: &mut ActivePageTable) {
+    //TODO: Unmap mode_info and map physbaseptr in kernel space
+
+    println!("Starting graphical debug");
+
+    let width;
+    let height;
+    let physbaseptr;
+
+    {
+        let mode_info_addr = 0x5200;
+
+        {
+            let page = Page::containing_address(VirtualAddress::new(mode_info_addr));
+            let frame = Frame::containing_address(PhysicalAddress::new(page.start_address().get()));
+            let result = active_table.map_to(page, frame, EntryFlags::PRESENT | EntryFlags::NO_EXECUTE);
+            result.flush(active_table);
+        }
+
+        let mode_info = unsafe { &*(mode_info_addr as *const VBEModeInfo) };
+
+        width = mode_info.xresolution as usize;
+        height = mode_info.yresolution as usize;
+        physbaseptr = mode_info.physbaseptr as usize;
+    }
+
+    {
+        let size = width * height;
+
+        {
+            let start_page = Page::containing_address(VirtualAddress::new(physbaseptr));
+            let end_page = Page::containing_address(VirtualAddress::new(physbaseptr + size * 4));
+            for page in Page::range_inclusive(start_page, end_page) {
+                let frame = Frame::containing_address(PhysicalAddress::new(page.start_address().get()));
+                let result = active_table.map_to(page, frame, EntryFlags::PRESENT | EntryFlags::NO_EXECUTE | EntryFlags::WRITABLE | EntryFlags::HUGE_PAGE);
+                result.flush(active_table);
+            }
+        }
+
+        unsafe { fast_set64(physbaseptr as *mut u64, 0, size/2) };
+
+        *DEBUG_DISPLAY.lock() = Some(DebugDisplay::new(Display::new(width, height, physbaseptr)));
+    }
+}
+
+pub fn fini(_active_table: &mut ActivePageTable) {
+    //TODO: Unmap physbaseptr
+    *DEBUG_DISPLAY.lock() = None;
+
+    println!("Finished graphical debug");
+}
diff --git a/src/arch/x86_64/graphical_debug/mode_info.rs b/src/arch/x86_64/graphical_debug/mode_info.rs
new file mode 100644
index 0000000000000000000000000000000000000000..7d59af64526523ac3d76d6f2c016473a13d353a0
--- /dev/null
+++ b/src/arch/x86_64/graphical_debug/mode_info.rs
@@ -0,0 +1,37 @@
+/// The info of the VBE mode
+#[derive(Copy, Clone, Default, Debug)]
+#[repr(packed)]
+pub struct VBEModeInfo {
+    attributes: u16,
+    win_a: u8,
+    win_b: u8,
+    granularity: u16,
+    winsize: u16,
+    segment_a: u16,
+    segment_b: u16,
+    winfuncptr: u32,
+    bytesperscanline: u16,
+    pub xresolution: u16,
+    pub yresolution: u16,
+    xcharsize: u8,
+    ycharsize: u8,
+    numberofplanes: u8,
+    bitsperpixel: u8,
+    numberofbanks: u8,
+    memorymodel: u8,
+    banksize: u8,
+    numberofimagepages: u8,
+    unused: u8,
+    redmasksize: u8,
+    redfieldposition: u8,
+    greenmasksize: u8,
+    greenfieldposition: u8,
+    bluemasksize: u8,
+    bluefieldposition: u8,
+    rsvdmasksize: u8,
+    rsvdfieldposition: u8,
+    directcolormodeinfo: u8,
+    pub physbaseptr: u32,
+    offscreenmemoryoffset: u32,
+    offscreenmemsize: u16,
+}
diff --git a/src/arch/x86_64/graphical_debug/primitive.rs b/src/arch/x86_64/graphical_debug/primitive.rs
new file mode 100644
index 0000000000000000000000000000000000000000..16c2536a24accfc3f3acd7c24652e2f71be27b3f
--- /dev/null
+++ b/src/arch/x86_64/graphical_debug/primitive.rs
@@ -0,0 +1,47 @@
+#[cfg(target_arch = "x86_64")]
+#[inline(always)]
+#[cold]
+pub unsafe fn fast_copy(dst: *mut u8, src: *const u8, len: usize) {
+    asm!("cld
+        rep movsb"
+        :
+        : "{rdi}"(dst as usize), "{rsi}"(src as usize), "{rcx}"(len)
+        : "cc", "memory", "rdi", "rsi", "rcx"
+        : "intel", "volatile");
+}
+
+#[cfg(target_arch = "x86_64")]
+#[inline(always)]
+#[cold]
+pub unsafe fn fast_copy64(dst: *mut u64, src: *const u64, len: usize) {
+    asm!("cld
+        rep movsq"
+        :
+        : "{rdi}"(dst as usize), "{rsi}"(src as usize), "{rcx}"(len)
+        : "cc", "memory", "rdi", "rsi", "rcx"
+        : "intel", "volatile");
+}
+
+#[cfg(target_arch = "x86_64")]
+#[inline(always)]
+#[cold]
+pub unsafe fn fast_set32(dst: *mut u32, src: u32, len: usize) {
+    asm!("cld
+        rep stosd"
+        :
+        : "{rdi}"(dst as usize), "{eax}"(src), "{rcx}"(len)
+        : "cc", "memory", "rdi", "rcx"
+        : "intel", "volatile");
+}
+
+#[cfg(target_arch = "x86_64")]
+#[inline(always)]
+#[cold]
+pub unsafe fn fast_set64(dst: *mut u64, src: u64, len: usize) {
+    asm!("cld
+        rep stosq"
+        :
+        : "{rdi}"(dst as usize), "{rax}"(src), "{rcx}"(len)
+        : "cc", "memory", "rdi", "rcx"
+        : "intel", "volatile");
+}
diff --git a/src/arch/x86_64/macros.rs b/src/arch/x86_64/macros.rs
index 1772c3d8e622c89cb6af1dd896e553f9aba2af30..d41ee9338b0c9d0395704274b926f6c0dc792c94 100644
--- a/src/arch/x86_64/macros.rs
+++ b/src/arch/x86_64/macros.rs
@@ -3,7 +3,7 @@
 macro_rules! print {
     ($($arg:tt)*) => ({
         use core::fmt::Write;
-        let _ = write!($crate::arch::device::serial::COM1.lock(), $($arg)*);
+        let _ = write!($crate::arch::debug::Writer::new(), $($arg)*);
     });
 }
 
diff --git a/src/arch/x86_64/mod.rs b/src/arch/x86_64/mod.rs
index ffa90a968d2940e24272c0a34008ad02f9e96099..b7857d29f203f714667a96d434dadb1b39ea02fc 100644
--- a/src/arch/x86_64/mod.rs
+++ b/src/arch/x86_64/mod.rs
@@ -1,12 +1,19 @@
 #[macro_use]
 pub mod macros;
 
+/// Debugging support
+pub mod debug;
+
 /// Devices
 pub mod device;
 
 /// Global descriptor table
 pub mod gdt;
 
+/// Graphical debug
+#[cfg(feature = "graphical_debug")]
+mod graphical_debug;
+
 /// Interrupt descriptor table
 pub mod idt;
 
diff --git a/src/arch/x86_64/start.rs b/src/arch/x86_64/start.rs
index 1d92afc0bdcfce2f72d5ec1faf5c6ad6a2346df6..afba151a49521a4b4cdced6a749f8d8b2149254a 100644
--- a/src/arch/x86_64/start.rs
+++ b/src/arch/x86_64/start.rs
@@ -9,6 +9,8 @@ use core::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT, AtomicUsize, ATOMIC_USIZE
 use allocator;
 #[cfg(feature = "acpi")]
 use acpi;
+#[cfg(feature = "graphical_debug")]
+use arch::x86_64::graphical_debug;
 use arch::x86_64::pti;
 use device;
 use gdt;
@@ -100,6 +102,10 @@ pub unsafe extern fn kstart(args_ptr: *const KernelArgs) -> ! {
         // Setup kernel heap
         allocator::init(&mut active_table);
 
+        // Use graphical debug
+        #[cfg(feature="graphical_debug")]
+        graphical_debug::init(&mut active_table);
+
         // Initialize devices
         device::init(&mut active_table);
 
@@ -113,6 +119,10 @@ pub unsafe extern fn kstart(args_ptr: *const KernelArgs) -> ! {
         // Initialize memory functions after core has loaded
         memory::init_noncore();
 
+        // Stop graphical debug
+        #[cfg(feature="graphical_debug")]
+        graphical_debug::fini(&mut active_table);
+
         BSP_READY.store(true, Ordering::SeqCst);
 
         slice::from_raw_parts(env_base as *const u8, env_size)