From 89e7f72c929fbeef8ad755bc85db22ae6102787d Mon Sep 17 00:00:00 2001 From: x2048 Date: Tue, 27 Dec 2022 18:44:18 +0100 Subject: Use multiple threads for mesh generation (#13062) Co-authored-by: sfan5 --- src/client/client.cpp | 29 ++++---- src/client/client.h | 4 +- src/client/mesh_generator_thread.cpp | 129 +++++++++++++++++++++++++++++------ src/client/mesh_generator_thread.h | 50 +++++++++++--- src/client/tile.cpp | 6 +- 5 files changed, 169 insertions(+), 49 deletions(-) (limited to 'src/client') diff --git a/src/client/client.cpp b/src/client/client.cpp index 04686c43c..27b0ca852 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -111,7 +111,7 @@ Client::Client( m_sound(sound), m_event(event), m_rendering_engine(rendering_engine), - m_mesh_update_thread(this), + m_mesh_update_manager(this), m_env( new ClientMap(this, rendering_engine, control, 666), tsrc, this @@ -312,7 +312,7 @@ void Client::Stop() if (m_mods_loaded) m_script->on_shutdown(); //request all client managed threads to stop - m_mesh_update_thread.stop(); + m_mesh_update_manager.stop(); // Save local server map if (m_localdb) { infostream << "Local map saving ended." << std::endl; @@ -325,7 +325,7 @@ void Client::Stop() bool Client::isShutdown() { - return m_shutdown || !m_mesh_update_thread.isRunning(); + return m_shutdown || !m_mesh_update_manager.isRunning(); } Client::~Client() @@ -335,13 +335,12 @@ Client::~Client() deleteAuthData(); - m_mesh_update_thread.stop(); - m_mesh_update_thread.wait(); - while (!m_mesh_update_thread.m_queue_out.empty()) { - MeshUpdateResult r = m_mesh_update_thread.m_queue_out.pop_frontNoEx(); + m_mesh_update_manager.stop(); + m_mesh_update_manager.wait(); + + MeshUpdateResult r; + while (m_mesh_update_manager.getNextResult(r)) delete r.mesh; - } - delete m_inventory_from_server; @@ -547,14 +546,14 @@ void Client::step(float dtime) int num_processed_meshes = 0; std::vector blocks_to_ack; bool force_update_shadows = false; - while (!m_mesh_update_thread.m_queue_out.empty()) + MeshUpdateResult r; + while (m_mesh_update_manager.getNextResult(r)) { num_processed_meshes++; MinimapMapblock *minimap_mapblock = NULL; bool do_mapper_update = true; - MeshUpdateResult r = m_mesh_update_thread.m_queue_out.pop_frontNoEx(); MapBlock *block = m_env.getMap().getBlockNoCreateNoEx(r.p); if (block) { // Delete the old mesh @@ -1655,12 +1654,12 @@ void Client::addUpdateMeshTask(v3s16 p, bool ack_to_server, bool urgent) if (b == NULL) return; - m_mesh_update_thread.updateBlock(&m_env.getMap(), p, ack_to_server, urgent); + m_mesh_update_manager.updateBlock(&m_env.getMap(), p, ack_to_server, urgent); } void Client::addUpdateMeshTaskWithEdge(v3s16 blockpos, bool ack_to_server, bool urgent) { - m_mesh_update_thread.updateBlock(&m_env.getMap(), blockpos, ack_to_server, urgent, true); + m_mesh_update_manager.updateBlock(&m_env.getMap(), blockpos, ack_to_server, urgent, true); } void Client::addUpdateMeshTaskForNode(v3s16 nodepos, bool ack_to_server, bool urgent) @@ -1674,7 +1673,7 @@ void Client::addUpdateMeshTaskForNode(v3s16 nodepos, bool ack_to_server, bool ur v3s16 blockpos = getNodeBlockPos(nodepos); v3s16 blockpos_relative = blockpos * MAP_BLOCKSIZE; - m_mesh_update_thread.updateBlock(&m_env.getMap(), blockpos, ack_to_server, urgent, false); + m_mesh_update_manager.updateBlock(&m_env.getMap(), blockpos, ack_to_server, urgent, false); // Leading edge if (nodepos.X == blockpos_relative.X) addUpdateMeshTask(blockpos + v3s16(-1, 0, 0), false, urgent); @@ -1793,7 +1792,7 @@ void Client::afterContentReceived() // Start mesh update thread after setting up content definitions infostream<<"- Starting mesh update thread"< m_con; diff --git a/src/client/mesh_generator_thread.cpp b/src/client/mesh_generator_thread.cpp index ec567c8c1..1456b26ef 100644 --- a/src/client/mesh_generator_thread.cpp +++ b/src/client/mesh_generator_thread.cpp @@ -146,16 +146,26 @@ QueuedMeshUpdate *MeshUpdateQueue::pop() for (std::vector::iterator i = m_queue.begin(); i != m_queue.end(); ++i) { QueuedMeshUpdate *q = *i; - if(must_be_urgent && m_urgents.count(q->p) == 0) + if (must_be_urgent && m_urgents.count(q->p) == 0) + continue; + // Make sure no two threads are processing the same mapblock, as that causes racing conditions + if (m_inflight_blocks.find(q->p) != m_inflight_blocks.end()) continue; m_queue.erase(i); m_urgents.erase(q->p); + m_inflight_blocks.insert(q->p); fillDataFromMapBlockCache(q); return q; } return NULL; } +void MeshUpdateQueue::done(v3s16 pos) +{ + MutexAutoLock lock(m_mutex); + m_inflight_blocks.erase(pos); +} + CachedMapBlockData* MeshUpdateQueue::cacheBlock(Map *map, v3s16 p, UpdateMode mode, size_t *cache_hit_counter) { @@ -264,18 +274,62 @@ void MeshUpdateQueue::cleanupCache() } /* - MeshUpdateThread + MeshUpdateWorkerThread */ -MeshUpdateThread::MeshUpdateThread(Client *client): - UpdateThread("Mesh"), - m_queue_in(client) +MeshUpdateWorkerThread::MeshUpdateWorkerThread(MeshUpdateQueue *queue_in, MeshUpdateManager *manager, v3s16 *camera_offset) : + UpdateThread("Mesh"), m_queue_in(queue_in), m_manager(manager), m_camera_offset(camera_offset) { m_generation_interval = g_settings->getU16("mesh_generation_interval"); m_generation_interval = rangelim(m_generation_interval, 0, 50); } -void MeshUpdateThread::updateBlock(Map *map, v3s16 p, bool ack_block_to_server, +void MeshUpdateWorkerThread::doUpdate() +{ + QueuedMeshUpdate *q; + while ((q = m_queue_in->pop())) { + if (m_generation_interval) + sleep_ms(m_generation_interval); + ScopeProfiler sp(g_profiler, "Client: Mesh making (sum)"); + + MapBlockMesh *mesh_new = new MapBlockMesh(q->data, *m_camera_offset); + + + + MeshUpdateResult r; + r.p = q->p; + r.mesh = mesh_new; + r.ack_block_to_server = q->ack_block_to_server; + r.urgent = q->urgent; + + m_manager->putResult(r); + m_queue_in->done(q->p); + delete q; + } +} + +/* + MeshUpdateManager +*/ + +MeshUpdateManager::MeshUpdateManager(Client *client): + m_queue_in(client) +{ + int number_of_threads = rangelim(g_settings->getS32("mesh_generation_threads"), 0, 8); + + // Automatically use 33% of the system cores for mesh generation, max 4 + if (number_of_threads == 0) + number_of_threads = MYMIN(4, Thread::getNumberOfProcessors() / 3); + + // use at least one thread + number_of_threads = MYMAX(1, number_of_threads); + infostream << "MeshUpdateManager: using " << number_of_threads << " threads" << std::endl; + + for (int i = 0; i < number_of_threads; i++) + m_workers.push_back(std::make_unique(&m_queue_in, this, &m_camera_offset)); +} + +void MeshUpdateManager::updateBlock(Map *map, v3s16 p, bool ack_block_to_server, bool urgent, bool update_neighbors) { static thread_local const bool many_neighbors = @@ -298,24 +352,57 @@ void MeshUpdateThread::updateBlock(Map *map, v3s16 p, bool ack_block_to_server, deferUpdate(); } -void MeshUpdateThread::doUpdate() +void MeshUpdateManager::putResult(const MeshUpdateResult &result) { - QueuedMeshUpdate *q; - while ((q = m_queue_in.pop())) { - if (m_generation_interval) - sleep_ms(m_generation_interval); - ScopeProfiler sp(g_profiler, "Client: Mesh making (sum)"); + if (result.urgent) + m_queue_out_urgent.push_back(result); + else + m_queue_out.push_back(result); +} - MapBlockMesh *mesh_new = new MapBlockMesh(q->data, m_camera_offset); +bool MeshUpdateManager::getNextResult(MeshUpdateResult &r) +{ + if (!m_queue_out_urgent.empty()) { + r = m_queue_out_urgent.pop_frontNoEx(); + return true; + } - MeshUpdateResult r; - r.p = q->p; - r.mesh = mesh_new; - r.ack_block_to_server = q->ack_block_to_server; - r.urgent = q->urgent; + if (!m_queue_out.empty()) { + r = m_queue_out.pop_frontNoEx(); + return true; + } - m_queue_out.push_back(r); + return false; +} - delete q; - } +void MeshUpdateManager::deferUpdate() +{ + for (auto &thread : m_workers) + thread->deferUpdate(); +} + +void MeshUpdateManager::start() +{ + for (auto &thread: m_workers) + thread->start(); +} + +void MeshUpdateManager::stop() +{ + for (auto &thread: m_workers) + thread->stop(); +} + +void MeshUpdateManager::wait() +{ + for (auto &thread: m_workers) + thread->wait(); +} + +bool MeshUpdateManager::isRunning() +{ + for (auto &thread: m_workers) + if (thread->isRunning()) + return true; + return false; } diff --git a/src/client/mesh_generator_thread.h b/src/client/mesh_generator_thread.h index 09400196d..bbb84b74a 100644 --- a/src/client/mesh_generator_thread.h +++ b/src/client/mesh_generator_thread.h @@ -26,6 +26,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "mapblock_mesh.h" #include "threading/mutex_auto_lock.h" #include "util/thread.h" +#include +#include struct CachedMapBlockData { @@ -75,6 +77,9 @@ public: // Returns NULL if queue is empty QueuedMeshUpdate *pop(); + // Marks a position as finished, unblocking the next update + void done(v3s16 pos); + u32 size() { MutexAutoLock lock(m_mutex); @@ -86,6 +91,7 @@ private: std::vector m_queue; std::unordered_set m_urgents; std::unordered_map m_cache; + std::unordered_set m_inflight_blocks; u64 m_next_cache_cleanup; // milliseconds std::mutex m_mutex; @@ -111,25 +117,53 @@ struct MeshUpdateResult MeshUpdateResult() = default; }; -class MeshUpdateThread : public UpdateThread +class MeshUpdateManager; + +class MeshUpdateWorkerThread : public UpdateThread +{ +public: + MeshUpdateWorkerThread(MeshUpdateQueue *queue_in, MeshUpdateManager *manager, v3s16 *camera_offset); + +protected: + virtual void doUpdate(); + +private: + MeshUpdateQueue *m_queue_in; + MeshUpdateManager *m_manager; + v3s16 *m_camera_offset; + + // TODO: Add callback to update these when g_settings changes + int m_generation_interval; +}; + +class MeshUpdateManager { public: - MeshUpdateThread(Client *client); + MeshUpdateManager(Client *client); // Caches the block at p and its neighbors (if needed) and queues a mesh // update for the block at p void updateBlock(Map *map, v3s16 p, bool ack_block_to_server, bool urgent, bool update_neighbors = false); + void putResult(const MeshUpdateResult &r); + bool getNextResult(MeshUpdateResult &r); + v3s16 m_camera_offset; - MutexedQueue m_queue_out; + + void start(); + void stop(); + void wait(); + + bool isRunning(); private: - MeshUpdateQueue m_queue_in; + void deferUpdate(); - // TODO: Add callback to update these when g_settings changes - int m_generation_interval; -protected: - virtual void doUpdate(); + MeshUpdateQueue m_queue_in; + MutexedQueue m_queue_out; + MutexedQueue m_queue_out_urgent; + + std::vector> m_workers; }; diff --git a/src/client/tile.cpp b/src/client/tile.cpp index 0336bd82a..582219bb8 100644 --- a/src/client/tile.cpp +++ b/src/client/tile.cpp @@ -491,16 +491,16 @@ u32 TextureSource::getTextureId(const std::string &name) infostream<<"getTextureId(): Queued: name=\""< result_queue; + static thread_local ResultQueue result_queue; // Throw a request in m_get_texture_queue.add(name, 0, 0, &result_queue); try { while(true) { - // Wait result for a second + // Wait for result for up to 4 seconds (empirical value) GetResult - result = result_queue.pop_front(1000); + result = result_queue.pop_front(4000); if (result.key == name) { return result.item; -- cgit v1.2.3