diff --git a/.envrc b/.envrc
new file mode 100644
index 0000000..3550a30
--- /dev/null
+++ b/.envrc
@@ -0,0 +1 @@
+use flake
diff --git a/.gitignore b/.gitignore
index cb23289..cb13758 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
 /target
+.direnv
 *.fd
 esp
diff --git a/Cargo.lock b/Cargo.lock
index 0b0fa8b..4c80c92 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -14,37 +14,12 @@ version = "2.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
 
-[[package]]
-name = "byteorder"
-version = "1.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
-
 [[package]]
 name = "cfg-if"
 version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 
-[[package]]
-name = "hash32"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606"
-dependencies = [
- "byteorder",
-]
-
-[[package]]
-name = "heapless"
-version = "0.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad"
-dependencies = [
- "hash32",
- "stable_deref_trait",
-]
-
 [[package]]
 name = "log"
 version = "0.4.22"
@@ -62,22 +37,22 @@ dependencies = [
 
 [[package]]
 name = "ptr_meta"
-version = "0.2.0"
+version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bcada80daa06c42ed5f48c9a043865edea5dc44cbf9ac009fda3b89526e28607"
+checksum = "fe9e76f66d3f9606f44e45598d155cb13ecf09f4a28199e48daf8c8fc937ea90"
 dependencies = [
  "ptr_meta_derive",
 ]
 
 [[package]]
 name = "ptr_meta_derive"
-version = "0.2.0"
+version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bca9224df2e20e7c5548aeb5f110a0f3b77ef05f8585139b7148b59056168ed2"
+checksum = "ca414edb151b4c8d125c12566ab0d74dc9cdba36fb80eb7b848c15f495fd32d1"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 1.0.109",
+ "syn",
 ]
 
 [[package]]
@@ -89,23 +64,6 @@ dependencies = [
  "proc-macro2",
 ]
 
-[[package]]
-name = "stable_deref_trait"
-version = "1.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
-
-[[package]]
-name = "syn"
-version = "1.0.109"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-ident",
-]
-
 [[package]]
 name = "syn"
 version = "2.0.92"
