Skip to content

Commit

Permalink
Let materials' shaders update happen on loader threads
Browse files Browse the repository at this point in the history
  • Loading branch information
RandomShaper committed May 6, 2024
1 parent 7ebc866 commit 187e5ef
Show file tree
Hide file tree
Showing 8 changed files with 35 additions and 119 deletions.
57 changes: 0 additions & 57 deletions core/object/message_queue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -224,64 +224,7 @@ void CallQueue::_call_function(const Callable &p_callable, const Variant *p_args
}
}

Error CallQueue::_transfer_messages_to_main_queue() {
if (pages.size() == 0) {
return OK;
}

CallQueue *mq = MessageQueue::main_singleton;
DEV_ASSERT(!mq->allocator_is_custom && !allocator_is_custom); // Transferring pages is only safe if using the same alloator parameters.

mq->mutex.lock();

// Here we're transferring the data from this queue to the main one.
// However, it's very unlikely big amounts of messages will be queued here,
// so PagedArray/Pool would be overkill. Also, in most cases the data will fit
// an already existing page of the main queue.

// Let's see if our first (likely only) page fits the current target queue page.
uint32_t src_page = 0;
{
if (mq->pages_used) {
uint32_t dst_page = mq->pages_used - 1;
uint32_t dst_offset = mq->page_bytes[dst_page];
if (dst_offset + page_bytes[0] < uint32_t(PAGE_SIZE_BYTES)) {
memcpy(mq->pages[dst_page]->data + dst_offset, pages[0]->data, page_bytes[0]);
mq->page_bytes[dst_page] += page_bytes[0];
src_page++;
}
}
}

// Any other possibly existing source page needs to be added.

if (mq->pages_used + (pages_used - src_page) > mq->max_pages) {
fprintf(stderr, "Failed appending thread queue. Message queue out of memory. %s\n", mq->error_text.utf8().get_data());
mq->statistics();
mq->mutex.unlock();
return ERR_OUT_OF_MEMORY;
}

for (; src_page < pages_used; src_page++) {
mq->_add_page();
memcpy(mq->pages[mq->pages_used - 1]->data, pages[src_page]->data, page_bytes[src_page]);
mq->page_bytes[mq->pages_used - 1] = page_bytes[src_page];
}

mq->mutex.unlock();

page_bytes[0] = 0;
pages_used = 1;

return OK;
}

