forked from Mirrorlandia_minetest/minetest
Use multiple threads for mesh generation (#13062)
Co-authored-by: sfan5 <sfan5@live.de>
This commit is contained in:
parent
03e710160f
commit
89e7f72c92
@ -1673,6 +1673,11 @@ enable_mesh_cache (Mesh cache) bool false
|
||||
# down the rate of mesh updates, thus reducing jitter on slower clients.
|
||||
mesh_generation_interval (Mapblock mesh generation delay) int 0 0 50
|
||||
|
||||
# Number of threads to use for mesh generation.
|
||||
# Value of 0 (default) will let Minetest autodetect the number of available threads.
|
||||
mesh_generation_threads (Mapblock mesh generation threads) int 0 0 8
|
||||
|
||||
|
||||
# Size of the MapBlock cache of the mesh generator. Increasing this will
|
||||
# increase the cache hit %, reducing the data being copied from the main
|
||||
# thread, thus reducing jitter.
|
||||
|
@ -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();
|
||||
delete r.mesh;
|
||||
}
|
||||
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<v3s16> 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"<<std::endl;
|
||||
m_mesh_update_thread.start();
|
||||
m_mesh_update_manager.start();
|
||||
|
||||
m_state = LC_Ready;
|
||||
sendReady();
|
||||
|
@ -313,7 +313,7 @@ public:
|
||||
void addUpdateMeshTaskForNode(v3s16 nodepos, bool ack_to_server=false, bool urgent=false);
|
||||
|
||||
void updateCameraOffset(v3s16 camera_offset)
|
||||
{ m_mesh_update_thread.m_camera_offset = camera_offset; }
|
||||
{ m_mesh_update_manager.m_camera_offset = camera_offset; }
|
||||
|
||||
bool hasClientEvents() const { return !m_client_event_queue.empty(); }
|
||||
// Get event from queue. If queue is empty, it triggers an assertion failure.
|
||||
@ -483,7 +483,7 @@ private:
|
||||
RenderingEngine *m_rendering_engine;
|
||||
|
||||
|
||||
MeshUpdateThread m_mesh_update_thread;
|
||||
MeshUpdateManager m_mesh_update_manager;
|
||||
ClientEnvironment m_env;
|
||||
ParticleManager m_particle_manager;
|
||||
std::unique_ptr<con::Connection> m_con;
|
||||
|
@ -148,14 +148,24 @@ QueuedMeshUpdate *MeshUpdateQueue::pop()
|
||||
QueuedMeshUpdate *q = *i;
|
||||
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<MeshUpdateWorkerThread>(&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)");
|
||||
|
||||
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_queue_out.push_back(r);
|
||||
|
||||
delete q;
|
||||
if (result.urgent)
|
||||
m_queue_out_urgent.push_back(result);
|
||||
else
|
||||
m_queue_out.push_back(result);
|
||||
}
|
||||
|
||||
bool MeshUpdateManager::getNextResult(MeshUpdateResult &r)
|
||||
{
|
||||
if (!m_queue_out_urgent.empty()) {
|
||||
r = m_queue_out_urgent.pop_frontNoEx();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!m_queue_out.empty()) {
|
||||
r = m_queue_out.pop_frontNoEx();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -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 <vector>
|
||||
#include <memory>
|
||||
|
||||
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<QueuedMeshUpdate *> m_queue;
|
||||
std::unordered_set<v3s16> m_urgents;
|
||||
std::unordered_map<v3s16, CachedMapBlockData *> m_cache;
|
||||
std::unordered_set<v3s16> 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:
|
||||
MeshUpdateThread(Client *client);
|
||||
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:
|
||||
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<MeshUpdateResult> m_queue_out;
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
void wait();
|
||||
|
||||
bool isRunning();
|
||||
|
||||
private:
|
||||
void deferUpdate();
|
||||
|
||||
|
||||
MeshUpdateQueue m_queue_in;
|
||||
MutexedQueue<MeshUpdateResult> m_queue_out;
|
||||
MutexedQueue<MeshUpdateResult> m_queue_out_urgent;
|
||||
|
||||
// TODO: Add callback to update these when g_settings changes
|
||||
int m_generation_interval;
|
||||
|
||||
protected:
|
||||
virtual void doUpdate();
|
||||
std::vector<std::unique_ptr<MeshUpdateWorkerThread>> m_workers;
|
||||
};
|
||||
|
@ -491,16 +491,16 @@ u32 TextureSource::getTextureId(const std::string &name)
|
||||
infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
|
||||
|
||||
// We're gonna ask the result to be put into here
|
||||
static ResultQueue<std::string, u32, u8, u8> result_queue;
|
||||
static thread_local ResultQueue<std::string, u32, u8, u8> 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<std::string, u32, u8, u8>
|
||||
result = result_queue.pop_front(1000);
|
||||
result = result_queue.pop_front(4000);
|
||||
|
||||
if (result.key == name) {
|
||||
return result.item;
|
||||
|
@ -44,6 +44,7 @@ void set_default_settings()
|
||||
settings->setDefault("mute_sound", "false");
|
||||
settings->setDefault("enable_mesh_cache", "false");
|
||||
settings->setDefault("mesh_generation_interval", "0");
|
||||
settings->setDefault("mesh_generation_threads", "0");
|
||||
settings->setDefault("meshgen_block_cache_size", "20");
|
||||
settings->setDefault("enable_vbo", "true");
|
||||
settings->setDefault("free_move", "false");
|
||||
|
@ -673,7 +673,7 @@ void Client::handleCommand_AnnounceMedia(NetworkPacket* pkt)
|
||||
|
||||
// Mesh update thread must be stopped while
|
||||
// updating content definitions
|
||||
sanity_check(!m_mesh_update_thread.isRunning());
|
||||
sanity_check(!m_mesh_update_manager.isRunning());
|
||||
|
||||
for (u16 i = 0; i < num_files; i++) {
|
||||
std::string name, sha1_base64;
|
||||
@ -733,7 +733,7 @@ void Client::handleCommand_Media(NetworkPacket* pkt)
|
||||
if (init_phase) {
|
||||
// Mesh update thread must be stopped while
|
||||
// updating content definitions
|
||||
sanity_check(!m_mesh_update_thread.isRunning());
|
||||
sanity_check(!m_mesh_update_manager.isRunning());
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < num_files; i++) {
|
||||
@ -770,7 +770,7 @@ void Client::handleCommand_NodeDef(NetworkPacket* pkt)
|
||||
|
||||
// Mesh update thread must be stopped while
|
||||
// updating content definitions
|
||||
sanity_check(!m_mesh_update_thread.isRunning());
|
||||
sanity_check(!m_mesh_update_manager.isRunning());
|
||||
|
||||
// Decompress node definitions
|
||||
std::istringstream tmp_is(pkt->readLongString(), std::ios::binary);
|
||||
@ -789,7 +789,7 @@ void Client::handleCommand_ItemDef(NetworkPacket* pkt)
|
||||
|
||||
// Mesh update thread must be stopped while
|
||||
// updating content definitions
|
||||
sanity_check(!m_mesh_update_thread.isRunning());
|
||||
sanity_check(!m_mesh_update_manager.isRunning());
|
||||
|
||||
// Decompress item definitions
|
||||
std::istringstream tmp_is(pkt->readLongString(), std::ios::binary);
|
||||
|
Loading…
Reference in New Issue
Block a user