Use WorkerThreadPool for Server threads

* Servers now use WorkerThreadPool for background computation.
* This helps keep the number of threads used fixed at all times.
* It also ensures everything works on HTML5 with threads.
* And makes it easier to support disabling threads for also HTML5.

CommandQueueMT now syncs with the servers via the WorkerThreadPool
yielding mechanism, which makes its classic main sync semaphore
superfluous.

Also, some warnings about calls that kill performance when using
threaded rendering are removed because there's a mechanism that
warns about that in a more general fashion.

Co-authored-by: Pedro J. Estébanez <pedrojrulez@gmail.com>
This commit is contained in:
Juan Linietsky 2024-04-04 10:18:08 +02:00 committed by Pedro J. Estébanez
parent c28f5901c7
commit 65686dedf9
15 changed files with 208 additions and 204 deletions

View file

@ -82,6 +82,17 @@ int Engine::get_audio_output_latency() const {
return _audio_output_latency;
}
void Engine::increment_frames_drawn() {
if (frame_server_synced) {
server_syncs++;
} else {
server_syncs = 0;
}
frame_server_synced = false;
frames_drawn++;
}
uint64_t Engine::get_frames_drawn() {
return frames_drawn;
}
@ -364,6 +375,11 @@ Engine *Engine::get_singleton() {
return singleton;
}
bool Engine::notify_frame_server_synced() {
frame_server_synced = true;
return server_syncs > SERVER_SYNC_FRAME_COUNT_WARNING;
}
Engine::Engine() {
singleton = this;
}

View file

@ -91,6 +91,10 @@ private:
String write_movie_path;
String shader_cache_path;
static constexpr int SERVER_SYNC_FRAME_COUNT_WARNING = 5;
int server_syncs = 0;
bool frame_server_synced = false;
public:
static Engine *get_singleton();
@ -179,6 +183,9 @@ public:
bool is_generate_spirv_debug_info_enabled() const;
int32_t get_gpu_index() const;
void increment_frames_drawn();
bool notify_frame_server_synced();
Engine();
virtual ~Engine() {}
};

View file

@ -70,14 +70,8 @@ CommandQueueMT::SyncSemaphore *CommandQueueMT::_alloc_sync_sem() {
return &sync_sems[idx];
}
CommandQueueMT::CommandQueueMT(bool p_sync) {
if (p_sync) {
sync = memnew(Semaphore);
}
CommandQueueMT::CommandQueueMT() {
}
CommandQueueMT::~CommandQueueMT() {
if (sync) {
memdelete(sync);
}
}

View file

@ -248,16 +248,17 @@
#define CMD_TYPE(N) Command##N<T, M COMMA(N) COMMA_SEP_LIST(TYPE_ARG, N)>
#define CMD_ASSIGN_PARAM(N) cmd->p##N = p##N
#define DECL_PUSH(N) \
template <typename T, typename M COMMA(N) COMMA_SEP_LIST(TYPE_PARAM, N)> \
void push(T *p_instance, M p_method COMMA(N) COMMA_SEP_LIST(PARAM, N)) { \
CMD_TYPE(N) *cmd = allocate_and_lock<CMD_TYPE(N)>(); \
cmd->instance = p_instance; \
cmd->method = p_method; \
SEMIC_SEP_LIST(CMD_ASSIGN_PARAM, N); \
unlock(); \
if (sync) \
sync->post(); \
#define DECL_PUSH(N) \
template <typename T, typename M COMMA(N) COMMA_SEP_LIST(TYPE_PARAM, N)> \
void push(T *p_instance, M p_method COMMA(N) COMMA_SEP_LIST(PARAM, N)) { \
CMD_TYPE(N) *cmd = allocate_and_lock<CMD_TYPE(N)>(); \
cmd->instance = p_instance; \
cmd->method = p_method; \
SEMIC_SEP_LIST(CMD_ASSIGN_PARAM, N); \
if (pump_task_id != WorkerThreadPool::INVALID_TASK_ID) { \
WorkerThreadPool::get_singleton()->notify_yield_over(pump_task_id); \
} \
unlock(); \
}
#define CMD_RET_TYPE(N) CommandRet##N<T, M, COMMA_SEP_LIST(TYPE_ARG, N) COMMA(N) R>
@ -272,9 +273,10 @@
SEMIC_SEP_LIST(CMD_ASSIGN_PARAM, N); \
cmd->ret = r_ret; \
cmd->sync_sem = ss; \
if (pump_task_id != WorkerThreadPool::INVALID_TASK_ID) { \
WorkerThreadPool::get_singleton()->notify_yield_over(pump_task_id); \
} \
unlock(); \
if (sync) \
sync->post(); \
ss->sem.wait(); \
ss->in_use = false; \
}
@ -290,9 +292,10 @@
cmd->method = p_method; \
SEMIC_SEP_LIST(CMD_ASSIGN_PARAM, N); \
cmd->sync_sem = ss; \
if (pump_task_id != WorkerThreadPool::INVALID_TASK_ID) { \
WorkerThreadPool::get_singleton()->notify_yield_over(pump_task_id); \
} \
unlock(); \
if (sync) \
sync->post(); \
ss->sem.wait(); \
ss->in_use = false; \
}
@ -340,7 +343,7 @@ class CommandQueueMT {
LocalVector<uint8_t> command_mem;
SyncSemaphore sync_sems[SYNC_SEMAPHORES];
Mutex mutex;
Semaphore *sync = nullptr;
WorkerThreadPool::TaskID pump_task_id = WorkerThreadPool::INVALID_TASK_ID;
uint64_t flush_read_ptr = 0;
template <typename T>
@ -421,12 +424,16 @@ public:
}
void wait_and_flush() {
ERR_FAIL_NULL(sync);
sync->wait();
ERR_FAIL_COND(pump_task_id == WorkerThreadPool::INVALID_TASK_ID);
WorkerThreadPool::get_singleton()->wait_for_task_completion(pump_task_id);
_flush();
}
CommandQueueMT(bool p_sync);
void set_pump_task_id(WorkerThreadPool::TaskID p_task_id) {
pump_task_id = p_task_id;
}
CommandQueueMT();
~CommandQueueMT();
};

