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