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