1 /**
2 Copyright: Copyright (c) 2015-2017 Andrey Penechko.
3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
4 Authors: Andrey Penechko.
5 */
6 module voxelman.world.mesh.chunkmeshman;
7 
8 import voxelman.log;
9 import std.datetime : Duration;
10 import std.typecons : Nullable;
11 import anchovy.isharedcontext;
12 
13 import voxelman.geometry.box;
14 import voxelman.math;
15 import voxelman.world.block;
16 import voxelman.world.blockentity;
17 import voxelman.world.mesh.chunkmesh;
18 import voxelman.core.config;
19 import voxelman.world.mesh.meshgen;
20 import voxelman.world.storage;
21 import voxelman.utils.worker;
22 import voxelman.container.hashset;
23 import voxelman.graphics;
24 import voxelman.geometry.cube;
25 
26 
27 alias MeshingPassDoneHandler = void delegate(size_t chunksRemeshed, Duration totalDuration);
28 struct MeshingPass
29 {
30 	size_t chunksToMesh;
31 	size_t meshGroupId;
32 	MeshingPassDoneHandler onDone;
33 	size_t chunksMeshed;
34 	Duration totalDuration;
35 }
36 
37 enum debug_wasted_meshes = true;
38 enum WAIT_FOR_EMPTY_QUEUES = false;
39 
40 //version = DBG;
41 
42 struct MeshGenResult
43 {
44 	MeshGenTaskType type;
45 	ChunkWorldPos cwp;
46 	ChunkMesh[2] meshes;
47 }
48 
49 struct UploadLimiter
50 {
51 	bool limitPreloadSpeed = false;
52 	size_t maxPreloadedMeshesPerFrame = 30;
53 	size_t maxPreloadedVertexesPerFrame = 100_000;
54 
55 	size_t thisFramePreloadedMeshes;
56 	size_t thisFramePreloadedVertexes;
57 
58 	bool frameUploadLimitExceeded()
59 	{
60 		//return thisFramePreloadedMeshes >= maxPreloadedMeshesPerFrame;
61 		return limitPreloadSpeed && thisFramePreloadedVertexes >= maxPreloadedVertexesPerFrame;
62 	}
63 
64 	void onMeshPreloaded(size_t numVertexes)
65 	{
66 		++thisFramePreloadedMeshes;
67 		thisFramePreloadedVertexes += numVertexes;
68 	}
69 
70 	void resetUploadLimits()
71 	{
72 		thisFramePreloadedMeshes = 0;
73 		thisFramePreloadedVertexes = 0;
74 	}
75 }
76 
77 ///
78 struct ChunkMeshMan
79 {
80 	shared WorkerGroup meshWorkers;
81 
82 	ubyte[ChunkWorldPos] wastedMeshes;
83 
84 	ChunkMesh[ChunkWorldPos][2] chunkMeshes;
85 
86 	MeshGenResult[] newChunkMeshes;
87 	MeshingPass[] meshingPasses;
88 	size_t currentMeshGroupId;
89 
90 	size_t numMeshChunkTasks;
91 	size_t totalMeshedChunks;
92 	size_t totalMeshes;
93 	long totalMeshDataBytes;
94 
95 	UploadLimiter uploadLimiter;
96 
97 	ChunkManager chunkManager;
98 	BlockInfoTable blocks;
99 	BlockEntityInfoTable beInfos;
100 	Box delegate(DimensionId) getDimensionBorders;
101 
102 	void init(ChunkManager _chunkManager, BlockInfoTable _blocks, BlockEntityInfoTable _beInfos, uint numMeshWorkers)
103 	{
104 		chunkManager = _chunkManager;
105 		blocks = _blocks;
106 		beInfos = _beInfos;
107 
108 		meshWorkers.startWorkers(numMeshWorkers, &meshWorkerThread, SeparatedBlockInfoTable(blocks), beInfos);
109 	}
110 
111 	void stop()
112 	{
113 		static if (WAIT_FOR_EMPTY_QUEUES)
114 		{
115 			while (!meshWorkers.queuesEmpty())
116 			{
117 				update();
118 			}
119 		}
120 		meshWorkers.stop();
121 	}
122 
123 	// Returns number of chunks sent to be meshed
124 	size_t remeshChangedChunks(HashSet!ChunkWorldPos modifiedChunks,
125 		MeshingPassDoneHandler onDone = null)
126 	{
127 		if (modifiedChunks.length == 0) return 0;
128 
129 		size_t numMeshed;
130 		foreach(cwp; modifiedChunks)
131 		{
132 			if (meshChunk(cwp))
133 				++numMeshed;
134 		}
135 
136 		if (numMeshed == 0) return 0;
137 
138 		meshingPasses ~= MeshingPass(numMeshed, currentMeshGroupId, onDone);
139 		++currentMeshGroupId;
140 
141 		return numMeshed;
142 	}
143 
144 	void update()
145 	{
146 		if (meshingPasses.length == 0) return;
147 
148 		uploadLimiter.resetUploadLimits();
149 
150 		foreach(ref w; meshWorkers.workers)
151 		{
152 			while(!w.resultQueue.empty)
153 			{
154 				bool breakLoop = receiveTaskResult(w);
155 				if (breakLoop) break;
156 			}
157 		}
158 
159 		commitMeshes();
160 	}
161 
162 	bool receiveTaskResult(ref shared Worker w)
163 	{
164 		auto taskHeader = w.resultQueue.peekItem!MeshGenTaskHeader();
165 
166 		// Process only current meshing pass. Leave next passes for later.
167 		if (taskHeader.meshGroupId != meshingPasses[0].meshGroupId)
168 		{
169 			//infof("meshGroup %s != %s", taskHeader.meshGroupId, meshingPasses[0].meshGroupId);
170 			return true;
171 		}
172 
173 		++meshingPasses[0].chunksMeshed;
174 		--numMeshChunkTasks;
175 
176 		w.resultQueue.dropItem!MeshGenTaskHeader();
177 
178 		if (taskHeader.type == MeshGenTaskType.genMesh)
179 		{
180 			MeshVertex[][2] meshes = w.resultQueue.popItem!(MeshVertex[][2])();
181 			uint[27] blockTimestamps = w.resultQueue.popItem!(uint[27])();
182 			uint[27] entityTimestamps = w.resultQueue.popItem!(uint[27])();
183 			uint[27] metadataTimestamps = w.resultQueue.popItem!(uint[27])();
184 			meshingPasses[0].totalDuration += w.resultQueue.popItem!Duration();
185 
186 			// Remove users
187 			auto positions = AdjChunkPositions27(taskHeader.cwp);
188 			foreach(i, pos; positions.all)
189 			{
190 				chunkManager.removeSnapshotUser(pos, blockTimestamps[i], BLOCK_LAYER);
191 				chunkManager.removeSnapshotUser(pos, entityTimestamps[i], ENTITY_LAYER);
192 				chunkManager.removeSnapshotUser(pos, metadataTimestamps[i], METADATA_LAYER);
193 			}
194 
195 			// save result for later. All new meshes are loaded at once to prevent holes in geometry.
196 			auto result = MeshGenResult(taskHeader.type, taskHeader.cwp);
197 			preloadMesh(result, meshes);
198 			newChunkMeshes ~= result;
199 		}
200 		else // taskHeader.type == MeshGenTaskType.unloadMesh
201 		{
202 			// even mesh deletions are saved in a queue.
203 			newChunkMeshes ~= MeshGenResult(taskHeader.type, taskHeader.cwp);
204 		}
205 
206 		if (uploadLimiter.frameUploadLimitExceeded)
207 			return true;
208 
209 		return false;
210 	}
211 
212 	void commitMeshes()
213 	{
214 		import std.algorithm : remove, SwapStrategy;
215 		if (meshingPasses[0].chunksMeshed != meshingPasses[0].chunksToMesh) return;
216 
217 		foreach(ref meshResult; newChunkMeshes)
218 		{
219 			if (meshResult.type == MeshGenTaskType.genMesh)
220 			{
221 				loadMeshData(meshResult);
222 			}
223 			else // taskHeader.type == MeshGenTaskType.unloadMesh
224 			{
225 				unloadChunkMesh(meshResult.cwp);
226 			}
227 			meshResult = MeshGenResult.init;
228 		}
229 		newChunkMeshes.length = 0;
230 		newChunkMeshes.assumeSafeAppend();
231 		if (meshingPasses[0].onDone)
232 			meshingPasses[0].onDone(meshingPasses[0].chunksToMesh, meshingPasses[0].totalDuration);
233 		meshingPasses = remove!(SwapStrategy.stable)(meshingPasses, 0);
234 		meshingPasses.assumeSafeAppend();
235 	}
236 
237 	void drawDebug(ref Batch debugBatch)
238 	{
239 		static if (debug_wasted_meshes)
240 		foreach(cwp; wastedMeshes.byKey)
241 		{
242 			vec3 blockPos = cwp.vector * CHUNK_SIZE;
243 			debugBatch.putCube(blockPos + CHUNK_SIZE/2-1, vec3(4,4,4), Colors.red, false);
244 		}
245 	}
246 
247 	void onChunkRemoved(ChunkWorldPos cwp)
248 	{
249 		unloadChunkMesh(cwp);
250 		static if (debug_wasted_meshes)
251 		{
252 			wastedMeshes.remove(cwp);
253 		}
254 	}
255 
256 	bool producesMesh(
257 		const ref Nullable!ChunkLayerSnap[6] adjacent,
258 		const ref ChunkLayerSnap central)
259 	{
260 		import voxelman.world.block;
261 
262 		Solidity solidity;
263 		bool singleSolidity = hasSingleSolidity(central.metadata, solidity);
264 
265 		if (singleSolidity)
266 		{
267 			// completely transparent chunks do not produce meshes.
268 			if (solidity == Solidity.transparent) {
269 				return false;
270 			} else {
271 				if (central.isUniform)
272 				{
273 					// uniform unknown chunks do not produce mesh.
274 					if (central.getUniform!BlockId == 0)
275 						return false;
276 				}
277 			}
278 
279 			foreach(CubeSide side, adj; adjacent)
280 			{
281 				if (!adj.isNull())
282 				{
283 					Solidity adjSideSolidity = chunkSideSolidity(adj.metadata, oppSide[side]);
284 					if (solidity.isMoreSolidThan(adjSideSolidity)) return true;
285 				}
286 				// otherwise it is unknown blocks, which are solid
287 			}
288 
289 			// uniformly solid chunk is surrounded by blocks with the same of higher solidity.
290 			// so mesh will not be produced.
291 			return false;
292 		}
293 		else
294 		{
295 			// on borders between different solidities mesh is present.
296 			return true;
297 		}
298 	}
299 
300 	// returns true if was sent to mesh
301 	bool meshChunk(ChunkWorldPos cwp)
302 	{
303 		Box dimBorders = getDimensionBorders(cwp.w);
304 		auto snapsPositions = AdjChunkPositions27(cwp);
305 
306 		foreach(pos; snapsPositions.all)
307 		{
308 			if (!chunkManager.isChunkLoaded(pos))
309 			{
310 				if (dimBorders.contains(pos.ivector3))
311 				{
312 					// chunk in dim borders is not loaded
313 					return false;
314 				}
315 			}
316 		}
317 
318 		assert(dimBorders.contains(snapsPositions.central.ivector3));
319 
320 		AdjChunkLayers27 snapsBlocks;
321 
322 		// get compressed layers first to look at metadata.
323 		snapsBlocks.central = chunkManager.getChunkSnapshot(snapsPositions.central, BLOCK_LAYER);
324 		if (snapsBlocks.central.isNull())
325 			return false;
326 		snapsBlocks.adjacent6 = chunkManager.getChunkSnapshots(snapsPositions.adjacent6, BLOCK_LAYER);
327 
328 		++numMeshChunkTasks;
329 
330 		if (!producesMesh(snapsBlocks.adjacent6, snapsBlocks.central))
331 		{
332 			version(DBG) tracef("meshChunk %s produces no mesh", cwp);
333 
334 			// send remove mesh task
335 			with(meshWorkers.nextWorker) {
336 				auto header = MeshGenTaskHeader(MeshGenTaskType.unloadMesh, currentMeshGroupId, cwp);
337 				taskQueue.pushItem(header);
338 				notify();
339 			}
340 
341 			return true;
342 		}
343 
344 		version(DBG) tracef("meshChunk %s", cwp);
345 
346 		// get uncompressed blocks to use for meshing
347 		snapsBlocks.all = chunkManager.getChunkSnapshots(
348 			snapsPositions.all, BLOCK_LAYER, Yes.Uncompress);
349 
350 		AdjChunkLayers27 snapsEntities;
351 		snapsEntities.all = chunkManager.getChunkSnapshots(
352 			snapsPositions.all, ENTITY_LAYER, Yes.Uncompress);
353 
354 		AdjChunkLayers27 snapsMetadatas;
355 		snapsMetadatas.all = chunkManager.getChunkSnapshots(
356 			snapsPositions.all, METADATA_LAYER, Yes.Uncompress);
357 
358 		ChunkLayerItem[27] blockLayers;
359 		ChunkLayerItem[27] entityLayers;
360 		ChunkLayerItem[27] metadataLayers;
361 		foreach(i; 0..27)
362 		{
363 			if (!dimBorders.contains(snapsPositions.all[i].ivector3)) // out-of-border chunk
364 			{
365 				blockLayers[i].metadata = solidity_metadatas[Solidity.solid];
366 			}
367 			else
368 			{
369 				blockLayers[i] = ChunkLayerItem(snapsBlocks.all[i].get(), BLOCK_LAYER);
370 				entityLayers[i] = ChunkLayerItem(snapsEntities.all[i].get(), ENTITY_LAYER);
371 				metadataLayers[i] = ChunkLayerItem(snapsMetadatas.all[i].get(), METADATA_LAYER);
372 			}
373 		}
374 
375 		foreach(i, pos; snapsPositions.all)
376 		{
377 			blockLayers[i].timestamp = chunkManager.addCurrentSnapshotUser(pos, BLOCK_LAYER);
378 			entityLayers[i].timestamp = chunkManager.addCurrentSnapshotUser(pos, ENTITY_LAYER);
379 			metadataLayers[i].timestamp = chunkManager.addCurrentSnapshotUser(pos, METADATA_LAYER);
380 		}
381 
382 		// debug
383 		foreach (i, layer; blockLayers) {
384 			import std..string : format;
385 			assert(layer.type != StorageType.compressedArray, "[MESHING] Data needs to be uncompressed 1");
386 			if (!layer.isUniform) {
387 				auto length = layer.getArray!ubyte.length;
388 				if (length != BLOCKS_DATA_LENGTH) infof("Wrong length of %s: %s", snapsPositions.all[i], length);
389 				assert(length == BLOCKS_DATA_LENGTH, format("Wrong length of %s: %s", snapsPositions.all[i], length));
390 			}
391 		}
392 		foreach (layer; entityLayers)
393 			assert(layer.type != StorageType.compressedArray, "[MESHING] Data needs to be uncompressed 2");
394 		foreach (layer; metadataLayers)
395 			assert(layer.type != StorageType.compressedArray, "[MESHING] Data needs to be uncompressed 3");
396 
397 		// send mesh task
398 		auto header = MeshGenTaskHeader(MeshGenTaskType.genMesh, currentMeshGroupId, cwp);
399 		with(meshWorkers.nextWorker) {
400 			taskQueue.startMessage();
401 			taskQueue.pushMessagePart(header);
402 			taskQueue.pushMessagePart(blockLayers);
403 			taskQueue.pushMessagePart(entityLayers);
404 			taskQueue.pushMessagePart(metadataLayers);
405 			taskQueue.endMessage();
406 			notify();
407 		}
408 
409 		return true;
410 	}
411 
412 	void preloadMesh(ref MeshGenResult result, MeshVertex[][2] meshes)
413 	{
414 		ChunkWorldPos cwp = result.cwp;
415 		if (!chunkManager.isChunkLoaded(cwp))
416 			return;
417 
418 		foreach(i, meshData; meshes)
419 		{
420 			if (meshData.length == 0) continue;
421 			auto mesh = ChunkMesh(vec3(cwp.vector * CHUNK_SIZE));
422 			mesh.uploadMeshData(meshData);
423 			uploadLimiter.onMeshPreloaded(meshData.length);
424 			freeChunkMeshData(meshData);
425 			result.meshes[i] = mesh;
426 		}
427 	}
428 
429 	void loadMeshData(MeshGenResult result)
430 	{
431 		ChunkWorldPos cwp = result.cwp;
432 		if (!chunkManager.isChunkLoaded(cwp))
433 		{
434 			version(DBG) tracef("loadMeshData %s chunk unloaded", cwp);
435 
436 			result.meshes[0].del();
437 			result.meshes[1].del();
438 
439 			return;
440 		}
441 
442 		// Attach mesh
443 		bool hasMesh = false;
444 		foreach(i, mesh; result.meshes)
445 		{
446 			unloadChunkSubmesh(cwp, i);
447 			if (mesh.empty) {
448 				mesh.del;
449 				continue;
450 			}
451 
452 			totalMeshDataBytes += mesh.uploadedBytes;
453 			chunkMeshes[i][cwp] = mesh;
454 			hasMesh = true;
455 		}
456 
457 		++totalMeshedChunks;
458 		if (hasMesh)
459 		{
460 			++totalMeshes;
461 		}
462 		else
463 		{
464 			version(DBG) tracef("loadMeshData %s no mesh", cwp);
465 
466 			static if (debug_wasted_meshes)
467 			{
468 				wastedMeshes[cwp] = 0;
469 			}
470 		}
471 	}
472 
473 	void unloadChunkMesh(ChunkWorldPos cwp)
474 	{
475 		version(DBG) tracef("unloadChunkMesh %s", cwp);
476 		foreach(i; 0..chunkMeshes.length)
477 		{
478 			unloadChunkSubmesh(cwp, i);
479 		}
480 	}
481 
482 	void unloadChunkSubmesh(ChunkWorldPos cwp, size_t index)
483 	{
484 		if (auto mesh = cwp in chunkMeshes[index])
485 		{
486 			totalMeshDataBytes -= mesh.uploadedBytes;
487 			mesh.del();
488 			chunkMeshes[index].remove(cwp);
489 		}
490 	}
491 }