]> git.lizzy.rs Git - minetest.git/blob - src/client/mesh_generator_thread.cpp
8x block meshes (#13133)
[minetest.git] / src / client / mesh_generator_thread.cpp
1 /*
2 Minetest
3 Copyright (C) 2013, 2017 celeron55, Perttu Ahola <celeron55@gmail.com>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include "mesh_generator_thread.h"
21 #include "settings.h"
22 #include "profiler.h"
23 #include "client.h"
24 #include "mapblock.h"
25 #include "map.h"
26 #include "util/directiontables.h"
27
28 static class BlockPlaceholder {
29 public:
30         MapNode data[MAP_BLOCKSIZE * MAP_BLOCKSIZE * MAP_BLOCKSIZE];
31
32         BlockPlaceholder()
33         {
34                 for (std::size_t i = 0; i < MAP_BLOCKSIZE * MAP_BLOCKSIZE * MAP_BLOCKSIZE; i++)
35                         data[i] = MapNode(CONTENT_IGNORE);
36         }
37
38 } block_placeholder;
39 /*
40         QueuedMeshUpdate
41 */
42
43 QueuedMeshUpdate::~QueuedMeshUpdate()
44 {
45         delete data;
46 }
47
48 /*
49         MeshUpdateQueue
50 */
51
52 MeshUpdateQueue::MeshUpdateQueue(Client *client):
53         m_client(client)
54 {
55         m_cache_enable_shaders = g_settings->getBool("enable_shaders");
56         m_cache_smooth_lighting = g_settings->getBool("smooth_lighting");
57         m_meshgen_block_cache_size = g_settings->getS32("meshgen_block_cache_size");
58 }
59
60 MeshUpdateQueue::~MeshUpdateQueue()
61 {
62         MutexAutoLock lock(m_mutex);
63
64         for (QueuedMeshUpdate *q : m_queue) {
65                 for (auto block : q->map_blocks)
66                         if (block)
67                                 block->refDrop();
68                 delete q;
69         }
70 }
71
72 bool MeshUpdateQueue::addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool urgent)
73 {
74         MapBlock *main_block = map->getBlockNoCreateNoEx(p);
75         if (!main_block)
76                 return false;
77
78         MutexAutoLock lock(m_mutex);
79
80         // Mesh is placed at even positions at all coordinates
81         // (every 8-th block) and will cover 8 blocks
82         v3s16 mesh_position(p.X & ~1, p.Y & ~1, p.Z & ~1);
83         /*
84                 Mark the block as urgent if requested
85         */
86         if (urgent)
87                 m_urgents.insert(mesh_position);
88
89         /*
90                 Find if block is already in queue.
91                 If it is, update the data and quit.
92         */
93         for (QueuedMeshUpdate *q : m_queue) {
94                 if (q->p == mesh_position) {
95                         // NOTE: We are not adding a new position to the queue, thus
96                         //       refcount_from_queue stays the same.
97                         if(ack_block_to_server)
98                                 q->ack_list.push_back(p);
99                         q->crack_level = m_client->getCrackLevel();
100                         q->crack_pos = m_client->getCrackPos();
101                         q->urgent |= urgent;
102                         for (std::size_t i = 0; i < q->map_blocks.size(); i++) {
103                                 if (!q->map_blocks[i]) {
104                                         MapBlock *block = map->getBlockNoCreateNoEx(q->p + g_64dirs[i]);
105                                         if (block) {
106                                                 block->refGrab();
107                                                 q->map_blocks[i] = block;
108                                         }
109                                 }
110                         }
111                         return true;
112                 }
113         }
114
115         /*
116                 Make a list of blocks necessary for mesh generation and lock the blocks in memory.
117         */
118         std::vector<MapBlock *> map_blocks;
119         map_blocks.reserve(4*4*4);
120         for (v3s16 dp : g_64dirs) {
121                 MapBlock *block = map->getBlockNoCreateNoEx(mesh_position + dp);
122                 map_blocks.push_back(block);
123                 if (block)
124                         block->refGrab();
125         }
126
127         /*
128                 Add the block
129         */
130         QueuedMeshUpdate *q = new QueuedMeshUpdate;
131         q->p = mesh_position;
132         if(ack_block_to_server)
133                 q->ack_list.push_back(p);
134         q->crack_level = m_client->getCrackLevel();
135         q->crack_pos = m_client->getCrackPos();
136         q->urgent = urgent;
137         q->map_blocks = std::move(map_blocks);
138         m_queue.push_back(q);
139
140         return true;
141 }
142
143 // Returned pointer must be deleted
144 // Returns NULL if queue is empty
145 QueuedMeshUpdate *MeshUpdateQueue::pop()
146 {
147         QueuedMeshUpdate *result = NULL;
148         {
149                 MutexAutoLock lock(m_mutex);
150
151                 bool must_be_urgent = !m_urgents.empty();
152                 for (std::vector<QueuedMeshUpdate*>::iterator i = m_queue.begin();
153                                 i != m_queue.end(); ++i) {
154                         QueuedMeshUpdate *q = *i;
155                         if (must_be_urgent && m_urgents.count(q->p) == 0)
156                                 continue;
157                         // Make sure no two threads are processing the same mapblock, as that causes racing conditions
158                         if (m_inflight_blocks.find(q->p) != m_inflight_blocks.end())
159                                 continue;
160                         m_queue.erase(i);
161                         m_urgents.erase(q->p);
162                         m_inflight_blocks.insert(q->p);
163                         result = q;
164                         break;
165                 }
166         }
167
168         if (result)
169                 fillDataFromMapBlocks(result);
170
171         return result;
172 }
173
174 void MeshUpdateQueue::done(v3s16 pos)
175 {
176         MutexAutoLock lock(m_mutex);
177         m_inflight_blocks.erase(pos);
178 }
179
180
181 void MeshUpdateQueue::fillDataFromMapBlocks(QueuedMeshUpdate *q)
182 {
183         MeshMakeData *data = new MeshMakeData(m_client, m_cache_enable_shaders);
184         q->data = data;
185         data->side_length = 2 * MAP_BLOCKSIZE;
186
187         data->fillBlockDataBegin(q->p);
188
189         for (std::size_t i = 0; i < 64; i++) {
190                 MapBlock *block = q->map_blocks[i];
191                 data->fillBlockData(g_64dirs[i], block ? block->getData() : block_placeholder.data);
192         }
193
194         data->setCrack(q->crack_level, q->crack_pos);
195         data->setSmoothLighting(m_cache_smooth_lighting);
196 }
197
198 /*
199         MeshUpdateWorkerThread
200 */
201
202 MeshUpdateWorkerThread::MeshUpdateWorkerThread(MeshUpdateQueue *queue_in, MeshUpdateManager *manager, v3s16 *camera_offset) :
203                 UpdateThread("Mesh"), m_queue_in(queue_in), m_manager(manager), m_camera_offset(camera_offset)
204 {
205         m_generation_interval = g_settings->getU16("mesh_generation_interval");
206         m_generation_interval = rangelim(m_generation_interval, 0, 50);
207 }
208
209 void MeshUpdateWorkerThread::doUpdate()
210 {
211         QueuedMeshUpdate *q;
212         while ((q = m_queue_in->pop())) {
213                 if (m_generation_interval)
214                         sleep_ms(m_generation_interval);
215                 ScopeProfiler sp(g_profiler, "Client: Mesh making (sum)");
216
217                 MapBlockMesh *mesh_new = new MapBlockMesh(q->data, *m_camera_offset);
218
219                 
220
221                 MeshUpdateResult r;
222                 r.p = q->p;
223                 r.mesh = mesh_new;
224                 r.solid_sides = get_solid_sides(q->data);
225                 r.ack_list = std::move(q->ack_list);
226                 r.urgent = q->urgent;
227                 r.map_blocks = q->map_blocks;
228
229                 m_manager->putResult(r);
230                 m_queue_in->done(q->p);
231                 delete q;
232         }
233 }
234
235 /*
236         MeshUpdateManager
237 */
238
239 MeshUpdateManager::MeshUpdateManager(Client *client):
240         m_queue_in(client)
241 {
242         int number_of_threads = rangelim(g_settings->getS32("mesh_generation_threads"), 0, 8);
243
244         // Automatically use 33% of the system cores for mesh generation, max 4
245         if (number_of_threads == 0)
246                 number_of_threads = MYMIN(4, Thread::getNumberOfProcessors() / 3);
247         
248         // use at least one thread
249         number_of_threads = MYMAX(1, number_of_threads);
250         infostream << "MeshUpdateManager: using " << number_of_threads << " threads" << std::endl;
251
252         for (int i = 0; i < number_of_threads; i++)
253                 m_workers.push_back(std::make_unique<MeshUpdateWorkerThread>(&m_queue_in, this, &m_camera_offset));
254 }
255
256 void MeshUpdateManager::updateBlock(Map *map, v3s16 p, bool ack_block_to_server,
257                 bool urgent, bool update_neighbors)
258 {
259         static thread_local const bool many_neighbors =
260                         g_settings->getBool("smooth_lighting")
261                         && !g_settings->getFlag("performance_tradeoffs");
262         if (!m_queue_in.addBlock(map, p, ack_block_to_server, urgent)) {
263                 warningstream << "Update requested for non-existent block at ("
264                                 << p.X << ", " << p.Y << ", " << p.Z << ")" << std::endl;
265                 return;
266         }
267         if (update_neighbors) {
268                 if (many_neighbors) {
269                         for (v3s16 dp : g_26dirs)
270                                 m_queue_in.addBlock(map, p + dp, false, urgent);
271                 } else {
272                         for (v3s16 dp : g_6dirs)
273                                 m_queue_in.addBlock(map, p + dp, false, urgent);
274                 }
275         }
276         deferUpdate();
277 }
278
279 void MeshUpdateManager::putResult(const MeshUpdateResult &result)
280 {
281         if (result.urgent)
282                 m_queue_out_urgent.push_back(result);
283         else
284                 m_queue_out.push_back(result);
285 }
286
287 bool MeshUpdateManager::getNextResult(MeshUpdateResult &r)
288 {
289         if (!m_queue_out_urgent.empty()) {
290                 r = m_queue_out_urgent.pop_frontNoEx();
291                 return true;
292         }
293
294         if (!m_queue_out.empty()) {
295                 r = m_queue_out.pop_frontNoEx();
296                 return true;
297         }
298
299         return false;
300 }
301
302 void MeshUpdateManager::deferUpdate()
303 {
304         for (auto &thread : m_workers)
305                 thread->deferUpdate();
306 }
307
308 void MeshUpdateManager::start()
309 {
310         for (auto &thread: m_workers)
311                 thread->start();
312 }
313
314 void MeshUpdateManager::stop()
315 {
316         for (auto &thread: m_workers)
317                 thread->stop();
318 }
319
320 void MeshUpdateManager::wait()
321 {
322         for (auto &thread: m_workers)
323                 thread->wait();
324 }
325
326 bool MeshUpdateManager::isRunning()
327 {
328         for (auto &thread: m_workers)
329                 if (thread->isRunning())
330                         return true;
331         return false;
332 }