Error CallQueue::flush() {
// Thread overrides are not meant to be flushed, but appended to the main one.
if (unlikely(this == MessageQueue::thread_singleton)) {
return _transfer_messages_to_main_queue();
}

LOCK_MUTEX;

if (pages.size() == 0) {
Expand Down
2 changes: 0 additions & 2 deletions core/object/message_queue.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,6 @@ class CallQueue {
}
}

Error _transfer_messages_to_main_queue();

void _add_page();

void _call_function(const Callable &p_callable, const Variant *p_args, int p_argcount, bool p_show_error);
Expand Down
26 changes: 9 additions & 17 deletions scene/resources/canvas_item_material.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,11 @@
#include "core/version.h"

Mutex CanvasItemMaterial::material_mutex;
SelfList<CanvasItemMaterial>::List *CanvasItemMaterial::dirty_materials = nullptr;
SelfList<CanvasItemMaterial>::List CanvasItemMaterial::dirty_materials;
HashMap<CanvasItemMaterial::MaterialKey, CanvasItemMaterial::ShaderData, CanvasItemMaterial::MaterialKey> CanvasItemMaterial::shader_map;
CanvasItemMaterial::ShaderNames *CanvasItemMaterial::shader_names = nullptr;

void CanvasItemMaterial::init_shaders() {
dirty_materials = memnew(SelfList<CanvasItemMaterial>::List);

shader_names = memnew(ShaderNames);

shader_names->particles_anim_h_frames = "particles_anim_h_frames";
Expand All @@ -48,14 +46,13 @@ void CanvasItemMaterial::init_shaders() {
}

void CanvasItemMaterial::finish_shaders() {
memdelete(dirty_materials);
dirty_materials.clear();

memdelete(shader_names);
dirty_materials = nullptr;
shader_names = nullptr;
}

void CanvasItemMaterial::_update_shader() {
dirty_materials->remove(&element);

MaterialKey mk = _compute_key();
if (mk.key == current_key.key) {
return; //no update required in the end
Expand Down Expand Up @@ -153,25 +150,20 @@ void CanvasItemMaterial::_update_shader() {
void CanvasItemMaterial::flush_changes() {
MutexLock lock(material_mutex);

while (dirty_materials->first()) {
dirty_materials->first()->self()->_update_shader();
while (dirty_materials.first()) {
dirty_materials.first()->self()->_update_shader();
dirty_materials.first()->remove_from_list();
}
}

void CanvasItemMaterial::_queue_shader_change() {
MutexLock lock(material_mutex);

if (_is_initialized() && !element.in_list()) {
dirty_materials->add(&element);
dirty_materials.add(&element);
}
}

bool CanvasItemMaterial::_is_shader_dirty() const {
MutexLock lock(material_mutex);

return element.in_list();
}

void CanvasItemMaterial::set_blend_mode(BlendMode p_blend_mode) {
blend_mode = p_blend_mode;
_queue_shader_change();
Expand Down Expand Up @@ -288,7 +280,7 @@ CanvasItemMaterial::CanvasItemMaterial() :

current_key.invalid_key = 1;

_mark_initialized(callable_mp(this, &CanvasItemMaterial::_queue_shader_change));
_mark_initialized(callable_mp(this, &CanvasItemMaterial::_queue_shader_change), callable_mp(this, &CanvasItemMaterial::_update_shader));
}

CanvasItemMaterial::~CanvasItemMaterial() {
Expand Down
3 changes: 1 addition & 2 deletions scene/resources/canvas_item_material.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,11 @@ class CanvasItemMaterial : public Material {
}

static Mutex material_mutex;
static SelfList<CanvasItemMaterial>::List *dirty_materials;
static SelfList<CanvasItemMaterial>::List dirty_materials;
SelfList<CanvasItemMaterial> element;

void _update_shader();
_FORCE_INLINE_ void _queue_shader_change();
_FORCE_INLINE_ bool _is_shader_dirty() const;

BlendMode blend_mode = BLEND_MODE_MIX;
LightMode light_mode = LIGHT_MODE_NORMAL;
Expand Down
34 changes: 14 additions & 20 deletions scene/resources/material.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,24 +82,25 @@ void Material::_validate_property(PropertyInfo &p_property) const {
}
}

void Material::_mark_initialized(const Callable &p_queue_shader_change_callable) {
void Material::_mark_ready() {
init_state = INIT_STATE_INITIALIZING;
}

void Material::_mark_initialized(const Callable &p_add_to_dirty_list, const Callable &p_update_shader) {
// If this is happening as part of resource loading, it is not safe to queue the update
// as an addition to the dirty list, unless the load is happening on the main thread.
if (ResourceLoader::is_within_load() && Thread::get_caller_id() != Thread::get_main_id()) {
// as an addition to the dirty list. It would be if the load is happening on the main thread,
// but even so we'd rather perform the update directly instead of using the dirty list.
if (ResourceLoader::is_within_load()) {
DEV_ASSERT(init_state != INIT_STATE_READY);
if (init_state == INIT_STATE_UNINITIALIZED) { // Prevent queueing twice.
// Let's mark this material as being initialized.
init_state = INIT_STATE_INITIALIZING;
// Knowing that the ResourceLoader will eventually feed deferred calls into the main message queue, let's do these:
// 1. Queue setting the init state to INIT_STATE_READY finally.
callable_mp(this, &Material::_mark_initialized).bind(p_queue_shader_change_callable).call_deferred();
// 2. Queue an individual update of this material.
p_queue_shader_change_callable.call_deferred();
callable_mp(this, &Material::_mark_ready).call_deferred();
p_update_shader.call_deferred();
}
} else {
// Straightforward conditions.
init_state = INIT_STATE_READY;
p_queue_shader_change_callable.callv(Array());
p_add_to_dirty_list.call();
}
}

Expand Down Expand Up @@ -603,8 +604,6 @@ void BaseMaterial3D::finish_shaders() {
}

void BaseMaterial3D::_update_shader() {
dirty_materials.remove(&element);

MaterialKey mk = _compute_key();
if (mk == current_key) {
return; //no update required in the end
Expand Down Expand Up @@ -1855,6 +1854,7 @@ void BaseMaterial3D::flush_changes() {

while (dirty_materials.first()) {
dirty_materials.first()->self()->_update_shader();
dirty_materials.first()->remove_from_list();
}
}

Expand All @@ -1866,12 +1866,6 @@ void BaseMaterial3D::_queue_shader_change() {
}
}

bool BaseMaterial3D::_is_shader_dirty() const {
MutexLock lock(material_mutex);

return element.in_list();
}

void BaseMaterial3D::set_albedo(const Color &p_albedo) {
albedo = p_albedo;

Expand Down Expand Up @@ -2824,7 +2818,7 @@ BaseMaterial3D::EmissionOperator BaseMaterial3D::get_emission_operator() const {

RID BaseMaterial3D::get_shader_rid() const {
MutexLock lock(material_mutex);
if (element.in_list()) { // _is_shader_dirty() would create anoder mutex lock
if (element.in_list()) {
((BaseMaterial3D *)this)->_update_shader();
}
ERR_FAIL_COND_V(!shader_map.has(current_key), RID());
Expand Down Expand Up @@ -3412,7 +3406,7 @@ BaseMaterial3D::BaseMaterial3D(bool p_orm) :

current_key.invalid_key = 1;

_mark_initialized(callable_mp(this, &BaseMaterial3D::_queue_shader_change));
_mark_initialized(callable_mp(this, &BaseMaterial3D::_queue_shader_change), callable_mp(this, &BaseMaterial3D::_update_shader));
}

BaseMaterial3D::~BaseMaterial3D() {
Expand Down
4 changes: 2 additions & 2 deletions scene/resources/material.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ class Material : public Resource {

void _validate_property(PropertyInfo &p_property) const;

void _mark_initialized(const Callable &p_queue_shader_change_callable);
void _mark_ready();
void _mark_initialized(const Callable &p_add_to_dirty_list, const Callable &p_update_shader);
bool _is_initialized() { return init_state == INIT_STATE_READY; }

GDVIRTUAL0RC(RID, _get_shader_rid)
Expand Down Expand Up @@ -466,7 +467,6 @@ class BaseMaterial3D : public Material {

void _update_shader();
_FORCE_INLINE_ void _queue_shader_change();
_FORCE_INLINE_ bool _is_shader_dirty() const;

bool orm;

Expand Down
25 changes: 8 additions & 17 deletions scene/resources/particle_process_material.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,12 @@
#include "core/version.h"

Mutex ParticleProcessMaterial::material_mutex;
SelfList<ParticleProcessMaterial>::List *ParticleProcessMaterial::dirty_materials = nullptr;
SelfList<ParticleProcessMaterial>::List ParticleProcessMaterial::dirty_materials;
HashMap<ParticleProcessMaterial::MaterialKey, ParticleProcessMaterial::ShaderData, ParticleProcessMaterial::MaterialKey> ParticleProcessMaterial::shader_map;
RBSet<String> ParticleProcessMaterial::min_max_properties;
ParticleProcessMaterial::ShaderNames *ParticleProcessMaterial::shader_names = nullptr;

void ParticleProcessMaterial::init_shaders() {
dirty_materials = memnew(SelfList<ParticleProcessMaterial>::List);

shader_names = memnew(ShaderNames);

shader_names->direction = "direction";
Expand Down Expand Up @@ -140,15 +138,13 @@ void ParticleProcessMaterial::init_shaders() {
}

void ParticleProcessMaterial::finish_shaders() {
memdelete(dirty_materials);
dirty_materials = nullptr;
dirty_materials.clear();

memdelete(shader_names);
shader_names = nullptr;
}

void ParticleProcessMaterial::_update_shader() {
dirty_materials->remove(&element);

MaterialKey mk = _compute_key();
if (mk == current_key) {
return; //no update required in the end
Expand Down Expand Up @@ -1170,25 +1166,20 @@ void ParticleProcessMaterial::_update_shader() {
void ParticleProcessMaterial::flush_changes() {
MutexLock lock(material_mutex);

while (dirty_materials->first()) {
dirty_materials->first()->self()->_update_shader();
while (dirty_materials.first()) {
dirty_materials.first()->self()->_update_shader();
dirty_materials.first()->remove_from_list();
}
}

void ParticleProcessMaterial::_queue_shader_change() {
MutexLock lock(material_mutex);

if (_is_initialized() && !element.in_list()) {
dirty_materials->add(&element);
dirty_materials.add(&element);
}
}

bool ParticleProcessMaterial::_is_shader_dirty() const {
MutexLock lock(material_mutex);

return element.in_list();
}

bool ParticleProcessMaterial::has_min_max_property(const String &p_name) {
return min_max_properties.has(p_name);
}
Expand Down Expand Up @@ -2330,7 +2321,7 @@ ParticleProcessMaterial::ParticleProcessMaterial() :

current_key.invalid_key = 1;

_mark_initialized(callable_mp(this, &ParticleProcessMaterial::_queue_shader_change));
_mark_initialized(callable_mp(this, &ParticleProcessMaterial::_queue_shader_change), callable_mp(this, &ParticleProcessMaterial::_update_shader));
}

ParticleProcessMaterial::~ParticleProcessMaterial() {
Expand Down
3 changes: 1 addition & 2 deletions scene/resources/particle_process_material.h
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ class ParticleProcessMaterial : public Material {
}

static Mutex material_mutex;
static SelfList<ParticleProcessMaterial>::List *dirty_materials;
static SelfList<ParticleProcessMaterial>::List dirty_materials;

struct ShaderNames {
StringName direction;
Expand Down Expand Up @@ -293,7 +293,6 @@ class ParticleProcessMaterial : public Material {

void _update_shader();
_FORCE_INLINE_ void _queue_shader_change();
_FORCE_INLINE_ bool _is_shader_dirty() const;

Vector3 direction;
float spread = 0.0f;
Expand Down

0 comments on commit 187e5ef

Please sign in to comment.