@@ -128,9 +86,9 @@ dependencies = [
 
 [[package]]
 name = "uefi"
-version = "0.33.0"
+version = "0.34.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6679b7fc2f6d6d2ea2f67555ef3ed9d71d30c5021faf9193091a5192db7dc468"
+checksum = "c25038e420a68d30a0e8002a3b51c075ad2342f5ae7a2383f042bd41fef73272"
 dependencies = [
  "bitflags",
  "cfg-if",
@@ -146,30 +104,28 @@ dependencies = [
 name = "uefi-gol"
 version = "0.1.0"
 dependencies = [
- "heapless",
  "log",
  "uefi",
 ]
 
 [[package]]
 name = "uefi-macros"
-version = "0.17.0"
+version = "0.18.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b24e77d3fc1e617051e630f99da24bcae6328abab37b8f9216bb68d06804f9a"
+checksum = "72cb3027736dad1b6f23437c63249025d960377fe7f9f769a111dfbc0dd2bdda"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.92",
+ "syn",
 ]
 
 [[package]]
 name = "uefi-raw"
-version = "0.9.0"
+version = "0.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f6d465de2c918779dafb769a5a4fe8d6e4fb7cc4cc6cb1a735f2f6ec68beea4"
+checksum = "d246ed5d6fd71c0331f0ac774a6aefb6cb9170a46f83148cacc70b09f82c87bb"
 dependencies = [
  "bitflags",
- "ptr_meta",
  "uguid",
 ]
 
diff --git a/Cargo.toml b/Cargo.toml
index e83db9a..2352a18 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,6 +6,5 @@ authors = [ "Tim Schubert <dadada@dadada.li>"]
 license = "MIT OR Apache-2.0"
 
 [dependencies]
-heapless = "0.8.0"
 log = "0.4.22"
-uefi = { version = "0.33.0", features = ["logger", "panic_handler"] }
+uefi = { version = "0.34.0", features = ["logger", "global_allocator", "panic_handler"] }
diff --git a/src/main.rs b/src/main.rs
index 4b7c0f6..560adb1 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,55 +1,45 @@
 #![no_main]
 #![no_std]
 
-use core::{
-    fmt::Write,
-    iter::repeat,
-    ops::{Deref, DerefMut},
+extern crate alloc;
+
+use alloc::vec::Vec;
+
+use core::iter::repeat;
+
+use uefi::{
+    prelude::*,
+    proto::console::gop::{BltOp::BufferToVideo, BltPixel, BltRegion, GraphicsOutput},
+    Result,
 };
 
-use heapless::{String, Vec};
-use system::with_stdout;
-use uefi::prelude::*;
-
-const WIDTH: usize = 30;
-const HEIGHT: usize = 30;
-
-type Matrix = Vec<Vec<bool, WIDTH>, HEIGHT>;
+const ALIVE: BltPixel = BltPixel::new(0xff, 0xff, 0xff);
+const DEAD: BltPixel = BltPixel::new(0, 0, 0);
 
 #[derive(Debug, Clone)]
 struct Life {
-    matrix: Matrix,
-}
-
-impl Deref for Life {
-    type Target = Matrix;
-
-    fn deref(&self) -> &Self::Target {
-        &self.matrix
-    }
-}
-
-impl Default for Life {
-    fn default() -> Self {
-        let mut matrix: Vec<Vec<bool, WIDTH>, HEIGHT> = Vec::default();
-        for _ in 0..HEIGHT {
-            matrix.push(repeat(false).take(WIDTH).collect()).unwrap();
-        }
-        Life { matrix }
-    }
-}
-
-impl DerefMut for Life {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut self.matrix
-    }
+    height: usize,
+    matrix: Vec<BltPixel>,
+    width: usize,
 }
 
 impl Life {
-    fn with_starter(starter: &[(usize, usize)]) -> Self {
-        let mut life = Self::default();
+    fn new(width: usize, height: usize) -> Self {
+        let mut matrix = Vec::default();
+        for _ in 0..height {
+            matrix.extend(repeat(DEAD).take(width));
+        }
+        Life {
+            matrix,
+            width,
+            height,
+        }
+    }
+
+    fn with_starter(width: usize, height: usize, starter: &[(usize, usize)]) -> Self {
+        let mut life = Self::new(width, height);
         for (i, j) in starter.iter() {
-            life[*i][*j] = true;
+            life.matrix[i * width + j] = ALIVE;
         }
         life
     }
@@ -65,85 +55,69 @@ impl Life {
             (1, 0),
             (1, 1),
         ];
-        let mut future = Life::default();
-        self.iter().enumerate().for_each(|(row, line)| {
-            line.iter().enumerate().for_each(|(column, alive)| {
+        let mut future = Self::new(self.width, self.height);
+        for h in 0..self.height {
+            for w in 0..self.width {
+                let alive = self.matrix[h * self.width + w];
                 let neighbors = OFFSETS
                     .iter()
                     .filter(|offset| {
-                        let height = (row as i64) + offset.0;
-                        let width = (column as i64) + offset.1;
-                        height >= 0
-                            && width >= 0
-                            && (height as usize) < HEIGHT
-                            && (width as usize) < WIDTH
-                            && self[height as usize][width as usize]
+                        let height = (h as i64) + offset.0;
+                        let width = (w as i64) + offset.1;
+                        self.is_neighbor_alive(height, width)
                     })
                     .count();
-                future[row][column] = neighbors == 3 || *alive && neighbors == 2;
-            })
-        });
+                future.matrix[h * self.width + w] = if can_replicate(alive, neighbors) {
+                    ALIVE
+                } else {
+                    DEAD
+                };
+            }
+        }
         future
     }
 
-    /// Buffers output into one string per frame since println does not buffer output in uefi-rs.
-    fn render(&self) -> Frame {
-        let mut frame = Frame::default();
-        for l in self.iter() {
-            for cell in l.iter() {
-                if *cell {
-                    frame.push('X').unwrap()
-                } else {
-                    frame.push(' ').unwrap()
-                }
+    fn is_neighbor_alive(&self, height: i64, width: i64) -> bool {
+        height >= 0
+            && width >= 0
+            && (height as usize) < self.height
+            && (width as usize) < self.width
+            && {
+                let neighbor = self.matrix[height as usize * self.width + width as usize];
+                neighbor.blue != 0 || neighbor.green != 0 || neighbor.red != 0
             }
-            frame.push('\n').unwrap()
-        }
-        frame
+    }
+
+    /// Blits entire display
+    fn render(&self, gop: &mut GraphicsOutput) -> Result {
+        gop.blt(BufferToVideo {
+            buffer: &self.matrix,
+            src: BltRegion::Full,
+            dest: (0, 0),
+            dims: (self.width, self.height),
+        })
     }
 }
 
-type FrameBuffer = String<{ (WIDTH * HEIGHT) + HEIGHT }>;
-
-#[derive(Default)]
-struct Frame {
-    buffer: FrameBuffer,
+fn can_replicate(alive: BltPixel, neighbors: usize) -> bool {
+    neighbors == 3 || (alive.blue != 0 || alive.green != 0 || alive.red != 0) && neighbors == 2
 }
 
-impl Deref for Frame {
-    type Target = FrameBuffer;
-
-    fn deref(&self) -> &Self::Target {
-        &self.buffer
-    }
-}
-
-impl DerefMut for Frame {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut self.buffer
-    }
-}
-
-const BREEDER: &[(usize, usize)] = &[(15, 16), (16, 15), (16, 16), (17, 16), (17, 17)];
-const RENDER_DELAY: usize = 100_000;
+const BREEDER: &[(usize, usize)] = &[(415, 416), (416, 415), (416, 416), (417, 416), (417, 417)];
 
 #[entry]
 fn main() -> Status {
     uefi::helpers::init().unwrap();
 
-    let mut life = Life::with_starter(BREEDER);
+    let mut life = Life::with_starter(800, 600, BREEDER);
+    let gop_handle = boot::get_handle_for_protocol::<GraphicsOutput>().unwrap();
+    let mut gop = boot::open_protocol_exclusive::<GraphicsOutput>(gop_handle).unwrap();
 
     for _ in repeat(()) {
         life = life.play();
 
-        let frame = life.render();
-        with_stdout(|stdout| {
-            stdout.reset(true).unwrap();
-            stdout.write_str(&frame).unwrap()
-        });
-        // Stall the loop a bit so it is easier to watch
-        boot::stall(RENDER_DELAY);
+        life.render(&mut gop).unwrap();
     }
 
-    Status::SUCCESS
+    unreachable!()
 }