+#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 = {
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;
}
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;
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);
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;
}
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);
}