]> git.lizzy.rs Git - dragonblocks_alpha.git/blobdiff - src/client/terrain_gfx.c
Fix client terrain crashes and performance
[dragonblocks_alpha.git] / src / client / terrain_gfx.c
index bfe8757e29151126d2fa7911b48e9b379ad8a959..ff6ec70a71f6e591f4c61046efe8cb8ba4347c20 100644 (file)
@@ -1,3 +1,4 @@
+#include <assert.h>
 #include <asprintf/asprintf.h>
 #include <linmath.h/linmath.h>
 #include <stdio.h>
 #include "facedir.h"
 
 typedef struct {
-       bool visible;
-       bool transparent;
-       ModelBatch *batch;
-       ModelBatch *batch_transparent;
-       TerrainChunk *chunk;
-       v3s32 chunkp;
-       TerrainChunk *nbrs[6];
-       bool *tried_nbrs;
-       bool show_edges;
-       bool shown_edges;
+       TerrainChunk *chunk;     // input: chunk pointer
+       TerrainChunkMeta *meta;  // input: coersed chunk metadata pointer
+       v3s32 chunkp;            // input: position of chunk
+       bool animate;            // input: disable edge culling
+       ModelBatch *batch;       // main output: vertex data
+       ModelBatch *batch_blend; // main output: vertex data for transparent textures
+       bool abort;              // output: state changes have occured that invalidate generated output
+       bool grabbed[6];         // output: neighbors that have been grabbed
+       bool visible;            // output: edge culled model would be visible
+       bool remake_needed;      // output: edge culled model would be different from non-culled
 } ChunkRenderData;
 
 static VertexLayout terrain_vertex_layout = {
@@ -50,23 +51,81 @@ static GLint loc_VP;
 static LightShader light_shader;
 static ModelShader model_shader;
 
-static inline bool show_face(NodeType self, NodeType nbr)
+static void grab_neighbor(ChunkRenderData *data, int i)
 {
-       switch (client_node_def[self].visibility) {
-               case VISIBILITY_CLIP:
+       // return if we've already subscribed/grabbed the lock
+       if (data->meta->depends[i])
+               return;
+
+       // we are now subscribed to state changes from the neighbor
+       data->meta->depends[i] = true;
+
+       TerrainChunk *neighbor = data->meta->neighbors[i];
+       TerrainChunkMeta *neighbor_meta = neighbor->extra;
+
+       pthread_rwlock_rdlock(&neighbor_meta->lock_state);
+       // check neighbor in case it was already in a bad state before we subscribed
+       if ((data->grabbed[i] = neighbor_meta->state > CHUNK_STATE_RECV))
+               // if state is good, actually grab the data lock in read mode
+               assert(pthread_rwlock_rdlock(&neighbor->lock) == 0);
+       else
+               // if state is bad, set flag to abort
+               data->abort = true;
+       pthread_rwlock_unlock(&neighbor_meta->lock_state);
+}
+
+static inline bool show_face(ChunkRenderData *data, NodeArgsRender *args, v3s32 offset)
+{
+       NodeVisibility visibility = client_node_def[args->node->type].visibility;
+
+       // always render clip nodes
+       if (visibility == VISIBILITY_CLIP)
+               return data->visible = true;
+
+       // calculate offset of neighbor node from current chunk
+       v3s32 nbr_offset = v3s32_add(offset, facedir[args->f]);
+
+       // if offset is outside bounds, we'll have to select a neighbor chunk
+       bool nbr_chunk =
+               nbr_offset.x < 0 || nbr_offset.x >= CHUNK_SIZE ||
+               nbr_offset.y < 0 || nbr_offset.y >= CHUNK_SIZE ||
+               nbr_offset.z < 0 || nbr_offset.z >= CHUNK_SIZE;
+
+       NodeType nbr_node = NODE_UNKNOWN;
+
+       if (nbr_chunk) {
+               // grab neighbor chunk data lock
+               grab_neighbor(data, args->f);
+
+               // if grabbing failed, return true so caller immediately takes notice of abort
+               if (data->abort)
                        return true;
 
-               case VISIBILITY_BLEND:
-                       return nbr != self;
+               nbr_offset = terrain_offset(nbr_offset);
 
-               case VISIBILITY_SOLID:
-                       return nbr != COUNT_NODE && client_node_def[nbr].visibility != VISIBILITY_SOLID;
+               // select node from neighbor chunk
+               nbr_node = data->meta->neighbors[args->f]->data
+                       [nbr_offset.x][nbr_offset.y][nbr_offset.z].type;
+       } else {
+               // select node from current chunk
+               nbr_node = data->chunk->data[nbr_offset.x][nbr_offset.y][nbr_offset.z].type;
+       }
 
-               default: // impossible
-                       break;
+       if (visibility == VISIBILITY_BLEND) {
+               // liquid nodes only render faces towards 'outsiders'
+               if (nbr_node != args->node->type)
+                       return data->visible = true;
+       } else { // visibility == VISIBILITY_SOLID
+               // faces between two solid nodes are culled
+               if (client_node_def[nbr_node].visibility != VISIBILITY_SOLID)
+                       return data->visible = true;
+
+               // if the chunk needs to be animated, dont cull faces to nodes in other chunks
+               // but remember to rebuild the chunk model after the animation has finished
+               if (nbr_chunk && data->animate)
+                       return data->remake_needed = true;
        }
 
-       // impossible
        return false;
 }
 
@@ -77,6 +136,7 @@ static inline void render_node(ChunkRenderData *data, v3s32 offset)
        args.node = &data->chunk->data[offset.x][offset.y][offset.z];
 
        ClientNodeDef *def = &client_node_def[args.node->type];
+
        if (def->visibility == VISIBILITY_NONE)
                return;
 
@@ -85,44 +145,13 @@ static inline void render_node(ChunkRenderData *data, v3s32 offset)
                args.pos = v3s32_add(offset, data->chunkp);
 
        for (args.f = 0; args.f < 6; args.f++) {
-               v3s32 nbr_offset = v3s32_add(offset, facedir[args.f]);
-
-               TerrainChunk *nbr_chunk;
-
-               if (nbr_offset.z >= 0 && nbr_offset.z < CHUNK_SIZE &&
-                       nbr_offset.x >= 0 && nbr_offset.x < CHUNK_SIZE &&
-                       nbr_offset.y >= 0 && nbr_offset.y < CHUNK_SIZE) {
-                       nbr_chunk = data->chunk;
-               } else if (!(nbr_chunk = data->nbrs[args.f]) && !data->tried_nbrs[args.f]) {
-                       nbr_chunk = data->nbrs[args.f] = terrain_get_chunk(client_terrain,
-                               v3s32_add(data->chunk->pos, facedir[args.f]), false);
-                       data->tried_nbrs[args.f] = true;
-               }
-
-               NodeType nbr_node = COUNT_NODE;
-               if (nbr_chunk)
-                       nbr_node = nbr_chunk->data
-                               [(nbr_offset.x + CHUNK_SIZE) % CHUNK_SIZE]
-                               [(nbr_offset.y + CHUNK_SIZE) % CHUNK_SIZE]
-                               [(nbr_offset.z + CHUNK_SIZE) % CHUNK_SIZE].type;
-
-               bool show = show_face(args.node->type, nbr_node);
-
-               if (show)
-                       data->visible = true;
-               else if (data->show_edges && nbr_chunk != data->chunk && def->visibility == VISIBILITY_SOLID)
-                       data->shown_edges = show = true;
-
-               if (!show)
+               if (!show_face(data, &args, offset))
                        continue;
 
-               ModelBatch *batch = data->batch;
-
-               if (def->visibility == VISIBILITY_BLEND) {
-                       data->transparent = true;
-                       batch = data->batch_transparent;
-               }
+               if (data->abort)
+                       return;
 
+               ModelBatch *batch = def->visibility == VISIBILITY_BLEND ? data->batch_blend : data->batch;
                for (args.v = 0; args.v < 6; args.v++) {
                        args.vertex.cube = cube_vertices[args.f][args.v];
                        args.vertex.cube.position = v3f32_add(args.vertex.cube.position, vertex_offset);
@@ -151,66 +180,35 @@ static void animate_chunk_model(Model *model, f64 dtime)
        if (finished) {
                model->callbacks.step = NULL;
 
-               if (model->extra) {
-                       TerrainChunk *chunk = model->extra;
-
-                       pthread_mutex_lock(&chunk->mtx);
-                       client_terrain_meshgen_task(chunk, false);
-                       pthread_mutex_unlock(&chunk->mtx);
-               }
+               if (model->extra)
+                       client_terrain_meshgen_task(model->extra, false);
        }
 }
 
-static Model *create_chunk_model(TerrainChunk *chunk, bool animate, bool *depends)
+static Model *create_chunk_model(ChunkRenderData *data)
 {
-       ChunkRenderData data = {
-               .visible = false,
-               .transparent = false,
-               .batch = model_batch_create(&model_shader, &terrain_vertex_layout, offsetof(TerrainVertex, textureIndex)),
-               .batch_transparent = model_batch_create(&model_shader, &terrain_vertex_layout, offsetof(TerrainVertex, textureIndex)),
-               .chunk = chunk,
-               .chunkp = v3s32_scale(chunk->pos, CHUNK_SIZE),
-               .nbrs = {NULL},
-               .tried_nbrs = depends,
-               .show_edges = animate,
-               .shown_edges = false,
-       };
-
-       CHUNK_ITERATE
-               render_node(&data, (v3s32) {x, y, z});
-
-       for (int i = 0; i < 6; i++)
-               if (data.nbrs[i])
-                       depends[i] = true;
-
-       if (!data.visible || (!data.batch->textures.siz && !data.batch_transparent->textures.siz)) {
-               model_batch_free(data.batch);
-               model_batch_free(data.batch_transparent);
+       if (!data->visible || (!data->batch->textures.siz && !data->batch_blend->textures.siz))
                return NULL;
-       }
 
        Model *model = model_create();
-       if (data.shown_edges)
-               model->extra = chunk;
+
+       if (data->remake_needed)
+               model->extra = data->chunk;
+
        model->box = (aabb3f32) {
                v3f32_sub((v3f32) {-1.0f, -1.0f, -1.0f}, center_offset),
                v3f32_add((v3f32) {+1.0f, +1.0f, +1.0f}, center_offset)};
-       model->callbacks.step = animate ? &animate_chunk_model : NULL;
+
+       model->callbacks.step = data->animate ? &animate_chunk_model : NULL;
        model->callbacks.delete = &model_free_meshes;
        model->flags.frustum_culling = 1;
-       model->flags.transparent = data.transparent;
+       model->flags.transparent = data->batch_blend->textures.siz > 0;
 
-       model->root->visible = data.visible;
-       model->root->pos = v3f32_add(v3s32_to_f32(data.chunkp), center_offset);
-       model->root->scale = (v3f32) {0.0f, 0.0f, 0.0f};
+       model->root->pos = v3f32_add(v3s32_to_f32(data->chunkp), center_offset);
+       model->root->scale = data->animate ? (v3f32) {0.0f, 0.0f, 0.0f} : (v3f32) {1.0f, 1.0f, 1.0f};
 
-       if (data.visible) {
-               model_node_add_batch(model->root, data.batch);
-               model_node_add_batch(model->root, data.batch_transparent);
-       } else {
-               model_batch_free(data.batch);
-               model_batch_free(data.batch_transparent);
-       }
+       model_node_add_batch(model->root, data->batch);
+       model_node_add_batch(model->root, data->batch_blend);
 
        return model;
 }
@@ -265,40 +263,108 @@ void terrain_gfx_update()
 
 void terrain_gfx_make_chunk_model(TerrainChunk *chunk)
 {
+       // type coersion
        TerrainChunkMeta *meta = chunk->extra;
-       pthread_mutex_lock(&chunk->mtx);
 
-       bool animate;
+       // lock model mutex
+       pthread_mutex_lock(&meta->mtx_model);
+
+       // giving 10 arguments to a function is slow and unmaintainable, use pointer to struct instead
+       ChunkRenderData data = {
+               .chunk = chunk,
+               .meta = meta,
+               .chunkp = v3s32_scale(chunk->pos, CHUNK_SIZE),
+               .animate = false,
+               .batch = model_batch_create(
+                       &model_shader, &terrain_vertex_layout, offsetof(TerrainVertex, textureIndex)),
+               .batch_blend = model_batch_create(
+                       &model_shader, &terrain_vertex_layout, offsetof(TerrainVertex, textureIndex)),
+               .abort = false,
+               .grabbed = {false},
+               .visible = false,
+               .remake_needed = false,
+       };
+
+       //  animate if old animation hasn't finished (or this is the first model)
        if (meta->model)
-               animate = meta->model->callbacks.step ? true : false;
+               data.animate = meta->model->callbacks.step ? true : false;
        else
-               animate = !meta->has_model;
+               data.animate = !meta->has_model;
+
+       // obtain own data lock
+       assert(pthread_rwlock_rdlock(&chunk->lock) == 0);
 
-       pthread_mutex_unlock(&chunk->mtx);
+       // clear dependencies, they are repopulated by calls to grab_neighbor
+       for (int i = 0; i < 6; i++)
+               meta->depends[i] = false;
+
+       // render nodes
+       CHUNK_ITERATE {
+               // obtain changed state
+               pthread_rwlock_rdlock(&meta->lock_state);
+               data.abort = meta->state < CHUNK_STATE_CLEAN;
+               pthread_rwlock_unlock(&meta->lock_state);
+
+               // abort if chunk has been changed
+               // just "break" won't work, the CHUNK_ITERATE macro is a nested loop
+               if (data.abort)
+                       goto abort;
+
+               // put vertex data into batches
+               render_node(&data, (v3s32) {x, y, z});
+
+               // abort if failed to grab a neighbor
+               if (data.abort)
+                       goto abort;
+       }
+       abort:
+
+       // release grabbed data locks
+       for (int i = 0; i < 6; i++)
+               if (data.grabbed[i])
+                       pthread_rwlock_unlock(&meta->neighbors[i]->lock);
 
-       bool depends[6] = {false};
-       Model *model = create_chunk_model(chunk, animate, depends);
+       // release own data lock
+       pthread_rwlock_unlock(&chunk->lock);
 
-       pthread_mutex_lock(&chunk->mtx);
+       // only create model if we didn't abort
+       Model *model = data.abort ? NULL : create_chunk_model(&data);
 
+       // make sure to free batch mem if it wasn't fed into model
+       if (!model) {
+               model_batch_free(data.batch);
+               model_batch_free(data.batch_blend);
+       }
+
+       // abort if chunk changed
+       if (data.abort) {
+               pthread_mutex_unlock(&meta->mtx_model);
+               return;
+       }
+
+       // replace old model
        if (meta->model) {
                if (model) {
+                       // copy animation callback
                        model->callbacks.step = meta->model->callbacks.step;
+                       // copy scale
                        model->root->scale = meta->model->root->scale;
                        model_node_transform(model->root);
                }
 
+               // if old model wasn't drawn in this frame yet, new model will be drawn instead
                meta->model->replace = model;
                meta->model->flags.delete = 1;
        } else if (model) {
+               // model will be drawn in next frame
                model_scene_add(model);
+               model_node_transform(model->root);
        }
 
+       // update pointers
        meta->model = model;
        meta->has_model = true;
 
-       for (int i = 0; i < 6; i++)
-               meta->depends[i] = depends[i];
-
-       pthread_mutex_unlock(&chunk->mtx);
+       // bye bye
+       pthread_mutex_unlock(&meta->mtx_model);
 }