summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt1
-rw-r--r--src/Compute/Queue.hpp5
-rw-r--r--src/GFX/Binder.cpp84
-rw-r--r--src/GFX/Binder.hpp48
-rw-r--r--src/GFX/Mesh.cpp75
-rw-r--r--src/GFX/Mesh.hpp18
-rw-r--r--src/Util/ImageViewer.cpp4
-rw-r--r--src/Util/ImageViewer.hpp7
-rw-r--r--src/World/Chunk.cpp8
-rw-r--r--src/World/Chunk.hpp9
-rw-r--r--src/World/ChunkRegistry.hpp17
-rw-r--r--src/World/Clouds.cpp6
-rw-r--r--src/World/Clouds.hpp8
-rw-r--r--src/World/Generation/ChunkMeshing.cpp88
-rw-r--r--src/World/Generation/ChunkMeshing.hpp42
-rw-r--r--src/World/Generation/ChunkNeighbors.cpp26
-rw-r--r--src/World/Generation/ChunkNeighbors.hpp13
-rw-r--r--src/World/Generation/Lighting.cpp9
-rw-r--r--src/World/Generation/Lighting.hpp4
-rw-r--r--src/World/Position.hpp10
-rw-r--r--src/World/World.cpp103
-rw-r--r--src/World/World.hpp24
-rw-r--r--src/main.cpp5
23 files changed, 349 insertions, 265 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 58ddaf0..de94623 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -18,7 +18,6 @@ add_executable(meowcraft
     src/GFX/Mesh.cpp src/GFX/Mesh.hpp
     src/Math/Vector.hpp
     src/Math/Common.hpp
-    src/GFX/Binder.cpp src/GFX/Binder.hpp
     src/GFX/Shading/Shader.cpp src/GFX/Shading/Shader.hpp
     src/GFX/Shading/Program.cpp src/GFX/Shading/Program.hpp
     src/Math/Matrix.hpp
diff --git a/src/Compute/Queue.hpp b/src/Compute/Queue.hpp
index 06469f8..e201932 100644
--- a/src/Compute/Queue.hpp
+++ b/src/Compute/Queue.hpp
@@ -25,6 +25,11 @@ public:
         m_control->work.jobs.emplace(id, priority, execute);
     }
 
+    USize size() const {
+        std::scoped_lock work_lock(m_control->work.mutex);
+        return m_control->work.jobs.size();
+    }
+
     struct Result {
         I id{};
         X res;
diff --git a/src/GFX/Binder.cpp b/src/GFX/Binder.cpp
deleted file mode 100644
index 49f82a7..0000000
--- a/src/GFX/Binder.cpp
+++ /dev/null
@@ -1,84 +0,0 @@
-#include <GL/glew.h>
-#include <cassert>
-#include "Binder.hpp"
-#include "Mesh.hpp"
-
-namespace MC::GFX {
-
-BindableMesh Binder::load(const Mesh& mesh) {
-    auto vao = create_vao();
-    if (!mesh.indices().empty()) {
-        store_indices(mesh.indices().data(), mesh.indices().size());
-    }
-
-    Int attribute_index = 0;
-    for (const auto& attribute : mesh.attributes()) {
-        store_in_attribute_list(
-            attribute_index++,
-            attribute.attribute_size,
-            attribute.type_size,
-            attribute.data,
-            attribute.data_size
-        );
-    }
-    unbind_vao();
-
-    return {vao, mesh.indices().size(), mesh.attributes().size()};
-}
-
-U32 Binder::create_vao() {
-    GLuint vao;
-    glGenVertexArrays(1, &vao);
-    glBindVertexArray(vao);
-
-    return vao;
-}
-
-void Binder::unbind_vao() {
-    glBindVertexArray(0);
-}
-
-void Binder::store_indices(const U32* indices, USize indices_size) {
-    GLuint ebo;
-    glGenBuffers(1, &ebo);
-
-    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
-    glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices_size * sizeof(U32), indices, GL_STATIC_DRAW);
-}
-
-
-void Binder::store_in_attribute_list(U32 attribute, Int attribute_size, Int type_size, const void* data, long data_size) {
-    assert(type_size == sizeof(F32));
-
-    GLuint vbo;
-    glGenBuffers(1, &vbo);
-
-    glBindBuffer(GL_ARRAY_BUFFER, vbo);
-    glBufferData(GL_ARRAY_BUFFER, data_size * attribute_size * type_size, data, GL_STATIC_DRAW);
-    glVertexAttribPointer(attribute, attribute_size, GL_FLOAT, GL_FALSE, attribute_size * type_size, nullptr);
-    glBindBuffer(GL_ARRAY_BUFFER, 0);
-}
-
-void BindableMesh::bind() const {
-    glBindVertexArray(m_vao);
-    for (Int i = 0; i < m_attribute_count; i++) {
-        glEnableVertexAttribArray(i);
-    }
-}
-
-void BindableMesh::unbind() const {
-    glBindVertexArray(0);
-    for (Int i = 0; i < m_attribute_count; i++) {
-        glDisableVertexAttribArray(i);
-    }
-}
-
-Bool BindableMesh::has_indices() const {
-    return m_has_indices;
-}
-
-USize BindableMesh::size() const {
-    return m_vertex_count;
-}
-
-}
\ No newline at end of file
diff --git a/src/GFX/Binder.hpp b/src/GFX/Binder.hpp
deleted file mode 100644
index 6103e09..0000000
--- a/src/GFX/Binder.hpp
+++ /dev/null
@@ -1,48 +0,0 @@
-#pragma once
-
-#include "../Common/Sizes.hpp"
-#include "Mesh.hpp"
-
-namespace MC::GFX {
-
-class BindableMesh {
-public:
-    void bind() const;
-    void unbind() const;
-
-    Bool has_indices() const;
-    USize size() const;
-
-private:
-    BindableMesh(
-        U32 vao,
-        USize vertex_count,
-        USize attribute_count
-    ) : m_vao(vao),
-        m_vertex_count(vertex_count),
-        m_has_indices(vertex_count > 0),
-        m_attribute_count(attribute_count) {}
-
-    U32 m_vao;
-    USize m_vertex_count;
-    Bool m_has_indices;
-    USize m_attribute_count;
-
-    friend class Binder;
-};
-
-class Binder {
-public:
-    Binder() = default;
-
-    static BindableMesh load(const Mesh& mesh);
-
-private:
-    static U32 create_vao();
-    static void unbind_vao();
-
-    static void store_in_attribute_list(U32 attribute, Int attribute_size, Int type_size, const void* data, long data_size);
-    static void store_indices(const U32* indices, USize indices_size);
-};
-
-}
\ No newline at end of file
diff --git a/src/GFX/Mesh.cpp b/src/GFX/Mesh.cpp
index c45b5c9..0633e11 100644
--- a/src/GFX/Mesh.cpp
+++ b/src/GFX/Mesh.cpp
@@ -2,12 +2,77 @@
 
 namespace MC::GFX {
 
-const std::vector<U32>& Mesh::indices() const {
-    return m_indices;
+USize Mesh::size() const {
+    return m_indices.size();
 }
 
-const std::vector<Mesh::Attribute>& Mesh::attributes() const {
-    return m_attributes;
+void Mesh::bind() {
+    if (!m_vao.has_value()) upload();
+
+    glBindVertexArray(m_vao.value());
+    for (Int i = 0; i < m_attributes.size(); i++) {
+        glEnableVertexAttribArray(i);
+    }
+}
+
+void Mesh::unbind() const {
+    glBindVertexArray(0);
+    for (Int i = 0; i < m_attributes.size(); i++) {
+        glDisableVertexAttribArray(i);
+    }
+}
+
+void Mesh::upload() {
+    auto vao = create_vao();
+    if (!m_indices.empty()) {
+        store_indices(m_indices.data(), m_indices.size());
+    }
+
+    Int attribute_index = 0;
+    for (const auto& attribute : m_attributes) {
+        store_in_attribute_list(
+            attribute_index++,
+            attribute.attribute_size,
+            attribute.type_size,
+            attribute.data,
+            attribute.data_size
+        );
+    }
+    unbind_vao();
+
+    m_vao = {vao};
+}
+
+GLuint Mesh::create_vao() {
+    GLuint vao;
+    glGenVertexArrays(1, &vao);
+    glBindVertexArray(vao);
+
+    return vao;
 }
 
-}
\ No newline at end of file
+void Mesh::unbind_vao() {
+    glBindVertexArray(0);
+}
+
+void Mesh::store_indices(const U32* indices, USize indices_size) {
+    GLuint ebo;
+    glGenBuffers(1, &ebo);
+
+    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
+    glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices_size * sizeof(U32), indices, GL_STATIC_DRAW);
+}
+
+void Mesh::store_in_attribute_list(U32 attribute, Int attribute_size, Int type_size, const void* data, long data_size) {
+    assert(type_size == sizeof(F32));
+
+    GLuint vbo;
+    glGenBuffers(1, &vbo);
+
+    glBindBuffer(GL_ARRAY_BUFFER, vbo);
+    glBufferData(GL_ARRAY_BUFFER, data_size * attribute_size * type_size, data, GL_STATIC_DRAW);
+    glVertexAttribPointer(attribute, attribute_size, GL_FLOAT, GL_FALSE, attribute_size * type_size, nullptr);
+    glBindBuffer(GL_ARRAY_BUFFER, 0);
+}
+
+}
diff --git a/src/GFX/Mesh.hpp b/src/GFX/Mesh.hpp
index 69625ff..435afe1 100644
--- a/src/GFX/Mesh.hpp
+++ b/src/GFX/Mesh.hpp
@@ -3,6 +3,8 @@
 #include <utility>
 #include <vector>
 #include <algorithm>
