Skip to content

Commit 3d98f3b

Browse files
committed
Improve chunk connectivity graph
- Store which directions a chunk has been visited from so there aren't repeat visits. - Use the traversed directions of the queue data so they don't travel backwards when visiting new chunks. - Fix a bug that caused some chunks to not be rendered. - Fix some array access patterns to be uniform with the rest of chunk index order.
1 parent 83622ce commit 3d98f3b

File tree

4 files changed

+89
-47
lines changed

4 files changed

+89
-47
lines changed

polymer/platform/unix/unix_main.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ int main(int argc, char* argv[]) {
303303
}
304304

305305
constexpr size_t kPermanentSize = Gigabytes(1);
306-
constexpr size_t kTransientSize = Megabytes(32);
306+
constexpr size_t kTransientSize = Megabytes(256);
307307

308308
u8* perm_memory = (u8*)malloc(kPermanentSize);
309309
u8* trans_memory = (u8*)malloc(kTransientSize);

polymer/platform/win32/win32_main.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,7 @@ int main(int argc, char* argv[]) {
360360
}
361361

362362
constexpr size_t kPermanentSize = Gigabytes(1);
363-
constexpr size_t kTransientSize = Megabytes(64);
363+
constexpr size_t kTransientSize = Megabytes(256);
364364

