diff options
30 files changed, 684 insertions, 387 deletions
diff --git a/core/object/message_queue.cpp b/core/object/message_queue.cpp index 1542decc6f..7a8dae2341 100644 --- a/core/object/message_queue.cpp +++ b/core/object/message_queue.cpp @@ -35,94 +35,55 @@ #include "core/object/class_db.h" #include "core/object/script_language.h" -MessageQueue *MessageQueue::singleton = nullptr; - -MessageQueue *MessageQueue::get_singleton() { - return singleton; -} - -Error MessageQueue::push_callp(ObjectID p_id, const StringName &p_method, const Variant **p_args, int p_argcount, bool p_show_error) { - return push_callablep(Callable(p_id, p_method), p_args, p_argcount, p_show_error); -} - -Error MessageQueue::push_set(ObjectID p_id, const StringName &p_prop, const Variant &p_value) { - _THREAD_SAFE_METHOD_ - - uint8_t room_needed = sizeof(Message) + sizeof(Variant); - - if ((buffer_end + room_needed) >= buffer_size) { - String type; - if (ObjectDB::get_instance(p_id)) { - type = ObjectDB::get_instance(p_id)->get_class(); - } - ERR_PRINT("Failed set: " + type + ":" + p_prop + " target ID: " + itos(p_id) + ". Message queue out of memory. Try increasing \"memory/limits/message_queue/max_size_kb\" in project settings."); - statistics(); - return ERR_OUT_OF_MEMORY; +void CallQueue::_add_page() { + if (pages_used == page_messages.size()) { + pages.push_back(allocator->alloc()); + page_messages.push_back(0); } - - Message *msg = memnew_placement(&buffer[buffer_end], Message); - msg->args = 1; - msg->callable = Callable(p_id, p_prop); - msg->type = TYPE_SET; - - buffer_end += sizeof(Message); - - Variant *v = memnew_placement(&buffer[buffer_end], Variant); - buffer_end += sizeof(Variant); - *v = p_value; - - return OK; + page_messages[pages_used] = 0; + pages_used++; + page_offset = 0; } -Error MessageQueue::push_notification(ObjectID p_id, int p_notification) { - _THREAD_SAFE_METHOD_ - - ERR_FAIL_COND_V(p_notification < 0, ERR_INVALID_PARAMETER); - - uint8_t room_needed = sizeof(Message); - - if ((buffer_end + room_needed) >= buffer_size) { - ERR_PRINT("Failed notification: " + itos(p_notification) + " target ID: " + itos(p_id) + ". Message queue out of memory. Try increasing \"memory/limits/message_queue/max_size_kb\" in project settings."); - statistics(); - return ERR_OUT_OF_MEMORY; - } - - Message *msg = memnew_placement(&buffer[buffer_end], Message); - - msg->type = TYPE_NOTIFICATION; - msg->callable = Callable(p_id, CoreStringNames::get_singleton()->notification); //name is meaningless but callable needs it - //msg->target; - msg->notification = p_notification; - - buffer_end += sizeof(Message); - - return OK; +Error CallQueue::push_callp(ObjectID p_id, const StringName &p_method, const Variant **p_args, int p_argcount, bool p_show_error) { + return push_callablep(Callable(p_id, p_method), p_args, p_argcount, p_show_error); } -Error MessageQueue::push_callp(Object *p_object, const StringName &p_method, const Variant **p_args, int p_argcount, bool p_show_error) { +Error CallQueue::push_callp(Object *p_object, const StringName &p_method, const Variant **p_args, int p_argcount, bool p_show_error) { return push_callp(p_object->get_instance_id(), p_method, p_args, p_argcount, p_show_error); } -Error MessageQueue::push_notification(Object *p_object, int p_notification) { +Error CallQueue::push_notification(Object *p_object, int p_notification) { return push_notification(p_object->get_instance_id(), p_notification); } -Error MessageQueue::push_set(Object *p_object, const StringName &p_prop, const Variant &p_value) { +Error CallQueue::push_set(Object *p_object, const StringName &p_prop, const Variant &p_value) { return push_set(p_object->get_instance_id(), p_prop, p_value); } -Error MessageQueue::push_callablep(const Callable &p_callable, const Variant **p_args, int p_argcount, bool p_show_error) { - _THREAD_SAFE_METHOD_ +Error CallQueue::push_callablep(const Callable &p_callable, const Variant **p_args, int p_argcount, bool p_show_error) { + mutex.lock(); + uint32_t room_needed = sizeof(Message) + sizeof(Variant) * p_argcount; - int room_needed = sizeof(Message) + sizeof(Variant) * p_argcount; + ERR_FAIL_COND_V_MSG(room_needed > uint32_t(PAGE_SIZE_BYTES), ERR_INVALID_PARAMETER, "Message is too large to fit on a page (" + itos(PAGE_SIZE_BYTES) + " bytes), consider passing less arguments."); - if ((buffer_end + room_needed) >= buffer_size) { - ERR_PRINT("Failed method: " + p_callable + ". Message queue out of memory. Try increasing \"memory/limits/message_queue/max_size_kb\" in project settings."); - statistics(); - return ERR_OUT_OF_MEMORY; + _ensure_first_page(); + + if ((page_offset + room_needed) > uint32_t(PAGE_SIZE_BYTES)) { + if (room_needed > uint32_t(PAGE_SIZE_BYTES) || pages_used == max_pages) { + ERR_PRINT("Failed method: " + p_callable + ". Message queue out of memory. " + error_text); + statistics(); + mutex.unlock(); + return ERR_OUT_OF_MEMORY; + } + _add_page(); } - Message *msg = memnew_placement(&buffer[buffer_end], Message); + Page *page = pages[pages_used - 1]; + + uint8_t *buffer_end = &page->data[page_offset]; + + Message *msg = memnew_placement(buffer_end, Message); msg->args = p_argcount; msg->callable = p_callable; msg->type = TYPE_CALL; @@ -133,89 +94,95 @@ Error MessageQueue::push_callablep(const Callable &p_callable, const Variant **p buffer_end += sizeof(Message); for (int i = 0; i < p_argcount; i++) { - Variant *v = memnew_placement(&buffer[buffer_end], Variant); + Variant *v = memnew_placement(buffer_end, Variant); buffer_end += sizeof(Variant); *v = *p_args[i]; } + page_messages[pages_used - 1]++; + page_offset += room_needed; + + mutex.unlock(); + return OK; } -void MessageQueue::statistics() { - HashMap<StringName, int> set_count; - HashMap<int, int> notify_count; - HashMap<Callable, int> call_count; - int null_count = 0; +Error CallQueue::push_set(ObjectID p_id, const StringName &p_prop, const Variant &p_value) { + mutex.lock(); + uint32_t room_needed = sizeof(Message) + sizeof(Variant); - uint32_t read_pos = 0; - while (read_pos < buffer_end) { - Message *message = (Message *)&buffer[read_pos]; + _ensure_first_page(); - Object *target = message->callable.get_object(); + if ((page_offset + room_needed) > uint32_t(PAGE_SIZE_BYTES)) { + if (pages_used == max_pages) { + String type; + if (ObjectDB::get_instance(p_id)) { + type = ObjectDB::get_instance(p_id)->get_class(); + } + ERR_PRINT("Failed set: " + type + ":" + p_prop + " target ID: " + itos(p_id) + ". Message queue out of memory. " + error_text); + statistics(); - if (target != nullptr) { - switch (message->type & FLAG_MASK) { - case TYPE_CALL: { - if (!call_count.has(message->callable)) { - call_count[message->callable] = 0; - } + mutex.unlock(); + return ERR_OUT_OF_MEMORY; + } + _add_page(); + } - call_count[message->callable]++; + Page *page = pages[pages_used - 1]; + uint8_t *buffer_end = &page->data[page_offset]; - } break; - case TYPE_NOTIFICATION: { - if (!notify_count.has(message->notification)) { - notify_count[message->notification] = 0; - } + Message *msg = memnew_placement(buffer_end, Message); + msg->args = 1; + msg->callable = Callable(p_id, p_prop); + msg->type = TYPE_SET; - notify_count[message->notification]++; + buffer_end += sizeof(Message); - } break; - case TYPE_SET: { - StringName t = message->callable.get_method(); - if (!set_count.has(t)) { - set_count[t] = 0; - } + Variant *v = memnew_placement(buffer_end, Variant); + *v = p_value; - set_count[t]++; + page_messages[pages_used - 1]++; + page_offset += room_needed; + mutex.unlock(); - } break; - } + return OK; +} - } else { - //object was deleted - print_line("Object was deleted while awaiting a callback"); +Error CallQueue::push_notification(ObjectID p_id, int p_notification) { + ERR_FAIL_COND_V(p_notification < 0, ERR_INVALID_PARAMETER); + mutex.lock(); + uint32_t room_needed = sizeof(Message); - null_count++; - } + _ensure_first_page(); - read_pos += sizeof(Message); - if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) { - read_pos += sizeof(Variant) * message->args; + if ((page_offset + room_needed) > uint32_t(PAGE_SIZE_BYTES)) { + if (pages_used == max_pages) { + ERR_PRINT("Failed notification: " + itos(p_notification) + " target ID: " + itos(p_id) + ". Message queue out of memory. " + error_text); + statistics(); + mutex.unlock(); + return ERR_OUT_OF_MEMORY; } + _add_page(); } - print_line("TOTAL BYTES: " + itos(buffer_end)); - print_line("NULL count: " + itos(null_count)); + Page *page = pages[pages_used - 1]; + uint8_t *buffer_end = &page->data[page_offset]; - for (const KeyValue<StringName, int> &E : set_count) { - print_line("SET " + E.key + ": " + itos(E.value)); - } + Message *msg = memnew_placement(buffer_end, Message); - for (const KeyValue<Callable, int> &E : call_count) { - print_line("CALL " + E.key + ": " + itos(E.value)); - } + msg->type = TYPE_NOTIFICATION; + msg->callable = Callable(p_id, CoreStringNames::get_singleton()->notification); //name is meaningless but callable needs it + //msg->target; + msg->notification = p_notification; - for (const KeyValue<int, int> &E : notify_count) { - print_line("NOTIFY " + itos(E.key) + ": " + itos(E.value)); - } -} + page_messages[pages_used - 1]++; + page_offset += room_needed; + mutex.unlock(); -int MessageQueue::get_max_buffer_usage() const { - return buffer_max_used; + return OK; } -void MessageQueue::_call_function(const Callable &p_callable, const Variant *p_args, int p_argcount, bool p_show_error) { +void CallQueue::_call_function(const Callable &p_callable, const Variant *p_args, int p_argcount, bool p_show_error) { const Variant **argptrs = nullptr; if (p_argcount) { argptrs = (const Variant **)alloca(sizeof(Variant *) * p_argcount); @@ -232,26 +199,32 @@ void MessageQueue::_call_function(const Callable &p_callable, const Variant *p_a } } -void MessageQueue::flush() { - if (buffer_end > buffer_max_used) { - buffer_max_used = buffer_end; - } +Error CallQueue::flush() { + mutex.lock(); - uint32_t read_pos = 0; - - //using reverse locking strategy - _THREAD_SAFE_LOCK_ + if (pages.size() == 0) { + // Never allocated + mutex.unlock(); + return OK; // Do nothing. + } if (flushing) { - _THREAD_SAFE_UNLOCK_ - ERR_FAIL_COND(flushing); //already flushing, you did something odd + mutex.unlock(); + return ERR_BUSY; } + flushing = true; - while (read_pos < buffer_end) { + uint32_t i = 0; + uint32_t j = 0; + uint32_t offset = 0; + + while (i < pages_used && j < page_messages[i]) { + Page *page = pages[i]; + //lock on each iteration, so a call can re-add itself to the message queue - Message *message = (Message *)&buffer[read_pos]; + Message *message = (Message *)&page->data[offset]; uint32_t advance = sizeof(Message); if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) { @@ -259,12 +232,12 @@ void MessageQueue::flush() { } //pre-advance so this function is reentrant - read_pos += advance; - - _THREAD_SAFE_UNLOCK_ + offset += advance; Object *target = message->callable.get_object(); + mutex.unlock(); + if (target != nullptr) { switch (message->type & FLAG_MASK) { case TYPE_CALL: { @@ -291,54 +264,208 @@ void MessageQueue::flush() { if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) { Variant *args = (Variant *)(message + 1); - for (int i = 0; i < message->args; i++) { - args[i].~Variant(); + for (int k = 0; k < message->args; k++) { + args[k].~Variant(); } } message->~Message(); - _THREAD_SAFE_LOCK_ + mutex.lock(); + j++; + if (j == page_messages[i]) { + j = 0; + i++; + offset = 0; + } } - buffer_end = 0; // reset buffer + page_messages[0] = 0; + page_offset = 0; + pages_used = 1; + flushing = false; - _THREAD_SAFE_UNLOCK_ + mutex.unlock(); + return OK; } -bool MessageQueue::is_flushing() const { - return flushing; -} +void CallQueue::clear() { + mutex.lock(); -MessageQueue::MessageQueue() { - ERR_FAIL_COND_MSG(singleton != nullptr, "A MessageQueue singleton already exists."); - singleton = this; + if (pages.size() == 0) { + mutex.unlock(); + return; // Nothing to clear. + } - buffer_size = GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "memory/limits/message_queue/max_size_kb", PROPERTY_HINT_RANGE, "1024,4096,1,or_greater"), DEFAULT_QUEUE_SIZE_KB); - buffer_size *= 1024; - buffer = memnew_arr(uint8_t, buffer_size); -} + for (uint32_t i = 0; i < pages_used; i++) { + uint32_t offset = 0; + for (uint32_t j = 0; j < page_messages[i]; j++) { + Page *page = pages[i]; -MessageQueue::~MessageQueue() { - uint32_t read_pos = 0; + //lock on each iteration, so a call can re-add itself to the message queue - while (read_pos < buffer_end) { - Message *message = (Message *)&buffer[read_pos]; - Variant *args = (Variant *)(message + 1); - int argc = message->args; - if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) { - for (int i = 0; i < argc; i++) { - args[i].~Variant(); + Message *message = (Message *)&page->data[offset]; + + uint32_t advance = sizeof(Message); + if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) { + advance += sizeof(Variant) * message->args; } + + //pre-advance so this function is reentrant + offset += advance; + + if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) { + Variant *args = (Variant *)(message + 1); + for (int k = 0; k < message->args; k++) { + args[k].~Variant(); + } + } + + message->~Message(); } - message->~Message(); + } - read_pos += sizeof(Message); - if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) { - read_pos += sizeof(Variant) * message->args; + pages_used = 1; + page_offset = 0; + page_messages[0] = 0; + + mutex.unlock(); +} + +void CallQueue::statistics() { + mutex.lock(); + HashMap<StringName, int> set_count; + HashMap<int, int> notify_count; + HashMap<Callable, int> call_count; + int null_count = 0; + + for (uint32_t i = 0; i < pages_used; i++) { + uint32_t offset = 0; + for (uint32_t j = 0; j < page_messages[i]; j++) { + Page *page = pages[i]; + + //lock on each iteration, so a call can re-add itself to the message queue + + Message *message = (Message *)&page->data[offset]; + + uint32_t advance = sizeof(Message); + if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) { + advance += sizeof(Variant) * message->args; + } + + Object *target = message->callable.get_object(); + + if (target != nullptr) { + switch (message->type & FLAG_MASK) { + case TYPE_CALL: { + if (!call_count.has(message->callable)) { + call_count[message->callable] = 0; + } + + call_count[message->callable]++; + + } break; + case TYPE_NOTIFICATION: { + if (!notify_count.has(message->notification)) { + notify_count[message->notification] = 0; + } + + notify_count[message->notification]++; + + } break; + case TYPE_SET: { + StringName t = message->callable.get_method(); + if (!set_count.has(t)) { + set_count[t] = 0; + } + + set_count[t]++; + + } break; + } + + } else { + //object was deleted + print_line("Object was deleted while awaiting a callback"); + + null_count++; + } + + //pre-advance so this function is reentrant + offset += advance; + + if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) { + Variant *args = (Variant *)(message + 1); + for (int k = 0; k < message->args; k++) { + args[k].~Variant(); + } + } + + message->~Message(); } } + print_line("TOTAL PAGES: " + itos(pages_used) + " (" + itos(pages_used * PAGE_SIZE_BYTES) + " bytes)."); + print_line("NULL count: " + itos(null_count)); + + for (const KeyValue<StringName, int> &E : set_count) { + print_line("SET " + E.key + ": " + itos(E.value)); + } + + for (const KeyValue<Callable, int> &E : call_count) { + print_line("CALL " + E.key + ": " + itos(E.value)); + } + + for (const KeyValue<int, int> &E : notify_count) { + print_line("NOTIFY " + itos(E.key) + ": " + itos(E.value)); + } + + mutex.unlock(); +} + +bool CallQueue::is_flushing() const { + return flushing; +} + +int CallQueue::get_max_buffer_usage() const { + return pages.size() * PAGE_SIZE_BYTES; +} + +CallQueue::CallQueue(Allocator *p_custom_allocator, uint32_t p_max_pages, const String &p_error_text) { + if (p_custom_allocator) { + allocator = p_custom_allocator; + allocator_is_custom = true; + } else { + allocator = memnew(Allocator(16)); // 16 elements per allocator page, 64kb per allocator page. Anything small will do, though. + allocator_is_custom = false; + } + max_pages = p_max_pages; + error_text = p_error_text; +} + +CallQueue::~CallQueue() { + clear(); + // Let go of pages. + for (uint32_t i = 0; i < pages.size(); i++) { + allocator->free(pages[i]); + } + if (!allocator_is_custom) { + memdelete(allocator); + } +} + +////////////////////// + +MessageQueue *MessageQueue::singleton = nullptr; + +MessageQueue::MessageQueue() : + CallQueue(nullptr, + int(GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "memory/limits/message_queue/max_size_mb", PROPERTY_HINT_RANGE, "1,512,1,or_greater"), 32)) * 1024 * 1024 / PAGE_SIZE_BYTES, + "Message queue out of memory. Try increasing 'memory/limits/message_queue/max_size_mb' in project settings.") { + ERR_FAIL_COND_MSG(singleton != nullptr, "A MessageQueue singleton already exists."); + singleton = this; +} + +MessageQueue::~MessageQueue() { singleton = nullptr; - memdelete_arr(buffer); } diff --git a/core/object/message_queue.h b/core/object/message_queue.h index ceb6f2a3f1..525285e061 100644 --- a/core/object/message_queue.h +++ b/core/object/message_queue.h @@ -33,26 +33,45 @@ #include "core/object/object_id.h" #include "core/os/thread_safe.h" +#include "core/templates/local_vector.h" +#include "core/templates/paged_allocator.h" #include "core/variant/variant.h" class Object; -class MessageQueue { - _THREAD_SAFE_CLASS_ - +class CallQueue { +public: enum { - DEFAULT_QUEUE_SIZE_KB = 4096 + PAGE_SIZE_BYTES = 4096 }; +private: enum { TYPE_CALL, TYPE_NOTIFICATION, TYPE_SET, + TYPE_END, // End marker. FLAG_SHOW_ERROR = 1 << 14, - FLAG_MASK = FLAG_SHOW_ERROR - 1 + FLAG_MASK = FLAG_SHOW_ERROR - 1, + }; + struct Page { + uint8_t data[PAGE_SIZE_BYTES]; }; + Mutex mutex; + typedef PagedAllocator<Page, true> Allocator; + + Allocator *allocator = nullptr; + bool allocator_is_custom = false; + + LocalVector<Page *> pages; + LocalVector<uint32_t> page_messages; + uint32_t max_pages = 0; + uint32_t pages_used = 0; + uint32_t page_offset = 0; + bool flushing = false; + struct Message { Callable callable; int16_t type; @@ -62,20 +81,21 @@ class MessageQueue { }; }; - uint8_t *buffer = nullptr; - uint32_t buffer_end = 0; - uint32_t buffer_max_used = 0; - uint32_t buffer_size = 0; + _FORCE_INLINE_ void _ensure_first_page() { + if (unlikely(pages.is_empty())) { + pages.push_back(allocator->alloc()); + page_messages.push_back(0); + pages_used = 1; + } + } + + void _add_page(); void _call_function(const Callable &p_callable, const Variant *p_args, int p_argcount, bool p_show_error); - static MessageQueue *singleton; - - bool flushing = false; + String error_text; public: - static MessageQueue *get_singleton(); - Error push_callp(ObjectID p_id, const StringName &p_method, const Variant **p_args, int p_argcount, bool p_show_error = false); template <typename... VarArgs> Error push_call(ObjectID p_id, const StringName &p_method, VarArgs... p_args) { @@ -87,9 +107,9 @@ public: return push_callp(p_id, p_method, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args)); } - Error push_notification(ObjectID p_id, int p_notification); - Error push_set(ObjectID p_id, const StringName &p_prop, const Variant &p_value); Error push_callablep(const Callable &p_callable, const Variant **p_args, int p_argcount, bool p_show_error = false); + Error push_set(ObjectID p_id, const StringName &p_prop, const Variant &p_value); + Error push_notification(ObjectID p_id, int p_notification); template <typename... VarArgs> Error push_callable(const Callable &p_callable, VarArgs... p_args) { @@ -115,13 +135,22 @@ public: Error push_notification(Object *p_object, int p_notification); Error push_set(Object *p_object, const StringName &p_prop, const Variant &p_value); + Error flush(); + void clear(); void statistics(); - void flush(); bool is_flushing() const; - int get_max_buffer_usage() const; + CallQueue(Allocator *p_custom_allocator = 0, uint32_t p_max_pages = 8192, const String &p_error_text = String()); + virtual ~CallQueue(); +}; + +class MessageQueue : public CallQueue { + static MessageQueue *singleton; + +public: + _FORCE_INLINE_ static MessageQueue *get_singleton() { return singleton; } MessageQueue(); ~MessageQueue(); }; diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 1a77e7c525..710b9889f1 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -1739,7 +1739,7 @@ <member name="layer_names/3d_render/layer_9" type="String" setter="" getter="" default=""""> Optional name for the 3D render layer 9. If left empty, the layer will display as "Layer 9". </member> - <member name="memory/limits/message_queue/max_size_kb" type="int" setter="" getter="" default="4096"> + <member name="memory/limits/message_queue/max_size_mb" type="int" setter="" getter="" default="32"> Godot uses a message queue to defer some function calls. If you run out of space on it (you will see an error), you can increase the size here. </member> <member name="memory/limits/multithreaded_server/rid_pool_prealloc" type="int" setter="" getter="" default="60"> diff --git a/doc/classes/RenderingDevice.xml b/doc/classes/RenderingDevice.xml index 745926d4a8..f597cb7efc 100644 --- a/doc/classes/RenderingDevice.xml +++ b/doc/classes/RenderingDevice.xml @@ -113,7 +113,7 @@ </method> <method name="compute_pipeline_is_valid"> <return type="bool" /> - <param index="0" name="compute_pieline" type="RID" /> + <param index="0" name="compute_pipeline" type="RID" /> <description> </description> </method> diff --git a/doc/classes/TextServer.xml b/doc/classes/TextServer.xml index 4f38785369..4118274732 100644 --- a/doc/classes/TextServer.xml +++ b/doc/classes/TextServer.xml @@ -1784,6 +1784,9 @@ <constant name="GRAPHEME_IS_SAFE_TO_INSERT_TATWEEL" value="2048" enum="GraphemeFlag" is_bitfield="true"> It is safe to insert a U+0640 before this grapheme for elongation. </constant> + <constant name="GRAPHEME_IS_EMBEDDED_OBJECT" value="4096" enum="GraphemeFlag" is_bitfield="true"> + Grapheme is an object replacement character for the embedded object. + </constant> <constant name="HINTING_NONE" value="0" enum="Hinting"> Disables font hinting (smoother but less crisp). </constant> diff --git a/drivers/gles3/rasterizer_canvas_gles3.cpp b/drivers/gles3/rasterizer_canvas_gles3.cpp index 522685bf87..19f2cfd47d 100644 --- a/drivers/gles3/rasterizer_canvas_gles3.cpp +++ b/drivers/gles3/rasterizer_canvas_gles3.cpp @@ -1310,6 +1310,7 @@ void RasterizerCanvasGLES3::_render_batch(Light *p_lights, uint32_t p_index) { uint32_t instance_color_offset = 0; bool instance_uses_color = false; bool instance_uses_custom_data = false; + bool use_instancing = false; if (state.canvas_instance_batches[p_index].command_type == Item::Command::TYPE_MESH) { const Item::CommandMesh *m = static_cast<const Item::CommandMesh *>(state.canvas_instance_batches[p_index].command); @@ -1336,6 +1337,7 @@ void RasterizerCanvasGLES3::_render_batch(Light *p_lights, uint32_t p_index) { instance_color_offset = mesh_storage->multimesh_get_color_offset(multimesh); instance_uses_color = mesh_storage->multimesh_uses_colors(multimesh); instance_uses_custom_data = mesh_storage->multimesh_uses_custom_data(multimesh); + use_instancing = true; } else if (state.canvas_instance_batches[p_index].command_type == Item::Command::TYPE_PARTICLES) { const Item::CommandParticles *pt = static_cast<const Item::CommandParticles *>(state.canvas_instance_batches[p_index].command); @@ -1362,6 +1364,7 @@ void RasterizerCanvasGLES3::_render_batch(Light *p_lights, uint32_t p_index) { instance_color_offset = 8; // 8 bytes for instance transform. instance_uses_color = true; instance_uses_custom_data = true; + use_instancing = true; } ERR_FAIL_COND(mesh.is_null()); @@ -1397,7 +1400,7 @@ void RasterizerCanvasGLES3::_render_batch(Light *p_lights, uint32_t p_index) { use_index_buffer = true; } - if (instance_count > 1) { + if (use_instancing) { if (instance_buffer == 0) { break; } @@ -1426,7 +1429,7 @@ void RasterizerCanvasGLES3::_render_batch(Light *p_lights, uint32_t p_index) { } glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - if (instance_count > 1) { + if (use_instancing) { glDisableVertexAttribArray(5); glDisableVertexAttribArray(6); glDisableVertexAttribArray(7); diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp index b3d6b01c6c..3d8f7924a7 100644 --- a/drivers/gles3/rasterizer_scene_gles3.cpp +++ b/drivers/gles3/rasterizer_scene_gles3.cpp @@ -1948,7 +1948,7 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_ glDepthFunc(GL_LEQUAL); glDepthMask(GL_TRUE); scene_state.current_depth_test = GLES3::SceneShaderData::DEPTH_TEST_ENABLED; - scene_state.current_depth_draw = GLES3::SceneShaderData::DEPTH_DRAW_OPAQUE; + scene_state.current_depth_draw = GLES3::SceneShaderData::DEPTH_DRAW_ALWAYS; if (!fb_cleared) { glClearDepth(1.0f); @@ -1976,19 +1976,17 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_ _render_list_template<PASS_MODE_COLOR>(&render_list_params, &render_data, 0, render_list[RENDER_LIST_OPAQUE].elements.size()); + glDepthMask(GL_FALSE); + scene_state.current_depth_draw = GLES3::SceneShaderData::DEPTH_DRAW_DISABLED; + if (draw_sky) { RENDER_TIMESTAMP("Render Sky"); - if (scene_state.current_depth_test != GLES3::SceneShaderData::DEPTH_TEST_ENABLED) { - glEnable(GL_DEPTH_TEST); - scene_state.current_depth_test = GLES3::SceneShaderData::DEPTH_TEST_ENABLED; - } + glEnable(GL_DEPTH_TEST); - glDepthMask(GL_FALSE); glDisable(GL_BLEND); glEnable(GL_CULL_FACE); glCullFace(GL_BACK); scene_state.current_depth_test = GLES3::SceneShaderData::DEPTH_TEST_ENABLED; - scene_state.current_depth_draw = GLES3::SceneShaderData::DEPTH_DRAW_DISABLED; scene_state.cull_mode = GLES3::SceneShaderData::CULL_BACK; _draw_sky(render_data.environment, render_data.cam_projection, render_data.cam_transform, sky_energy_multiplier, p_camera_data->view_count > 1, flip_y); diff --git a/drivers/gles3/shaders/particles.glsl b/drivers/gles3/shaders/particles.glsl index f8741a22ab..40881a1808 100644 --- a/drivers/gles3/shaders/particles.glsl +++ b/drivers/gles3/shaders/particles.glsl @@ -300,28 +300,23 @@ void main() { vec3 rel_vec = xform[3].xyz - attractors[i].transform[3].xyz; vec3 local_pos = rel_vec * mat3(attractors[i].transform); - switch (attractors[i].type) { - case ATTRACTOR_TYPE_SPHERE: { - dir = safe_normalize(rel_vec); - float d = length(local_pos) / attractors[i].extents.x; - if (d > 1.0) { - continue; - } - amount = max(0.0, 1.0 - d); - } break; - case ATTRACTOR_TYPE_BOX: { - dir = safe_normalize(rel_vec); - - vec3 abs_pos = abs(local_pos / attractors[i].extents.xyz); - float d = max(abs_pos.x, max(abs_pos.y, abs_pos.z)); - if (d > 1.0) { - continue; - } - amount = max(0.0, 1.0 - d); - - } break; - case ATTRACTOR_TYPE_VECTOR_FIELD: { - } break; + if (attractors[i].type == ATTRACTOR_TYPE_SPHERE) { + dir = safe_normalize(rel_vec); + float d = length(local_pos) / attractors[i].extents.x; + if (d > 1.0) { + continue; + } + amount = max(0.0, 1.0 - d); + } else if (attractors[i].type == ATTRACTOR_TYPE_BOX) { + dir = safe_normalize(rel_vec); + + vec3 abs_pos = abs(local_pos / attractors[i].extents.xyz); + float d = max(abs_pos.x, max(abs_pos.y, abs_pos.z)); + if (d > 1.0) { + continue; + } + amount = max(0.0, 1.0 - d); + } else if (attractors[i].type == ATTRACTOR_TYPE_VECTOR_FIELD) { } amount = pow(amount, attractors[i].attenuation); dir = safe_normalize(mix(dir, attractors[i].transform[2].xyz, attractors[i].directionality)); @@ -383,80 +378,72 @@ void main() { vec3 rel_vec = xform[3].xyz - colliders[i].transform[3].xyz; vec3 local_pos = rel_vec * mat3(colliders[i].transform); - switch (colliders[i].type) { - case COLLIDER_TYPE_SPHERE: { - float d = length(rel_vec) - (particle_size + colliders[i].extents.x); + if (colliders[i].type == COLLIDER_TYPE_SPHERE) { + float d = length(rel_vec) - (particle_size + colliders[i].extents.x); - if (d < 0.0) { - col = true; - depth = -d; - normal = normalize(rel_vec); - } + if (d < 0.0) { + col = true; + depth = -d; + normal = normalize(rel_vec); + } + } else if (colliders[i].type == COLLIDER_TYPE_BOX) { + vec3 abs_pos = abs(local_pos); + vec3 sgn_pos = sign(local_pos); - } break; - case COLLIDER_TYPE_BOX: { - vec3 abs_pos = abs(local_pos); - vec3 sgn_pos = sign(local_pos); - - if (any(greaterThan(abs_pos, colliders[i].extents.xyz))) { - //point outside box - - vec3 closest = min(abs_pos, colliders[i].extents.xyz); - vec3 rel = abs_pos - closest; - depth = length(rel) - particle_size; - if (depth < 0.0) { - col = true; - normal = mat3(colliders[i].transform) * (normalize(rel) * sgn_pos); - depth = -depth; - } - } else { - //point inside box - vec3 axis_len = colliders[i].extents.xyz - abs_pos; - // there has to be a faster way to do this? - if (all(lessThan(axis_len.xx, axis_len.yz))) { - normal = vec3(1, 0, 0); - } else if (all(lessThan(axis_len.yy, axis_len.xz))) { - normal = vec3(0, 1, 0); - } else { - normal = vec3(0, 0, 1); - } + if (any(greaterThan(abs_pos, colliders[i].extents.xyz))) { + //point outside box + vec3 closest = min(abs_pos, colliders[i].extents.xyz); + vec3 rel = abs_pos - closest; + depth = length(rel) - particle_size; + if (depth < 0.0) { col = true; - depth = dot(normal * axis_len, vec3(1)) + particle_size; - normal = mat3(colliders[i].transform) * (normal * sgn_pos); + normal = mat3(colliders[i].transform) * (normalize(rel) * sgn_pos); + depth = -depth; } - - } break; - case COLLIDER_TYPE_SDF: { - } break; - case COLLIDER_TYPE_HEIGHT_FIELD: { - vec3 local_pos_bottom = local_pos; - local_pos_bottom.y -= particle_size; - - if (any(greaterThan(abs(local_pos_bottom), colliders[i].extents.xyz))) { - continue; + } else { + //point inside box + vec3 axis_len = colliders[i].extents.xyz - abs_pos; + // there has to be a faster way to do this? + if (all(lessThan(axis_len.xx, axis_len.yz))) { + normal = vec3(1, 0, 0); + } else if (all(lessThan(axis_len.yy, axis_len.xz))) { + normal = vec3(0, 1, 0); + } else { + normal = vec3(0, 0, 1); } - const float DELTA = 1.0 / 8192.0; - vec3 uvw_pos = vec3(local_pos_bottom / colliders[i].extents.xyz) * 0.5 + 0.5; + col = true; + depth = dot(normal * axis_len, vec3(1)) + particle_size; + normal = mat3(colliders[i].transform) * (normal * sgn_pos); + } + } else if (colliders[i].type == COLLIDER_TYPE_SDF) { + } else if (colliders[i].type == COLLIDER_TYPE_HEIGHT_FIELD) { + vec3 local_pos_bottom = local_pos; + local_pos_bottom.y -= particle_size; - float y = 1.0 - texture(height_field_texture, uvw_pos.xz).r; + if (any(greaterThan(abs(local_pos_bottom), colliders[i].extents.xyz))) { + continue; + } + const float DELTA = 1.0 / 8192.0; - if (y > uvw_pos.y) { - //inside heightfield + vec3 uvw_pos = vec3(local_pos_bottom / colliders[i].extents.xyz) * 0.5 + 0.5; - vec3 pos1 = (vec3(uvw_pos.x, y, uvw_pos.z) * 2.0 - 1.0) * colliders[i].extents.xyz; - vec3 pos2 = (vec3(uvw_pos.x + DELTA, 1.0 - texture(height_field_texture, uvw_pos.xz + vec2(DELTA, 0)).r, uvw_pos.z) * 2.0 - 1.0) * colliders[i].extents.xyz; - vec3 pos3 = (vec3(uvw_pos.x, 1.0 - texture(height_field_texture, uvw_pos.xz + vec2(0, DELTA)).r, uvw_pos.z + DELTA) * 2.0 - 1.0) * colliders[i].extents.xyz; + float y = 1.0 - texture(height_field_texture, uvw_pos.xz).r; - normal = normalize(cross(pos1 - pos2, pos1 - pos3)); - float local_y = (vec3(local_pos / colliders[i].extents.xyz) * 0.5 + 0.5).y; + if (y > uvw_pos.y) { + //inside heightfield - col = true; - depth = dot(normal, pos1) - dot(normal, local_pos_bottom); - } + vec3 pos1 = (vec3(uvw_pos.x, y, uvw_pos.z) * 2.0 - 1.0) * colliders[i].extents.xyz; + vec3 pos2 = (vec3(uvw_pos.x + DELTA, 1.0 - texture(height_field_texture, uvw_pos.xz + vec2(DELTA, 0)).r, uvw_pos.z) * 2.0 - 1.0) * colliders[i].extents.xyz; + vec3 pos3 = (vec3(uvw_pos.x, 1.0 - texture(height_field_texture, uvw_pos.xz + vec2(0, DELTA)).r, uvw_pos.z + DELTA) * 2.0 - 1.0) * colliders[i].extents.xyz; - } break; + normal = normalize(cross(pos1 - pos2, pos1 - pos3)); + float local_y = (vec3(local_pos / colliders[i].extents.xyz) * 0.5 + 0.5).y; + + col = true; + depth = dot(normal, pos1) - dot(normal, local_pos_bottom); + } } if (col) { diff --git a/drivers/gles3/storage/particles_storage.cpp b/drivers/gles3/storage/particles_storage.cpp index 2b47271408..4b64e2aec1 100644 --- a/drivers/gles3/storage/particles_storage.cpp +++ b/drivers/gles3/storage/particles_storage.cpp @@ -919,7 +919,7 @@ void ParticlesStorage::_particles_update_instance_buffer(Particles *particles, c glBeginTransformFeedback(GL_POINTS); if (particles->draw_order == RS::PARTICLES_DRAW_ORDER_LIFETIME) { - uint32_t lifetime_split = MIN(particles->amount * particles->phase, particles->amount - 1); + uint32_t lifetime_split = (MIN(int(particles->amount * particles->phase), particles->amount - 1) + 1) % particles->amount; uint32_t stride = particles->process_buffer_stride_cache; glBindBuffer(GL_ARRAY_BUFFER, particles->back_process_buffer); @@ -1135,14 +1135,13 @@ void ParticlesStorage::_particles_reverse_lifetime_sort(Particles *particles) { glGetBufferSubData(GL_ARRAY_BUFFER, 0, buffer_size, particle_array); #endif - uint32_t lifetime_split = MIN(particles->amount * particles->sort_buffer_phase, particles->amount - 1); - + uint32_t lifetime_split = (MIN(int(particles->amount * particles->sort_buffer_phase), particles->amount - 1) + 1) % particles->amount; for (uint32_t i = 0; i < lifetime_split / 2; i++) { - SWAP(particle_array[i], particle_array[lifetime_split - i]); + SWAP(particle_array[i], particle_array[lifetime_split - i - 1]); } for (uint32_t i = 0; i < (particles->amount - lifetime_split) / 2; i++) { - SWAP(particle_array[lifetime_split + i + 1], particle_array[particles->amount - 1 - i]); + SWAP(particle_array[lifetime_split + i], particle_array[particles->amount - 1 - i]); } #ifndef __EMSCRIPTEN__ diff --git a/editor/editor_log.cpp b/editor/editor_log.cpp index 8d199ad989..1113a10e38 100644 --- a/editor/editor_log.cpp +++ b/editor/editor_log.cpp @@ -276,6 +276,11 @@ void EditorLog::_add_log_line(LogMessage &p_message, bool p_replace_previous) { return; } + if (unlikely(log->is_updating())) { + // The new message arrived during log RTL text processing/redraw (invalid BiDi control characters / font error), ignore it to avoid RTL data corruption. + return; + } + // Only add the message to the log if it passes the filters. bool filter_active = type_filter_map[p_message.type]->is_active(); String search_text = search_box->get_text(); diff --git a/editor/editor_properties_array_dict.cpp b/editor/editor_properties_array_dict.cpp index f7c789a453..be66fbafac 100644 --- a/editor/editor_properties_array_dict.cpp +++ b/editor/editor_properties_array_dict.cpp @@ -38,34 +38,48 @@ #include "editor/gui/editor_spin_slider.h" #include "editor/inspector_dock.h" #include "scene/gui/button.h" +#include "scene/resources/packed_scene.h" bool EditorPropertyArrayObject::_set(const StringName &p_name, const Variant &p_value) { String name = p_name; - if (name.begins_with("indices")) { - int index = name.get_slicec('/', 1).to_int(); - array.set(index, p_value); - return true; + if (!name.begins_with("indices") && !name.begins_with(PackedScene::META_POINTER_PROPERTY_BASE + "indices")) { + return false; } - return false; + int index; + if (name.begins_with("metadata/")) { + index = name.get_slice("/", 2).to_int(); + } else { + index = name.get_slice("/", 1).to_int(); + } + + array.set(index, p_value); + return true; } bool EditorPropertyArrayObject::_get(const StringName &p_name, Variant &r_ret) const { String name = p_name; - if (name.begins_with("indices")) { - int index = name.get_slicec('/', 1).to_int(); - bool valid; - r_ret = array.get(index, &valid); - if (r_ret.get_type() == Variant::OBJECT && Object::cast_to<EncodedObjectAsID>(r_ret)) { - r_ret = Object::cast_to<EncodedObjectAsID>(r_ret)->get_object_id(); - } + if (!name.begins_with("indices") && !name.begins_with(PackedScene::META_POINTER_PROPERTY_BASE + "indices")) { + return false; + } - return valid; + int index; + if (name.begins_with("metadata/")) { + index = name.get_slice("/", 2).to_int(); + } else { + index = name.get_slice("/", 1).to_int(); } - return false; + bool valid; + r_ret = array.get(index, &valid); + + if (r_ret.get_type() == Variant::OBJECT && Object::cast_to<EncodedObjectAsID>(r_ret)) { + r_ret = Object::cast_to<EncodedObjectAsID>(r_ret)->get_object_id(); + } + + return valid; } void EditorPropertyArrayObject::set_array(const Variant &p_array) { @@ -178,15 +192,22 @@ void EditorPropertyArray::initialize_array(Variant &p_array) { } void EditorPropertyArray::_property_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) { - if (p_property.begins_with("indices")) { - int index = p_property.get_slice("/", 1).to_int(); - - Variant array = object->get_array().duplicate(); - array.set(index, p_value); + if (!p_property.begins_with("indices") && !p_property.begins_with(PackedScene::META_POINTER_PROPERTY_BASE + "indices")) { + return; + } - object->set_array(array); - emit_changed(get_edited_property(), array, "", true); + int index; + if (p_property.begins_with("metadata/")) { + index = p_property.get_slice("/", 2).to_int(); + } else { + index = p_property.get_slice("/", 1).to_int(); } + + Array array; + array.assign(object->get_array().duplicate()); + array.set(index, p_value); + object->set_array(array); + emit_changed(get_edited_property(), array, "", true); } void EditorPropertyArray::_change_type(Object *p_button, int p_index) { @@ -690,11 +711,6 @@ EditorPropertyArray::EditorPropertyArray() { add_child(edit); add_focusable(edit); - container = nullptr; - property_vbox = nullptr; - size_slider = nullptr; - button_add_item = nullptr; - paginator = nullptr; change_type = memnew(PopupMenu); add_child(change_type); change_type->connect("id_pressed", callable_mp(this, &EditorPropertyArray::_change_type_menu)); diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp index 43c7f49395..dfc820050f 100644 --- a/modules/text_server_adv/text_server_adv.cpp +++ b/modules/text_server_adv/text_server_adv.cpp @@ -4138,6 +4138,7 @@ RID TextServerAdvanced::_shaped_text_substr(const RID &p_shaped, int64_t p_start new_sd->direction = sd->direction; new_sd->custom_punct = sd->custom_punct; new_sd->para_direction = sd->para_direction; + new_sd->base_para_direction = sd->base_para_direction; for (int i = 0; i < TextServer::SPACING_MAX; i++) { new_sd->extra_spacing[i] = sd->extra_spacing[i]; } @@ -4188,9 +4189,33 @@ bool TextServerAdvanced::_shape_substr(ShapedTextDataAdvanced *p_new_sd, const S if (U_SUCCESS(err)) { ubidi_setLine(p_sd->bidi_iter[ov], start, end, bidi_iter, &err); if (U_FAILURE(err)) { - ubidi_close(bidi_iter); - bidi_iter = nullptr; - ERR_PRINT(vformat("BiDi reordering for the line failed: %s", u_errorName(err))); + // Line BiDi failed (string contains incompatible control characters), try full paragraph BiDi instead. + err = U_ZERO_ERROR; + const UChar *data = p_sd->utf16.get_data(); + switch (static_cast<TextServer::Direction>(p_sd->bidi_override[ov].z)) { + case DIRECTION_LTR: { + ubidi_setPara(bidi_iter, data + start, end - start, UBIDI_LTR, nullptr, &err); + } break; + case DIRECTION_RTL: { + ubidi_setPara(bidi_iter, data + start, end - start, UBIDI_RTL, nullptr, &err); + } break; + case DIRECTION_INHERITED: { + ubidi_setPara(bidi_iter, data + start, end - start, p_sd->base_para_direction, nullptr, &err); + } break; + case DIRECTION_AUTO: { + UBiDiDirection direction = ubidi_getBaseDirection(data + start, end - start); + if (direction != UBIDI_NEUTRAL) { + ubidi_setPara(bidi_iter, data + start, end - start, direction, nullptr, &err); + } else { + ubidi_setPara(bidi_iter, data + start, end - start, p_sd->base_para_direction, nullptr, &err); + } + } break; + } + if (U_FAILURE(err)) { + ubidi_close(bidi_iter); + bidi_iter = nullptr; + ERR_PRINT(vformat("BiDi reordering for the line failed: %s", u_errorName(err))); + } } } else { bidi_iter = nullptr; @@ -5619,25 +5644,25 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) { sd->script_iter = memnew(ScriptIterator(sd->text, 0, sd->text.length())); } - int base_para_direction = UBIDI_DEFAULT_LTR; + sd->base_para_direction = UBIDI_DEFAULT_LTR; switch (sd->direction) { case DIRECTION_LTR: { sd->para_direction = DIRECTION_LTR; - base_para_direction = UBIDI_LTR; + sd->base_para_direction = UBIDI_LTR; } break; case DIRECTION_RTL: { sd->para_direction = DIRECTION_RTL; - base_para_direction = UBIDI_RTL; + sd->base_para_direction = UBIDI_RTL; } break; case DIRECTION_INHERITED: case DIRECTION_AUTO: { UBiDiDirection direction = ubidi_getBaseDirection(data, sd->utf16.length()); if (direction != UBIDI_NEUTRAL) { sd->para_direction = (direction == UBIDI_RTL) ? DIRECTION_RTL : DIRECTION_LTR; - base_para_direction = direction; + sd->base_para_direction = direction; } else { sd->para_direction = DIRECTION_LTR; - base_para_direction = UBIDI_DEFAULT_LTR; + sd->base_para_direction = UBIDI_DEFAULT_LTR; } } break; } @@ -5666,14 +5691,14 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) { ubidi_setPara(bidi_iter, data + start, end - start, UBIDI_RTL, nullptr, &err); } break; case DIRECTION_INHERITED: { - ubidi_setPara(bidi_iter, data + start, end - start, base_para_direction, nullptr, &err); + ubidi_setPara(bidi_iter, data + start, end - start, sd->base_para_direction, nullptr, &err); } break; case DIRECTION_AUTO: { UBiDiDirection direction = ubidi_getBaseDirection(data + start, end - start); if (direction != UBIDI_NEUTRAL) { ubidi_setPara(bidi_iter, data + start, end - start, direction, nullptr, &err); } else { - ubidi_setPara(bidi_iter, data + start, end - start, base_para_direction, nullptr, &err); + ubidi_setPara(bidi_iter, data + start, end - start, sd->base_para_direction, nullptr, &err); } } break; } @@ -5760,7 +5785,7 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) { gl.start = span.start; gl.end = span.end; gl.count = 1; - gl.flags = GRAPHEME_IS_VALID | GRAPHEME_IS_VIRTUAL; + gl.flags = GRAPHEME_IS_VALID | GRAPHEME_IS_EMBEDDED_OBJECT; if (sd->orientation == ORIENTATION_HORIZONTAL) { gl.advance = sd->objects[span.embedded_key].rect.size.x; } else { diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h index af3a0c7876..4b8c3f7cd3 100644 --- a/modules/text_server_adv/text_server_adv.h +++ b/modules/text_server_adv/text_server_adv.h @@ -476,6 +476,7 @@ class TextServerAdvanced : public TextServerExtension { /* Shaped data */ TextServer::Direction para_direction = DIRECTION_LTR; // Detected text direction. + int base_para_direction = UBIDI_DEFAULT_LTR; bool valid = false; // String is shaped. bool line_breaks_valid = false; // Line and word break flags are populated (and virtual zero width spaces inserted). bool justification_ops_valid = false; // Virtual elongation glyphs are added to the string. diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp index 9095be46af..8b210bdc38 100644 --- a/modules/text_server_fb/text_server_fb.cpp +++ b/modules/text_server_fb/text_server_fb.cpp @@ -3670,7 +3670,7 @@ bool TextServerFallback::_shaped_text_shape(const RID &p_shaped) { gl.end = span.end; gl.count = 1; gl.index = 0; - gl.flags = GRAPHEME_IS_VALID | GRAPHEME_IS_VIRTUAL; + gl.flags = GRAPHEME_IS_VALID | GRAPHEME_IS_EMBEDDED_OBJECT; if (sd->orientation == ORIENTATION_HORIZONTAL) { gl.advance = sd->objects[span.embedded_key].rect.size.x; } else { diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index dcd99f4374..d699236053 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -952,7 +952,7 @@ void LineEdit::_notification(int p_what) { if (ceil(ofs.x) >= x_ofs && (ofs.x + glyphs[i].advance) <= ofs_max) { if (glyphs[i].font_rid != RID()) { TS->font_draw_glyph(glyphs[i].font_rid, ci, glyphs[i].font_size, ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, selected ? font_selected_color : font_color); - } else if ((glyphs[i].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { + } else if (((glyphs[i].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) && ((glyphs[i].flags & TextServer::GRAPHEME_IS_EMBEDDED_OBJECT) != TextServer::GRAPHEME_IS_EMBEDDED_OBJECT)) { TS->draw_hex_code_box(ci, glyphs[i].font_size, ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, selected ? font_selected_color : font_color); } } diff --git a/scene/gui/range.cpp b/scene/gui/range.cpp index bd081074ec..97a1483204 100644 --- a/scene/gui/range.cpp +++ b/scene/gui/range.cpp @@ -74,16 +74,26 @@ void Range::Shared::emit_changed(const char *p_what) { } } +void Range::Shared::redraw_owners() { + for (Range *E : owners) { + Range *r = E; + if (!r->is_inside_tree()) { + continue; + } + r->queue_redraw(); + } +} + void Range::set_value(double p_val) { double prev_val = shared->val; - set_value_no_signal(p_val); + _set_value_no_signal(p_val); if (shared->val != prev_val) { shared->emit_value_changed(); } } -void Range::set_value_no_signal(double p_val) { +void Range::_set_value_no_signal(double p_val) { if (shared->step > 0) { p_val = Math::round((p_val - shared->min) / shared->step) * shared->step + shared->min; } @@ -107,6 +117,15 @@ void Range::set_value_no_signal(double p_val) { shared->val = p_val; } +void Range::set_value_no_signal(double p_val) { + double prev_val = shared->val; + _set_value_no_signal(p_val); + + if (shared->val != prev_val) { + shared->redraw_owners(); + } +} + void Range::set_min(double p_min) { if (shared->min == p_min) { return; diff --git a/scene/gui/range.h b/scene/gui/range.h index bf71fcc1c9..9b4f0707e6 100644 --- a/scene/gui/range.h +++ b/scene/gui/range.h @@ -48,6 +48,7 @@ class Range : public Control { HashSet<Range *> owners; void emit_value_changed(); void emit_changed(const char *p_what = ""); + void redraw_owners(); }; Shared *shared = nullptr; @@ -59,6 +60,7 @@ class Range : public Control { void _value_changed_notify(); void _changed_notify(const char *p_what = ""); + void _set_value_no_signal(double p_val); protected: virtual void _value_changed(double p_value); diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 1ae24b6d70..43c68a8eca 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -1323,7 +1323,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o if (!skip) { if (frid != RID()) { TS->font_draw_glyph(frid, ci, glyphs[i].font_size, p_ofs + fx_offset + off, gl, font_color); - } else if ((glyphs[i].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { + } else if (((glyphs[i].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) && ((glyphs[i].flags & TextServer::GRAPHEME_IS_EMBEDDED_OBJECT) != TextServer::GRAPHEME_IS_EMBEDDED_OBJECT)) { TS->draw_hex_code_box(ci, glyphs[i].font_size, p_ofs + fx_offset + off, gl, font_color); } } @@ -2705,6 +2705,10 @@ bool RichTextLabel::is_ready() const { return (main->first_invalid_line.load() == (int)main->lines.size() && main->first_resized_line.load() == (int)main->lines.size() && main->first_invalid_font_line.load() == (int)main->lines.size()); } +bool RichTextLabel::is_updating() const { + return updating.load() || validating.load(); +} + void RichTextLabel::set_threaded(bool p_threaded) { if (threaded != p_threaded) { _stop_thread(); @@ -2729,6 +2733,7 @@ bool RichTextLabel::_validate_line_caches() { if (updating.load()) { return false; } + validating.store(true); if (main->first_invalid_line.load() == (int)main->lines.size()) { MutexLock data_lock(data_mutex); Rect2 text_rect = _get_text_rect(); @@ -2747,6 +2752,7 @@ bool RichTextLabel::_validate_line_caches() { if (main->first_resized_line.load() == (int)main->lines.size()) { vscroll->set_value(old_scroll); + validating.store(false); return true; } @@ -2798,8 +2804,10 @@ bool RichTextLabel::_validate_line_caches() { if (fit_content) { update_minimum_size(); } + validating.store(false); return true; } + validating.store(false); stop_thread.store(false); if (threaded) { updating.store(true); @@ -2809,7 +2817,9 @@ bool RichTextLabel::_validate_line_caches() { loading_started = OS::get_singleton()->get_ticks_msec(); return false; } else { + updating.store(true); _process_line_caches(); + updating.store(false); queue_redraw(); return true; } @@ -5895,6 +5905,7 @@ RichTextLabel::RichTextLabel(const String &p_text) { set_text(p_text); updating.store(false); + validating.store(false); stop_thread.store(false); set_clip_contents(true); diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index fef34f7260..567528652c 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -377,6 +377,7 @@ private: bool threaded = false; std::atomic<bool> stop_thread; std::atomic<bool> updating; + std::atomic<bool> validating; std::atomic<double> loaded; uint64_t loading_started = 0; @@ -678,6 +679,7 @@ public: void deselect(); bool is_ready() const; + bool is_updating() const; void set_threaded(bool p_threaded); bool is_threaded() const; diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp index f99b2edd54..e4c7be33f0 100644 --- a/scene/gui/spin_box.cpp +++ b/scene/gui/spin_box.cpp @@ -39,7 +39,7 @@ Size2 SpinBox::get_minimum_size() const { return ms; } -void SpinBox::_value_changed(double p_value) { +void SpinBox::_update_text() { String value = String::num(get_value(), Math::range_step_decimals(get_step())); if (is_localizing_numeral_system()) { value = TS->format_number(value); @@ -55,7 +55,6 @@ void SpinBox::_value_changed(double p_value) { } line_edit->set_text(value); - Range::_value_changed(p_value); } void SpinBox::_text_submitted(const String &p_string) { @@ -73,7 +72,7 @@ void SpinBox::_text_submitted(const String &p_string) { if (value.get_type() != Variant::NIL) { set_value(value); } - _value_changed(0); + _update_text(); } void SpinBox::_text_changed(const String &p_string) { @@ -192,7 +191,7 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) { void SpinBox::_line_edit_focus_enter() { int col = line_edit->get_caret_column(); - _value_changed(0); // Update the LineEdit's text. + _update_text(); line_edit->set_caret_column(col); // LineEdit text might change and it clears any selection. Have to re-select here. @@ -202,6 +201,10 @@ void SpinBox::_line_edit_focus_enter() { } void SpinBox::_line_edit_focus_exit() { + // Discontinue because the focus_exit was caused by left-clicking the arrows. + if (get_viewport()->gui_get_focus_owner() == get_line_edit()) { + return; + } // Discontinue because the focus_exit was caused by right-click context menu. if (line_edit->is_menu_visible()) { return; @@ -228,6 +231,7 @@ void SpinBox::_update_theme_item_cache() { void SpinBox::_notification(int p_what) { switch (p_what) { case NOTIFICATION_DRAW: { + _update_text(); _adjust_width_for_icon(theme_cache.updown_icon); RID ci = get_canvas_item(); @@ -242,7 +246,7 @@ void SpinBox::_notification(int p_what) { case NOTIFICATION_ENTER_TREE: { _adjust_width_for_icon(theme_cache.updown_icon); - _value_changed(0); + _update_text(); } break; case NOTIFICATION_EXIT_TREE: { @@ -250,7 +254,6 @@ void SpinBox::_notification(int p_what) { } break; case NOTIFICATION_TRANSLATION_CHANGED: { - _value_changed(0); queue_redraw(); } break; @@ -279,7 +282,7 @@ void SpinBox::set_suffix(const String &p_suffix) { } suffix = p_suffix; - _value_changed(0); + _update_text(); } String SpinBox::get_suffix() const { @@ -292,7 +295,7 @@ void SpinBox::set_prefix(const String &p_prefix) { } prefix = p_prefix; - _value_changed(0); + _update_text(); } String SpinBox::get_prefix() const { diff --git a/scene/gui/spin_box.h b/scene/gui/spin_box.h index 0e9d424f68..29b278c50e 100644 --- a/scene/gui/spin_box.h +++ b/scene/gui/spin_box.h @@ -46,8 +46,8 @@ class SpinBox : public Range { void _range_click_timeout(); void _release_mouse(); + void _update_text(); void _text_submitted(const String &p_string); - virtual void _value_changed(double p_value) override; void _text_changed(const String &p_string); String prefix; diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 0839e4066d..db674c50fb 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -1280,7 +1280,7 @@ void TextEdit::_notification(int p_what) { if (glyphs[j].font_rid != RID()) { TS->font_draw_glyph(glyphs[j].font_rid, ci, glyphs[j].font_size, Vector2(char_margin + char_ofs + ofs_x + glyphs[j].x_off, ofs_y + glyphs[j].y_off), glyphs[j].index, gl_color); had_glyphs_drawn = true; - } else if ((glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { + } else if (((glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) && ((glyphs[j].flags & TextServer::GRAPHEME_IS_EMBEDDED_OBJECT) != TextServer::GRAPHEME_IS_EMBEDDED_OBJECT)) { TS->draw_hex_code_box(ci, glyphs[j].font_size, Vector2(char_margin + char_ofs + ofs_x + glyphs[j].x_off, ofs_y + glyphs[j].y_off), glyphs[j].index, gl_color); had_glyphs_drawn = true; } diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index e71c236dc7..e22890562f 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -4594,8 +4594,8 @@ int Tree::get_item_offset(TreeItem *p_item) const { return ofs; } - ofs += compute_item_height(it); - if (it != root || !hide_root) { + if ((it != root || !hide_root) && it->is_visible()) { + ofs += compute_item_height(it); ofs += theme_cache.v_separation; } diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp index 1e9038139e..a1e7558653 100644 --- a/scene/resources/packed_scene.cpp +++ b/scene/resources/packed_scene.cpp @@ -44,7 +44,9 @@ #include "scene/property_utils.h" #define PACKED_SCENE_VERSION 3 -#define META_POINTER_PROPERTY_BASE "metadata/_editor_prop_ptr_" + +const String PackedScene::META_POINTER_PROPERTY_BASE = "metadata/_editor_prop_ptr_"; + bool SceneState::can_instantiate() const { return nodes.size() > 0; } @@ -239,15 +241,38 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { if (nprops[j].name & FLAG_PATH_PROPERTY_IS_NODE) { uint32_t name_idx = nprops[j].name & (FLAG_PATH_PROPERTY_IS_NODE - 1); ERR_FAIL_UNSIGNED_INDEX_V(name_idx, (uint32_t)sname_count, nullptr); - if (Engine::get_singleton()->is_editor_hint()) { - // If editor, just set the metadata and be it - node->set(META_POINTER_PROPERTY_BASE + String(snames[name_idx]), props[nprops[j].value]); + + const StringName &prop_name = snames[name_idx]; + const Variant &prop_variant = props[nprops[j].value]; + + if (prop_variant.get_type() == Variant::ARRAY) { + if (Engine::get_singleton()->is_editor_hint()) { + // If editor, simply set the original array of NodePaths. + node->set(prop_name, prop_variant); + continue; + } + + const Array &array = prop_variant; + for (int k = 0; k < array.size(); k++) { + DeferredNodePathProperties dnp; + dnp.path = array[k]; + dnp.base = node; + // Use special property name to signify an array. This is only used in deferred_node_paths. + dnp.property = String(prop_name) + "/indices/" + itos(k); + deferred_node_paths.push_back(dnp); + } + } else { + if (Engine::get_singleton()->is_editor_hint()) { + // If editor, just set the metadata and be it. + node->set(PackedScene::META_POINTER_PROPERTY_BASE + String(prop_name), prop_variant); + continue; + } // Do an actual deferred sed of the property path. DeferredNodePathProperties dnp; - dnp.path = props[nprops[j].value]; + dnp.path = prop_variant; dnp.base = node; - dnp.property = snames[name_idx]; + dnp.property = prop_name; deferred_node_paths.push_back(dnp); } continue; @@ -415,8 +440,26 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { } for (const DeferredNodePathProperties &dnp : deferred_node_paths) { + // Replace properties stored as NodePaths with actual Nodes. Node *other = dnp.base->get_node_or_null(dnp.path); - dnp.base->set(dnp.property, other); + + const String string_property = dnp.property; + if (string_property.contains("/indices/")) { + // For properties with "/indices/", the replacement takes place inside an Array. + const String base_property = string_property.get_slice("/", 0); + const int index = string_property.get_slice("/", 2).to_int(); + + Array array = dnp.base->get(base_property); + if (array.size() >= index) { + array.push_back(other); + } else { + array.set(index, other); + } + + dnp.base->set(base_property, array); + } else { + dnp.base->set(dnp.property, other); + } } for (KeyValue<Ref<Resource>, Ref<Resource>> &E : resources_local_to_scene) { @@ -584,7 +627,7 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Has if (E.name == META_PROPERTY_MISSING_RESOURCES) { continue; // Ignore this property when packing. } - if (E.name.begins_with(META_POINTER_PROPERTY_BASE)) { + if (E.name.begins_with(PackedScene::META_POINTER_PROPERTY_BASE)) { continue; // do not save. } @@ -600,7 +643,7 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Has bool use_deferred_node_path_bit = false; if (E.type == Variant::OBJECT && E.hint == PROPERTY_HINT_NODE_TYPE) { - value = p_node->get(META_POINTER_PROPERTY_BASE + E.name); + value = p_node->get(PackedScene::META_POINTER_PROPERTY_BASE + E.name); if (value.get_type() != Variant::NODE_PATH) { continue; //was never set, ignore. } @@ -611,6 +654,20 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Has if (ures.is_null()) { value = missing_resource_properties[E.name]; } + } else if (E.type == Variant::ARRAY && E.hint == PROPERTY_HINT_TYPE_STRING) { + int hint_subtype_separator = E.hint_string.find(":"); + if (hint_subtype_separator >= 0) { + String subtype_string = E.hint_string.substr(0, hint_subtype_separator); + int slash_pos = subtype_string.find("/"); + PropertyHint subtype_hint = PropertyHint::PROPERTY_HINT_NONE; + if (slash_pos >= 0) { + subtype_hint = PropertyHint(subtype_string.get_slice("/", 1).to_int()); + subtype_string = subtype_string.substr(0, slash_pos); + } + Variant::Type subtype = Variant::Type(subtype_string.to_int()); + + use_deferred_node_path_bit = subtype == Variant::OBJECT && subtype_hint == PROPERTY_HINT_NODE_TYPE; + } } if (!pinned_props.has(name)) { @@ -1736,7 +1793,7 @@ void SceneState::add_editable_instance(const NodePath &p_path) { } String SceneState::get_meta_pointer_property(const String &p_property) { - return META_POINTER_PROPERTY_BASE + p_property; + return PackedScene::META_POINTER_PROPERTY_BASE + p_property; } Vector<String> SceneState::_get_node_groups(int p_idx) const { diff --git a/scene/resources/packed_scene.h b/scene/resources/packed_scene.h index 5c53ffdb45..1967deab36 100644 --- a/scene/resources/packed_scene.h +++ b/scene/resources/packed_scene.h @@ -221,6 +221,8 @@ protected: virtual void reset_state() override; public: + static const String META_POINTER_PROPERTY_BASE; + enum GenEditState { GEN_EDIT_STATE_DISABLED, GEN_EDIT_STATE_INSTANCE, diff --git a/servers/rendering/renderer_rd/storage_rd/particles_storage.cpp b/servers/rendering/renderer_rd/storage_rd/particles_storage.cpp index d8b78de526..b36e027f07 100644 --- a/servers/rendering/renderer_rd/storage_rd/particles_storage.cpp +++ b/servers/rendering/renderer_rd/storage_rd/particles_storage.cpp @@ -1202,7 +1202,7 @@ void ParticlesStorage::particles_set_view_axis(RID p_particles, const Vector3 &p } copy_push_constant.order_by_lifetime = (particles->draw_order == RS::PARTICLES_DRAW_ORDER_LIFETIME || particles->draw_order == RS::PARTICLES_DRAW_ORDER_REVERSE_LIFETIME); - copy_push_constant.lifetime_split = MIN(particles->amount * particles->phase, particles->amount - 1); + copy_push_constant.lifetime_split = (MIN(int(particles->amount * particles->phase), particles->amount - 1) + 1) % particles->amount; copy_push_constant.lifetime_reverse = particles->draw_order == RS::PARTICLES_DRAW_ORDER_REVERSE_LIFETIME; copy_push_constant.frame_remainder = particles->interpolate ? particles->frame_remainder : 0.0; @@ -1520,7 +1520,7 @@ void ParticlesStorage::update_particles() { } copy_push_constant.order_by_lifetime = (particles->draw_order == RS::PARTICLES_DRAW_ORDER_LIFETIME || particles->draw_order == RS::PARTICLES_DRAW_ORDER_REVERSE_LIFETIME); - copy_push_constant.lifetime_split = MIN(particles->amount * particles->phase, particles->amount - 1); + copy_push_constant.lifetime_split = (MIN(int(particles->amount * particles->phase), particles->amount - 1) + 1) % particles->amount; copy_push_constant.lifetime_reverse = particles->draw_order == RS::PARTICLES_DRAW_ORDER_REVERSE_LIFETIME; RD::ComputeListID compute_list = RD::get_singleton()->compute_list_begin(); diff --git a/servers/rendering/rendering_device.cpp b/servers/rendering/rendering_device.cpp index 286d1b683f..744fd051f5 100644 --- a/servers/rendering/rendering_device.cpp +++ b/servers/rendering/rendering_device.cpp @@ -752,7 +752,7 @@ void RenderingDevice::_bind_methods() { ClassDB::bind_method(D_METHOD("render_pipeline_is_valid", "render_pipeline"), &RenderingDevice::render_pipeline_is_valid); ClassDB::bind_method(D_METHOD("compute_pipeline_create", "shader", "specialization_constants"), &RenderingDevice::_compute_pipeline_create, DEFVAL(TypedArray<RDPipelineSpecializationConstant>())); - ClassDB::bind_method(D_METHOD("compute_pipeline_is_valid", "compute_pieline"), &RenderingDevice::compute_pipeline_is_valid); + ClassDB::bind_method(D_METHOD("compute_pipeline_is_valid", "compute_pipeline"), &RenderingDevice::compute_pipeline_is_valid); ClassDB::bind_method(D_METHOD("screen_get_width", "screen"), &RenderingDevice::screen_get_width, DEFVAL(DisplayServer::MAIN_WINDOW_ID)); ClassDB::bind_method(D_METHOD("screen_get_height", "screen"), &RenderingDevice::screen_get_height, DEFVAL(DisplayServer::MAIN_WINDOW_ID)); diff --git a/servers/rendering/shader_language.cpp b/servers/rendering/shader_language.cpp index 084fb64a53..12e99ba606 100644 --- a/servers/rendering/shader_language.cpp +++ b/servers/rendering/shader_language.cpp @@ -10529,36 +10529,42 @@ Error ShaderLanguage::complete(const String &p_code, const ShaderCompileInfo &p_ r_options->push_back(option); } - } else if ((int(completion_base) > int(TYPE_MAT4) && int(completion_base) < int(TYPE_STRUCT)) && !completion_base_array) { + } else if ((int(completion_base) > int(TYPE_MAT4) && int(completion_base) < int(TYPE_STRUCT))) { Vector<String> options; - if (current_uniform_filter == FILTER_DEFAULT) { - options.push_back("filter_linear"); - options.push_back("filter_linear_mipmap"); - options.push_back("filter_linear_mipmap_anisotropic"); - options.push_back("filter_nearest"); - options.push_back("filter_nearest_mipmap"); - options.push_back("filter_nearest_mipmap_anisotropic"); - } - if (current_uniform_hint == ShaderNode::Uniform::HINT_NONE) { - options.push_back("hint_anisotropy"); - options.push_back("hint_default_black"); - options.push_back("hint_default_white"); - options.push_back("hint_default_transparent"); - options.push_back("hint_normal"); - options.push_back("hint_roughness_a"); - options.push_back("hint_roughness_b"); - options.push_back("hint_roughness_g"); - options.push_back("hint_roughness_gray"); - options.push_back("hint_roughness_normal"); - options.push_back("hint_roughness_r"); - options.push_back("hint_screen_texture"); - options.push_back("hint_normal_roughness_texture"); - options.push_back("hint_depth_texture"); - options.push_back("source_color"); - } - if (current_uniform_repeat == REPEAT_DEFAULT) { - options.push_back("repeat_enable"); - options.push_back("repeat_disable"); + if (completion_base_array) { + if (current_uniform_hint == ShaderNode::Uniform::HINT_NONE) { + options.push_back("source_color"); + } + } else { + if (current_uniform_filter == FILTER_DEFAULT) { + options.push_back("filter_linear"); + options.push_back("filter_linear_mipmap"); + options.push_back("filter_linear_mipmap_anisotropic"); + options.push_back("filter_nearest"); + options.push_back("filter_nearest_mipmap"); + options.push_back("filter_nearest_mipmap_anisotropic"); + } + if (current_uniform_hint == ShaderNode::Uniform::HINT_NONE) { + options.push_back("hint_anisotropy"); + options.push_back("hint_default_black"); + options.push_back("hint_default_white"); + options.push_back("hint_default_transparent"); + options.push_back("hint_normal"); + options.push_back("hint_roughness_a"); + options.push_back("hint_roughness_b"); + options.push_back("hint_roughness_g"); + options.push_back("hint_roughness_gray"); + options.push_back("hint_roughness_normal"); + options.push_back("hint_roughness_r"); + options.push_back("hint_screen_texture"); + options.push_back("hint_normal_roughness_texture"); + options.push_back("hint_depth_texture"); + options.push_back("source_color"); + } + if (current_uniform_repeat == REPEAT_DEFAULT) { + options.push_back("repeat_enable"); + options.push_back("repeat_disable"); + } } for (int i = 0; i < options.size(); i++) { diff --git a/servers/text_server.cpp b/servers/text_server.cpp index 88f1e76811..36c3137472 100644 --- a/servers/text_server.cpp +++ b/servers/text_server.cpp @@ -547,6 +547,7 @@ void TextServer::_bind_methods() { BIND_BITFIELD_FLAG(GRAPHEME_IS_UNDERSCORE); BIND_BITFIELD_FLAG(GRAPHEME_IS_CONNECTED); BIND_BITFIELD_FLAG(GRAPHEME_IS_SAFE_TO_INSERT_TATWEEL); + BIND_BITFIELD_FLAG(GRAPHEME_IS_EMBEDDED_OBJECT); /* Hinting */ BIND_ENUM_CONSTANT(HINTING_NONE); @@ -1449,7 +1450,7 @@ void TextServer::shaped_text_draw(const RID &p_shaped, const RID &p_canvas, cons if (glyphs[i].font_rid != RID()) { font_draw_glyph(glyphs[i].font_rid, p_canvas, glyphs[i].font_size, ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, p_color); - } else if (hex_codes && ((glyphs[i].flags & GRAPHEME_IS_VIRTUAL) != GRAPHEME_IS_VIRTUAL)) { + } else if (hex_codes && ((glyphs[i].flags & GRAPHEME_IS_VIRTUAL) != GRAPHEME_IS_VIRTUAL) && ((glyphs[i].flags & GRAPHEME_IS_EMBEDDED_OBJECT) != GRAPHEME_IS_EMBEDDED_OBJECT)) { TextServer::draw_hex_code_box(p_canvas, glyphs[i].font_size, ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, p_color); } if (orientation == ORIENTATION_HORIZONTAL) { diff --git a/servers/text_server.h b/servers/text_server.h index e3c668bd5c..af6efb8c7d 100644 --- a/servers/text_server.h +++ b/servers/text_server.h @@ -125,18 +125,19 @@ public: }; enum GraphemeFlag { - GRAPHEME_IS_VALID = 1 << 0, // Glyph is valid. - GRAPHEME_IS_RTL = 1 << 1, // Glyph is right-to-left. - GRAPHEME_IS_VIRTUAL = 1 << 2, // Glyph is not part of source string (added by fit_to_width function, do not affect caret movement). + GRAPHEME_IS_VALID = 1 << 0, // Grapheme is valid. + GRAPHEME_IS_RTL = 1 << 1, // Grapheme is right-to-left. + GRAPHEME_IS_VIRTUAL = 1 << 2, // Grapheme is not part of source string (added by fit_to_width function, do not affect caret movement). GRAPHEME_IS_SPACE = 1 << 3, // Is whitespace (for justification and word breaks). GRAPHEME_IS_BREAK_HARD = 1 << 4, // Is line break (mandatory break, e.g. "\n"). GRAPHEME_IS_BREAK_SOFT = 1 << 5, // Is line break (optional break, e.g. space). GRAPHEME_IS_TAB = 1 << 6, // Is tab or vertical tab. - GRAPHEME_IS_ELONGATION = 1 << 7, // Elongation (e.g. kashida), glyph can be duplicated or truncated to fit line to width. + GRAPHEME_IS_ELONGATION = 1 << 7, // Elongation (e.g. kashida), grapheme can be duplicated or truncated to fit line to width. GRAPHEME_IS_PUNCTUATION = 1 << 8, // Punctuation, except underscore (can be used as word break, but not line break or justifiction). GRAPHEME_IS_UNDERSCORE = 1 << 9, // Underscore (can be used as word break). GRAPHEME_IS_CONNECTED = 1 << 10, // Connected to previous grapheme. GRAPHEME_IS_SAFE_TO_INSERT_TATWEEL = 1 << 11, // It is safe to insert a U+0640 before this grapheme for elongation. + GRAPHEME_IS_EMBEDDED_OBJECT = 1 << 12, // Grapheme is an object replacement character for the embedded object. }; enum Hinting { |