+#include <optional>
+#include <GL/glew.h>
 #include "../Common/Sizes.hpp"
 #include "../Math/Common.hpp"
 
@@ -58,12 +60,24 @@ public:
         std::vector<Attribute> attributes
     ) : m_attributes(std::move(attributes)) {}
 
-    const std::vector<U32>& indices() const;
-    const std::vector<Attribute>& attributes() const;
+    USize size() const;
+
+    void bind();
+    void unbind() const;
 
 private:
+    void upload();
+
+    static GLuint create_vao();
+    static void unbind_vao();
+
+    static void store_in_attribute_list(U32 attribute, Int attribute_size, Int type_size, const void* data, long data_size);
+    static void store_indices(const U32* indices, USize indices_size);
+
     std::vector<Attribute> m_attributes;
     std::vector<U32> m_indices;
+
+    std::optional<GLuint> m_vao;
 };
 
 }
\ No newline at end of file
diff --git a/src/Util/ImageViewer.cpp b/src/Util/ImageViewer.cpp
index 6c88c78..4c78750 100644
--- a/src/Util/ImageViewer.cpp
+++ b/src/Util/ImageViewer.cpp
@@ -12,7 +12,7 @@ ImageViewer::ImageViewer(
         {GFX::Shading::Shader::Type::Vertex, vertex},
         {GFX::Shading::Shader::Type::Fragment, fragment}
     ),
