terrain

Real-time terrain generation using marching cubes
git clone git://git.christianermann.dev/terrain
Log | Files | Refs | README | LICENSE

commit deae69d49607fd68023adfdf1c5171ff4915fefa
parent 34ce6801da6310dc1c83e26e14c247b135da8368
Author: Christian Ermann <christianermann@gmail.com>
Date:   Wed, 18 Aug 2021 13:25:46 -0500

Load chunk meshes on separate threads

Diffstat:
Minclude/chunk.h | 18++++++++++++++----
Minclude/chunk_manager.h | 6++++++
Minclude/mesh.h | 18+++++++++---------
Ainclude/threadpool.h | 16++++++++++++++++
Mshaders/basic.fs | 2+-
Msrc/camera.c | 2+-
Msrc/chunk.c | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Msrc/chunk_manager.c | 87+++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Msrc/main.c | 2+-
Msrc/mesh.c | 115+++++++++++++++++++++++++++++++++++--------------------------------------------
Asrc/threadpool.c | 207+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
11 files changed, 426 insertions(+), 123 deletions(-)

diff --git a/include/chunk.h b/include/chunk.h @@ -4,23 +4,33 @@ #include "mesh.h" #include "sdf.h" +#include <pthread.h> + #define CHUNK_WIDTH 16 #define CHUNK_VOLUME (CHUNK_WIDTH * CHUNK_WIDTH * CHUNK_WIDTH) #define CHUNK_PWIDTH (CHUNK_WIDTH + 1) #define CHUNK_PVOLUME (CHUNK_PWIDTH * CHUNK_PWIDTH * CHUNK_PWIDTH) +typedef struct UpdateArgs UpdateArgs; + typedef struct { IVec3 origin; Mesh mesh; -} Chunk; -void Chunk_init(Chunk *c, IVec3 origin); + UpdateArgs *update_args; + pthread_mutex_t mesh_mutex; + bool mesh_updated; +} Chunk; +void Chunk_init(Chunk *c); void Chunk_free(Chunk *c); -void Chunk_updateMesh(Chunk *c, SDF f, float isolevel); +void Chunk_updateOrigin(Chunk *c, IVec3 origin); + +void Chunk_setUpdateArgs(Chunk *c, SDF f, float isolevel); +void Chunk_updateFunc(void *arg); -void Chunk_drawMesh(const Chunk *c); +void Chunk_drawMesh(Chunk *c); #endif diff --git a/include/chunk_manager.h b/include/chunk_manager.h @@ -2,6 +2,7 @@ #define CHUNK_MANAGER_H #include "chunk.h" +#include "threadpool.h" typedef struct { IVec3 origin; @@ -14,6 +15,11 @@ typedef struct { IVec3 *offsets; Chunk *chunks; + bool *is_new_offset; + bool *is_old_chunk; + + ThreadPool *pool; + } ChunkManager; ChunkManager ChunkManager_create(const Vec3 target, int radius, SDF f, diff --git a/include/mesh.h b/include/mesh.h @@ -3,6 +3,8 @@ #include "vec.h" +#include "glad/glad.h" + typedef struct { unsigned int vertex_capacity; unsigned int vertex_count; @@ -12,20 +14,18 @@ typedef struct { unsigned int index_count; unsigned int *indices; - unsigned int vao; - unsigned int vbo; - unsigned int ebo; + unsigned int draw_index; + unsigned int write_index; + unsigned int vao[2]; + unsigned int vbo[2]; + unsigned int ebo[2]; } Mesh; -void Mesh_init(Mesh *m); +void Mesh_init(Mesh *m, unsigned int vertex_count, unsigned int index_count); void Mesh_free(Mesh *m); -void Mesh_reserveVertices(Mesh *m, unsigned int vertex_count); -void Mesh_reserveIndices(Mesh *m, unsigned int index_count); - -void Mesh_updateVertexBuffer(const Mesh *m); -void Mesh_updateIndexBuffer(const Mesh *m); +void Mesh_swapBuffers(Mesh *m); void Mesh_draw(const Mesh *m); diff --git a/include/threadpool.h b/include/threadpool.h @@ -0,0 +1,16 @@ +#ifndef THREADPOOL_H +#define THREADPOOL_H + +// https://nachtimwald.com/2019/04/12/thread-pool-in-c/ + +typedef void (*ThreadFunc)(void *arg); + +typedef struct ThreadPool ThreadPool; + +ThreadPool *ThreadPool_make(unsigned int thread_count); +void ThreadPool_free(ThreadPool *pool); + +void ThreadPool_addWork(ThreadPool *pool, ThreadFunc func, void *arg); +void ThreadPool_wait(ThreadPool *pool); + +#endif diff --git a/shaders/basic.fs b/shaders/basic.fs @@ -19,7 +19,7 @@ void main() vec3 object_color = mix(vec3(1.0, 0.8, 0.6), vec3(0.7, 0.7, 0.7), slope); float d = length(pointlight_pos - frag_pos); - float attenuation = 1.0 / (1.0 + 0.07 * d + 0.017 * d * d); + float attenuation = 1.0 / (1.0 + 0.022 * d + 0.0019 * d * d); vec3 light_dir = normalize(frag_pos - pointlight_pos); vec3 view_dir = normalize(view_pos - frag_pos); diff --git a/src/camera.c b/src/camera.c @@ -22,7 +22,7 @@ void Camera_defaultSettings(Camera* camera) camera->fovy = 90.0f; camera->aspect = 1.0f; camera->near = 0.1f; - camera->far = 100.0f; + camera->far = 200.0f; camera->speed = 0.1f; camera->sensitivity = 0.3f; } diff --git a/src/chunk.c b/src/chunk.c @@ -4,20 +4,42 @@ #include <stdlib.h> -void Chunk_init(Chunk *c, IVec3 origin) +struct UpdateArgs { + Chunk *chunk; + SDF f; + float isolevel; +}; + +static UpdateArgs *UpdateArgs_make(Chunk *c) { - c->origin[0] = origin[0]; - c->origin[1] = origin[1]; - c->origin[2] = origin[2]; + UpdateArgs *args = malloc(sizeof *args); + args->chunk = c; + args->f = NULL; + args->isolevel = 0.0f; + return args; +} + +static void UpdateArgs_free(UpdateArgs *args) +{ + if (args) + { + free(args); + } +} - Mesh_init(&c->mesh); - Mesh_reserveVertices(&c->mesh, CHUNK_PVOLUME * 3); - Mesh_reserveIndices(&c->mesh, CHUNK_VOLUME * 15); +void Chunk_init(Chunk *c) +{ + Mesh_init(&c->mesh, CHUNK_PVOLUME * 3, CHUNK_VOLUME * 15); + + c->update_args = UpdateArgs_make(c); + pthread_mutex_init(&c->mesh_mutex, NULL); + c->mesh_updated = false; } void Chunk_free(Chunk *c) { Mesh_free(&c->mesh); + UpdateArgs_free(c->update_args); } static void worldOrigin(const IVec3 chunk_origin, Vec3 world_origin) @@ -116,19 +138,45 @@ static void Chunk_updateMeshData(Chunk *c, SDF f, float isolevel) } } -static void Chunk_updateMeshBuffers(const Chunk *c) +void Chunk_setUpdateArgs(Chunk *c, SDF f, float isolevel) { - Mesh_updateVertexBuffer(&c->mesh); - Mesh_updateIndexBuffer(&c->mesh); + c->update_args->chunk = c; + c->update_args->f = f; + c->update_args->isolevel = isolevel; } -void Chunk_updateMesh(Chunk *c, SDF f, float isolevel) +void Chunk_updateFunc(void *arg) { - Chunk_updateMeshData(c, f, isolevel); - Chunk_updateMeshBuffers(c); + UpdateArgs *args = (UpdateArgs*)arg; + + pthread_mutex_lock(&args->chunk->mesh_mutex); + + Chunk_updateMeshData(args->chunk, args->f, args->isolevel); + + args->chunk->mesh_updated = true; + + pthread_mutex_unlock(&args->chunk->mesh_mutex); } -void Chunk_drawMesh(const Chunk *c) +void Chunk_updateOrigin(Chunk *c, IVec3 origin) { + pthread_mutex_lock(&c->mesh_mutex); + c->origin[0] = origin[0]; + c->origin[1] = origin[1]; + c->origin[2] = origin[2]; + pthread_mutex_unlock(&c->mesh_mutex); +} + +void Chunk_drawMesh(Chunk *c) +{ + if (pthread_mutex_trylock(&c->mesh_mutex) == 0) + { + if (c->mesh_updated) + { + Mesh_swapBuffers(&c->mesh); + c->mesh_updated = false; + } + pthread_mutex_unlock(&c->mesh_mutex); + } Mesh_draw(&c->mesh); } diff --git a/src/chunk_manager.c b/src/chunk_manager.c @@ -40,65 +40,84 @@ static void ChunkManager_createChunks(ChunkManager *cm) cm->chunks = malloc(sizeof *cm->chunks * cm->chunk_count); for (int i = 0; i < cm->chunk_count; i++) { - Chunk_init(&cm->chunks[i], cm->offsets[i]); - Chunk_updateMesh(&cm->chunks[i], cm->f, cm->isolevel); + Chunk_init(&cm->chunks[i]); + + IVec3 chunk_origin = { + cm->origin[0] + cm->offsets[i][0], + cm->origin[1] + cm->offsets[i][1], + cm->origin[2] + cm->offsets[i][2] + }; + Chunk_updateOrigin(&cm->chunks[i], chunk_origin); + Chunk_setUpdateArgs(&cm->chunks[i], cm->f, cm->isolevel); + ThreadPool_addWork(cm->pool, Chunk_updateFunc, + cm->chunks[i].update_args); } } -static void ChunkManager_updateChunks(ChunkManager *cm) +static void ChunkManager_resetFlags(ChunkManager *cm) { - // is_new indicates whether an offset is new - bool *is_new = malloc(sizeof *is_new * cm->chunk_count); - // is_old indicates whether a chunk is old - bool *is_old = malloc(sizeof *is_old * cm->chunk_count); for (int i = 0; i < cm->chunk_count; i++) { - is_new[i] = true; - is_old[i] = true; + cm->is_new_offset[i] = true; + cm->is_old_chunk[i] = true; } +} - // Determine which offsets are new and which chunks are old - IVec3 *new_origins = malloc(sizeof *new_origins * cm->chunk_count); +static void ChunkManager_updateFlags(ChunkManager *cm) +{ for (int i = 0; i < cm->chunk_count; i++) { - new_origins[i][0] = cm->origin[0] + cm->offsets[i][0]; - new_origins[i][1] = cm->origin[1] + cm->offsets[i][1]; - new_origins[i][2] = cm->origin[2] + cm->offsets[i][2]; + IVec3 chunk_origin = { + cm->origin[0] + cm->offsets[i][0], + cm->origin[1] + cm->offsets[i][1], + cm->origin[2] + cm->offsets[i][2] + }; for (int j = 0; j < cm->chunk_count; j++) { - if (IVec3_equal(new_origins[i], cm->chunks[j].origin) != 0) + if (IVec3_equal(chunk_origin, cm->chunks[j].origin)) { - is_new[i] = false; - is_old[j] = false; + cm->is_new_offset[i] = false; + cm->is_old_chunk[j] = false; break; } } } +} - // Set old chunks to have new origins +static void ChunkManager_updateChunkMeshes(ChunkManager *cm) +{ for (int i = 0; i < cm->chunk_count; i++) { - if (is_new[i]) + if (cm->is_new_offset[i]) { for (int j = 0; j < cm->chunk_count; j++) { - if (is_old[j]) + if (cm->is_old_chunk[j]) { - cm->chunks[j].origin[0] = new_origins[i][0]; - cm->chunks[j].origin[1] = new_origins[i][1]; - cm->chunks[j].origin[2] = new_origins[i][2]; - Chunk_updateMesh(&cm->chunks[j], cm->f, cm->isolevel); - is_new[i] = false; - is_old[j] = false; + IVec3 chunk_origin = { + cm->origin[0] + cm->offsets[i][0], + cm->origin[1] + cm->offsets[i][1], + cm->origin[2] + cm->offsets[i][2] + }; + Chunk_updateOrigin(&cm->chunks[j], chunk_origin); + Chunk_setUpdateArgs(&cm->chunks[j], cm->f, cm->isolevel); + ThreadPool_addWork(cm->pool, Chunk_updateFunc, + cm->chunks[j].update_args); + + cm->is_new_offset[i] = false; + cm->is_old_chunk[j] = false; break; } } } } +} - free(new_origins); - free(is_old); - free(is_new); +static void ChunkManager_updateChunks(ChunkManager *cm) +{ + ChunkManager_resetFlags(cm); + ChunkManager_updateFlags(cm); + ChunkManager_updateChunkMeshes(cm); } static void ChunkManager_worldToChunk(const Vec3 src, IVec3 dst) @@ -113,6 +132,8 @@ ChunkManager ChunkManager_create(const Vec3 target, int radius, SDF f, { ChunkManager cm; + cm.pool = ThreadPool_make(5); + ChunkManager_worldToChunk(target, cm.origin); cm.radius = radius; @@ -123,6 +144,9 @@ ChunkManager ChunkManager_create(const Vec3 target, int radius, SDF f, ChunkManager_createOffsets(&cm); ChunkManager_createChunks(&cm); + cm.is_new_offset = malloc(sizeof *cm.is_new_offset * cm.chunk_count); + cm.is_old_chunk = malloc(sizeof *cm.is_old_chunk * cm.chunk_count); + return cm; } @@ -130,12 +154,17 @@ void ChunkManager_free(ChunkManager *cm) { free(cm->offsets); + ThreadPool_free(cm->pool); + for (int i = 0; i < cm->chunk_count; i++) { Chunk_free(&cm->chunks[i]); } free(cm->chunks); + free(cm->is_new_offset); + free(cm->is_old_chunk); + cm->chunk_count = 0; } diff --git a/src/main.c b/src/main.c @@ -82,7 +82,7 @@ int main(int argc, char** argv) glClearColor(0.0f, 0.0f, 0.0f, 1.0f); - ChunkManager chunk_manager = ChunkManager_create(camera.position, 2, + ChunkManager chunk_manager = ChunkManager_create(camera.position, 3, perlinSDF, 0.0f); Shader shader = Shader_create("shaders/basic.vs", "shaders/basic.fs"); diff --git a/src/mesh.c b/src/mesh.c @@ -4,99 +4,86 @@ #include <stdlib.h> -void Mesh_init(Mesh *m) +void Mesh_init(Mesh *m, unsigned int vertex_count, unsigned int index_count) { - m->vertex_capacity = 0; + m->vertex_capacity = vertex_count; m->vertex_count = 0; - m->vertices = NULL; - m->index_capacity = 0; + m->index_capacity = index_count; m->index_count = 0; - m->indices = NULL; - glGenVertexArrays(1, &m->vao); - glBindVertexArray(m->vao); + m->draw_index = 0; + m->write_index = 1; + + glGenVertexArrays(2, m->vao); + glGenBuffers(2, m->vbo); + glGenBuffers(2, m->ebo); + for (int i = 0; i < 2; i++) + { + glBindVertexArray(m->vao[i]); - glGenBuffers(1, &m->vbo); - glBindBuffer(GL_ARRAY_BUFFER, m->vbo); + glBindBuffer(GL_ARRAY_BUFFER, m->vbo[i]); + glBufferData(GL_ARRAY_BUFFER, sizeof *m->vertices * vertex_count, NULL, + GL_STATIC_DRAW); - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vec3), (GLvoid*)0); - glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vec3), 0); + glEnableVertexAttribArray(0); - glGenBuffers(1, &m->ebo); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m->ebo); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m->ebo[i]); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof *m->indices * index_count, + NULL, GL_STATIC_DRAW); - glBindVertexArray(0); + glBindVertexArray(0); + } + + glBindBuffer(GL_ARRAY_BUFFER, m->vbo[m->write_index]); + m->vertices = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m->ebo[m->write_index]); + m->indices = glMapBuffer(GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY); } void Mesh_free(Mesh *m) { - if (m->vertices) - { - free(m->vertices); - m->vertices = NULL; - } m->vertex_capacity = 0; m->vertex_count = 0; - if (m->indices) - { - free(m->indices); - m->indices = NULL; - } m->index_capacity = 0; m->index_count = 0; + + glBindBuffer(GL_ARRAY_BUFFER, m->vbo[m->write_index]); + glUnmapBuffer(GL_ARRAY_BUFFER); - glDeleteVertexArrays(1, &m->vao); - glDeleteBuffers(1, &m->vbo); - glDeleteBuffers(1, &m->ebo); -} + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m->ebo[m->write_index]); + glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER); -void Mesh_reserveVertices(Mesh *m, unsigned int vertex_count) -{ - if (vertex_count > m->vertex_capacity) - { - if (m->vertices) - { - free(m->vertices); - m->vertices = NULL; - } - m->vertices = malloc(sizeof *m->vertices * vertex_count); - m->vertex_capacity = vertex_count; - } + glDeleteVertexArrays(2, m->vao); + glDeleteBuffers(2, m->vbo); + glDeleteBuffers(2, m->ebo); } -void Mesh_reserveIndices(Mesh *m, unsigned int index_count) +void Mesh_swapBuffers(Mesh *m) { - if (index_count > m->index_capacity) - { - if (m->indices) - { - free(m->indices); - m->indices = NULL; - } - m->indices = malloc(sizeof *m->indices * index_count); - m->index_capacity = index_count; - } -} + glBindBuffer(GL_ARRAY_BUFFER, m->vbo[m->draw_index]); + m->vertices = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY); -void Mesh_updateVertexBuffer(const Mesh *m) -{ - glBindBuffer(GL_ARRAY_BUFFER, m->vbo); - glBufferData(GL_ARRAY_BUFFER, sizeof *m->vertices * m->vertex_count, - m->vertices, GL_STATIC_DRAW); -} + glBindBuffer(GL_ARRAY_BUFFER, m->vbo[m->write_index]); + glUnmapBuffer(GL_ARRAY_BUFFER); -void Mesh_updateIndexBuffer(const Mesh *m) -{ - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m->ebo); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof *m->indices * m->index_count, - m->indices, GL_STATIC_DRAW); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m->ebo[m->draw_index]); + m->indices = glMapBuffer(GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m->ebo[m->write_index]); + glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER); + + unsigned int temp = m->draw_index; + m->draw_index = m->write_index; + m->write_index = temp; } void Mesh_draw(const Mesh *m) { - glBindVertexArray(m->vao); + glBindVertexArray(m->vao[m->draw_index]); glDrawElements(GL_TRIANGLES, m->index_count, GL_UNSIGNED_INT, (GLvoid*)0); glBindVertexArray(0); } diff --git a/src/threadpool.c b/src/threadpool.c @@ -0,0 +1,207 @@ +#include "threadpool.h" + +#include <pthread.h> +#include <stdbool.h> +#include <stdlib.h> + +typedef struct ThreadWork ThreadWork; + +struct ThreadWork { + ThreadFunc func; + void *arg; + ThreadWork *next; +}; + +static ThreadWork *ThreadWork_make(ThreadFunc func, void *arg) +{ + ThreadWork *work; + + work = malloc(sizeof *work); + work->func = func; + work->arg = arg; + work->next = NULL; + + return work; +} + +static void ThreadWork_free(ThreadWork *work) +{ + if (work) + { + free(work); + } +} + +struct ThreadPool { + ThreadWork *work_first; + ThreadWork *work_last; + pthread_mutex_t work_mutex; + pthread_cond_t work_cond; + pthread_cond_t active_cond; + unsigned int thread_count; + unsigned int active_count; + bool stop; +}; + +void ThreadPool_addWork(ThreadPool *pool, ThreadFunc func, void *arg) +{ + ThreadWork *work; + work = ThreadWork_make(func, arg); + + pthread_mutex_lock(&pool->work_mutex); + if (pool->work_first == NULL) + { + pool->work_first = work; + pool->work_last = pool->work_first; + } + else + { + pool->work_last->next = work; + pool->work_last = work; + } + + pthread_cond_broadcast(&pool->work_cond); + pthread_mutex_unlock(&pool->work_mutex); +} + +static ThreadWork *ThreadPool_getWork(ThreadPool *pool) +{ + ThreadWork *work; + + work = pool->work_first; + if (work == NULL) + { + return NULL; + } + + if (work->next == NULL) + { + pool->work_first = NULL; + pool->work_last = NULL; + } + else + { + pool->work_first = work->next; + } + + return work; +} + +static void *ThreadPool_worker(void *arg) +{ + ThreadPool *pool = arg; + ThreadWork *work; + + while (true) + { + pthread_mutex_lock(&pool->work_mutex); + + while (pool->work_first == NULL && !pool->stop) + { + pthread_cond_wait(&pool->work_cond, &pool->work_mutex); + } + + if (pool->stop) + { + break; + } + + work = ThreadPool_getWork(pool); + pool->active_count += 1; + + pthread_mutex_unlock(&pool->work_mutex); + + if (work != NULL) + { + work->func(work->arg); + ThreadWork_free(work); + } + + pthread_mutex_lock(&pool->work_mutex); + + pool->active_count -= 1; + if (!pool->stop && pool->active_count == 0 && pool->work_first == NULL) + { + pthread_cond_signal(&pool->active_cond); + } + + pthread_mutex_unlock(&pool->work_mutex); + } + + pool->thread_count -= 1; + pthread_cond_signal(&pool->active_cond); + pthread_mutex_unlock(&pool->work_mutex); + + return NULL; +} + +ThreadPool *ThreadPool_make(unsigned int thread_count) +{ + ThreadPool *pool; + pool = malloc(sizeof *pool); + pool->thread_count = thread_count; + + pthread_mutex_init(&pool->work_mutex, NULL); + pthread_cond_init(&pool->work_cond, NULL); + pthread_cond_init(&pool->active_cond, NULL); + + pool->work_first = NULL; + pool->work_last = NULL; + + pthread_t thread; + for (unsigned int i = 0; i < thread_count; i++) + { + pthread_create(&thread, NULL, ThreadPool_worker, pool); + pthread_detach(thread); + } + + return pool; +} + +void ThreadPool_wait(ThreadPool *pool) +{ + pthread_mutex_lock(&pool->work_mutex); + while (true) + { + if ((!pool->stop && pool->active_count != 0) + || (pool->stop && pool->thread_count != 0)) + { + pthread_cond_wait(&pool->active_cond, &pool->work_mutex); + } + else + { + break; + } + } + pthread_mutex_unlock(&pool->work_mutex); +} + +void ThreadPool_free(ThreadPool *pool) +{ + ThreadWork *work1; + ThreadWork *work2; + + if (pool == NULL) + { + return; + } + + pthread_mutex_lock(&pool->work_mutex); + work1 = pool->work_first; + while (work1 != NULL) + { + work2 = work1->next; + ThreadWork_free(work1); + work1 = work2; + } + + pool->stop = true; + pthread_cond_broadcast(&pool->work_cond); + pthread_mutex_unlock(&pool->work_mutex); + + ThreadPool_wait(pool); + + pthread_mutex_destroy(&pool->work_mutex); + pthread_cond_destroy(&pool->work_cond); + pthread_cond_destroy(&pool->active_cond); +}