From 7f13b0abaa76a5e90674d5733f8162f02ceab693 Mon Sep 17 00:00:00 2001 From: Aaditya Dhruv Date: Sat, 31 Jan 2026 03:22:27 -0600 Subject: 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 --- src/block.c | 200 +--------------------------------------- src/block.h | 37 ++------ src/camera.c | 2 +- src/chunk.c | 294 ++++++++++++++++++++++++++++++++++++++++++++++++++--------- src/chunk.h | 27 +++++- src/engine.c | 32 +++++-- src/engine.h | 4 +- src/world.c | 6 +- src/world.h | 4 +- 9 files changed, 314 insertions(+), 292 deletions(-) (limited to 'src') diff --git a/src/block.c b/src/block.c index d03a137..8911ba3 100644 --- a/src/block.c +++ b/src/block.c @@ -15,203 +15,7 @@ #include #include - -#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0])) - -void block_update(struct block* blk); -int block_init(vec3 pos, struct block* blk) { - // Store buffer data into struct - // Initialize vbo and ebo for block - - memcpy(blk->coords, pos, sizeof(vec3)); - block_load_gpu(blk); - block_update(blk); +int block_init(struct block* blk, enum BLOCK_ID block_id) { + blk->block_id = block_id; return 0; } - -void block_load_gpu(struct block* blk) { - // ========== Constants of a block ================ - // Local world coordinates - float vertices[] = { - 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, - - 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, - - 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, - - 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, - - 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, - - 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_order[] = { - // 1, 2, 3, 3, 0, 1, // Front - // 5, 6, 7, 7, 4, 5, // Back - // 9, 10, 11, 11, 8, 9, // Right - // 13, 14, 15, 15, 12, 13, // Left - // 17, 18, 19, 19, 16, 17, // Top - // 21, 22, 23, 23, 20, 21, // Bottom - // - // }; - int vertex_order[] = { - 1, 2, 3, 3, 0, 1, // Front - 5, 6, 7, 7, 4, 5, // Back - 9, 10, 11, 11, 8, 9, // Right - 13, 14, 15, 15, 12, 13, // Left - 17, 18, 19, 19, 16, 17, // Top - 21, 22, 23, 23, 20, 21, // Bottom - }; - - // ================ OpenGL work ================ - create_vbo(&blk->_vbo, (void*)vertices, sizeof(float) * ARRAY_SIZE(vertices)); - create_ebo(&blk->_ebo, (void*)vertex_order, sizeof(int) * ARRAY_SIZE(vertex_order)); - - - blk->_vertex_count = ARRAY_SIZE(vertex_order); - glGenVertexArrays(1, &blk->_vao); - glBindVertexArray(blk->_vao); - // Enable 3 attribs - position normals texture - glEnableVertexAttribArray(0); - glEnableVertexAttribArray(1); - glEnableVertexAttribArray(2); - // set vao_buffer to pos buffer obj - glBindBuffer(GL_ARRAY_BUFFER, blk->_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, blk->_ebo); - //NOTE: This is important, otherwise with multiple block_init calls, it - //creates a segfault since the bindings get all messed up. Why it gets - //messed up? Let's say we make 2 blocks. Block 1 creates VBOs, then VAO, - //then binds everything. Now VAO is still bound. Block 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); -} - -void block_update(struct block* blk) { - //=============== Matrix Work ============== - - // RTS matrix - rotate, translate, scale - glm_mat4_identity(blk->model); - float angle = glm_rad(blk->angle); - // vec3 scale = { 0.90f, 0.90f, 0.90f }; - glm_translate(blk->model, blk->coords); - // glm_scale(blk->model, scale); - // glm_rotate_at(blk->model, pivot, angle, rot_axis); - // View matrix (camera) - //blk->angle = fmodf(blk->angle + 0.005f, 360.0f); -} - -// Register block vbos and ebos to context -int block_draw(struct block* blk, struct shader* shader, struct texture* texture) { - glBindVertexArray(blk->_vao); - set_uniform_mat4("model", shader, blk->model); - // GLuint loc = glGetUniformLocation(shader->program, "face_colors"); - // if (loc == -1) { - // fprintf(stderr, "Invalid var %s for get_uniform_mat4: Does not exist\n", "face_colors"); - // exit(1); - // return -1; - // } - // float colors[] = { - // 0.761f, 0.424f, 0.0f, - // 0.761f, 0.424f, 0.0f, - // 0.761f, 0.424f, 0.0f, - // 0.761f, 0.424f, 0.0f, - // 0.404f, 0.776f, 0.027f, - // 0.761f, 0.424f, 0.0f, - // }; - // glUniform3fv(loc, 6, (void*)colors); - // texture_draw(texture); - glDrawElements(GL_TRIANGLES, blk->_vertex_count, GL_UNSIGNED_INT, 0); - glBindVertexArray(0); - return 0; -} - -void block_debug(struct block *blk) { - fprintf(stderr, "==== Block Debug ====\n"); - fprintf(stderr, "==== Block Coords ====\n"); - glm_vec3_print(blk->coords, stderr); - fprintf(stderr, "==== Block Model ====\n"); - glm_mat4_print(blk->model, stderr); - -} - -void block_unload(struct block *blk) { - // Clear VBO data - glDeleteBuffers(1, &blk->_vbo); - // Clear EBO data - glDeleteBuffers(1, &blk->_ebo); - // Clear VAO - glDeleteVertexArrays(1, &blk->_vao); -} diff --git a/src/block.h b/src/block.h index ad81c47..d8e75d7 100644 --- a/src/block.h +++ b/src/block.h @@ -1,38 +1,13 @@ #pragma once -#include "cglm/types.h" -#include "glad/glad.h" -#include "shader.h" -#include "texture.h" +enum BLOCK_ID { + BLOCK_GRASS, +}; struct block { - vec3 coords; - GLuint _vao; - GLuint _vbo; - GLuint _ebo; - GLuint _tbo; - int _vertex_count; - mat4 model; - float angle; + enum BLOCK_ID block_id; }; /** - * Create a "block" object, which is the building blocks of this world. - * Blocks belong in chunks, and chunks belong in worlds. vec3 pos here is the coordinates of the block in WORLD space. - * However, a common method to render these blocks will be that the chunk will set the coordinates in "chunk space", and - * on a chunk_load, we will translate the blocks to wherever the chunk is loaded - * - * - */ -int block_init(vec3 pos, struct block* blk); -int block_draw(struct block* blk, struct shader* shader, struct texture* texture); -void block_debug(struct block* blk); -void block_update(struct block* blk); - -/** - * Remove GPU related data of a block. This is usually called by chunk_unload + * A block struct defines what kind of block we will be rendering. It's the metadata of the block array in a chunk * */ -void block_unload(struct block* blk); -/** - * Load GPU data of a block - */ -void block_load_gpu(struct block* blk); +int block_init(struct block* blk, enum BLOCK_ID block_id); diff --git a/src/camera.c b/src/camera.c index 7a23a17..cbea1ff 100644 --- a/src/camera.c +++ b/src/camera.c @@ -56,7 +56,7 @@ void camera_move(struct camera *camera, enum DIRECTION move) { // Right hand rule - this will be on the righ (positive) glm_vec3_crossn(unit_direction, camera->up, unit_direction); } - float scale = 0.2f; + float scale = 0.8f; glm_vec3_scale(unit_direction, scale, unit_direction); glm_vec3_add(camera->position, unit_direction, camera->position); } 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 +#include #include #include #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) { } diff --git a/src/chunk.h b/src/chunk.h index d49ba80..12fa111 100644 --- a/src/chunk.h +++ b/src/chunk.h @@ -1,8 +1,11 @@ #pragma once #include "block.h" +#include "cglm/types.h" +#include "shader.h" #include "texture.h" #include "world.h" #include +#include #define CHUNK_WIDTH 16 #define CHUNK_LENGTH 16 #define CHUNK_HEIGHT 128 @@ -17,11 +20,21 @@ enum biome { // World.c compiles -> includes world.h -> first line is include chunk.h -> struct world // is used here, which messes it up. Forward declare to avoid errors struct world; +/** + * A chunk is the "basic" rendering unit used - it will allocate buffers for all blocks in a chunk, hide covered faces of blocks, generate a chunk mesh and dispatch that buffer data to the GPU + * + */ struct chunk { struct block* blocks[CHUNK_WIDTH][CHUNK_LENGTH][CHUNK_HEIGHT]; + GLuint _vao; + GLuint _vbo; + GLuint _ebo; + int vertex_count; + mat4 model; enum biome biome; vec2 coord; int loaded; + int dirty; }; /** @@ -35,19 +48,27 @@ int chunk_gen(struct world* wld, vec2 coord, struct chunk** chunk); * it's local coordinate system. We want to load this particular chunk to a * location in WORLD coordinates, which is what coord is. This vec2 will be * used to translate the blocks that constitute the chunk + * + * The chunk will allocate a VAO/VBO/EBO buffer to render the chunk mesh. This GPU data is usually not updated in the loop, unless a chunk_update is called * @param chunk Chunk to load * @param coord coordinates in world space */ void chunk_load(struct chunk* chunk, int coord[2]); /** - * Unload a chunk. Delete block GPU and memory data, not the chunk data itself + * Chunk updates are performed on already loaded chunks. It will redraw the + * chunk mesh as needed based on block updates and whatnot. + * @param chunk Chunk to load + * @param coord coordinates in world space + */ +void chunk_update(struct chunk* chunk); +/** + * Unload a chunk. Delete GPU and memory data, not the chunk data itself * * @param chunk Chunk to load */ void chunk_unload(struct chunk* chunk); /* - * Similar to block_draw, this dispatches calls to OpenGL to draw the chunk. - * Technically this wraps block_draw, so block_draw is the one doing all the work + * This dispatches calls to OpenGL to draw the chunk. * @param chunk Chunk to draw * @param shader Shader to pass to block_draw * @param texture Textures that block_draw will use diff --git a/src/engine.c b/src/engine.c index c5a9220..ab6cf92 100644 --- a/src/engine.c +++ b/src/engine.c @@ -14,8 +14,6 @@ #include #include -void _engine_insert_chunk_ptrs(struct engine* engine, struct chunk* chunk); - int engine_init(struct engine *engine) { // Setup the Window struct window* window = malloc(sizeof(struct window)); @@ -54,6 +52,16 @@ int engine_init(struct engine *engine) { struct world* world; world_init(time(NULL), &world); engine->world = world; + //TODO: Move this loop to a function and flip chunk_coord sign correctly ONCE + for (int i = -CHUNK_DISTANCE; i <= CHUNK_DISTANCE; i++) { + for (int j = -CHUNK_DISTANCE; j <= CHUNK_DISTANCE; j++) { + struct chunk* chunk; + int chunk_coord[2] = { engine->curr_chunk[0] + i, -engine->curr_chunk[1] + j }; + world_get_chunk(engine->world, chunk_coord, &chunk); + // Load chunk + chunk_load(chunk, chunk_coord); + } + } // Final step - Start the game engine->game_loop = 1; return 0; @@ -77,19 +85,25 @@ void engine_update(struct engine* engine) { for (int i = -CHUNK_DISTANCE; i <= CHUNK_DISTANCE; i++) { for (int j = -CHUNK_DISTANCE; j <= CHUNK_DISTANCE; j++) { struct chunk* chunk; - int chunk_coord[2] = { engine->curr_chunk[0] + i, engine->curr_chunk[1] + j }; + int chunk_coord[2] = { engine->curr_chunk[0] + i, -engine->curr_chunk[1] + j }; world_get_chunk(engine->world, chunk_coord, &chunk); - // Get "real" coords as in non-negative numbers, that can go in a array - int real_coord[2] = { i + CHUNK_DISTANCE, j + CHUNK_DISTANCE }; - // engine->loaded_chunks[real_coord[0]][real_coord[1]] = chunk; - // Load chunk + // unload chunk // TODO: Fix some VAO/VBO bug when negative y - // chunk_unload(chunk); + chunk_unload(chunk); } } // Update the curr_chunk memcpy(engine->curr_chunk, curr_chunk, sizeof(vec2)); // Load chunks of CHUNK_DISTANCE around curr_chunk + for (int i = -CHUNK_DISTANCE; i <= CHUNK_DISTANCE; i++) { + for (int j = -CHUNK_DISTANCE; j <= CHUNK_DISTANCE; j++) { + struct chunk* chunk; + int chunk_coord[2] = { engine->curr_chunk[0] + i, -engine->curr_chunk[1] + j }; + world_get_chunk(engine->world, chunk_coord, &chunk); + // Load chunk + chunk_load(chunk, chunk_coord); + } + } } } @@ -139,8 +153,6 @@ void engine_start(struct engine* engine) { // user, so we want inwards to be positive, so flip sign int chunk_coord[2] = { engine->curr_chunk[0] + i, -engine->curr_chunk[1] + j }; world_get_chunk(engine->world, chunk_coord, &chunk); - // Load chunk - chunk_load(chunk, chunk_coord); chunk_draw(chunk, engine->shader, engine->texture); } } diff --git a/src/engine.h b/src/engine.h index e7ffdaa..567d9a5 100644 --- a/src/engine.h +++ b/src/engine.h @@ -9,7 +9,7 @@ // We want a square around curr_chunk, and a side of the square will be 1 // (center chunk) + 2 * CHUNK_DISTANCE (either side of center) // loaded chunks = (1 + CHUNK_DISTANCE * 2)^2 -#define CHUNK_DISTANCE 3 +#define CHUNK_DISTANCE 5 struct engine { struct window* window; @@ -31,7 +31,7 @@ int engine_init(struct engine* engine); /** * Take all objects in the engine, apply the shader pipeline and draw on the window - * Apply block, chunk and camera updates as well. This is the main game loop + * Apply chunk and camera updates as well. This is the main game loop * * @param engine The target engine */ diff --git a/src/world.c b/src/world.c index 41fdd17..4ce248b 100644 --- a/src/world.c +++ b/src/world.c @@ -5,7 +5,7 @@ #include // LOAD_DISTANCE determines how many chunks are loaded on world creation -#define LOAD_DISTANCE 4 +#define LOAD_DISTANCE 10 int world_init(int32_t seed, struct world** world) { srand(seed); @@ -13,8 +13,8 @@ int world_init(int32_t seed, struct world** world) { memset(wld, 0, sizeof(struct world)); wld->seed = seed; //TODO: Improve loading here - for (int i = 0; i < LOAD_DISTANCE; i++) { - for (int j = 0; j < LOAD_DISTANCE; j++) { + for (int i = -LOAD_DISTANCE; i <= LOAD_DISTANCE; i++) { + for (int j = -LOAD_DISTANCE; j <= LOAD_DISTANCE; j++) { struct chunk* chunk; int coords[2] = { i, j }; world_get_chunk(wld, coords, &chunk); diff --git a/src/world.h b/src/world.h index ec0328b..a447d8e 100644 --- a/src/world.h +++ b/src/world.h @@ -1,8 +1,8 @@ #pragma once #include "chunk.h" #include -#define WORLD_LENGTH 6 -#define WORLD_WIDTH 6 +#define WORLD_LENGTH 32 +#define WORLD_WIDTH 32 struct world { struct chunk* chunks[WORLD_WIDTH][WORLD_LENGTH]; -- cgit