summaryrefslogtreecommitdiff
path: root/src/chunk.c
diff options
context:
space:
mode:
authorAaditya Dhruv <[email protected]>2026-01-31 03:22:27 -0600
committerAaditya Dhruv <[email protected]>2026-01-31 03:22:27 -0600
commit7f13b0abaa76a5e90674d5733f8162f02ceab693 (patch)
treee280ad2a4a881e79d2519f2558dcab6a36d5b389 /src/chunk.c
parent0c3e1f450f591d871c2779504b4113daf891fd1b (diff)
Rework chunk rendering to get a 100x performance boost!
- Switching to semi-chunk mesh rendering just 100x the framerate, it was running at around 100fps for 3 CHUNK_DISTANCE, and pushing it above would make it drop to 30. Now it runs at 9000 frames per second with CHUNK_DISTANCE of 8, probably can push it even more - What is bizarre is this is just from the reduction in draw calls, I still need to implement face culling for invisible blocks (simple) and frustrum culling (using AABB) or maybe octrees - Block is way way more simplifed, it's just metadata about a coordinate in the chunk block array - All rendering and mesh generation code is handled by chunks. There is a VAO, single VBO and EBO for each chunk. The data buffer is loaded into the GPU with a chunk_load, and it stays like that until it is loaded again. chunk_load is called if we move chunks, in engine_update. Here we unload existing chunks, then load the new ones
Diffstat (limited to 'src/chunk.c')
-rw-r--r--src/chunk.c294
1 files changed, 252 insertions, 42 deletions
diff --git a/src/chunk.c b/src/chunk.c
index 3a7b334..80395d1 100644
--- a/src/chunk.c
+++ b/src/chunk.c
@@ -1,13 +1,20 @@
#include "chunk.h"
#include "block.h"
+#include "cglm/types.h"
+#include "cglm/vec3.h"
+#include "shader.h"
+#include "util.h"
#include "world.h"
#include "cglm/cglm.h"
+#include <junk/vector.h>
+#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MIN(x, y) (x < y) ? x : y
#define MAX(x, y) (x > y) ? x : y
+#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0]))
void _chunk_plains_gen(struct chunk* chunk);
@@ -108,8 +115,7 @@ void _chunk_plains_gen(struct chunk* chunk) {
for (int h = 0; h < z_final; h++) {
struct block* blk = malloc(sizeof(struct block));
// Adjust block coordinates with global chunk coordinates
- vec3 pos = {x, h, -y };
- block_init(pos, blk);
+ block_init(blk, BLOCK_GRASS);
chunk->blocks[x][y][h] = blk;
}
}
@@ -118,63 +124,267 @@ void _chunk_plains_gen(struct chunk* chunk) {
}
-// Kind of like the block_update of chunks
+int* _chunk_face_order_add(int* face_order, int size, int idx) {
+ int* buf = malloc(size);
+ memcpy(buf, face_order, size);
+ for (int i = 0; i < size / sizeof(int); i++) {
+ buf[i] += idx;
+ }
+ return buf;
+}
+float* _chunk_face_add(float* face, int size, vec3 pos) {
+ // "Hack" to update the face coords,
+ // glm_vec3_add just does a[0] = a[0] + b[0], so using
+ // offsets will work
+ int unit = 8;
+ float* buf = malloc(size);
+ memcpy(buf, face, size);
+ glm_vec3_add(buf, pos, buf);
+ glm_vec3_add(buf + unit, pos, buf + unit);
+ glm_vec3_add(buf + 2 * unit, pos, buf + 2 * unit);
+ glm_vec3_add(buf + 3 * unit, pos, buf + 3 * unit);
+ return buf;
+}
+
+/**
+ * Two step function:
+ * 1. Generate mesh based on neighboring block data
+ * 2. Send data to GPU
+ *
+ * NOTE: GPU
+ */
void chunk_load(struct chunk *chunk, int coord[2]) {
- vec3 translation = {CHUNK_WIDTH * coord[0], 0, - (CHUNK_LENGTH * coord[1])};
+ fprintf(stderr, "Loaded chunk (%d, %d)\n", coord[0], coord[1]);
+ // ================ OpenGL work ================
+ // Initalize vertices and vertex order vectors. These will be dynamically
+ // sized buffer data we send to the GPU
+ struct vector* vertices;
+ struct vector* vertex_order;
+ vector_init(&vertices);
+ vector_init(&vertex_order);
+
+ // =============== Face Data ===================
+ float front_face[] = {
+ 1.0f, 1.0f, 0.0f, // top-right
+ 0.0f, 0.0f, 1.0f, // Front normal
+ 1.0f, 1.0f,
+ 0.0f, 1.0f, 0.0f, // top-left
+ 0.0f, 0.0f, 1.0f, // Front normal
+ 0.0f, 1.0f,
+ 0.0f, 0.0f, 0.0f, // bottom-left
+ 0.0f, 0.0f, 1.0f, // Front normal
+ 0.0f, 0.0f,
+ 1.0f, 0.0f, 0.0f, // bottom-right
+ 0.0f, 0.0f, 1.0f, // Front normal
+ 1.0f, 0.0f,
+ };
+ float back_face[] = {
+ 0.0f, 1.0f, -1.0f, // top-left (back plane)
+ 0.0f, 0.0f, -1.0f, // Back normal
+ 0.0f, 1.0f,
+ 1.0f, 1.0f, -1.0f, // top-right (back plane)
+ 0.0f, 0.0f, -1.0f, // Back normal
+ 1.0f, 1.0f,
+ 1.0f, 0.0f, -1.0f, // bottom-right (back plane)
+ 0.0f, 0.0f, -1.0f, // Back normal
+ 1.0f, 0.0f,
+ 0.0f, 0.0f, -1.0f, // bottom-left (back plane)
+ 0.0f, 0.0f, -1.0f, // Back normal
+ 0.0f, 0.0f,
+ };
+ float right_face[] = {
+ 1.0f, 1.0f, -1.0f, // top-right (back plane)
+ 1.0f, 0.0f, 0.0f, // Right normal
+ 1.0f, 1.0f,
+ 1.0f, 1.0f, 0.0f, // top-right
+ 1.0f, 0.0f, 0.0f, // Right normal
+ 0.0f, 1.0f,
+ 1.0f, 0.0f, 0.0f, // bottom-right
+ 1.0f, 0.0f, 0.0f, // Right normal
+ 0.0f, 0.0f,
+ 1.0f, 0.0f, -1.0f, // bottom-right (back plane)
+ 1.0f, 0.0f, 0.0f, // Right normal
+ 1.0f, 0.0f,
+ };
+ float left_face[] = {
+ 0.0f, 1.0f, 0.0f, // top-left
+ -1.0f, 0.0f, 0.0f, // Left normal
+ 1.0f, 1.0f,
+ 0.0f, 1.0f, -1.0f, // top-left (back plane)
+ -1.0f, 0.0f, 0.0f, // Left normal
+ 0.0f, 1.0f,
+ 0.0f, 0.0f, -1.0f, // bottom-left (back plane)
+ -1.0f, 0.0f, 0.0f, // Left normal
+ 0.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f, // bottom-left
+ -1.0f, 0.0f, 0.0f, // Left normal
+ 1.0f, 0.0f,
+ };
+ float top_face[] = {
+ 1.0f, 1.0f, -1.0f, // top-right (back plane)
+ 0.0f, 1.0f, 0.0f, // Top normal
+ 1.0f, 1.0f,
+ 0.0f, 1.0f, -1.0f, // top-left (back plane)
+ 0.0f, 1.0f, 0.0f, // Top normal
+ 0.0f, 1.0f,
+ 0.0f, 1.0f, 0.0f, // top-left
+ 0.0f, 1.0f, 0.0f, // Top normal
+ 0.0f, 0.0f,
+ 1.0f, 1.0f, 0.0f, // top-right
+ 0.0f, 1.0f, 0.0f, // Top normal
+ 1.0f, 0.0f,
+ };
+ float bottom_face[] = {
+ 1.0f, 0.0f, -1.0f, // bottom-right (back plane)
+ 0.0f, -1.0f, 0.0f, // Bottom normal
+ 1.0f, 1.0f,
+ 0.0f, 0.0f, -1.0f, // bottom-left (back plane)
+ 0.0f, -1.0f, 0.0f, // Bottom normal
+ 0.0f, 1.0f,
+ 0.0f, 0.0f, 0.0f, // bottom-left
+ 0.0f, -1.0f, 0.0f, // Bottom normal
+ 0.0f, 0.0f,
+ 1.0f, 0.0f, 0.0f, // bottom-right
+ 0.0f, -1.0f, 0.0f, // Bottom normal
+ 1.0f, 0.0f,
+ };
+
+ int vertex_draw_order[] = {
+ 1, 2, 3, 3, 0, 1, // CCW 2-triangles (quad)
+ };
+ // ============= Face detection algorithm =============
+ int vertex_index = 0;
+ int blk_c = 0;
for (int x = 0; x < CHUNK_WIDTH; x++) {
for (int y = 0; y < CHUNK_HEIGHT; y++) {
for (int z = 0; z < CHUNK_LENGTH; z++) {
struct block* blk = chunk->blocks[x][z][y];
+ // If not air block
if (blk != NULL) {
- // If chunk is unloaded, send block data to GPU
- if (chunk->loaded == 0) {
- // Add GPU data
- block_load_gpu(blk);
- }
- // Translate to world coordinates
- // First do block updates, set the position of the block in local
- // chunk coordinates
- block_update(blk);
- // Then translate them to world coordinates
- glm_translate(blk->model, translation);
+ blk_c += 1;
+ vec3 pos = { x, y, -z };
+ // Position of block in world coords
+ // glm_vec3_add(pos, translation, pos);
+
+ VECTOR_INSERT(vertices, _chunk_face_add(front_face,
+ sizeof(front_face), pos));
+ VECTOR_INSERT(vertex_order,
+ _chunk_face_order_add(vertex_draw_order,
+ sizeof(vertex_draw_order), vertex_index));
+ vertex_index += 4;
+
+ VECTOR_INSERT(vertices, _chunk_face_add(back_face,
+ sizeof(back_face), pos));
+ VECTOR_INSERT(vertex_order,
+ _chunk_face_order_add(vertex_draw_order,
+ sizeof(vertex_draw_order), vertex_index));
+ vertex_index += 4;
+
+ VECTOR_INSERT(vertices, _chunk_face_add(right_face,
+ sizeof(right_face), pos));
+ VECTOR_INSERT(vertex_order,
+ _chunk_face_order_add(vertex_draw_order,
+ sizeof(vertex_draw_order), vertex_index));
+ vertex_index += 4;
+
+ VECTOR_INSERT(vertices, _chunk_face_add(left_face,
+ sizeof(left_face), pos));
+ VECTOR_INSERT(vertex_order,
+ _chunk_face_order_add(vertex_draw_order,
+ sizeof(vertex_draw_order), vertex_index));
+ vertex_index += 4;
+
+ VECTOR_INSERT(vertices, _chunk_face_add(top_face,
+ sizeof(top_face), pos));
+ VECTOR_INSERT(vertex_order,
+ _chunk_face_order_add(vertex_draw_order,
+ sizeof(vertex_draw_order), vertex_index));
+ vertex_index += 4;
+
+ VECTOR_INSERT(vertices, _chunk_face_add(bottom_face,
+ sizeof(bottom_face), pos));
+ VECTOR_INSERT(vertex_order,
+ _chunk_face_order_add(vertex_draw_order,
+ sizeof(vertex_draw_order), vertex_index));
+ vertex_index += 4;
}
}
}
}
- if (chunk->loaded == 0) {
- //We've reloaded all data - flip bit again
- chunk->loaded = 1;
+ fprintf(stderr, "Chunk blk_c: %d\n", blk_c);
+
+ float tmp_vertex[vector_length(vertices) * sizeof(front_face)];
+ int tmp_order[vector_length(vertex_order) * sizeof(vertex_draw_order)];
+ fprintf(stderr, "Chunk blk_c: %d v_s: %d, v_o: %d\n", blk_c, vector_length(vertices) / 6, vector_length(vertex_order) / 6);
+ for (int i = 0; i < vector_length(vertices); i++) {
+ float* face = vector_get(vertices, i);
+ // Copy from heap mem to tmp buffer, and then free
+ memcpy(tmp_vertex + (i*ARRAY_SIZE(front_face)), face, sizeof(front_face));
+ free(face);
}
+ for (int i = 0; i < vector_length(vertex_order); i++) {
+ int* order = vector_get(vertex_order, i);
+ // Copy from heap mem to tmp buffer, and then free
+ memcpy(tmp_order + (i*ARRAY_SIZE(vertex_draw_order)), order, sizeof(vertex_draw_order));
+ free(order);
+ }
+
+ // Create VBO and EBO buffer data
+ // VBO EBO size is sizeof() because we want TOTAL BYTES (float * count)
+ create_vbo(&chunk->_vbo, (void*)tmp_vertex, sizeof(tmp_vertex));
+ create_ebo(&chunk->_ebo, (void*)tmp_order, sizeof(tmp_order));
+ // Here we only want ARRAY_SIZE, not float * count
+ chunk->vertex_count = vector_length(vertex_order) * ARRAY_SIZE(vertex_draw_order);
+
+
+ glGenVertexArrays(1, &chunk->_vao);
+ glBindVertexArray(chunk->_vao);
+ // Enable 3 attribs - position normals texture
+ glEnableVertexAttribArray(0);
+ glEnableVertexAttribArray(1);
+ glEnableVertexAttribArray(2);
+ // set vao_buffer to pos buffer obj
+ glBindBuffer(GL_ARRAY_BUFFER, chunk->_vbo);
+ glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), 0);
+ // set vao_buffer to normals buffer obj
+ glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (GLvoid*)(3*sizeof(float)));
+ // set vao_buffer to texture buffer obj
+ glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (GLvoid*)(6*sizeof(float)));
+ // Set EBO to the vertex_order
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, chunk->_ebo);
+ //NOTE: This is important, otherwise with multiple chunk_load calls, it
+ //creates a segfault since the bindings get all messed up. Why it gets
+ //messed up? Let's say we make 2 chunks. Chunk 1 creates VBOs, then VAO,
+ //then binds everything. Now VAO is still bound. Chunk 2 init starts. First
+ //call is create_vbo. Since VAO is already bound, it gets bound to the OLD
+ //VAO!! Always clear before use.
+ glBindVertexArray(0);
+ // Translation to WORLD units
+ vec3 translation = {CHUNK_WIDTH * coord[0], 0, - (CHUNK_LENGTH * coord[1])};
+ // Set the matrix for world coordinate translation
+ glm_mat4_identity(chunk->model);
+ glm_translate(chunk->model, translation);
+ chunk->loaded = 1;
}
void chunk_draw(struct chunk* chunk, struct shader* shader, struct texture* texture) {
- int counter = 0;
- for (int i = 0; i < CHUNK_WIDTH; i++) {
- for (int j = 0; j < CHUNK_LENGTH; j++) {
- for (int k = 0; k < CHUNK_HEIGHT; k++) {
- struct block* blk = chunk->blocks[i][j][k];
- if (blk == NULL) {
- continue;
- }
- block_draw(blk, shader, texture);
- counter += 1;
- }
- }
- }
+ glBindVertexArray(chunk->_vao);
+ set_uniform_mat4("model", shader, chunk->model);
+ glDrawElements(GL_TRIANGLES, chunk->vertex_count, GL_UNSIGNED_INT, 0);
+ glBindVertexArray(0);
}
void chunk_unload(struct chunk* chunk) {
- for (int i = 0; i < CHUNK_WIDTH; i++) {
- for (int j = 0; j < CHUNK_LENGTH; j++) {
- for (int k = 0; k < CHUNK_HEIGHT; k++) {
- struct block* blk = chunk->blocks[i][j][k];
- if (blk == NULL) {
- continue;
- }
- block_unload(blk);
- }
- }
- }
chunk->loaded = 0;
+ // Clear VBO data
+ glDeleteBuffers(1, &chunk->_vbo);
+ // Clear EBO data
+ glDeleteBuffers(1, &chunk->_ebo);
+ // Clear VAO
+ glDeleteVertexArrays(1, &chunk->_vao);
+ chunk->loaded = 0;
+}
+// Regenerate chunk data
+void chunk_update(struct chunk *chunk) {
}