-    m_mesh(GFX::Binder::load(create_mesh(window_aspect, image.width(), image.height()))) {
+    m_mesh(create_mesh(window_aspect, image.width(), image.height())) {
     m_program.bind();
     auto model_uniform = m_program.uniform("model_matrix");
     auto view_uniform = m_program.uniform("view_matrix");
@@ -25,7 +25,7 @@ ImageViewer::ImageViewer(
     m_program.unbind();
 }
 
-void ImageViewer::render() const {
+void ImageViewer::render() {
     m_program.bind();
     m_texture.bind();
     m_mesh.bind();
diff --git a/src/Util/ImageViewer.hpp b/src/Util/ImageViewer.hpp
index 1e18103..aa48b1e 100644
--- a/src/Util/ImageViewer.hpp
+++ b/src/Util/ImageViewer.hpp
@@ -1,8 +1,7 @@
 #pragma once
 
-#include <ostream>
+#include "../GFX/Mesh.hpp"
 #include "../GFX/Image/RawImage.hpp"
-#include "../GFX/Binder.hpp"
 #include "../GFX/Texture.hpp"
 #include "../GFX/Shading/Program.hpp"
 
@@ -12,7 +11,7 @@ class ImageViewer {
 public:
     explicit ImageViewer(const GFX::Image::RawImage& image, Real window_aspect);
 
-    void render() const;
+    void render();
 private:
     static GFX::Mesh create_mesh(Real window_aspect, U32 image_width, U32 image_height);
 
@@ -21,7 +20,7 @@ private:
     static const Char* vertex;
     static const Char* fragment;
 
-    GFX::BindableMesh m_mesh;
+    GFX::Mesh m_mesh;
     GFX::Shading::Program m_program;
     GFX::Texture m_texture;
 };
diff --git a/src/World/Chunk.cpp b/src/World/Chunk.cpp
index 31a2c90..8b204c0 100644
--- a/src/World/Chunk.cpp
+++ b/src/World/Chunk.cpp
@@ -31,6 +31,14 @@ Vector<3> Chunk::position() const {
     return m_position;
 }
 
+Bool Chunk::is_damaged() const {
+    return m_damaged;
+}
+
+void Chunk::damage() {
+    m_damaged = true;
+}
+
 Bool Chunk::is_valid_position(U32 x, U32 y, U32 z) {
     return x < Width && y < Height && z < Width;
 }
diff --git a/src/World/Chunk.hpp b/src/World/Chunk.hpp
index 32d3ab3..1aa2dd1 100644
--- a/src/World/Chunk.hpp
+++ b/src/World/Chunk.hpp
@@ -21,8 +21,8 @@ public:
         m_position{(Real)x * Width, 0.0f, (Real)y * Width} {}
 
     struct BlockData {
-        BlockData() : type(BlockType::Air) {}
-        BlockData(BlockType t) : type(t), light{} {}
+        BlockData() : type(BlockType::Air), light{200} {}
+        BlockData(BlockType t) : type(t), light{200} {}
 
         BlockType type;
         U8 light;
@@ -52,6 +52,9 @@ public:
     ChunkIndex index() const;
     Vector<3> position() const;
 
+    Bool is_damaged() const;
+    void damage();
+
     static Bool is_valid_position(U32 x, U32 y, U32 z);
 private:
     static U64 pos(U32 x, U32 y, U32 z);
@@ -60,6 +63,8 @@ private:
     Vector<3> m_position;
     std::vector<BlockData> m_blocks;
 
+    Bool m_damaged = false;
+
     Details m_details;
 };
 
diff --git a/src/World/ChunkRegistry.hpp b/src/World/ChunkRegistry.hpp
index 86e5d06..2c515c5 100644
--- a/src/World/ChunkRegistry.hpp
+++ b/src/World/ChunkRegistry.hpp
@@ -5,7 +5,6 @@
 #include "Chunk.hpp"
 #include "ChunkIndex.hpp"
 #include "Position.hpp"
-#include "../GFX/Binder.hpp"
 
 namespace MC::World {
 
@@ -14,7 +13,9 @@ public:
     enum class Status {
         Empty,
         WaitingForGeneration,
+        NeedsReification,
         WaitingForReification,
+        Damaged,
         Done
     };
 
@@ -24,11 +25,17 @@ public:
         Status status;
         std::optional<Chunk> chunk = {};
 
-        std::optional<GFX::Mesh> land_mesh_data = {};
-        std::optional<GFX::Mesh> water_mesh_data = {};
+        std::optional<GFX::Mesh> land_mesh = {};
+        std::optional<GFX::Mesh> water_mesh = {};
 
-        std::optional<GFX::BindableMesh> land_mesh = {};
-        std::optional<GFX::BindableMesh> water_mesh = {};
+        Status get_status() const {
+            if (status == Status::Done && chunk.value().is_damaged()) { return Status::Damaged; }
+            return status;
+        }
+
+        void damage() {
+            if (status == Status::Done) chunk.value().damage();
+        }
     };
 
     Data& get(ChunkIndex index);
diff --git a/src/World/Clouds.cpp b/src/World/Clouds.cpp
index 2e0f76c..2567985 100644
--- a/src/World/Clouds.cpp
+++ b/src/World/Clouds.cpp
@@ -15,7 +15,7 @@ Clouds::Clouds(Real ascept, Real fov, Real near, Real far, Vector<3, F32> sky_co
     {GFX::Shading::Shader::Type::Vertex, vertex},
     {GFX::Shading::Shader::Type::Fragment, fragment}
     ),
-    m_mesh(GFX::Binder::load(create_mesh(create_cloud_matrix()))),
+    m_mesh(create_mesh(create_cloud_matrix())),
     m_model_uniform(), m_view_uniform(), m_projection_uniform() {
     m_program.bind();
 
@@ -38,7 +38,7 @@ void Clouds::update(const Time& time) {
     m_x_offset += 5.0 * time.delta();
 }
 
-void Clouds::render(const GFX::Camera& camera) const {
+void Clouds::render(const GFX::Camera& camera) {
     auto position = camera.position();
 
     I32 center_x = std::round((position.x() - m_x_offset) / TileSize);
@@ -51,7 +51,7 @@ void Clouds::render(const GFX::Camera& camera) const {
     }
 }
 
-void Clouds::render_single_instance(const GFX::Camera& camera, Int x, Int y) const {
+void Clouds::render_single_instance(const GFX::Camera& camera, Int x, Int y) {
     Vector<3> position{TileSize * x + m_x_offset, Height, TileSize * y};
 
     m_program.bind();
diff --git a/src/World/Clouds.hpp b/src/World/Clouds.hpp
index 35e7155..4bd0637 100644
--- a/src/World/Clouds.hpp
+++ b/src/World/Clouds.hpp
@@ -1,10 +1,10 @@
 #pragma once
 
 #include "../Time.hpp"
-#include "../GFX/Binder.hpp"
 #include "../GFX/Shading/Program.hpp"
 #include "../GFX/Shading/Uniform.hpp"
 #include "../GFX/Camera.hpp"
+#include "../GFX/Mesh.hpp"
 
 namespace MC::World {
 
@@ -13,7 +13,7 @@ public:
     Clouds(Real ascept, Real fov, Real near, Real far, Vector<3, F32> sky_color, Vector<3, F32> sun_direction);
 
     void update(const Time& time);
-    void render(const GFX::Camera& camera) const;
+    void render(const GFX::Camera& camera);
 private:
     constexpr static U32 CloudMatrixSize = 128;
     constexpr static Int Height = 200;
@@ -22,7 +22,7 @@ private:
 
     using CloudMatrix = Matrix<CloudMatrixSize, CloudMatrixSize, Bool>;
 
-    void render_single_instance(const GFX::Camera& camera, Int x, Int y) const;
+    void render_single_instance(const GFX::Camera& camera, Int x, Int y);
 
     static CloudMatrix create_cloud_matrix();
     static GFX::Mesh create_mesh(const CloudMatrix& cloud_matrix);
@@ -33,7 +33,7 @@ private:
     Real m_x_offset = 0.0;
 
     GFX::Shading::Program m_program;
-    GFX::BindableMesh m_mesh;
+    GFX::Mesh m_mesh;
 
     GFX::Shading::Uniform m_model_uniform;
     GFX::Shading::Uniform m_view_uniform;
diff --git a/src/World/Generation/ChunkMeshing.cpp b/src/World/Generation/ChunkMeshing.cpp
index 002dd79..3855de4 100644
--- a/src/World/Generation/ChunkMeshing.cpp
+++ b/src/World/Generation/ChunkMeshing.cpp
@@ -2,15 +2,53 @@
 
 namespace MC::World::Generation::ChunkMeshing {
 
-ChunkMesh mesh_chunk(Chunk& chunk, const ChunkNeighbors& neighbors) {
+ChunkMesh mesh_chunk(Chunk& chunk, const SurroundingContext& context) {
     using namespace Detail;
 
     return {
-        create_mesh<DefaultMeshDecisions>(chunk, neighbors),
-        create_mesh<WaterMeshDecisions>(chunk, neighbors)
+        create_mesh<DefaultMeshDecisions>(chunk, context),
+        create_mesh<WaterMeshDecisions>(chunk, context)
     };
 }
 
+SurroundingContext::Block& SurroundingContext::at(Position::BlockOffset p) {
+    return m_blocks[pos(p)];
+}
+
+const SurroundingContext::Block& SurroundingContext::at(Position::BlockOffset p) const {
+    return m_blocks[pos(p)];
+}
+
+USize SurroundingContext::pos(Position::BlockOffset p) {
+    // First we calculate the index as if there were no gaps.
+    USize pos = 0;
+    pos += p.x() + 1;
+    pos += (p.z() + 1) * (Chunk::Width + 2);
+
+    // Then we substract the gaps.
+    Int g = (p.z() + 1) - (p.x() < 0);
+    UInt gap_count = std::clamp<Int>(g, 0, Chunk::Width);
+    pos -= gap_count * Chunk::Width;
+
+    pos += p.y() * surrounding_block_count;
+    return pos;
+}
+
+SurroundingContext create_meshing_context(const Chunk& chunk, ChunkNeighbors& neighbors) {
+    SurroundingContext context{};
+    for (I16 x = -1; x <= (I16)Chunk::Width; x++) {
+        for (I16 z = -1; z <= (I16)Chunk::Width; z++) {
+            for (I16 y = 0; y < (I16)Chunk::Height; y++) {
+                Position::BlockOffset pos{x, y, z};
+                if (pos.fits_within_chunk()) continue;
+                auto [does_exist, block] = get_block_wrapping(chunk, neighbors, {pos.x(), pos.y(), pos.z()});
+                context.at(pos) = {does_exist, block};
+            }
+        }
+    }
+    return context;
+}
+
 namespace Detail {
 
 Face<Vertex> DefaultMeshDecisions::face_positions(BlockSide side, U32 x, U32 y, U32 z) {
@@ -124,15 +162,15 @@ Face<Normal> DefaultMeshDecisions::face_normals(BlockSide side) {
     return {normal, normal, normal, normal};
 }
 
-Face<Light> DefaultMeshDecisions::face_light(Chunk& chunk, const ChunkNeighbors& neighbors, U32 x, U32 y, U32 z, BlockSide side) {
-    auto neighbor = get_opposing_neighbor(chunk, neighbors, x, y, z, side);
+Face<Light> DefaultMeshDecisions::face_light(Chunk& chunk, const SurroundingContext& context, U32 x, U32 y, U32 z, BlockSide side) {
+    auto neighbor = get_opposing_neighbor(chunk, context, x, y, z, side).block;
     if (!neighbor.type.is_translucent()) return {};
 
     auto light = (F32)neighbor.light / (F32)255;
     return {light, light, light, light};
 }
 
-Face<AO> DefaultMeshDecisions::face_ao_values(Chunk& chunk, const ChunkNeighbors& neighbors, U32 x, U32 y, U32 z, BlockSide side) {
+Face<AO> DefaultMeshDecisions::face_ao_values(Chunk& chunk, const SurroundingContext& context, U32 x, U32 y, U32 z, BlockSide side) {
     std::array<Vector<3, I32>, 8> offsets{};
     // Given a block position, these offsets can be added to it to get the 8 blocks necessary to calculate AO.
     // There are 4 corners and 4 sides, corners are visually distinguished by the lack of spaces and
@@ -167,13 +205,15 @@ Face<AO> DefaultMeshDecisions::face_ao_values(Chunk& chunk, const ChunkNeighbors
         auto b = offsets[++offset_index]; // corner
         auto c = offsets[++offset_index % 8];
 
-        auto block_a = get_block_wrapping(chunk, neighbors, {x + a.x(), y + a.y(), z + a.z()});
-        auto block_b = get_block_wrapping(chunk, neighbors, {x + b.x(), y + b.y(), z + b.z()});
-        auto block_c = get_block_wrapping(chunk, neighbors, {x + c.x(), y + c.y(), z + c.z()});
+        auto p = [=](auto o) -> Position::BlockOffset { return {(I16)((I16)x + o.x()), (I16)((I16)y + o.y()), (I16)((I16)z + o.z())}; };
+        auto block_a = get_block_from_chunk_or_context(chunk, context, p(a));
+        auto block_b = get_block_from_chunk_or_context(chunk, context, p(b));
+        auto block_c = get_block_from_chunk_or_context(chunk, context, p(c));
 
-        F32 occlusion_a = block_a.empty() ? 0 : 1;
-        F32 occlusion_b = block_b.empty() ? 0 : 1;
-        F32 occlusion_c = block_c.empty() ? 0 : 1;
+        auto is_occupied = [](auto b) -> F32 { return b.does_exist && !b.block.empty(); };
+        F32 occlusion_a = is_occupied(block_a);
+        F32 occlusion_b = is_occupied(block_b);
+        F32 occlusion_c = is_occupied(block_c);
 
         if (occlusion_a + occlusion_c == 2.0f) {
             vertex_ao[vertex] = 1.0f;
@@ -195,15 +235,24 @@ Vector<3, I32> DefaultMeshDecisions::get_face_normal(BlockSide side) {
     };
 }
 
-Chunk::BlockData DefaultMeshDecisions::get_opposing_neighbor(const Chunk& chunk, const ChunkNeighbors& neighbors, U32 x, U32 y, U32 z, BlockSide side) {
+SurroundingContext::Block DefaultMeshDecisions::get_block_from_chunk_or_context(
+    const Chunk& chunk, const SurroundingContext& context, Position::BlockOffset pos
+) {
+    if (pos.fits_within_chunk()) return {true, chunk.at(pos)};
+    return context.at(pos);
+}
+
+SurroundingContext::Block DefaultMeshDecisions::get_opposing_neighbor(const Chunk& chunk, const SurroundingContext& context, U32 x, U32 y, U32 z, BlockSide side) {
     Vector<3, I32> offset = get_face_normal(side);
-    auto neighbor_position = offset + Vector<3, I32>{x, y, z};
+    auto pos = offset + Vector<3, I32>{x, y, z};
 
-    return get_block_wrapping(chunk, neighbors, neighbor_position);
+    return get_block_from_chunk_or_context(chunk, context, {(I16)pos.x(), (I16)pos.y(), (I16)pos.z()});
 }
 
-Bool DefaultMeshDecisions::is_face_visible(Chunk& chunk, const ChunkNeighbors& neighbors, U32 x, U32 y, U32 z, BlockSide side) {
-    auto block = get_opposing_neighbor(chunk, neighbors, x, y, z, side);
+Bool DefaultMeshDecisions::is_face_visible(Chunk& chunk, const SurroundingContext& context, U32 x, U32 y, U32 z, BlockSide side) {
+    auto [does_exist, block] = get_opposing_neighbor(chunk, context, x, y, z, side);
+    // Faces on the chunk border are hidden by default, even if the opposing chunk does not exist.
+    if (!does_exist) return false;
     return block.type.is_translucent();
 }
 
@@ -211,8 +260,9 @@ Bool DefaultMeshDecisions::should_ignore_block(Chunk::BlockData block) {
     return block.empty() || block.type == BlockType::Water;
 }
 
-Bool WaterMeshDecisions::is_face_visible(Chunk& chunk, const ChunkNeighbors& neighbors, U32 x, U32 y, U32 z, BlockSide side) {
-    auto block = get_opposing_neighbor(chunk, neighbors, x, y, z, side);
+Bool WaterMeshDecisions::is_face_visible(Chunk& chunk, const SurroundingContext& context, U32 x, U32 y, U32 z, BlockSide side) {
+    auto [does_exist, block] = get_opposing_neighbor(chunk, context, x, y, z, side);
+    if (!does_exist) return false; // See above.
     return block.type.is_translucent() && block.type != BlockType::Water;
 }
 
diff --git a/src/World/Generation/ChunkMeshing.hpp b/src/World/Generation/ChunkMeshing.hpp
index fb5fea9..5401580 100644
--- a/src/World/Generation/ChunkMeshing.hpp
+++ b/src/World/Generation/ChunkMeshing.hpp
@@ -5,11 +5,32 @@
 #include "../BlockSide.hpp"
 #include "../Chunk.hpp"
 #include "ChunkNeighbors.hpp"
+#include "../ChunkRegistry.hpp"
 
 namespace MC::World::Generation::ChunkMeshing {
 
+// Literally just the blocks squarely surrounding a chunk.
+// Example context for a 2x1x2 chunk:
+// c c c c
+// c x x c
+// c x x c
+// c c c c
+class SurroundingContext {
+public:
+    struct Block { Bool does_exist; Chunk::BlockData block; };
+
+    Block& at(Position::BlockOffset p);
+    const Block& at(Position::BlockOffset p) const;
+private:
+    static USize pos(Position::BlockOffset p);
+    static constexpr USize surrounding_block_count = Chunk::Width * 4 + 4;
+
+    Block m_blocks[surrounding_block_count * Chunk::Height] = {};
+};
+SurroundingContext create_meshing_context(const Chunk& chunk, ChunkNeighbors& neighbors);
+
 struct ChunkMesh { GFX::Mesh land_mesh, water_mesh; };
-ChunkMesh mesh_chunk(Chunk& chunk, const ChunkNeighbors& neighbors);
+ChunkMesh mesh_chunk(Chunk& chunk, const SurroundingContext& context);
 
 namespace Detail {
 
@@ -23,7 +44,7 @@ template <typename T>
 using Face = std::array<T, 4>;
 
 template <typename Decisions>
-GFX::Mesh create_mesh(Chunk& chunk, const ChunkNeighbors& neighbors) {
+GFX::Mesh create_mesh(Chunk& chunk, const SurroundingContext& context) {
     GFX::Util::MeshBuilder<Normal, TexCoord, Light, AO> builder;
 
     for (Int x = 0; x < Chunk::Width; x++) {
@@ -34,7 +55,7 @@ GFX::Mesh create_mesh(Chunk& chunk, const ChunkNeighbors& neighbors) {
                     continue;
 
                 for (auto side: BlockSide::all()) {
-                    if (!Decisions::is_face_visible(chunk, neighbors, x, y, z, side))
+                    if (!Decisions::is_face_visible(chunk, context, x, y, z, side))
                         continue;
 
                     U32 s = builder.vertex_count();
@@ -42,8 +63,8 @@ GFX::Mesh create_mesh(Chunk& chunk, const ChunkNeighbors& neighbors) {
                     builder.positions(Decisions::face_positions(side, x, y, z));
                     builder.attributes<0>(Decisions::face_normals(side));
                     builder.attributes<1>(Decisions::face_tex_coords(block.type, side));
-                    builder.attributes<2>(Decisions::face_light(chunk, neighbors, x, y, z, side));
-                    builder.attributes<3>(Decisions::face_ao_values(chunk, neighbors, x, y, z, side));
+                    builder.attributes<2>(Decisions::face_light(chunk, context, x, y, z, side));
+                    builder.attributes<3>(Decisions::face_ao_values(chunk, context, x, y, z, side));
                     builder.indices(std::array{ s + 0, s + 1, s + 2, s + 2, s + 3, s + 0 });
                 }
             }
@@ -58,19 +79,20 @@ public:
     static Face<Vertex> face_positions(BlockSide side, U32 x, U32 y, U32 z);
     static Face<TexCoord> face_tex_coords(BlockType type, BlockSide side);
     static Face<Normal> face_normals(BlockSide side);
-    static Face<Light> face_light(Chunk& chunk, const ChunkNeighbors& neighbors, U32 x, U32 y, U32 z, BlockSide side);
-    static Face<AO> face_ao_values(Chunk& chunk, const ChunkNeighbors& neighbors, U32 x, U32 y, U32 z, BlockSide side);
+    static Face<Light> face_light(Chunk& chunk, const SurroundingContext& context, U32 x, U32 y, U32 z, BlockSide side);
+    static Face<AO> face_ao_values(Chunk& chunk, const SurroundingContext& context, U32 x, U32 y, U32 z, BlockSide side);
 
     static Vector<3, I32> get_face_normal(BlockSide side);
-    static Chunk::BlockData get_opposing_neighbor(const Chunk& chunk, const ChunkNeighbors& neighbors, U32 x, U32 y, U32 z, BlockSide side);
+    static SurroundingContext::Block get_block_from_chunk_or_context(const Chunk& chunk, const SurroundingContext& context, Position::BlockOffset pos);
+    static SurroundingContext::Block get_opposing_neighbor(const Chunk& chunk, const SurroundingContext& context, U32 x, U32 y, U32 z, BlockSide side);
 
-    static Bool is_face_visible(Chunk& chunk, const ChunkNeighbors& neighbors, U32 x, U32 y, U32 z, BlockSide side);
+    static Bool is_face_visible(Chunk& chunk, const SurroundingContext& context, U32 x, U32 y, U32 z, BlockSide side);
     static Bool should_ignore_block(Chunk::BlockData block);
 };
 
 class WaterMeshDecisions final : public DefaultMeshDecisions {
 public:
-    static Bool is_face_visible(Chunk& chunk, const ChunkNeighbors& neighbors, U32 x, U32 y, U32 z, BlockSide side);
+    static Bool is_face_visible(Chunk& chunk, const SurroundingContext& context, U32 x, U32 y, U32 z, BlockSide side);
     static Bool should_ignore_block(Chunk::BlockData block);
 };
 
diff --git a/src/World/Generation/ChunkNeighbors.cpp b/src/World/Generation/ChunkNeighbors.cpp
index f34466e..c3ae5cb 100644
--- a/src/World/Generation/ChunkNeighbors.cpp
+++ b/src/World/Generation/ChunkNeighbors.cpp
@@ -2,7 +2,28 @@
 
 namespace MC::World::Generation {
 
-Chunk::BlockData get_block_wrapping(const Chunk& chunk, const ChunkNeighbors& neighbors, Vector<3, I32> pos) {
+ChunkNeighbors find_chunk_neighbors(ChunkIndex chunk, ChunkRegistry& chunks) {
+    UInt neighbor_index = 0;
+    std::array<Chunk*, 8> neighbors{};
+    for (I32 x = -1; x <= 1; x++) {
+        for (I32 y = -1; y <= 1; y++) {
+            if (x == 0 && y == 0) continue;
+
+            auto& neighbor_data = chunks.get({chunk.x + x, chunk.y + y});
+            if (neighbor_data.chunk.has_value()) neighbors[neighbor_index++] = &neighbor_data.chunk.value();
+        }
+    }
+
+    // Layout of neighboring chunks in `neighbors` array:
+    // (-1; -1) > (-1;  0) > (-1; 1) > (0; -1)
+    // ( 0;  1) > ( 1; -1) > ( 1; 0) > (1;  1)
+    return {
+        neighbors[3], neighbors[6], neighbors[4], neighbors[1],
+        neighbors[5], neighbors[7], neighbors[2], neighbors[0],
+    };
+}
+
+GetBlockWrappingResult get_block_wrapping(const Chunk& chunk, const ChunkNeighbors& neighbors, Vector<3, I32> pos) {
     const Chunk* chunk_to_ask;
 
     auto overflow = [](I32& c, I32 max) -> I8 {
@@ -28,7 +49,8 @@ Chunk::BlockData get_block_wrapping(const Chunk& chunk, const ChunkNeighbors& ne
     else if (zo == -1) { chunk_to_ask = neighbors.north; }
     else { chunk_to_ask = &chunk; }
 
-    return chunk_to_ask->at(pos.x(), pos.y(), pos.z());
+    if (!chunk_to_ask) return {false};
+    return {true, chunk_to_ask->at(pos.x(), pos.y(), pos.z())};
 }
 
 }
\ No newline at end of file
diff --git a/src/World/Generation/ChunkNeighbors.hpp b/src/World/Generation/ChunkNeighbors.hpp
index 1207c60..83fca4c 100644
--- a/src/World/Generation/ChunkNeighbors.hpp
+++ b/src/World/Generation/ChunkNeighbors.hpp
@@ -1,13 +1,22 @@
 #pragma once
 
 #include "../Chunk.hpp"
+#include "../ChunkRegistry.hpp"
 
 namespace MC::World::Generation {
 struct ChunkNeighbors {
     Chunk *north, *east, *south, *west;
     Chunk *north_east, *south_east, *south_west, *north_west;
+
+    Bool all_exist() const { return north && east && south && west && north_east && south_east && south_west && north_west; }
 };
 
-Chunk::BlockData get_block_wrapping(const Chunk& chunk, const ChunkNeighbors& neighbors, Vector<3, I32> pos);
+ChunkNeighbors find_chunk_neighbors(ChunkIndex chunk, ChunkRegistry& chunks);
+
+struct GetBlockWrappingResult {
+    bool does_exist;
+    Chunk::BlockData block;
+};
+GetBlockWrappingResult get_block_wrapping(const Chunk& chunk, const ChunkNeighbors& neighbors, Vector<3, I32> pos);
 
-}
\ No newline at end of file
+}
diff --git a/src/World/Generation/Lighting.cpp b/src/World/Generation/Lighting.cpp
index 39d8320..f7c7bbf 100644
--- a/src/World/Generation/Lighting.cpp
+++ b/src/World/Generation/Lighting.cpp
@@ -23,12 +23,12 @@ void Lighting::add_block(Chunk& chunk, Position::BlockLocal position, U8 light)
     enqueue(chunk.index(), position, light);
 }
 
-void Lighting::illuminate(ChunkRegistry& chunks) {
+void Lighting::illuminate(Chunk& chunk) {
     while (!m_queue.empty()) {
         auto op = m_queue.front();
         m_queue.pop();
 
-        process(chunks, op);
+        process(chunk, op);
     }
 }
 
@@ -36,10 +36,7 @@ void Lighting::enqueue(ChunkIndex chunk, Position::BlockLocal position, U8 origi
     m_queue.push({chunk, position, origin});
 }
 
-void Lighting::process(ChunkRegistry& chunks, Operation op) {
-    auto& chunk_data = chunks.get(op.chunk);
-    auto& chunk = chunk_data.chunk.value();
-
+void Lighting::process(Chunk& chunk, Operation op) {
     for (auto direction : Position::axis_directions) {
         auto neighbor_pos = op.position.offset(direction);
         if (!neighbor_pos.fits_within_chunk()) continue;
diff --git a/src/World/Generation/Lighting.hpp b/src/World/Generation/Lighting.hpp
index 72af0e1..4587266 100644
--- a/src/World/Generation/Lighting.hpp
+++ b/src/World/Generation/Lighting.hpp
@@ -13,7 +13,7 @@ public:
     void add_chunk(Chunk& chunk);
     void add_block(Chunk& chunk, Position::BlockLocal position, U8 light);
 
-    void illuminate(ChunkRegistry& chunks);
+    void illuminate(Chunk& chunk);
 private:
     static constexpr U8 SunBrightness = 200;
     static constexpr U8 DefaultFalloff = 10;
@@ -25,7 +25,7 @@ private:
     };
 
     void enqueue(ChunkIndex chunk, Position::BlockLocal position, U8 origin);
-    void process(ChunkRegistry& chunks, Operation op);
+    void process(Chunk& chunk, Operation op);
 
     std::queue<Operation> m_queue;
 };
diff --git a/src/World/Position.hpp b/src/World/Position.hpp
index 49cbbb6..b603ee4 100644
--- a/src/World/Position.hpp
+++ b/src/World/Position.hpp
@@ -9,9 +9,9 @@ namespace MC::World::Position {
 using World = Vector<3>;
 
 // Offset between block positions within single chunk.
-class OffsetBlock : public Vector<3, I8> {
+class BlockOffset : public Vector<3, I16> {
 public:
-    OffsetBlock(I8 x, I8 y, I8 z) : Vector(x, y, z) {}
+    BlockOffset(I16 x, I16 y, I16 z) : Vector(x, y, z) {}
     Bool fits_within_chunk() const {
         using namespace ChunkDimensions;
         return x() >= 0 && x() < Width && y() >= 0 && y() < Height && z() >= 0 && z() < Width;
@@ -28,8 +28,8 @@ public:
 class BlockLocal : public Vector<3, U8> {
 public:
     BlockLocal(U8 x, U8 y, U8 z) : Vector(x, y, z) {}
-    BlockLocal(OffsetBlock offset) : BlockLocal(offset.x(), offset.y(), offset.z()) {}
-    OffsetBlock offset(OffsetBlock by) {
+    BlockLocal(BlockOffset offset) : BlockLocal(offset.x(), offset.y(), offset.z()) {}
+    BlockOffset offset(BlockOffset by) {
         return {
             static_cast<I8>(x() + by.x()),
             static_cast<I8>(y() + by.y()),
@@ -38,7 +38,7 @@ public:
     }
 };
 
-const std::array<OffsetBlock, 6> axis_directions = {{
+const std::array<BlockOffset, 6> axis_directions = {{
     {1, 0, 0}, {-1, 0, 0}, {0, 1, 0}, {0, -1, 0}, {0, 0, 1}, {0, 0, -1},
 }};
 
diff --git a/src/World/World.cpp b/src/World/World.cpp
index 5548866..3d273bd 100644
--- a/src/World/World.cpp
+++ b/src/World/World.cpp
@@ -5,8 +5,8 @@
 
 namespace MC::World {
 
-std::vector<ChunkRegistry::Data*> World::get_visible_chunks(Vector<3> position) {
-    load_finished_chunks_from_queue();
+std::vector<ChunkRegistry::Data*> World::get_visible_chunks(Position::World position) {
+    process_chunk_updates();
 
     auto visible_chunks = get_visible_chunk_indices(position);
 
@@ -14,24 +14,30 @@ std::vector<ChunkRegistry::Data*> World::get_visible_chunks(Vector<3> position)
     chunks.reserve(visible_chunks.size());
     for (auto index : visible_chunks) {
         auto& data = m_registry.get(index);
-        if (data.status == ChunkRegistry::Status::Empty) {
-            request_generation(index, position.distance(index.middle()));
+
+        if (data.get_status() == ChunkRegistry::Status::Empty) {
+            request_generation(index, calculate_priority(index, position, RequestType::Initial));
             data.status = ChunkRegistry::Status::WaitingForGeneration;
             continue;
         }
 
-        if (data.status == ChunkRegistry::Status::WaitingForReification) {
-            try_to_reify_chunk(data);
-        }
-        if (data.status == ChunkRegistry::Status::Done) {
-            chunks.push_back(&data);
+        if (data.get_status() == ChunkRegistry::Status::NeedsReification || data.get_status() == ChunkRegistry::Status::Damaged) {
+            auto do_all_exist = Generation::find_chunk_neighbors(index, m_registry).all_exist();
+            if (m_reification_queue.size() <= 50 && do_all_exist) {
+                auto request_type = data.get_status() == ChunkRegistry::Status::Damaged ? RequestType::Update : RequestType::Initial;
+                request_reification(index, calculate_priority(index, position, request_type));
+                data.status = ChunkRegistry::Status::WaitingForReification;
+            }
         }
+
+        // TODO: Use a better indicator than `land_mesh.has_value()`.
+        if (data.land_mesh.has_value()) chunks.push_back(&data);
     }
 
     return chunks;
 }
 
-std::vector<ChunkIndex> World::get_visible_chunk_indices(const Vector<3> position) const {
+std::vector<ChunkIndex> World::get_visible_chunk_indices(const Position::World position) const {
     I32 center_x = std::round(position.x() / Chunk::Width);
     I32 center_y = std::round(position.z() / Chunk::Width);
 
@@ -53,61 +59,62 @@ std::vector<ChunkIndex> World::get_visible_chunk_indices(const Vector<3> positio
     return indices;
 }
 
-void World::load_finished_chunks_from_queue() {
-    auto results = m_queue.done();
-    for (auto& [id, res] : results) {
-        m_registry.get(id) = {id, ChunkRegistry::Status::WaitingForReification, {res.chunk}};
+void World::process_chunk_updates() {
+    auto generation_results = m_generation_queue.done();
+    for (auto& [id, res] : generation_results) {
+        m_registry.get(id) = {id, ChunkRegistry::Status::NeedsReification, {res.chunk}};
+
         log_chunk_time(res.generation_duration);
     }
+
+    auto reification_results = m_reification_queue.done();
+    for (auto& [id, res] : reification_results) {
+        m_registry.get(id) = {
+            id, ChunkRegistry::Status::Done,
+            {res.chunk},
+            res.chunk_mesh.land_mesh,
+            res.chunk_mesh.water_mesh
+        };
+
+        // TODO: Damage surrounding chunks.
+    }
 }
 
 void World::request_generation(ChunkIndex index, Real priority) {
-    m_queue.add(index, priority, [=]() -> GenerationResult {
+    m_generation_queue.add(index, priority, [=]() -> GenerationResult {
         auto start = Time::now();
         auto chunk = m_generator.generate(index.x, index.y);
         return {chunk, Time::now() - start};
     });
 }
 
-void World::try_to_reify_chunk(ChunkRegistry::Data& data) {
-    auto index = data.index;
+void World::request_reification(ChunkIndex index, Real priority) {
+    auto& data = m_registry.get(index);
     auto& chunk = data.chunk.value();
 
-    UInt neighbor_index = 0;
-    std::array<Chunk*, 8> neighbors;
-    for (I32 x = -1; x <= 1; x++) {
-        for (I32 y = -1; y <= 1; y++) {
-            if (x == 0 && y == 0) continue;
+    auto neighbors = Generation::find_chunk_neighbors(index, m_registry);
+    auto meshing_context = Generation::ChunkMeshing::create_meshing_context(data.chunk.value(), neighbors);
+    m_reification_queue.add(index, priority, [=]() mutable -> ReificationResult {
+        Generation::Lighting intitial_lighting{};
+        intitial_lighting.add_chunk(chunk);
+        intitial_lighting.illuminate(chunk);
 
-            auto& neighbor_data = m_registry.get({index.x + x, index.y + y});
-            if (!neighbor_data.chunk.has_value()) return; // All neighbors need to be generated first.
-
-            neighbors[neighbor_index++] = &neighbor_data.chunk.value();
-        }
-    }
-
-    // Layout of neighboring chunks in `neighbors` array:
-    // (-1; -1) > (-1;  0) > (-1; 1) > (0; -1)
-    // ( 0;  1) > ( 1; -1) > ( 1; 0) > (1;  1)
-    Generation::ChunkNeighbors chunk_neighbors {
-        neighbors[3], neighbors[6], neighbors[4], neighbors[1],
-        neighbors[5], neighbors[7], neighbors[2], neighbors[0],
-    };
-
-    // Lighting
-    m_lighting.add_chunk(chunk);
-    m_lighting.illuminate(m_registry);
-
-    // Meshing
-    auto meshes = Generation::ChunkMeshing::mesh_chunk(chunk, chunk_neighbors);
+        auto meshes = Generation::ChunkMeshing::mesh_chunk(chunk, meshing_context);
+        return {chunk, meshes};
+    });
+}
 
-    data.land_mesh_data = meshes.land_mesh;
-    data.land_mesh = GFX::Binder::load(data.land_mesh_data.value());
+Real World::calculate_priority(ChunkIndex chunk, Position::World player_position, RequestType request_type) {
+    auto chunk_position = chunk.middle();
+    auto flat_distance = Position::World{player_position.x(), chunk_position.y(), player_position.z()}.distance(chunk_position);
 
-    data.water_mesh_data = meshes.water_mesh;
-    data.water_mesh = GFX::Binder::load(data.water_mesh_data.value());
+    Real request_type_penalty;
+    switch (request_type) {
+    case RequestType::Initial: request_type_penalty = 0; break;
+    case RequestType::Update: request_type_penalty = 500; break;
+    }
 
-    data.status = ChunkRegistry::Status::Done;
+    return flat_distance + request_type_penalty;
 }
 
 void World::log_chunk_time(U64 chunk_time_ms) {
diff --git a/src/World/World.hpp b/src/World/World.hpp
index db9d0db..92367f6 100644
--- a/src/World/World.hpp
+++ b/src/World/World.hpp
@@ -4,33 +4,41 @@
 #include "ChunkIndex.hpp"
 #include "ChunkRegistry.hpp"
 #include "../Compute/Queue.hpp"
+#include "Generation/ChunkMeshing.hpp"
 #include "Generation/Lighting.hpp"
 
 namespace MC::World {
 
 class World {
 public:
-    World() : m_queue(8) {}
-
-    std::vector<ChunkRegistry::Data*> get_visible_chunks(Vector<3> position);
+    std::vector<ChunkRegistry::Data*> get_visible_chunks(Position::World position);
 
     Real get_average_chunk_time() const;
 private:
-    std::vector<ChunkIndex> get_visible_chunk_indices(Vector<3> position) const;
+    std::vector<ChunkIndex> get_visible_chunk_indices(Position::World position) const;
 
-    void load_finished_chunks_from_queue();
+    void process_chunk_updates();
     void request_generation(ChunkIndex index, Real priority);
-    void try_to_reify_chunk(ChunkRegistry::Data& data);
+    void request_reification(ChunkIndex index, Real priority);
+
+    enum class RequestType { Initial, Update };
+    static Real calculate_priority(ChunkIndex chunk, Position::World player_position, RequestType request_type);
 
     void log_chunk_time(U64 chunk_time_ms);
 
-    U8 m_view_distance_radius = 10;
+    U8 m_view_distance_radius = 12;
 
     struct GenerationResult {
         Chunk chunk;
         U64 generation_duration;
     };
-    Compute::Queue<GenerationResult, ChunkIndex> m_queue;
+    Compute::Queue<GenerationResult, ChunkIndex> m_generation_queue{4};
+    struct ReificationResult {
+        Chunk chunk;
+        Generation::ChunkMeshing::ChunkMesh chunk_mesh;
+    };
+    Compute::Queue<ReificationResult, ChunkIndex> m_reification_queue{4};
+
     Generation::Generator m_generator;
     Generation::Lighting m_lighting;
     ChunkRegistry m_registry;
diff --git a/src/main.cpp b/src/main.cpp
index 1c6aa27..815de28 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -6,7 +6,6 @@
 #include "Common/Sizes.hpp"
 #include "GFX/Window.hpp"
 #include "GFX/Camera.hpp"
-#include "GFX/Binder.hpp"
 #include "Math/MVP.hpp"
 #include "GFX/Shading/Program.hpp"
 #include "GFX/Texture.hpp"
@@ -23,7 +22,7 @@
 #define FOV 90
 
 void run();
-void render(const MC::GFX::BindableMesh&, const MC::GFX::Texture&);
+void render(MC::GFX::Mesh&, MC::GFX::Texture&);
 void process_input(MC::GFX::Window&, MC::GFX::Camera&, MC::Time&);
 void setup_gl();
 void fix_macos_render(const MC::GFX::Window&);
@@ -135,7 +134,7 @@ void run() {
     }
 }
 
-void render(const MC::GFX::BindableMesh& mesh, const MC::GFX::Texture& texture) {
+void render(MC::GFX::Mesh& mesh, MC::GFX::Texture& texture) {
     texture.bind();
     mesh.bind();
     glDrawElements(GL_TRIANGLES, mesh.size(), GL_UNSIGNED_INT, nullptr);