365365
u8* perm_memory = (u8*)VirtualAlloc(NULL, kPermanentSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
366366
u8* trans_memory = (u8*)VirtualAlloc(NULL, kTransientSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

polymer/world/chunk.cpp

Lines changed: 85 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -42,20 +42,41 @@ void ChunkConnectivityGraph::Update(MemoryArena& trans_arena, World& world, cons
4242
s32 chunk_y;
4343
s32 chunk_z;
4444
u16 from;
45-
u16 dirs;
45+
u16 traversed;
4646
};
4747

48+
// Record the visit state for each chunk, so they are only visited once from each direction
49+
struct VisitState {
50+
u8 directions;
51+
52+
inline bool CanVisit(BlockFace through_face) const {
53+
return !(directions & (1 << (u8)through_face));
54+
}
55+
56+
inline void VisitThrough(BlockFace through_face) {
57+
directions |= (1 << (u8)through_face);
58+
}
59+
};
60+
61+
constexpr size_t kVisitStateCount = kChunkCacheSize * kChunkCacheSize * kChunkColumnCount;
62+
63+
VisitState* visit_states = memory_arena_push_type_count(&trans_arena, VisitState, kVisitStateCount);
64+
memset(visit_states, 0, sizeof(VisitState) * kVisitStateCount);
65+
4866
ProcessChunk* process_queue = memory_arena_push_type(&trans_arena, ProcessChunk);
4967
size_t process_queue_size = 0;
5068

51-
process_queue[process_queue_size++] = {start_chunk->chunk_x, start_chunk->chunk_y, start_chunk->chunk_z,
52-
(u16)BlockFace::Down, (u16)0};
69+
process_queue[process_queue_size++] = {start_chunk->chunk_x, start_chunk->chunk_y, start_chunk->chunk_z, (u16)0xFF,
70+
0};
5371

5472
Frustum frustum = camera.GetViewFrustum();
5573

56-
size_t process_index = 0;
57-
while (process_index < process_queue_size) {
58-
ProcessChunk process_chunk = process_queue[process_index];
74+
size_t largest_queue_size = 1;
75+
76+
while (process_queue_size > 0) {
77+
// Pop front of queue and replace it with the last item so it doesn't grow forever.
78+
ProcessChunk process_chunk = process_queue[0];
79+
process_queue[0] = process_queue[--process_queue_size];
5980

6081
size_t x_index = world::GetChunkCacheIndex(process_chunk.chunk_x);
6182
size_t z_index = world::GetChunkCacheIndex(process_chunk.chunk_z);
@@ -64,7 +85,11 @@ void ChunkConnectivityGraph::Update(MemoryArena& trans_arena, World& world, cons
6485
ChunkSectionInfo& info = world.chunk_infos[z_index][x_index];
6586

6687
if (info.bitmask & (1 << process_chunk.chunk_y)) {
67-
if (info.dirty_connectivity_set & (1 << process_chunk.chunk_y)) {
88+
// We are a non-empty chunk, so check if we are dirty.
89+
bool is_dirty = info.dirty_connectivity_set & (1 << process_chunk.chunk_y);
90+
bool is_loaded = world.chunks[z_index][x_index].chunks[process_chunk.chunk_y];
91+
92+
if (is_dirty && is_loaded) {
6893
connect_set.Build(world, *world.chunks[z_index][x_index].chunks[process_chunk.chunk_y]);
6994
info.dirty_connectivity_set &= ~(1 << process_chunk.chunk_y);
7095
}
@@ -75,6 +100,11 @@ void ChunkConnectivityGraph::Update(MemoryArena& trans_arena, World& world, cons
75100
for (size_t i = 0; i < 6; ++i) {
76101
BlockFace through_face = (BlockFace)i;
77102
ChunkOffset offset = kOffsets[i];
103+
BlockFace opposite_face = GetOppositeFace(through_face);
104+
105+
// Each processed child can only go in one direction along each axis, so check if the opposite side has been
106+
// traversed.
107+
if (process_chunk.traversed & (1 << (u16)opposite_face)) continue;
78108

79109
s32 chunk_x = process_chunk.chunk_x + offset.x;
80110
s32 chunk_y = process_chunk.chunk_y + offset.y;
@@ -85,42 +115,54 @@ void ChunkConnectivityGraph::Update(MemoryArena& trans_arena, World& world, cons
85115
size_t new_x_index = world::GetChunkCacheIndex(chunk_x);
86116
size_t new_z_index = world::GetChunkCacheIndex(chunk_z);
87117

88-
if (!world.chunk_infos[new_z_index][new_x_index].loaded) continue;
118+
VisitState* visit_state =
119+
visit_states + chunk_y * kChunkCacheSize * kChunkCacheSize + new_z_index * kChunkCacheSize + new_x_index;
120+
if (!visit_state->CanVisit(through_face)) continue;
121+
122+
ChunkSectionInfo& new_info = world.chunk_infos[new_z_index][new_x_index];
123+
124+
if (!new_info.loaded) continue;
89125

90-
if ((process_index == 0 && connect_set.HasFaceConnectivity(through_face)) ||
91-
connect_set.IsConnected((BlockFace)process_chunk.from, through_face)) {
92-
BlockFace from = GetOppositeFace(through_face);
126+
// Always travel through camera-connected chunks.
127+
bool is_camera_connected = (process_chunk.from == 0xFF && connect_set.HasFaceConnectivity(through_face));
128+
// If we can go from the 'from' side that reached here to the new through-side, then we might be able to see
129+
// through this chunk.
130+
bool visibility_potential =
131+
process_chunk.from != 0xFF && connect_set.IsConnected(through_face, (BlockFace)process_chunk.from);
93132

94-
size_t view_index = (size_t)new_z_index * kChunkCacheSize * kChunkColumnCount +
95-
(size_t)new_x_index * kChunkColumnCount + chunk_y;
96-
if (!view_set.test(view_index)) {
97-
view_set.set(view_index);
133+
if (is_camera_connected || visibility_potential) {
134+
Vector3f chunk_min(chunk_x * 16.0f, chunk_y * 16.0f - 64.0f, chunk_z * 16.0f);
135+
Vector3f chunk_max(chunk_x * 16.0f + 16.0f, chunk_y * 16.0f - 48.0f, chunk_z * 16.0f + 16.0f);
98136

99-
Vector3f chunk_min(chunk_x * 16.0f, chunk_y * 16.0f - 64.0f, chunk_z * 16.0f);
100-
Vector3f chunk_max(chunk_x * 16.0f + 16.0f, chunk_y * 16.0f - 48.0f, chunk_z * 16.0f + 16.0f);
137+
if (frustum.Intersects(chunk_min, chunk_max)) {
138+
size_t view_index = (size_t)new_z_index * kChunkCacheSize * kChunkColumnCount +
139+
(size_t)new_x_index * kChunkColumnCount + chunk_y;
140+
141+
// Only add each chunk to the visibility set once.
142+
if (!view_set.test(view_index)) {
143+
view_set.set(view_index);
101144

102-
if (frustum.Intersects(chunk_min, chunk_max)) {
103145
if (world.chunk_infos[new_z_index][new_x_index].bitmask & (1 << chunk_y)) {
104146
VisibleChunk* next_chunk = visible_set + visible_count++;
105147

106148
next_chunk->chunk_x = chunk_x;
107149
next_chunk->chunk_y = chunk_y;
108150
next_chunk->chunk_z = chunk_z;
109151
}
152+
}
110153

111-
BlockFace opposite_face = GetOppositeFace(through_face);
112-
113-
if (!(process_chunk.dirs & (1 << (u16)opposite_face))) {
114-
trans_arena.Allocate(sizeof(ProcessChunk), 1);
115-
process_queue[process_queue_size++] = {chunk_x, chunk_y, chunk_z, (u16)from,
116-
(u16)(process_chunk.dirs | (1 << i))};
117-
}
154+
if (process_queue_size >= largest_queue_size) {
155+
largest_queue_size = process_queue_size + 1;
156+
trans_arena.Allocate(sizeof(ProcessChunk), 1);
118157
}
158+
159+
u16 new_traversed = process_chunk.traversed | (1 << (u16)through_face);
160+
process_queue[process_queue_size++] = {chunk_x, chunk_y, chunk_z, (u16)opposite_face, new_traversed};
161+
162+
visit_state->VisitThrough(through_face);
119163
}
120164
}
121165
}
122-
123-
++process_index;
124166
}
125167
}
126168

@@ -155,7 +197,7 @@ bool ChunkConnectivitySet::Build(const World& world, const Chunk& chunk) {
155197

156198
if (!(model.element_count == 0 || model.HasTransparency())) continue;
157199

158-
if (!visited.test((size_t)z * 16 * 16 + (size_t)y * 16 + (size_t)x)) {
200+
if (!visited.test((size_t)y * 16 * 16 + (size_t)z * 16 + (size_t)x)) {
159201
u8 current_set = FloodFill(world, chunk, visited, queue, x, y, z);
160202

161203
for (size_t i = 0; i < 6; ++i) {
@@ -188,7 +230,7 @@ u8 ChunkConnectivitySet::FloodFill(const World& world, const Chunk& chunk, Visit
188230

189231
u8 current_set = 0;
190232

191-
queue_set.set((size_t)(start_z) * 16 * 16 + (size_t)(start_y) * 16 + (size_t)(start_x));
233+
queue_set.set((size_t)(start_y) * 16 * 16 + (size_t)(start_z) * 16 + (size_t)(start_x));
192234

193235
while (queue_index < queue_count) {
194236
s8 x = queue[queue_index].x;
@@ -224,37 +266,37 @@ u8 ChunkConnectivitySet::FloodFill(const World& world, const Chunk& chunk, Visit
224266
}
225267
}
226268

227-
if (!visited.test((size_t)z * 16 * 16 + (size_t)y * 16 + (size_t)x)) {
228-
visited.set((size_t)z * 16 * 16 + (size_t)y * 16 + (size_t)x);
269+
if (!visited.test((size_t)y * 16 * 16 + (size_t)z * 16 + (size_t)x)) {
270+
visited.set((size_t)y * 16 * 16 + (size_t)z * 16 + (size_t)x);
229271

230-
if (x > 0 && !queue_set.test((size_t)(z) * 16 * 16 + (size_t)(y) * 16 + (size_t)(x - 1))) {
272+
if (x > 0 && !queue_set.test((size_t)(y) * 16 * 16 + (size_t)(z) * 16 + (size_t)(x - 1))) {
231273
queue[queue_count++] = {(s8)(x - 1), y, z};
232-
queue_set.set((size_t)(z) * 16 * 16 + (size_t)(y) * 16 + (size_t)(x - 1));
274+
queue_set.set((size_t)(y) * 16 * 16 + (size_t)(z) * 16 + (size_t)(x - 1));
233275
}
234276

235-
if (x < 15 && !queue_set.test((size_t)(z) * 16 * 16 + (size_t)(y) * 16 + (size_t)(x + 1))) {
277+
if (x < 15 && !queue_set.test((size_t)(y) * 16 * 16 + (size_t)(z) * 16 + (size_t)(x + 1))) {
236278
queue[queue_count++] = {(s8)(x + 1), y, z};
237-
queue_set.set((size_t)(z) * 16 * 16 + (size_t)(y) * 16 + (size_t)(x + 1));
279+
queue_set.set((size_t)(y) * 16 * 16 + (size_t)(z) * 16 + (size_t)(x + 1));
238280
}
239281

240-
if (y > 0 && !queue_set.test((size_t)(z) * 16 * 16 + (size_t)(y - 1) * 16 + (size_t)(x))) {
282+
if (y > 0 && !queue_set.test((size_t)(y - 1) * 16 * 16 + (size_t)(z) * 16 + (size_t)(x))) {
241283
queue[queue_count++] = {x, (s8)(y - 1), z};
242-
queue_set.set((size_t)(z) * 16 * 16 + (size_t)(y - 1) * 16 + (size_t)(x));
284+
queue_set.set((size_t)(y - 1) * 16 * 16 + (size_t)(z) * 16 + (size_t)(x));
243285
}
244286

245-
if (y < 15 && !queue_set.test((size_t)(z) * 16 * 16 + (size_t)(y + 1) * 16 + (size_t)(x))) {
287+
if (y < 15 && !queue_set.test((size_t)(y + 1) * 16 * 16 + (size_t)(z) * 16 + (size_t)(x))) {
246288
queue[queue_count++] = {x, (s8)(y + 1), z};
247-
queue_set.set((size_t)(z) * 16 * 16 + (size_t)(y + 1) * 16 + (size_t)(x));
289+
queue_set.set((size_t)(y + 1) * 16 * 16 + (size_t)(z) * 16 + (size_t)(x));
248290
}
249291

250-
if (z > 0 && !queue_set.test((size_t)(z - 1) * 16 * 16 + (size_t)(y) * 16 + (size_t)(x))) {
292+
if (z > 0 && !queue_set.test((size_t)(y) * 16 * 16 + (size_t)(z - 1) * 16 + (size_t)(x))) {
251293
queue[queue_count++] = {x, y, (s8)(z - 1)};
252-
queue_set.set((size_t)(z - 1) * 16 * 16 + (size_t)(y) * 16 + (size_t)(x));
294+
queue_set.set((size_t)(y) * 16 * 16 + (size_t)(z - 1) * 16 + (size_t)(x));
253295
}
254296

255-
if (z < 15 && !queue_set.test((size_t)(z + 1) * 16 * 16 + (size_t)(y) * 16 + (size_t)(x))) {
297+
if (z < 15 && !queue_set.test((size_t)(y) * 16 * 16 + (size_t)(z + 1) * 16 + (size_t)(x))) {
256298
queue[queue_count++] = {x, y, (s8)(z + 1)};
257-
queue_set.set((size_t)(z + 1) * 16 * 16 + (size_t)(y) * 16 + (size_t)(x));
299+
queue_set.set((size_t)(y) * 16 * 16 + (size_t)(z + 1) * 16 + (size_t)(x));
258300
}
259301
}
260302
}

polymer/world/world.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ void World::OnChunkUnload(s32 chunk_x, s32 chunk_z) {
150150

151151
section_info->loaded = false;
152152
section_info->bitmask = 0;
153-
section_info->dirty_connectivity_set = 0;
153+
section_info->dirty_connectivity_set = 0xFFFFFF;
154154
section_info->dirty_mesh_set = 0;
155155

156156
for (s32 chunk_y = 0; chunk_y < kChunkColumnCount; ++chunk_y) {
@@ -183,7 +183,7 @@ void World::OnDimensionChange() {
183183
ChunkMesh* meshes = this->meshes[chunk_z][chunk_x];
184184

185185
section_info->loaded = false;
186-
section_info->dirty_connectivity_set = 0;
186+
section_info->dirty_connectivity_set = 0xFFFFFF;
187187
section_info->dirty_mesh_set = 0;
188188
section_info->bitmask = 0;
189189

0 commit comments

Comments
 (0)