View file

@ -135,7 +135,6 @@ bool ParticlesStorage::particles_get_emitting(RID p_particles) {
return false;
}
ERR_FAIL_COND_V_MSG(RSG::threaded, false, "This function should never be used with threaded rendering, as it stalls the renderer.");
Particles *particles = particles_owner.get_or_null(p_particles);
ERR_FAIL_NULL_V(particles, false);
@ -380,10 +379,6 @@ void ParticlesStorage::particles_request_process(RID p_particles) {
}
AABB ParticlesStorage::particles_get_current_aabb(RID p_particles) {
if (RSG::threaded) {
WARN_PRINT_ONCE("Calling this function with threaded rendering enabled stalls the renderer, use with care.");
}
const Particles *particles = particles_owner.get_or_null(p_particles);
ERR_FAIL_NULL_V(particles, AABB());
@ -1207,7 +1202,6 @@ Dependency *ParticlesStorage::particles_get_dependency(RID p_particles) const {
}
bool ParticlesStorage::particles_is_inactive(RID p_particles) const {
ERR_FAIL_COND_V_MSG(RSG::threaded, false, "This function should never be used with threaded rendering, as it stalls the renderer.");
const Particles *particles = particles_owner.get_or_null(p_particles);
ERR_FAIL_NULL_V(particles, false);
return !particles->emitting && particles->inactive;

View file

@ -2274,6 +2274,9 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
// Editor and project manager cannot run with rendering in a separate thread (they will crash on startup).
rtm = OS::RENDER_THREAD_SAFE;
}
#if !defined(THREADS_ENABLED)
rtm = OS::RENDER_THREAD_SAFE;
#endif
OS::get_singleton()->_render_thread_mode = OS::RenderThreadMode(rtm);
}
@ -2717,7 +2720,9 @@ Error Main::setup2() {
}
if (OS::get_singleton()->_render_thread_mode == OS::RENDER_SEPARATE_THREAD) {
WARN_PRINT("The Multi-Threaded rendering thread model is experimental, and has known issues which can lead to project crashes. Use the Single-Safe option in the project settings instead.");
WARN_PRINT("The Multi-Threaded rendering thread model is experimental. Feel free to try it since it will eventually become a stable feature.\n"
"However, bear in mind that at the moment it can lead to project crashes or instability.\n"
"So, unless you want to test the engine, use the Single-Safe option in the project settings instead.");
}
/* Initialize Pen Tablet Driver */
@ -4025,11 +4030,11 @@ bool Main::iteration() {
if ((!force_redraw_requested) && OS::get_singleton()->is_in_low_processor_usage_mode()) {
if (RenderingServer::get_singleton()->has_changed()) {
RenderingServer::get_singleton()->draw(true, scaled_step); // flush visual commands
Engine::get_singleton()->frames_drawn++;
Engine::get_singleton()->increment_frames_drawn();
}
} else {
RenderingServer::get_singleton()->draw(true, scaled_step); // flush visual commands
Engine::get_singleton()->frames_drawn++;
Engine::get_singleton()->increment_frames_drawn();
force_redraw_requested = false;
}
}

View file

