1 /** 2 Copyright: Copyright (c) 2015-2016 Andrey Penechko. 3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 4 Authors: Andrey Penechko. 5 */ 6 module voxelman.client.chunkmeshman; 7 8 import std.experimental.logger; 9 10 import voxelman.math; 11 import voxelman.block.utils; 12 import voxelman.blockentity.utils; 13 import voxelman.core.chunkmesh; 14 import voxelman.core.config; 15 import voxelman.core.meshgen; 16 import voxelman.world.storage.chunk; 17 import voxelman.world.storage.coordinates; 18 import voxelman.world.storage.chunkmanager; 19 import voxelman.utils.worker; 20 import voxelman.container.hashset; 21 import voxelman.utils.renderutils; 22 23 24 struct MeshingPass 25 { 26 size_t chunksToMesh; 27 size_t meshGroupId; 28 void delegate(size_t chunksRemeshed) onDone; 29 size_t chunksMeshed; 30 } 31 32 enum debug_wasted_meshes = true; 33 enum WAIT_FOR_EMPTY_QUEUES = false; 34 //version = DBG; 35 36 struct MeshGenResult 37 { 38 MeshGenTaskType type; 39 ChunkWorldPos cwp; 40 MeshVertex[][2] meshes; 41 ChunkMesh[2] preloadedMeshes; 42 } 43 44 /// 45 struct ChunkMeshMan 46 { 47 shared WorkerGroup meshWorkers; 48 49 ubyte[ChunkWorldPos] wastedMeshes; 50 51 ChunkMesh[ChunkWorldPos][2] chunkMeshes; 52 53 MeshGenResult[] newChunkMeshes; 54 MeshingPass[] meshingPasses; 55 size_t currentMeshGroupId; 56 57 size_t numMeshChunkTasks; 58 size_t totalMeshedChunks; 59 size_t totalMeshes; 60 long totalMeshDataBytes; 61 62 ChunkManager chunkManager; 63 BlockInfoTable blocks; 64 BlockEntityInfoTable beInfos; 65 66 void init(ChunkManager _chunkManager, BlockInfoTable _blocks, BlockEntityInfoTable _beInfos, uint numMeshWorkers) 67 { 68 chunkManager = _chunkManager; 69 blocks = _blocks; 70 beInfos = _beInfos; 71 meshWorkers.startWorkers(numMeshWorkers, &meshWorkerThread, blocks, beInfos); 72 } 73 74 void stop() 75 { 76 static if (WAIT_FOR_EMPTY_QUEUES) 77 { 78 while (!meshWorkers.queuesEmpty()) 79 { 80 update(); 81 } 82 } 83 meshWorkers.stop(); 84 } 85 86 void remeshChangedChunks(HashSet!ChunkWorldPos modifiedChunks, 87 void delegate(size_t chunksRemeshed) onDone = null) 88 { 89 if (modifiedChunks.length == 0) return; 90 91 size_t numMeshed; 92 foreach(cwp; modifiedChunks.items) 93 { 94 if (meshChunk(cwp)) 95 ++numMeshed; 96 } 97 98 if (numMeshed == 0) return; 99 100 meshingPasses ~= MeshingPass(numMeshed, currentMeshGroupId, onDone); 101 ++currentMeshGroupId; 102 } 103 104 void update() 105 { 106 if (meshingPasses.length == 0) return; 107 108 foreach(ref w; meshWorkers.workers) 109 { 110 while(!w.resultQueue.empty) 111 { 112 bool breakLoop = receiveTaskResult(w); 113 if (breakLoop) break; 114 } 115 } 116 117 commitMeshes(); 118 } 119 120 bool receiveTaskResult(ref shared Worker w) 121 { 122 auto taskHeader = w.resultQueue.peekItem!MeshGenTaskHeader(); 123 124 // Process only current meshing pass. Leave next passes for later. 125 if (taskHeader.meshGroupId != meshingPasses[0].meshGroupId) 126 { 127 //infof("meshGroup %s != %s", taskHeader.meshGroupId, meshingPasses[0].meshGroupId); 128 return true; 129 } 130 131 ++meshingPasses[0].chunksMeshed; 132 --numMeshChunkTasks; 133 134 w.resultQueue.dropItem!MeshGenTaskHeader(); 135 136 if (taskHeader.type == MeshGenTaskType.genMesh) 137 { 138 MeshVertex[][2] meshes = w.resultQueue.popItem!(MeshVertex[][2])(); 139 uint[7] blockTimestamps = w.resultQueue.popItem!(uint[7])(); 140 uint[7] entityTimestamps = w.resultQueue.popItem!(uint[7])(); 141 142 // Remove users 143 ChunkWorldPos[7] positions; 144 positions[0..6] = adjacentPositions(taskHeader.cwp); 145 positions[6] = taskHeader.cwp; 146 foreach(i, pos; positions) 147 { 148 chunkManager.removeSnapshotUser(pos, blockTimestamps[i], FIRST_LAYER); 149 chunkManager.removeSnapshotUser(pos, entityTimestamps[i], ENTITY_LAYER); 150 } 151 152 // save result for later. All new meshes are loaded at once to prevent holes in geometry. 153 auto result = MeshGenResult(taskHeader.type, taskHeader.cwp, meshes); 154 preloadMesh(result); 155 newChunkMeshes ~= result; 156 157 // Remove root, added on chunk load and gen. 158 // Data can be collected by GC if no-one is referencing it. 159 import core.memory : GC; 160 if (meshes[0].ptr) GC.removeRoot(meshes[0].ptr); // TODO remove when moved to non-GC allocator 161 if (meshes[1].ptr) GC.removeRoot(meshes[1].ptr); // 162 } 163 else // taskHeader.type == MeshGenTaskType.unloadMesh 164 { 165 // even mesh deletions are saved in a queue. 166 newChunkMeshes ~= MeshGenResult(taskHeader.type, taskHeader.cwp); 167 } 168 169 return false; 170 } 171 172 void commitMeshes() 173 { 174 import std.algorithm : remove, SwapStrategy; 175 if (meshingPasses[0].chunksMeshed != meshingPasses[0].chunksToMesh) return; 176 177 foreach(ref meshResult; newChunkMeshes) 178 { 179 if (meshResult.type == MeshGenTaskType.genMesh) 180 { 181 loadMeshData(meshResult); 182 } 183 else // taskHeader.type == MeshGenTaskType.unloadMesh 184 { 185 unloadChunkMesh(meshResult.cwp); 186 } 187 meshResult = MeshGenResult.init; 188 } 189 newChunkMeshes.length = 0; 190 newChunkMeshes.assumeSafeAppend(); 191 if (meshingPasses[0].onDone) 192 meshingPasses[0].onDone(meshingPasses[0].chunksToMesh); 193 meshingPasses = remove!(SwapStrategy.stable)(meshingPasses, 0); 194 meshingPasses.assumeSafeAppend(); 195 } 196 197 void drawDebug(ref Batch debugBatch) 198 { 199 static if (debug_wasted_meshes) 200 foreach(cwp; wastedMeshes.byKey) 201 { 202 vec3 blockPos = cwp.vector * CHUNK_SIZE; 203 debugBatch.putCube(blockPos + CHUNK_SIZE/2-1, vec3(4,4,4), Colors.red, false); 204 } 205 } 206 207 void onChunkRemoved(ChunkWorldPos cwp) 208 { 209 unloadChunkMesh(cwp); 210 static if (debug_wasted_meshes) 211 { 212 wastedMeshes.remove(cwp); 213 } 214 } 215 216 bool producesMesh(ChunkSnapWithAdjacent snapWithAdjacent) 217 { 218 import voxelman.block.utils; 219 220 Solidity solidity; 221 bool singleSolidity = hasSingleSolidity(snapWithAdjacent.centralSnapshot.metadata, solidity); 222 223 if (singleSolidity) 224 { 225 // completely transparent chunks do not produce meshes. 226 if (solidity == Solidity.transparent) { 227 return false; 228 } 229 230 foreach(Side side, adj; snapWithAdjacent.adjacentSnapshots) 231 { 232 Solidity adjSideSolidity = chunkSideSolidity(adj.metadata, oppSide[side]); 233 if (solidity.isMoreSolidThan(adjSideSolidity)) return true; 234 } 235 236 // uniformly solid chunk is surrounded by blocks with the same of higher solidity. 237 // so mesh will not be produced. 238 return false; 239 } 240 else 241 { 242 // on borders between different solidities mesh is present. 243 return true; 244 } 245 } 246 247 // returns true if was sent to mesh 248 bool meshChunk(ChunkWorldPos cwp) 249 { 250 ChunkSnapWithAdjacent snapWithAdjacentBlocks = chunkManager.getSnapWithAdjacent(cwp, FIRST_LAYER); 251 ChunkSnapWithAdjacent snapWithAdjacentEntities = chunkManager.getSnapWithAdjacent(cwp, ENTITY_LAYER); 252 253 if (!snapWithAdjacentBlocks.allLoaded) 254 { 255 version(DBG) tracef("meshChunk %s !allLoaded", cwp); 256 return false; 257 } 258 259 ++numMeshChunkTasks; 260 261 if (!producesMesh(snapWithAdjacentBlocks)) 262 { 263 version(DBG) tracef("meshChunk %s produces no mesh", cwp); 264 265 // send remove mesh task 266 with(meshWorkers.nextWorker) { 267 auto header = MeshGenTaskHeader(MeshGenTaskType.unloadMesh, currentMeshGroupId, cwp); 268 taskQueue.pushItem(header); 269 notify(); 270 } 271 272 //unloadChunkMesh(cwp); 273 return true; 274 } 275 276 version(DBG) tracef("meshChunk %s", cwp); 277 278 foreach(pos; snapWithAdjacentBlocks.positions) 279 { 280 chunkManager.addCurrentSnapshotUser(pos, FIRST_LAYER); 281 chunkManager.addCurrentSnapshotUser(pos, ENTITY_LAYER); 282 } 283 284 ChunkLayerItem[7] blockLayers; 285 ChunkLayerItem[7] entityLayers; 286 foreach(i; 0..7) 287 { 288 blockLayers[i] = ChunkLayerItem(snapWithAdjacentBlocks.snapshots[i].get(), FIRST_LAYER); 289 entityLayers[i] = ChunkLayerItem(snapWithAdjacentEntities.snapshots[i].get(), ENTITY_LAYER); 290 } 291 292 // send mesh task 293 auto header = MeshGenTaskHeader(MeshGenTaskType.genMesh, currentMeshGroupId, cwp); 294 with(meshWorkers.nextWorker) { 295 taskQueue.startMessage(); 296 taskQueue.pushMessagePart(header); 297 taskQueue.pushMessagePart(blockLayers); 298 taskQueue.pushMessagePart(entityLayers); 299 taskQueue.endMessage(); 300 notify(); 301 } 302 303 return true; 304 } 305 306 void preloadMesh(ref MeshGenResult result) 307 { 308 ChunkWorldPos cwp = result.cwp; 309 if (!chunkManager.isChunkLoaded(cwp)) 310 return; 311 312 foreach(i, meshData; result.meshes) 313 { 314 if (meshData.length == 0) continue; 315 auto mesh = ChunkMesh(vec3(cwp.vector * CHUNK_SIZE), cwp.w, meshData); 316 317 mesh.bind; 318 mesh.uploadBuffer; 319 mesh.unbind; 320 321 result.preloadedMeshes[i] = mesh; 322 } 323 } 324 325 void loadMeshData(MeshGenResult result) 326 { 327 ChunkWorldPos cwp = result.cwp; 328 if (!chunkManager.isChunkLoaded(cwp)) 329 { 330 import core.memory : GC; 331 332 version(DBG) tracef("loadMeshData %s chunk unloaded", cwp); 333 result.preloadedMeshes[0].deleteBuffers(); 334 GC.free(result.preloadedMeshes[0].data.ptr); 335 result.preloadedMeshes[1].deleteBuffers(); 336 GC.free(result.preloadedMeshes[1].data.ptr); 337 return; 338 } 339 340 // Attach mesh 341 bool hasMesh = false; 342 foreach(i, chunkMesh; result.preloadedMeshes) 343 { 344 unloadChunkSubmesh(cwp, i); 345 if (chunkMesh.empty) { 346 continue; 347 } 348 349 totalMeshDataBytes += chunkMesh.dataBytes; 350 chunkMeshes[i][cwp] = result.preloadedMeshes[i]; 351 hasMesh = true; 352 } 353 354 ++totalMeshedChunks; 355 if (hasMesh) 356 { 357 ++totalMeshes; 358 } 359 else 360 { 361 version(DBG) tracef("loadMeshData %s no mesh", cwp); 362 363 static if (debug_wasted_meshes) 364 { 365 wastedMeshes[cwp] = 0; 366 } 367 } 368 } 369 370 void unloadChunkMesh(ChunkWorldPos cwp) 371 { 372 version(DBG) tracef("unloadChunkMesh %s", cwp); 373 foreach(i; 0..chunkMeshes.length) 374 { 375 unloadChunkSubmesh(cwp, i); 376 } 377 } 378 379 void unloadChunkSubmesh(ChunkWorldPos cwp, size_t index) 380 { 381 if (auto mesh = cwp in chunkMeshes[index]) 382 { 383 import core.memory : GC; 384 totalMeshDataBytes -= mesh.dataBytes; 385 mesh.deleteBuffers(); 386 GC.free(mesh.data.ptr); 387 chunkMeshes[index].remove(cwp); 388 } 389 } 390 }