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 }