@ -33,7 +33,7 @@
#include "core/os/os.h"
void PhysicsServer2DWrapMT::thread_exit() {
exit.set();
exit = true;
}
void PhysicsServer2DWrapMT::thread_step(real_t p_delta) {
@ -41,25 +41,18 @@ void PhysicsServer2DWrapMT::thread_step(real_t p_delta) {
step_sem.post();
}
void PhysicsServer2DWrapMT::_thread_callback(void *_instance) {
PhysicsServer2DWrapMT *vsmt = reinterpret_cast<PhysicsServer2DWrapMT *>(_instance);
vsmt->thread_loop();
}
void PhysicsServer2DWrapMT::thread_loop() {
server_thread = Thread::get_caller_id();
physics_server_2d->init();
exit.clear();
step_thread_up.set();
while (!exit.is_set()) {
// flush commands one by one, until exit is requested
command_queue.wait_and_flush();
command_queue.set_pump_task_id(server_task_id);
while (!exit) {
WorkerThreadPool::get_singleton()->yield();
command_queue.flush_all();
}
command_queue.flush_all(); // flush all
command_queue.flush_all();
physics_server_2d->finish();
}
@ -70,18 +63,14 @@ void PhysicsServer2DWrapMT::step(real_t p_step) {
if (create_thread) {
command_queue.push(this, &PhysicsServer2DWrapMT::thread_step, p_step);
} else {
command_queue.flush_all(); //flush all pending from other threads
command_queue.flush_all(); // Flush all pending from other threads.
physics_server_2d->step(p_step);
}
}
void PhysicsServer2DWrapMT::sync() {
if (create_thread) {
if (first_frame) {
first_frame = false;
} else {
step_sem.wait(); //must not wait if a step was not issued
}
step_sem.wait();
}
physics_server_2d->sync();
}
@ -96,39 +85,34 @@ void PhysicsServer2DWrapMT::end_sync() {
void PhysicsServer2DWrapMT::init() {
if (create_thread) {
thread.start(_thread_callback, this);
while (!step_thread_up.is_set()) {
OS::get_singleton()->delay_usec(1000);
}
exit = false;
server_task_id = WorkerThreadPool::get_singleton()->add_task(callable_mp(this, &PhysicsServer2DWrapMT::thread_loop), true);
step_sem.post();
} else {
physics_server_2d->init();
}
}
void PhysicsServer2DWrapMT::finish() {
if (thread.is_started()) {
if (create_thread) {
command_queue.push(this, &PhysicsServer2DWrapMT::thread_exit);
thread.wait_to_finish();
if (server_task_id != WorkerThreadPool::INVALID_TASK_ID) {
WorkerThreadPool::get_singleton()->wait_for_task_completion(server_task_id);
server_task_id = WorkerThreadPool::INVALID_TASK_ID;
}
} else {
physics_server_2d->finish();
}
}
PhysicsServer2DWrapMT::PhysicsServer2DWrapMT(PhysicsServer2D *p_contained, bool p_create_thread) :
command_queue(p_create_thread) {
PhysicsServer2DWrapMT::PhysicsServer2DWrapMT(PhysicsServer2D *p_contained, bool p_create_thread) {
physics_server_2d = p_contained;
create_thread = p_create_thread;
if (!p_create_thread) {
server_thread = Thread::get_caller_id();
} else {
server_thread = 0;
if (!create_thread) {
server_thread = Thread::MAIN_ID;
}
main_thread = Thread::get_caller_id();
}
PhysicsServer2DWrapMT::~PhysicsServer2DWrapMT() {
memdelete(physics_server_2d);
//finish();
}

View file

@ -32,6 +32,7 @@
#define PHYSICS_SERVER_2D_WRAP_MT_H
#include "core/config/project_settings.h"
#include "core/object/worker_thread_pool.h"
#include "core/os/thread.h"
#include "core/templates/command_queue_mt.h"
#include "core/templates/safe_refcount.h"
@ -43,30 +44,27 @@
#define SYNC_DEBUG
#endif
#ifdef DEBUG_ENABLED
#define MAIN_THREAD_SYNC_WARN WARN_PRINT("Call to " + String(__FUNCTION__) + " causing PhysicsServer2D synchronizations on every frame. This significantly affects performance.");
#endif
class PhysicsServer2DWrapMT : public PhysicsServer2D {
mutable PhysicsServer2D *physics_server_2d;
mutable PhysicsServer2D *physics_server_2d = nullptr;
mutable CommandQueueMT command_queue;
static void _thread_callback(void *_instance);
void thread_loop();
Thread::ID server_thread;
Thread::ID main_thread;
SafeFlag exit;
Thread thread;
SafeFlag step_thread_up;
Thread::ID server_thread = Thread::UNASSIGNED_ID;
WorkerThreadPool::TaskID server_task_id = WorkerThreadPool::INVALID_TASK_ID;
bool exit = false;
Semaphore step_sem;
bool create_thread = false;
Semaphore step_sem;
void thread_step(real_t p_delta);
void thread_exit();
bool first_frame = true;
Mutex alloc_mutex;
public:
#define ServerName PhysicsServer2D
#define ServerNameWrapMT PhysicsServer2DWrapMT
@ -94,7 +92,7 @@ public:
//these work well, but should be used from the main thread only
bool shape_collide(RID p_shape_A, const Transform2D &p_xform_A, const Vector2 &p_motion_A, RID p_shape_B, const Transform2D &p_xform_B, const Vector2 &p_motion_B, Vector2 *r_results, int p_result_max, int &r_result_count) override {
ERR_FAIL_COND_V(main_thread != Thread::get_caller_id(), false);
ERR_FAIL_COND_V(!Thread::is_main_thread(), false);
return physics_server_2d->shape_collide(p_shape_A, p_xform_A, p_motion_A, p_shape_B, p_xform_B, p_motion_B, r_results, p_result_max, r_result_count);
}
@ -109,18 +107,18 @@ public:
// this function only works on physics process, errors and returns null otherwise
PhysicsDirectSpaceState2D *space_get_direct_state(RID p_space) override {
ERR_FAIL_COND_V(main_thread != Thread::get_caller_id(), nullptr);
ERR_FAIL_COND_V(!Thread::is_main_thread(), nullptr);
return physics_server_2d->space_get_direct_state(p_space);
}
FUNC2(space_set_debug_contacts, RID, int);
virtual Vector<Vector2> space_get_contacts(RID p_space) const override {
ERR_FAIL_COND_V(main_thread != Thread::get_caller_id(), Vector<Vector2>());
ERR_FAIL_COND_V(!Thread::is_main_thread(), Vector<Vector2>());
return physics_server_2d->space_get_contacts(p_space);
}
virtual int space_get_contact_count(RID p_space) const override {
ERR_FAIL_COND_V(main_thread != Thread::get_caller_id(), 0);
ERR_FAIL_COND_V(!Thread::is_main_thread(), 0);
return physics_server_2d->space_get_contact_count(p_space);
}
@ -261,13 +259,13 @@ public:
FUNC2(body_set_pickable, RID, bool);
bool body_test_motion(RID p_body, const MotionParameters &p_parameters, MotionResult *r_result = nullptr) override {
ERR_FAIL_COND_V(main_thread != Thread::get_caller_id(), false);
ERR_FAIL_COND_V(!Thread::is_main_thread(), false);
return physics_server_2d->body_test_motion(p_body, p_parameters, r_result);
}
// this function only works on physics process, errors and returns null otherwise
PhysicsDirectBodyState2D *body_get_direct_state(RID p_body) override {
ERR_FAIL_COND_V(main_thread != Thread::get_caller_id(), nullptr);
ERR_FAIL_COND_V(!Thread::is_main_thread(), nullptr);
return physics_server_2d->body_get_direct_state(p_body);
}
@ -338,4 +336,8 @@ public:
#endif
#undef SYNC_DEBUG
#ifdef DEBUG_ENABLED
#undef MAIN_THREAD_SYNC_WARN
#endif
#endif // PHYSICS_SERVER_2D_WRAP_MT_H

View file

@ -41,22 +41,15 @@ void PhysicsServer3DWrapMT::thread_step(real_t p_delta) {
step_sem.post();
}
void PhysicsServer3DWrapMT::_thread_callback(void *_instance) {
PhysicsServer3DWrapMT *vsmt = reinterpret_cast<PhysicsServer3DWrapMT *>(_instance);
vsmt->thread_loop();
}
void PhysicsServer3DWrapMT::thread_loop() {
server_thread = Thread::get_caller_id();
physics_server_3d->init();
exit = false;
step_thread_up = true;
command_queue.set_pump_task_id(server_task_id);
while (!exit) {
// flush commands one by one, until exit is requested
command_queue.wait_and_flush();
WorkerThreadPool::get_singleton()->yield();
command_queue.flush_all();
}
command_queue.flush_all(); // flush all
@ -70,18 +63,14 @@ void PhysicsServer3DWrapMT::step(real_t p_step) {
if (create_thread) {
command_queue.push(this, &PhysicsServer3DWrapMT::thread_step, p_step);
} else {
command_queue.flush_all(); //flush all pending from other threads
command_queue.flush_all(); // Flush all pending from other threads.
physics_server_3d->step(p_step);
}
}
void PhysicsServer3DWrapMT::sync() {
if (create_thread) {
if (first_frame) {
first_frame = false;
} else {
step_sem.wait(); //must not wait if a step was not issued
}
step_sem.wait();
}
physics_server_3d->sync();
}
@ -96,39 +85,34 @@ void PhysicsServer3DWrapMT::end_sync() {
void PhysicsServer3DWrapMT::init() {
if (create_thread) {
thread.start(_thread_callback, this);
while (!step_thread_up) {
OS::get_singleton()->delay_usec(1000);
}
exit = false;
server_task_id = WorkerThreadPool::get_singleton()->add_task(callable_mp(this, &PhysicsServer3DWrapMT::thread_loop), true);
step_sem.post();
} else {
physics_server_3d->init();
}
}
void PhysicsServer3DWrapMT::finish() {
if (thread.is_started()) {
if (create_thread) {
command_queue.push(this, &PhysicsServer3DWrapMT::thread_exit);
thread.wait_to_finish();
if (server_task_id != WorkerThreadPool::INVALID_TASK_ID) {
WorkerThreadPool::get_singleton()->wait_for_task_completion(server_task_id);
server_task_id = WorkerThreadPool::INVALID_TASK_ID;
}
} else {
physics_server_3d->finish();
}
}
PhysicsServer3DWrapMT::PhysicsServer3DWrapMT(PhysicsServer3D *p_contained, bool p_create_thread) :
command_queue(p_create_thread) {
PhysicsServer3DWrapMT::PhysicsServer3DWrapMT(PhysicsServer3D *p_contained, bool p_create_thread) {
physics_server_3d = p_contained;
create_thread = p_create_thread;
if (!p_create_thread) {
server_thread = Thread::get_caller_id();
} else {
server_thread = 0;
if (!create_thread) {
server_thread = Thread::MAIN_ID;
}
main_thread = Thread::get_caller_id();
}
PhysicsServer3DWrapMT::~PhysicsServer3DWrapMT() {
memdelete(physics_server_3d);
//finish();
}

View file

@ -32,6 +32,7 @@
#define PHYSICS_SERVER_3D_WRAP_MT_H
#include "core/config/project_settings.h"
#include "core/object/worker_thread_pool.h"
#include "core/os/thread.h"
#include "core/templates/command_queue_mt.h"
#include "servers/physics_server_3d.h"
@ -42,30 +43,27 @@
#define SYNC_DEBUG
#endif
#ifdef DEBUG_ENABLED
#define MAIN_THREAD_SYNC_WARN WARN_PRINT("Call to " + String(__FUNCTION__) + " causing PhysicsServer3D synchronizations on every frame. This significantly affects performance.");
#endif
class PhysicsServer3DWrapMT : public PhysicsServer3D {
mutable PhysicsServer3D *physics_server_3d;
mutable PhysicsServer3D *physics_server_3d = nullptr;
mutable CommandQueueMT command_queue;
static void _thread_callback(void *_instance);
void thread_loop();
Thread::ID server_thread;
Thread::ID main_thread;
volatile bool exit = false;
Thread thread;
volatile bool step_thread_up = false;
Thread::ID server_thread = Thread::UNASSIGNED_ID;
WorkerThreadPool::TaskID server_task_id = WorkerThreadPool::INVALID_TASK_ID;
bool exit = false;
Semaphore step_sem;
bool create_thread = false;
Semaphore step_sem;
void thread_step(real_t p_delta);
void thread_exit();
bool first_frame = true;
Mutex alloc_mutex;
public:
#define ServerName PhysicsServer3D
#define ServerNameWrapMT PhysicsServer3DWrapMT
@ -98,7 +96,7 @@ public:
#if 0
//these work well, but should be used from the main thread only
bool shape_collide(RID p_shape_A, const Transform &p_xform_A, const Vector3 &p_motion_A, RID p_shape_B, const Transform &p_xform_B, const Vector3 &p_motion_B, Vector3 *r_results, int p_result_max, int &r_result_count) {
ERR_FAIL_COND_V(main_thread != Thread::get_caller_id(), false);
ERR_FAIL_COND_V(!Thread::is_main_thread(), false);
return physics_server_3d->shape_collide(p_shape_A, p_xform_A, p_motion_A, p_shape_B, p_xform_B, p_motion_B, r_results, p_result_max, r_result_count);
}
#endif
@ -113,18 +111,18 @@ public:
// this function only works on physics process, errors and returns null otherwise
PhysicsDirectSpaceState3D *space_get_direct_state(RID p_space) override {
ERR_FAIL_COND_V(main_thread != Thread::get_caller_id(), nullptr);
ERR_FAIL_COND_V(!Thread::is_main_thread(), nullptr);
return physics_server_3d->space_get_direct_state(p_space);
}
FUNC2(space_set_debug_contacts, RID, int);
virtual Vector<Vector3> space_get_contacts(RID p_space) const override {
ERR_FAIL_COND_V(main_thread != Thread::get_caller_id(), Vector<Vector3>());
ERR_FAIL_COND_V(!Thread::is_main_thread(), Vector<Vector3>());
return physics_server_3d->space_get_contacts(p_space);
}
virtual int space_get_contact_count(RID p_space) const override {
ERR_FAIL_COND_V(main_thread != Thread::get_caller_id(), 0);
ERR_FAIL_COND_V(!Thread::is_main_thread(), 0);
return physics_server_3d->space_get_contact_count(p_space);
}
@ -260,13 +258,13 @@ public:
FUNC2(body_set_ray_pickable, RID, bool);
bool body_test_motion(RID p_body, const MotionParameters &p_parameters, MotionResult *r_result = nullptr) override {
ERR_FAIL_COND_V(main_thread != Thread::get_caller_id(), false);
ERR_FAIL_COND_V(!Thread::is_main_thread(), false);
return physics_server_3d->body_test_motion(p_body, p_parameters, r_result);
}
// this function only works on physics process, errors and returns null otherwise
PhysicsDirectBodyState3D *body_get_direct_state(RID p_body) override {
ERR_FAIL_COND_V(main_thread != Thread::get_caller_id(), nullptr);
ERR_FAIL_COND_V(!Thread::is_main_thread(), nullptr);
return physics_server_3d->body_get_direct_state(p_body);
}
@ -411,4 +409,8 @@ public:
#endif
#undef SYNC_DEBUG
#ifdef DEBUG_ENABLED
#undef MAIN_THREAD_SYNC_WARN
#endif
#endif // PHYSICS_SERVER_3D_WRAP_MT_H

View file

@ -257,7 +257,6 @@ void ParticlesStorage::particles_set_emitting(RID p_particles, bool p_emitting)
}
bool ParticlesStorage::particles_get_emitting(RID p_particles) {
ERR_FAIL_COND_V_MSG(RSG::threaded, false, "This function should never be used with threaded rendering, as it stalls the renderer.");
Particles *particles = particles_owner.get_or_null(p_particles);
ERR_FAIL_NULL_V(particles, false);
@ -608,10 +607,6 @@ void ParticlesStorage::particles_request_process(RID p_particles) {
}
AABB ParticlesStorage::particles_get_current_aabb(RID p_particles) {
if (RSG::threaded) {
WARN_PRINT_ONCE("Calling this function with threaded rendering enabled stalls the renderer, use with care.");
}
const Particles *particles = particles_owner.get_or_null(p_particles);
ERR_FAIL_NULL_V(particles, AABB());
@ -1642,7 +1637,6 @@ Dependency *ParticlesStorage::particles_get_dependency(RID p_particles) const {
}
bool ParticlesStorage::particles_is_inactive(RID p_particles) const {
ERR_FAIL_COND_V_MSG(RSG::threaded, false, "This function should never be used with threaded rendering, as it stalls the renderer.");
const Particles *particles = particles_owner.get_or_null(p_particles);
ERR_FAIL_NULL_V(particles, false);
return !particles->emitting && particles->inactive;

View file

@ -69,9 +69,6 @@ void RenderingServerDefault::request_frame_drawn_callback(const Callable &p_call
}
void RenderingServerDefault::_draw(bool p_swap_buffers, double frame_step) {
//needs to be done before changes is reset to 0, to not force the editor to redraw
RS::get_singleton()->emit_signal(SNAME("frame_pre_draw"));
changes = 0;
RSG::rasterizer->begin_frame(frame_step);
@ -220,16 +217,9 @@ void RenderingServerDefault::_finish() {
void RenderingServerDefault::init() {
if (create_thread) {
print_verbose("RenderingServerWrapMT: Creating render thread");
print_verbose("RenderingServerWrapMT: Starting render thread");
DisplayServer::get_singleton()->release_rendering_thread();
if (create_thread) {
thread.start(_thread_callback, this);
print_verbose("RenderingServerWrapMT: Starting render thread");
}
while (!draw_thread_up.is_set()) {
OS::get_singleton()->delay_usec(1000);
}
print_verbose("RenderingServerWrapMT: Finished render thread");
server_task_id = WorkerThreadPool::get_singleton()->add_task(callable_mp(this, &RenderingServerDefault::_thread_loop), true);
} else {
_init();
}
@ -238,8 +228,9 @@ void RenderingServerDefault::init() {
void RenderingServerDefault::finish() {
if (create_thread) {
command_queue.push(this, &RenderingServerDefault::_thread_exit);
if (thread.is_started()) {
thread.wait_to_finish();
if (server_task_id != WorkerThreadPool::INVALID_TASK_ID) {
WorkerThreadPool::get_singleton()->wait_for_task_completion(server_task_id);
server_task_id = WorkerThreadPool::INVALID_TASK_ID;
}
} else {
_finish();
@ -337,37 +328,29 @@ Size2i RenderingServerDefault::get_maximum_viewport_size() const {
}
void RenderingServerDefault::_thread_exit() {
exit.set();
exit = true;
}
void RenderingServerDefault::_thread_draw(bool p_swap_buffers, double frame_step) {
_draw(p_swap_buffers, frame_step);
}
void RenderingServerDefault::_thread_flush() {
}
void RenderingServerDefault::_thread_callback(void *_instance) {
RenderingServerDefault *vsmt = reinterpret_cast<RenderingServerDefault *>(_instance);
vsmt->_thread_loop();
}
void RenderingServerDefault::_thread_loop() {
server_thread = Thread::get_caller_id();
DisplayServer::get_singleton()->gl_window_make_current(DisplayServer::MAIN_WINDOW_ID); // Move GL to this thread.
_init();
draw_thread_up.set();
while (!exit.is_set()) {
// flush commands one by one, until exit is requested
command_queue.wait_and_flush();
command_queue.set_pump_task_id(server_task_id);
while (!exit) {
WorkerThreadPool::get_singleton()->yield();
command_queue.flush_all();
}
command_queue.flush_all(); // flush all
command_queue.flush_all();
_finish();
DisplayServer::get_singleton()->release_rendering_thread();
}
/* INTERPOLATION */
@ -383,15 +366,15 @@ void RenderingServerDefault::set_physics_interpolation_enabled(bool p_enabled) {
/* EVENT QUEUING */
void RenderingServerDefault::sync() {
if (create_thread) {
command_queue.push_and_sync(this, &RenderingServerDefault::_thread_flush);
} else {
command_queue.flush_all(); //flush all pending from other threads
if (!create_thread) {
command_queue.flush_all(); // Flush all pending from other threads.
}
}
void RenderingServerDefault::draw(bool p_swap_buffers, double frame_step) {
ERR_FAIL_COND_MSG(!Thread::is_main_thread(), "Manually triggering the draw function from the RenderingServer can only be done on the main thread. Call this function from the main thread or use call_deferred().");
// Needs to be done before changes is reset to 0, to not force the editor to redraw.
RS::get_singleton()->emit_signal(SNAME("frame_pre_draw"));
if (create_thread) {
command_queue.push(this, &RenderingServerDefault::_thread_draw, p_swap_buffers, frame_step);
} else {
@ -403,21 +386,14 @@ void RenderingServerDefault::_call_on_render_thread(const Callable &p_callable)
p_callable.call();
}
RenderingServerDefault::RenderingServerDefault(bool p_create_thread) :
command_queue(p_create_thread) {
RenderingServerDefault::RenderingServerDefault(bool p_create_thread) {
RenderingServer::init();
#ifdef THREADS_ENABLED
create_thread = p_create_thread;
if (!create_thread) {
server_thread = Thread::get_caller_id();
} else {
server_thread = 0;
server_thread = Thread::MAIN_ID;
}
#else
create_thread = false;
server_thread = Thread::get_main_id();
#endif
RSG::threaded = create_thread;
RSG::canvas = memnew(RendererCanvasCull);

View file

@ -31,6 +31,7 @@
#ifndef RENDERING_SERVER_DEFAULT_H
#define RENDERING_SERVER_DEFAULT_H
#include "core/object/worker_thread_pool.h"
#include "core/os/thread.h"
#include "core/templates/command_queue_mt.h"
#include "core/templates/hash_map.h"
@ -75,22 +76,17 @@ class RenderingServerDefault : public RenderingServer {
mutable CommandQueueMT command_queue;
static void _thread_callback(void *_instance);
void _thread_loop();
Thread::ID server_thread = 0;
SafeFlag exit;
Thread thread;
SafeFlag draw_thread_up;
bool create_thread;
Thread::ID server_thread = Thread::UNASSIGNED_ID;
WorkerThreadPool::TaskID server_task_id = WorkerThreadPool::INVALID_TASK_ID;
bool exit = false;
bool create_thread = false;
void _thread_draw(bool p_swap_buffers, double frame_step);
void _thread_flush();
void _thread_exit();
Mutex alloc_mutex;
void _draw(bool p_swap_buffers, double frame_step);
void _init();
void _finish();
@ -127,6 +123,10 @@ public:
#define SYNC_DEBUG
#endif
#ifdef DEBUG_ENABLED
#define MAIN_THREAD_SYNC_WARN WARN_PRINT("Call to " + String(__FUNCTION__) + " causing RenderingServer synchronizations on every frame. This significantly affects performance.");
#endif
#include "servers/server_wrap_mt_common.h"
/* TEXTURE API */
@ -1013,6 +1013,9 @@ public:
#undef ServerName
#undef WRITE_ACTION
#undef SYNC_DEBUG
#ifdef DEBUG_ENABLED
#undef MAIN_THREAD_SYNC_WARN
#endif
virtual uint64_t get_rendering_info(RenderingInfo p_info) override;
virtual RenderingDevice::DeviceType get_video_adapter_type() const override;

View file

@ -83,25 +83,16 @@ static PackedInt64Array to_int_array(const Vector<ObjectID> &ids) {
}
PackedInt64Array RenderingServer::_instances_cull_aabb_bind(const AABB &p_aabb, RID p_scenario) const {
if (RSG::threaded) {
WARN_PRINT_ONCE("Using this function with a threaded renderer hurts performance, as it causes a server stall.");
}
Vector<ObjectID> ids = instances_cull_aabb(p_aabb, p_scenario);
return to_int_array(ids);
}
PackedInt64Array RenderingServer::_instances_cull_ray_bind(const Vector3 &p_from, const Vector3 &p_to, RID p_scenario) const {
if (RSG::threaded) {
WARN_PRINT_ONCE("Using this function with a threaded renderer hurts performance, as it causes a server stall.");
}
Vector<ObjectID> ids = instances_cull_ray(p_from, p_to, p_scenario);
return to_int_array(ids);
}
PackedInt64Array RenderingServer::_instances_cull_convex_bind(const TypedArray<Plane> &p_convex, RID p_scenario) const {
if (RSG::threaded) {
WARN_PRINT_ONCE("Using this function with a threaded renderer hurts performance, as it causes a server stall.");
}
Vector<Plane> planes;
for (int i = 0; i < p_convex.size(); ++i) {
const Variant &v = p_convex[i];

View file

@ -31,12 +31,22 @@
#ifndef SERVER_WRAP_MT_COMMON_H
#define SERVER_WRAP_MT_COMMON_H
#ifdef DEBIG_ENABLED
#define MAIN_THREAD_SYNC_CHECK \
if (unlikely(Thread::is_main_thread() && Engine::get_singleton()->notify_frame_server_synced())) { \
MAIN_THREAD_SYNC_WARN \
}
#else
#define MAIN_THREAD_SYNC_CHECK
#endif
#define FUNC0R(m_r, m_type) \
virtual m_r m_type() override { \
if (Thread::get_caller_id() != server_thread) { \
m_r ret; \
command_queue.push_and_ret(server_name, &ServerName::m_type, &ret); \
SYNC_DEBUG \
MAIN_THREAD_SYNC_CHECK \
return ret; \
} else { \
command_queue.flush_if_pending(); \
@ -68,6 +78,7 @@
m_r ret; \
command_queue.push_and_ret(server_name, &ServerName::m_type, &ret); \
SYNC_DEBUG \
MAIN_THREAD_SYNC_CHECK \
return ret; \
} else { \
command_queue.flush_if_pending(); \
@ -102,6 +113,7 @@
if (Thread::get_caller_id() != server_thread) { \
command_queue.push_and_sync(server_name, &ServerName::m_type); \
SYNC_DEBUG \
MAIN_THREAD_SYNC_CHECK \
} else { \
command_queue.flush_if_pending(); \
server_name->m_type(); \
@ -113,6 +125,7 @@
if (Thread::get_caller_id() != server_thread) { \
command_queue.push_and_sync(server_name, &ServerName::m_type); \
SYNC_DEBUG \
MAIN_THREAD_SYNC_CHECK \
} else { \
command_queue.flush_if_pending(); \
server_name->m_type(); \
@ -128,6 +141,7 @@
m_r ret; \
command_queue.push_and_ret(server_name, &ServerName::m_type, p1, &ret); \
SYNC_DEBUG \
MAIN_THREAD_SYNC_CHECK \
return ret; \
} else { \
command_queue.flush_if_pending(); \
@ -141,6 +155,7 @@
m_r ret; \
command_queue.push_and_ret(server_name, &ServerName::m_type, p1, &ret); \
SYNC_DEBUG \
MAIN_THREAD_SYNC_CHECK \
return ret; \
} else { \
command_queue.flush_if_pending(); \
@ -154,6 +169,7 @@
if (Thread::get_caller_id() != server_thread) { \
command_queue.push_and_sync(server_name, &ServerName::m_type, p1); \
SYNC_DEBUG \
MAIN_THREAD_SYNC_CHECK \
} else { \
command_queue.flush_if_pending(); \
server_name->m_type(p1); \
@ -165,6 +181,7 @@
if (Thread::get_caller_id() != server_thread) { \
command_queue.push_and_sync(server_name, &ServerName::m_type, p1); \
SYNC_DEBUG \
MAIN_THREAD_SYNC_CHECK \
} else { \
command_queue.flush_if_pending(); \
server_name->m_type(p1); \
@ -199,6 +216,7 @@
m_r ret; \
command_queue.push_and_ret(server_name, &ServerName::m_type, p1, p2, &ret); \
SYNC_DEBUG \
MAIN_THREAD_SYNC_CHECK \
return ret; \
} else { \
command_queue.flush_if_pending(); \
@ -212,6 +230,7 @@
m_r ret; \
command_queue.push_and_ret(server_name, &ServerName::m_type, p1, p2, &ret); \
SYNC_DEBUG \
MAIN_THREAD_SYNC_CHECK \
return ret; \
} else { \
command_queue.flush_if_pending(); \
@ -225,6 +244,7 @@
if (Thread::get_caller_id() != server_thread) { \
command_queue.push_and_sync(server_name, &ServerName::m_type, p1, p2); \
SYNC_DEBUG \
MAIN_THREAD_SYNC_CHECK \
} else { \
command_queue.flush_if_pending(); \
server_name->m_type(p1, p2); \
@ -236,6 +256,7 @@
if (Thread::get_caller_id() != server_thread) { \
command_queue.push_and_sync(server_name, &ServerName::m_type, p1, p2); \
SYNC_DEBUG \
MAIN_THREAD_SYNC_CHECK \
} else { \
command_queue.flush_if_pending(); \
server_name->m_type(p1, p2); \
@ -270,6 +291,7 @@
m_r ret; \
command_queue.push_and_ret(server_name, &ServerName::m_type, p1, p2, p3, &ret); \
SYNC_DEBUG \
MAIN_THREAD_SYNC_CHECK \
return ret; \
} else { \
command_queue.flush_if_pending(); \
@ -283,6 +305,7 @@
m_r ret; \
command_queue.push_and_ret(server_name, &ServerName::m_type, p1, p2, p3, &ret); \
SYNC_DEBUG \
MAIN_THREAD_SYNC_CHECK \
return ret; \
} else { \
command_queue.flush_if_pending(); \
@ -296,6 +319,7 @@
if (Thread::get_caller_id() != server_thread) { \
command_queue.push_and_sync(server_name, &ServerName::m_type, p1, p2, p3); \
SYNC_DEBUG \
MAIN_THREAD_SYNC_CHECK \
} else { \
command_queue.flush_if_pending(); \
server_name->m_type(p1, p2, p3); \
@ -307,6 +331,7 @@
if (Thread::get_caller_id() != server_thread) { \
command_queue.push_and_sync(server_name, &ServerName::m_type, p1, p2, p3); \
SYNC_DEBUG \
MAIN_THREAD_SYNC_CHECK \
} else { \
command_queue.flush_if_pending(); \
server_name->m_type(p1, p2, p3); \
@ -341,6 +366,7 @@
m_r ret; \
command_queue.push_and_ret(server_name, &ServerName::m_type, p1, p2, p3, p4, &ret); \
SYNC_DEBUG \
MAIN_THREAD_SYNC_CHECK \
return ret; \
} else { \
command_queue.flush_if_pending(); \
@ -354,6 +380,7 @@
m_r ret; \
command_queue.push_and_ret(server_name, &ServerName::m_type, p1, p2, p3, p4, &ret); \
SYNC_DEBUG \
MAIN_THREAD_SYNC_CHECK \
return ret; \
} else { \
command_queue.flush_if_pending(); \
@ -367,6 +394,7 @@
if (Thread::get_caller_id() != server_thread) { \
command_queue.push_and_sync(server_name, &ServerName::m_type, p1, p2, p3, p4); \
SYNC_DEBUG \
MAIN_THREAD_SYNC_CHECK \
} else { \
command_queue.flush_if_pending(); \
server_name->m_type(p1, p2, p3, p4); \
@ -378,6 +406,7 @@
if (Thread::get_caller_id() != server_thread) { \
command_queue.push_and_sync(server_name, &ServerName::m_type, p1, p2, p3, p4); \
SYNC_DEBUG \
MAIN_THREAD_SYNC_CHECK \
} else { \
command_queue.flush_if_pending(); \
server_name->m_type(p1, p2, p3, p4); \
@ -412,6 +441,7 @@
m_r ret; \
command_queue.push_and_ret(server_name, &ServerName::m_type, p1, p2, p3, p4, p5, &ret); \
SYNC_DEBUG \
MAIN_THREAD_SYNC_CHECK \
return ret; \
} else { \
command_queue.flush_if_pending(); \
@ -425,6 +455,7 @@
m_r ret; \
command_queue.push_and_ret(server_name, &ServerName::m_type, p1, p2, p3, p4, p5, &ret); \
SYNC_DEBUG \
MAIN_THREAD_SYNC_CHECK \
return ret; \
} else { \
command_queue.flush_if_pending(); \
@ -438,6 +469,7 @@
if (Thread::get_caller_id() != server_thread) { \
command_queue.push_and_sync(server_name, &ServerName::m_type, p1, p2, p3, p4, p5); \
SYNC_DEBUG \
MAIN_THREAD_SYNC_CHECK \
} else { \
command_queue.flush_if_pending(); \
server_name->m_type(p1, p2, p3, p4, p5); \
@ -449,6 +481,7 @@
if (Thread::get_caller_id() != server_thread) { \
command_queue.push_and_sync(server_name, &ServerName::m_type, p1, p2, p3, p4, p5); \
SYNC_DEBUG \
MAIN_THREAD_SYNC_CHECK \
} else { \
command_queue.flush_if_pending(); \
server_name->m_type(p1, p2, p3, p4, p5); \
@ -483,6 +516,7 @@
m_r ret; \
command_queue.push_and_ret(server_name, &ServerName::m_type, p1, p2, p3, p4, p5, p6, &ret); \
SYNC_DEBUG \
MAIN_THREAD_SYNC_CHECK \
return ret; \
} else { \
command_queue.flush_if_pending(); \
@ -496,6 +530,7 @@
m_r ret; \
command_queue.push_and_ret(server_name, &ServerName::m_type, p1, p2, p3, p4, p5, p6, &ret); \
SYNC_DEBUG \
MAIN_THREAD_SYNC_CHECK \
return ret; \
} else { \
command_queue.flush_if_pending(); \
@ -509,6 +544,7 @@
if (Thread::get_caller_id() != server_thread) { \
command_queue.push_and_sync(server_name, &ServerName::m_type, p1, p2, p3, p4, p5, p6); \
SYNC_DEBUG \
MAIN_THREAD_SYNC_CHECK \
} else { \
command_queue.flush_if_pending(); \
server_name->m_type(p1, p2, p3, p4, p5, p6); \
@ -520,6 +556,7 @@
if (Thread::get_caller_id() != server_thread) { \
command_queue.push_and_sync(server_name, &ServerName::m_type, p1, p2, p3, p4, p5, p6); \
SYNC_DEBUG \
MAIN_THREAD_SYNC_CHECK \
} else { \
command_queue.flush_if_pending(); \
server_name->m_type(p1, p2, p3, p4, p5, p6); \
@ -554,6 +591,7 @@
m_r ret; \
command_queue.push_and_ret(server_name, &ServerName::m_type, p1, p2, p3, p4, p5, p6, p7, &ret); \
SYNC_DEBUG \
MAIN_THREAD_SYNC_CHECK \
return ret; \
} else { \
command_queue.flush_if_pending(); \
@ -567,6 +605,7 @@
m_r ret; \
command_queue.push_and_ret(server_name, &ServerName::m_type, p1, p2, p3, p4, p5, p6, p7, &ret); \
SYNC_DEBUG \
MAIN_THREAD_SYNC_CHECK \
return ret; \
} else { \
command_queue.flush_if_pending(); \
@ -580,6 +619,7 @@
if (Thread::get_caller_id() != server_thread) { \
command_queue.push_and_sync(server_name, &ServerName::m_type, p1, p2, p3, p4, p5, p6, p7); \
SYNC_DEBUG \
MAIN_THREAD_SYNC_CHECK \
} else { \
command_queue.flush_if_pending(); \
server_name->m_type(p1, p2, p3, p4, p5, p6, p7); \
@ -591,6 +631,7 @@
if (Thread::get_caller_id() != server_thread) { \
command_queue.push_and_sync(server_name, &ServerName::m_type, p1, p2, p3, p4, p5, p6, p7); \
SYNC_DEBUG \
MAIN_THREAD_SYNC_CHECK \
} else { \
command_queue.flush_if_pending(); \
server_name->m_type(p1, p2, p3, p4, p5, p6, p7); \
@ -625,6 +666,7 @@
m_r ret; \
command_queue.push_and_ret(server_name, &ServerName::m_type, p1, p2, p3, p4, p5, p6, p7, p8, &ret); \
SYNC_DEBUG \
MAIN_THREAD_SYNC_CHECK \
return ret; \
} else { \
command_queue.flush_if_pending(); \
@ -638,6 +680,7 @@
m_r ret; \
command_queue.push_and_ret(server_name, &ServerName::m_type, p1, p2, p3, p4, p5, p6, p7, p8, &ret); \
SYNC_DEBUG \
MAIN_THREAD_SYNC_CHECK \
return ret; \
} else { \
command_queue.flush_if_pending(); \
@ -651,6 +694,7 @@
if (Thread::get_caller_id() != server_thread) { \
command_queue.push_and_sync(server_name, &ServerName::m_type, p1, p2, p3, p4, p5, p6, p7, p8); \
SYNC_DEBUG \
MAIN_THREAD_SYNC_CHECK \
} else { \
command_queue.flush_if_pending(); \
server_name->m_type(p1, p2, p3, p4, p5, p6, p7, p8); \
@ -662,6 +706,7 @@
if (Thread::get_caller_id() != server_thread) { \
command_queue.push_and_sync(server_name, &ServerName::m_type, p1, p2, p3, p4, p5, p6, p7, p8); \
SYNC_DEBUG \
MAIN_THREAD_SYNC_CHECK \
} else { \
command_queue.flush_if_pending(); \
server_name->m_type(p1, p2, p3, p4, p5, p6, p7, p8); \