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 }