diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt index 607daba2fbc..fd4d98fad06 100644 --- a/COPYRIGHT.txt +++ b/COPYRIGHT.txt @@ -249,6 +249,11 @@ Comment: Noto Sans font Copyright: 2012, Google Inc. License: OFL-1.1 +Files: ./thirdparty/fonts/Vazirmatn*.woff2 +Comment: Vazirmatn font +Copyright: 2015, The Vazirmatn Project Authors. +License: OFL-1.1 + Files: ./thirdparty/freetype/ Comment: The FreeType Project Copyright: 1996-2023, David Turner, Robert Wilhelm, and Werner Lemberg. diff --git a/core/config/engine.cpp b/core/config/engine.cpp index bcec1a0ad4d..b0cd614b3c1 100644 --- a/core/config/engine.cpp +++ b/core/config/engine.cpp @@ -301,6 +301,9 @@ String Engine::get_architecture_name() const { return "ppc"; #endif +#elif defined(__loongarch64) + return "loongarch64"; + #elif defined(__wasm__) #if defined(__wasm64__) return "wasm64"; diff --git a/core/core_string_names.h b/core/core_string_names.h index 3922bfe66e8..4968f64bff4 100644 --- a/core/core_string_names.h +++ b/core/core_string_names.h @@ -36,61 +36,56 @@ #include "core/string/string_name.h" class CoreStringNames { - friend void register_core_types(); - friend void unregister_core_types(); + inline static CoreStringNames *singleton = nullptr; +public: static void create() { singleton = memnew(CoreStringNames); } static void free() { memdelete(singleton); singleton = nullptr; } - CoreStringNames(); - -public: _FORCE_INLINE_ static CoreStringNames *get_singleton() { return singleton; } - static CoreStringNames *singleton; - - StringName free_; // "free", conflict with C++ keyword. - StringName changed; - StringName script; - StringName script_changed; - StringName _iter_init; - StringName _iter_next; - StringName _iter_get; - StringName get_rid; - StringName _to_string; - StringName _custom_features; + const StringName free_ = StaticCString::create("free"); // free would conflict with C++ keyword. + const StringName changed = StaticCString::create("changed"); + const StringName script = StaticCString::create("script"); + const StringName script_changed = StaticCString::create("script_changed"); + const StringName _iter_init = StaticCString::create("_iter_init"); + const StringName _iter_next = StaticCString::create("_iter_next"); + const StringName _iter_get = StaticCString::create("_iter_get"); + const StringName get_rid = StaticCString::create("get_rid"); + const StringName _to_string = StaticCString::create("_to_string"); + const StringName _custom_features = StaticCString::create("_custom_features"); - StringName x; - StringName y; - StringName z; - StringName w; - StringName r; - StringName g; - StringName b; - StringName a; - StringName position; - StringName size; - StringName end; - StringName basis; - StringName origin; - StringName normal; - StringName d; - StringName h; - StringName s; - StringName v; - StringName r8; - StringName g8; - StringName b8; - StringName a8; + const StringName x = StaticCString::create("x"); + const StringName y = StaticCString::create("y"); + const StringName z = StaticCString::create("z"); + const StringName w = StaticCString::create("w"); + const StringName r = StaticCString::create("r"); + const StringName g = StaticCString::create("g"); + const StringName b = StaticCString::create("b"); + const StringName a = StaticCString::create("a"); + const StringName position = StaticCString::create("position"); + const StringName size = StaticCString::create("size"); + const StringName end = StaticCString::create("end"); + const StringName basis = StaticCString::create("basis"); + const StringName origin = StaticCString::create("origin"); + const StringName normal = StaticCString::create("normal"); + const StringName d = StaticCString::create("d"); + const StringName h = StaticCString::create("h"); + const StringName s = StaticCString::create("s"); + const StringName v = StaticCString::create("v"); + const StringName r8 = StaticCString::create("r8"); + const StringName g8 = StaticCString::create("g8"); + const StringName b8 = StaticCString::create("b8"); + const StringName a8 = StaticCString::create("a8"); - StringName call; - StringName call_deferred; - StringName bind; - StringName notification; - StringName property_list_changed; + const StringName call = StaticCString::create("call"); + const StringName call_deferred = StaticCString::create("call_deferred"); + const StringName bind = StaticCString::create("bind"); + const StringName notification = StaticCString::create("notification"); + const StringName property_list_changed = StaticCString::create("property_list_changed"); }; #define CoreStringName(m_name) CoreStringNames::get_singleton()->m_name diff --git a/core/debugger/remote_debugger_peer.cpp b/core/debugger/remote_debugger_peer.cpp index 17b6f5a48cf..61fa04915a6 100644 --- a/core/debugger/remote_debugger_peer.cpp +++ b/core/debugger/remote_debugger_peer.cpp @@ -225,7 +225,7 @@ RemoteDebuggerPeer *RemoteDebuggerPeerTCP::create(const String &p_uri) { String debug_host = p_uri.replace("tcp://", ""); uint16_t debug_port = 6007; - if (debug_host.contains(":")) { + if (debug_host.contains_char(':')) { int sep_pos = debug_host.rfind_char(':'); debug_port = debug_host.substr(sep_pos + 1).to_int(); debug_host = debug_host.substr(0, sep_pos); diff --git a/core/extension/extension_api_dump.cpp b/core/extension/extension_api_dump.cpp index b70bd23918b..aa9b9db9bad 100644 --- a/core/extension/extension_api_dump.cpp +++ b/core/extension/extension_api_dump.cpp @@ -1208,7 +1208,7 @@ Dictionary GDExtensionAPIDump::generate_extension_api(bool p_include_docs) { if (F.name.begins_with("_")) { continue; //hidden property } - if (F.name.contains("/")) { + if (F.name.contains_char('/')) { // Ignore properties with '/' (slash) in the name. These are only meant for use in the inspector. continue; } diff --git a/core/input/input_event.cpp b/core/input/input_event.cpp index 2871ef24dde..97d0c832837 100644 --- a/core/input/input_event.cpp +++ b/core/input/input_event.cpp @@ -756,6 +756,8 @@ Ref InputEventMouseButton::xformed_by(const Transform2D &p_xform, co mb->set_factor(factor); mb->set_button_index(button_index); + mb->merge_meta_from(this); + return mb; } @@ -976,6 +978,8 @@ Ref InputEventMouseMotion::xformed_by(const Transform2D &p_xform, co mm->set_velocity(p_xform.basis_xform(get_velocity())); mm->set_screen_velocity(get_screen_velocity()); + mm->merge_meta_from(this); + return mm; } @@ -1368,6 +1372,8 @@ Ref InputEventScreenTouch::xformed_by(const Transform2D &p_xform, co st->set_canceled(canceled); st->set_double_tap(double_tap); + st->merge_meta_from(this); + return st; } @@ -1496,6 +1502,8 @@ Ref InputEventScreenDrag::xformed_by(const Transform2D &p_xform, con sd->set_velocity(p_xform.basis_xform(velocity)); sd->set_screen_velocity(get_screen_velocity()); + sd->merge_meta_from(this); + return sd; } @@ -1707,6 +1715,8 @@ Ref InputEventMagnifyGesture::xformed_by(const Transform2D &p_xform, ev->set_position(p_xform.xform(get_position() + p_local_ofs)); ev->set_factor(get_factor()); + ev->merge_meta_from(this); + return ev; } @@ -1747,6 +1757,8 @@ Ref InputEventPanGesture::xformed_by(const Transform2D &p_xform, con ev->set_position(p_xform.xform(get_position() + p_local_ofs)); ev->set_delta(get_delta()); + ev->merge_meta_from(this); + return ev; } diff --git a/core/io/file_access.cpp b/core/io/file_access.cpp index 9899a4ba4dd..8f5180d4367 100644 --- a/core/io/file_access.cpp +++ b/core/io/file_access.cpp @@ -742,7 +742,7 @@ bool FileAccess::store_csv_line(const Vector &p_values, const String &p_ for (int i = 0; i < size; ++i) { String value = p_values[i]; - if (value.contains("\"") || value.contains(p_delim) || value.contains("\n")) { + if (value.contains_char('"') || value.contains(p_delim) || value.contains_char('\n')) { value = "\"" + value.replace("\"", "\"\"") + "\""; } if (i < size - 1) { diff --git a/core/io/file_access_pack.cpp b/core/io/file_access_pack.cpp index 202e6ee8390..774888af88b 100644 --- a/core/io/file_access_pack.cpp +++ b/core/io/file_access_pack.cpp @@ -73,7 +73,7 @@ void PackedData::add_path(const String &p_pkg_path, const String &p_path, uint64 // Search for directory. PackedDir *cd = root; - if (simplified_path.contains("/")) { // In a subdirectory. + if (simplified_path.contains_char('/')) { // In a subdirectory. Vector ds = simplified_path.get_base_dir().split("/"); for (int j = 0; j < ds.size(); j++) { @@ -106,7 +106,7 @@ void PackedData::remove_path(const String &p_path) { // Search for directory. PackedDir *cd = root; - if (simplified_path.contains("/")) { // In a subdirectory. + if (simplified_path.contains_char('/')) { // In a subdirectory. Vector ds = simplified_path.get_base_dir().split("/"); for (int j = 0; j < ds.size(); j++) { diff --git a/core/io/ip_address.cpp b/core/io/ip_address.cpp index fcbab3185ed..1fe1f89c518 100644 --- a/core/io/ip_address.cpp +++ b/core/io/ip_address.cpp @@ -204,7 +204,7 @@ IPAddress::IPAddress(const String &p_string) { // Wildcard (not a valid IP) wildcard = true; - } else if (p_string.contains(":")) { + } else if (p_string.contains_char(':')) { // IPv6 _parse_ipv6(p_string); valid = true; diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp index b85525cb7d4..ed4ff3520eb 100644 --- a/core/object/class_db.cpp +++ b/core/object/class_db.cpp @@ -1118,7 +1118,7 @@ void ClassDB::bind_integer_constant(const StringName &p_class, const StringName String enum_name = p_enum; if (!enum_name.is_empty()) { - if (enum_name.contains(".")) { + if (enum_name.contains_char('.')) { enum_name = enum_name.get_slicec('.', 1); } diff --git a/core/os/os.cpp b/core/os/os.cpp index 1887bf92722..743159e10e0 100644 --- a/core/os/os.cpp +++ b/core/os/os.cpp @@ -520,6 +520,10 @@ bool OS::has_feature(const String &p_feature) { if (p_feature == "wasm") { return true; } +#elif defined(__loongarch64) + if (p_feature == "loongarch64") { + return true; + } #endif #if defined(IOS_SIMULATOR) diff --git a/core/os/spin_lock.h b/core/os/spin_lock.h index ddf5c2e87a6..86f45ab35cb 100644 --- a/core/os/spin_lock.h +++ b/core/os/spin_lock.h @@ -33,8 +33,15 @@ #ifndef SPIN_LOCK_H #define SPIN_LOCK_H +#include "core/os/thread.h" #include "core/typedefs.h" +#ifdef THREADS_ENABLED + +// Note the implementations below avoid false sharing by ensuring their +// sizes match the assumed cache line. We can't use align attributes +// because these objects may end up unaligned in semi-tightly packed arrays. + #ifdef _MSC_VER #include #endif @@ -44,7 +51,10 @@ #include class SpinLock { - mutable os_unfair_lock _lock = OS_UNFAIR_LOCK_INIT; + union { + mutable os_unfair_lock _lock = OS_UNFAIR_LOCK_INIT; + char aligner[Thread::CACHE_LINE_BYTES]; + }; public: _ALWAYS_INLINE_ void lock() const { @@ -56,9 +66,7 @@ class SpinLock { } }; -#else - -#include "core/os/thread.h" +#else // __APPLE__ #include @@ -86,8 +94,11 @@ _ALWAYS_INLINE_ static void _cpu_pause() { static_assert(std::atomic_bool::is_always_lock_free); -class alignas(Thread::CACHE_LINE_BYTES) SpinLock { - mutable std::atomic locked = ATOMIC_VAR_INIT(false); +class SpinLock { + union { + mutable std::atomic locked = ATOMIC_VAR_INIT(false); + char aligner[Thread::CACHE_LINE_BYTES]; + }; public: _ALWAYS_INLINE_ void lock() const { @@ -109,4 +120,14 @@ class alignas(Thread::CACHE_LINE_BYTES) SpinLock { #endif // __APPLE__ +#else // THREADS_ENABLED + +class SpinLock { +public: + void lock() const {} + void unlock() const {} +}; + +#endif // THREADS_ENABLED + #endif // SPIN_LOCK_H diff --git a/core/string/print_string.cpp b/core/string/print_string.cpp index a4b6ef68810..cc5584e490c 100644 --- a/core/string/print_string.cpp +++ b/core/string/print_string.cpp @@ -95,80 +95,178 @@ void __print_line_rich(const String &p_string) { // Convert a subset of BBCode tags to ANSI escape codes for correct display in the terminal. // Support of those ANSI escape codes varies across terminal emulators, // especially for italic and strikethrough. - String p_string_ansi = p_string; - - p_string_ansi = p_string_ansi.replace("[b]", "\u001b[1m"); - p_string_ansi = p_string_ansi.replace("[/b]", "\u001b[22m"); - p_string_ansi = p_string_ansi.replace("[i]", "\u001b[3m"); - p_string_ansi = p_string_ansi.replace("[/i]", "\u001b[23m"); - p_string_ansi = p_string_ansi.replace("[u]", "\u001b[4m"); - p_string_ansi = p_string_ansi.replace("[/u]", "\u001b[24m"); - p_string_ansi = p_string_ansi.replace("[s]", "\u001b[9m"); - p_string_ansi = p_string_ansi.replace("[/s]", "\u001b[29m"); - - p_string_ansi = p_string_ansi.replace("[indent]", " "); - p_string_ansi = p_string_ansi.replace("[/indent]", ""); - p_string_ansi = p_string_ansi.replace("[code]", "\u001b[2m"); - p_string_ansi = p_string_ansi.replace("[/code]", "\u001b[22m"); - p_string_ansi = p_string_ansi.replace("[url]", ""); - p_string_ansi = p_string_ansi.replace("[/url]", ""); - p_string_ansi = p_string_ansi.replace("[center]", "\n\t\t\t"); - p_string_ansi = p_string_ansi.replace("[/center]", ""); - p_string_ansi = p_string_ansi.replace("[right]", "\n\t\t\t\t\t\t"); - p_string_ansi = p_string_ansi.replace("[/right]", ""); - - if (p_string_ansi.contains("[color")) { - p_string_ansi = p_string_ansi.replace("[color=black]", "\u001b[30m"); - p_string_ansi = p_string_ansi.replace("[color=red]", "\u001b[91m"); - p_string_ansi = p_string_ansi.replace("[color=green]", "\u001b[92m"); - p_string_ansi = p_string_ansi.replace("[color=lime]", "\u001b[92m"); - p_string_ansi = p_string_ansi.replace("[color=yellow]", "\u001b[93m"); - p_string_ansi = p_string_ansi.replace("[color=blue]", "\u001b[94m"); - p_string_ansi = p_string_ansi.replace("[color=magenta]", "\u001b[95m"); - p_string_ansi = p_string_ansi.replace("[color=pink]", "\u001b[38;5;218m"); - p_string_ansi = p_string_ansi.replace("[color=purple]", "\u001b[38;5;98m"); - p_string_ansi = p_string_ansi.replace("[color=cyan]", "\u001b[96m"); - p_string_ansi = p_string_ansi.replace("[color=white]", "\u001b[97m"); - p_string_ansi = p_string_ansi.replace("[color=orange]", "\u001b[38;5;208m"); - p_string_ansi = p_string_ansi.replace("[color=gray]", "\u001b[90m"); - p_string_ansi = p_string_ansi.replace("[/color]", "\u001b[39m"); - } - if (p_string_ansi.contains("[bgcolor")) { - p_string_ansi = p_string_ansi.replace("[bgcolor=black]", "\u001b[40m"); - p_string_ansi = p_string_ansi.replace("[bgcolor=red]", "\u001b[101m"); - p_string_ansi = p_string_ansi.replace("[bgcolor=green]", "\u001b[102m"); - p_string_ansi = p_string_ansi.replace("[bgcolor=lime]", "\u001b[102m"); - p_string_ansi = p_string_ansi.replace("[bgcolor=yellow]", "\u001b[103m"); - p_string_ansi = p_string_ansi.replace("[bgcolor=blue]", "\u001b[104m"); - p_string_ansi = p_string_ansi.replace("[bgcolor=magenta]", "\u001b[105m"); - p_string_ansi = p_string_ansi.replace("[bgcolor=pink]", "\u001b[48;5;218m"); - p_string_ansi = p_string_ansi.replace("[bgcolor=purple]", "\u001b[48;5;98m"); - p_string_ansi = p_string_ansi.replace("[bgcolor=cyan]", "\u001b[106m"); - p_string_ansi = p_string_ansi.replace("[bgcolor=white]", "\u001b[107m"); - p_string_ansi = p_string_ansi.replace("[bgcolor=orange]", "\u001b[48;5;208m"); - p_string_ansi = p_string_ansi.replace("[bgcolor=gray]", "\u001b[100m"); - p_string_ansi = p_string_ansi.replace("[/bgcolor]", "\u001b[49m"); - } - if (p_string_ansi.contains("[fgcolor")) { - p_string_ansi = p_string_ansi.replace("[fgcolor=black]", "\u001b[30;40m"); - p_string_ansi = p_string_ansi.replace("[fgcolor=red]", "\u001b[91;101m"); - p_string_ansi = p_string_ansi.replace("[fgcolor=green]", "\u001b[92;102m"); - p_string_ansi = p_string_ansi.replace("[fgcolor=lime]", "\u001b[92;102m"); - p_string_ansi = p_string_ansi.replace("[fgcolor=yellow]", "\u001b[93;103m"); - p_string_ansi = p_string_ansi.replace("[fgcolor=blue]", "\u001b[94;104m"); - p_string_ansi = p_string_ansi.replace("[fgcolor=magenta]", "\u001b[95;105m"); - p_string_ansi = p_string_ansi.replace("[fgcolor=pink]", "\u001b[38;5;218;48;5;218m"); - p_string_ansi = p_string_ansi.replace("[fgcolor=purple]", "\u001b[38;5;98;48;5;98m"); - p_string_ansi = p_string_ansi.replace("[fgcolor=cyan]", "\u001b[96;106m"); - p_string_ansi = p_string_ansi.replace("[fgcolor=white]", "\u001b[97;107m"); - p_string_ansi = p_string_ansi.replace("[fgcolor=orange]", "\u001b[38;5;208;48;5;208m"); - p_string_ansi = p_string_ansi.replace("[fgcolor=gray]", "\u001b[90;100m"); - p_string_ansi = p_string_ansi.replace("[/fgcolor]", "\u001b[39;49m"); - } - p_string_ansi += "\u001b[0m"; // Reset. + String output; + int pos = 0; + while (pos <= p_string.length()) { + int brk_pos = p_string.find_char('[', pos); + + if (brk_pos < 0) { + brk_pos = p_string.length(); + } + + String txt = brk_pos > pos ? p_string.substr(pos, brk_pos - pos) : ""; + if (brk_pos == p_string.length()) { + output += txt; + break; + } + + int brk_end = p_string.find_char(']', brk_pos + 1); + + if (brk_end == -1) { + txt += p_string.substr(brk_pos, p_string.length() - brk_pos); + output += txt; + break; + } + pos = brk_end + 1; + output += txt; + + String tag = p_string.substr(brk_pos + 1, brk_end - brk_pos - 1); + if (tag == "b") { + output += "\u001b[1m"; + } else if (tag == "/b") { + output += "\u001b[22m"; + } else if (tag == "i") { + output += "\u001b[3m"; + } else if (tag == "/i") { + output += "\u001b[23m"; + } else if (tag == "u") { + output += "\u001b[4m"; + } else if (tag == "/u") { + output += "\u001b[24m"; + } else if (tag == "s") { + output += "\u001b[9m"; + } else if (tag == "/s") { + output += "\u001b[29m"; + } else if (tag == "indent") { + output += " "; + } else if (tag == "/indent") { + output += ""; + } else if (tag == "code") { + output += "\u001b[2m"; + } else if (tag == "/code") { + output += "\u001b[22m"; + } else if (tag == "url") { + output += ""; + } else if (tag == "/url") { + output += ""; + } else if (tag == "center") { + output += "\n\t\t\t"; + } else if (tag == "center") { + output += ""; + } else if (tag == "right") { + output += "\n\t\t\t\t\t\t"; + } else if (tag == "/right") { + output += ""; + } else if (tag.begins_with("color=")) { + String color_name = tag.trim_prefix("color="); + if (color_name == "black") { + output += "\u001b[30m"; + } else if (color_name == "red") { + output += "\u001b[91m"; + } else if (color_name == "green") { + output += "\u001b[92m"; + } else if (color_name == "lime") { + output += "\u001b[92m"; + } else if (color_name == "yellow") { + output += "\u001b[93m"; + } else if (color_name == "blue") { + output += "\u001b[94m"; + } else if (color_name == "magenta") { + output += "\u001b[95m"; + } else if (color_name == "pink") { + output += "\u001b[38;5;218m"; + } else if (color_name == "purple") { + output += "\u001b[38;5;98m"; + } else if (color_name == "cyan") { + output += "\u001b[96m"; + } else if (color_name == "white") { + output += "\u001b[97m"; + } else if (color_name == "orange") { + output += "\u001b[38;5;208m"; + } else if (color_name == "gray") { + output += "\u001b[90m"; + } else { + Color c = Color::from_string(color_name, Color()); + output += vformat("\u001b[38;2;%d;%d;%dm", c.r * 255, c.g * 255, c.b * 255); + } + } else if (tag == "/color") { + output += "\u001b[39m"; + } else if (tag.begins_with("bgcolor=")) { + String color_name = tag.trim_prefix("bgcolor="); + if (color_name == "black") { + output += "\u001b[40m"; + } else if (color_name == "red") { + output += "\u001b[101m"; + } else if (color_name == "green") { + output += "\u001b[102m"; + } else if (color_name == "lime") { + output += "\u001b[102m"; + } else if (color_name == "yellow") { + output += "\u001b[103m"; + } else if (color_name == "blue") { + output += "\u001b[104m"; + } else if (color_name == "magenta") { + output += "\u001b[105m"; + } else if (color_name == "pink") { + output += "\u001b[48;5;218m"; + } else if (color_name == "purple") { + output += "\u001b[48;5;98m"; + } else if (color_name == "cyan") { + output += "\u001b[106m"; + } else if (color_name == "white") { + output += "\u001b[107m"; + } else if (color_name == "orange") { + output += "\u001b[48;5;208m"; + } else if (color_name == "gray") { + output += "\u001b[100m"; + } else { + Color c = Color::from_string(color_name, Color()); + output += vformat("\u001b[48;2;%d;%d;%dm", c.r * 255, c.g * 255, c.b * 255); + } + } else if (tag == "/bgcolor") { + output += "\u001b[49m"; + } else if (tag.begins_with("fgcolor=")) { + String color_name = tag.trim_prefix("fgcolor="); + if (color_name == "black") { + output += "\u001b[30;40m"; + } else if (color_name == "red") { + output += "\u001b[91;101m"; + } else if (color_name == "green") { + output += "\u001b[92;102m"; + } else if (color_name == "lime") { + output += "\u001b[92;102m"; + } else if (color_name == "yellow") { + output += "\u001b[93;103m"; + } else if (color_name == "blue") { + output += "\u001b[94;104m"; + } else if (color_name == "magenta") { + output += "\u001b[95;105m"; + } else if (color_name == "pink") { + output += "\u001b[38;5;218;48;5;218m"; + } else if (color_name == "purple") { + output += "\u001b[38;5;98;48;5;98m"; + } else if (color_name == "cyan") { + output += "\u001b[96;106m"; + } else if (color_name == "white") { + output += "\u001b[97;107m"; + } else if (color_name == "orange") { + output += "\u001b[38;5;208;48;5;208m"; + } else if (color_name == "gray") { + output += "\u001b[90;100m"; + } else { + Color c = Color::from_string(color_name, Color()); + output += vformat("\u001b[38;2;%d;%d;%d;48;2;%d;%d;%dm", c.r * 255, c.g * 255, c.b * 255, c.r * 255, c.g * 255, c.b * 255); + } + } else if (tag == "/fgcolor") { + output += "\u001b[39;49m"; + } else { + output += vformat("[%s]", tag); + } + } + output += "\u001b[0m"; // Reset. - OS::get_singleton()->print_rich("%s\n", p_string_ansi.utf8().get_data()); + OS::get_singleton()->print_rich("%s\n", output.utf8().get_data()); _global_lock(); PrintHandlerList *l = print_handler_list; diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp index ccb5566ecef..e449d5563a2 100644 --- a/core/string/ustring.cpp +++ b/core/string/ustring.cpp @@ -67,6 +67,15 @@ const char16_t Char16String::_null = 0; const char32_t String::_null = 0; const char32_t String::_replacement_char = 0xfffd; +// strlen equivalent function for char32_t * arguments. +_FORCE_INLINE_ size_t strlen(const char32_t *p_str) { + const char32_t *ptr = p_str; + while (*ptr != 0) { + ++ptr; + } + return ptr - p_str; +} + bool select_word(const String &p_s, int p_col, int &r_beg, int &r_end) { const String &s = p_s; int beg = CLAMP(p_col, 0, s.length()); @@ -322,21 +331,14 @@ void String::copy_from(const char *p_cstr) { resize(len + 1); // include 0 + const char *end = p_cstr + len; char32_t *dst = ptrw(); - for (size_t i = 0; i <= len; i++) { -#if CHAR_MIN == 0 - uint8_t c = p_cstr[i]; -#else - uint8_t c = p_cstr[i] >= 0 ? p_cstr[i] : uint8_t(256 + p_cstr[i]); -#endif - if (c == 0 && i < len) { - print_unicode_error("NUL character", true); - dst[i] = _replacement_char; - } else { - dst[i] = c; - } + for (; p_cstr < end; ++p_cstr, ++dst) { + // If char is int8_t, a set sign bit will be reinterpreted as 256 - val implicitly. + *dst = static_cast(*p_cstr); } + *dst = 0; } void String::copy_from(const char *p_cstr, const int p_clip_to) { @@ -359,22 +361,14 @@ void String::copy_from(const char *p_cstr, const int p_clip_to) { resize(len + 1); // include 0 + const char *end = p_cstr + len; char32_t *dst = ptrw(); - for (int i = 0; i < len; i++) { -#if CHAR_MIN == 0 - uint8_t c = p_cstr[i]; -#else - uint8_t c = p_cstr[i] >= 0 ? p_cstr[i] : uint8_t(256 + p_cstr[i]); -#endif - if (c == 0) { - print_unicode_error("NUL character", true); - dst[i] = _replacement_char; - } else { - dst[i] = c; - } + for (; p_cstr < end; ++p_cstr, ++dst) { + // If char is int8_t, a set sign bit will be reinterpreted as 256 - val implicitly. + *dst = static_cast(*p_cstr); } - dst[len] = 0; + *dst = 0; } void String::copy_from(const wchar_t *p_cstr) { @@ -426,11 +420,7 @@ void String::copy_from(const char32_t *p_cstr) { return; } - int len = 0; - const char32_t *ptr = p_cstr; - while (*(ptr++) != 0) { - len++; - } + const int len = strlen(p_cstr); if (len == 0) { resize(0); @@ -466,27 +456,25 @@ void String::copy_from(const char32_t *p_cstr, const int p_clip_to) { // p_length <= p_char strlen void String::copy_from_unchecked(const char32_t *p_char, const int p_length) { resize(p_length + 1); + + const char32_t *end = p_char + p_length; char32_t *dst = ptrw(); - dst[p_length] = 0; - for (int i = 0; i < p_length; i++) { - if (p_char[i] == 0) { - print_unicode_error("NUL character", true); - dst[i] = _replacement_char; + for (; p_char < end; ++p_char, ++dst) { + const char32_t chr = *p_char; + if ((chr & 0xfffff800) == 0xd800) { + print_unicode_error(vformat("Unpaired surrogate (%x)", (uint32_t)chr)); + *dst = _replacement_char; continue; } - if ((p_char[i] & 0xfffff800) == 0xd800) { - print_unicode_error(vformat("Unpaired surrogate (%x)", (uint32_t)p_char[i])); - dst[i] = _replacement_char; + if (chr > 0x10ffff) { + print_unicode_error(vformat("Invalid unicode codepoint (%x)", (uint32_t)chr)); + *dst = _replacement_char; continue; } - if (p_char[i] > 0x10ffff) { - print_unicode_error(vformat("Invalid unicode codepoint (%x)", (uint32_t)p_char[i])); - dst[i] = _replacement_char; - continue; - } - dst[i] = p_char[i]; + *dst = chr; } + *dst = 0; } void String::operator=(const char *p_str) { @@ -631,12 +619,7 @@ String &String::operator+=(char32_t p_char) { bool String::operator==(const char *p_str) const { // compare Latin-1 encoded c-string - int len = 0; - const char *aux = p_str; - - while (*(aux++) != 0) { - len++; - } + int len = strlen(p_str); if (length() != len) { return false; @@ -670,12 +653,7 @@ bool String::operator==(const wchar_t *p_str) const { } bool String::operator==(const char32_t *p_str) const { - int len = 0; - const char32_t *aux = p_str; - - while (*(aux++) != 0) { - len++; - } + const int len = strlen(p_str); if (length() != len) { return false; @@ -1111,17 +1089,21 @@ String String::_camelcase_to_underscore() const { String new_string; int start_index = 0; - for (int i = 1; i < size(); i++) { - bool is_prev_upper = is_unicode_upper_case(cstr[i - 1]); - bool is_prev_lower = is_unicode_lower_case(cstr[i - 1]); - bool is_prev_digit = is_digit(cstr[i - 1]); + if (length() == 0) { + return *this; + } + + bool is_prev_upper = is_unicode_upper_case(cstr[0]); + bool is_prev_lower = is_unicode_lower_case(cstr[0]); + bool is_prev_digit = is_digit(cstr[0]); - bool is_curr_upper = is_unicode_upper_case(cstr[i]); - bool is_curr_lower = is_unicode_lower_case(cstr[i]); - bool is_curr_digit = is_digit(cstr[i]); + for (int i = 1; i < length(); i++) { + const bool is_curr_upper = is_unicode_upper_case(cstr[i]); + const bool is_curr_lower = is_unicode_lower_case(cstr[i]); + const bool is_curr_digit = is_digit(cstr[i]); bool is_next_lower = false; - if (i + 1 < size()) { + if (i + 1 < length()) { is_next_lower = is_unicode_lower_case(cstr[i + 1]); } @@ -1134,6 +1116,10 @@ String String::_camelcase_to_underscore() const { new_string += substr(start_index, i - start_index) + "_"; start_index = i; } + + is_prev_upper = is_curr_upper; + is_prev_lower = is_curr_lower; + is_prev_digit = is_curr_digit; } new_string += substr(start_index, size() - start_index); @@ -3313,6 +3299,10 @@ int String::find(const String &p_str, int p_from) const { return -1; // won't find anything! } + if (src_len == 1) { + return find_char(p_str[0], p_from); // Optimize with single-char find. + } + const char32_t *src = get_data(); const char32_t *str = p_str.get_data(); @@ -3353,6 +3343,10 @@ int String::find(const char *p_str, int p_from) const { return -1; // won't find anything! } + if (src_len == 1) { + return find_char(*p_str, p_from); // Optimize with single-char find. + } + const char32_t *src = get_data(); if (src_len == 1) { @@ -4080,7 +4074,7 @@ String String::format(const Variant &values, const String &placeholder) const { Variant v_val = values_arr[i]; String val = v_val; - if (placeholder.contains("_")) { + if (placeholder.contains_char('_')) { new_string = new_string.replace(placeholder.replace("_", i_as_str), val); } else { new_string = new_string.replace_first(placeholder, val); @@ -4607,7 +4601,7 @@ String String::simplify_path() const { dirs.remove_at(i); i--; } else if (d == "..") { - if (i != 0) { + if (i != 0 && dirs[i - 1] != "..") { dirs.remove_at(i); dirs.remove_at(i - 1); i -= 2; @@ -5288,7 +5282,7 @@ bool String::is_valid_html_color() const { } // Changes made to the set of invalid filename characters must also be reflected in the String documentation for is_valid_filename. -static const char *invalid_filename_characters = ": / \\ ? * \" | % < >"; +static const char *invalid_filename_characters[] = { ":", "/", "\\", "?", "*", "\"", "|", "%", "<", ">" }; bool String::is_valid_filename() const { String stripped = strip_edges(); @@ -5300,8 +5294,7 @@ bool String::is_valid_filename() const { return false; } - Vector chars = String(invalid_filename_characters).split(" "); - for (const String &ch : chars) { + for (const char *ch : invalid_filename_characters) { if (contains(ch)) { return false; } @@ -5310,10 +5303,9 @@ bool String::is_valid_filename() const { } String String::validate_filename() const { - Vector chars = String(invalid_filename_characters).split(" "); String name = strip_edges(); - for (int i = 0; i < chars.size(); i++) { - name = name.replace(chars[i], "_"); + for (const char *ch : invalid_filename_characters) { + name = name.replace(ch, "_"); } return name; } diff --git a/core/string/ustring.h b/core/string/ustring.h index d363228ecb1..70f33bc6266 100644 --- a/core/string/ustring.h +++ b/core/string/ustring.h @@ -434,6 +434,7 @@ class String { _FORCE_INLINE_ bool is_empty() const { return length() == 0; } _FORCE_INLINE_ bool contains(const char *p_str) const { return find(p_str) != -1; } _FORCE_INLINE_ bool contains(const String &p_str) const { return find(p_str) != -1; } + _FORCE_INLINE_ bool contains_char(char32_t p_chr) const { return find_char(p_chr) != -1; } _FORCE_INLINE_ bool containsn(const char *p_str) const { return findn(p_str) != -1; } _FORCE_INLINE_ bool containsn(const String &p_str) const { return findn(p_str) != -1; } diff --git a/core/variant/variant_parser.cpp b/core/variant/variant_parser.cpp index fd04d14f2a1..4e68b667d9b 100644 --- a/core/variant/variant_parser.cpp +++ b/core/variant/variant_parser.cpp @@ -1963,7 +1963,7 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str case Variant::FLOAT: { String s = rtos_fix(p_variant.operator double()); if (s != "inf" && s != "inf_neg" && s != "nan") { - if (!s.contains(".") && !s.contains("e")) { + if (!s.contains_char('.') && !s.contains_char('e')) { s += ".0"; } } diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml index 6fbbca1cb6c..6549a8e1a21 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -863,7 +863,6 @@ Converts one or more arguments of any type to string in the best way possible and prints them to the console. The following BBCode tags are supported: [code]b[/code], [code]i[/code], [code]u[/code], [code]s[/code], [code]indent[/code], [code]code[/code], [code]url[/code], [code]center[/code], [code]right[/code], [code]color[/code], [code]bgcolor[/code], [code]fgcolor[/code]. - Color tags only support the following named colors: [code]black[/code], [code]red[/code], [code]green[/code], [code]yellow[/code], [code]blue[/code], [code]magenta[/code], [code]pink[/code], [code]purple[/code], [code]cyan[/code], [code]white[/code], [code]orange[/code], [code]gray[/code]. Hexadecimal color codes are not supported. URL tags only support URLs wrapped by a URL tag, not URLs with a different title. When printing to standard output, the supported subset of BBCode is converted to ANSI escape codes for the terminal emulator to display. Support for ANSI escape codes varies across terminal emulators, especially for italic and strikethrough. In standard output, [code]code[/code] is represented with faint text but without any font change. Unsupported tags are left as-is in standard output. [codeblocks] diff --git a/doc/classes/ColorPalette.xml b/doc/classes/ColorPalette.xml new file mode 100644 index 00000000000..045d01370b0 --- /dev/null +++ b/doc/classes/ColorPalette.xml @@ -0,0 +1,16 @@ + + + + A resource class for managing a palette of colors, which can be loaded and saved using [ColorPicker]. + + + The [ColorPalette] resource is designed to store and manage a collection of colors. This resource is useful in scenarios where a predefined set of colors is required, such as for creating themes, designing user interfaces, or managing game assets. The built-in [ColorPicker] control can also make use of [ColorPalette] without additional code. + + + + + + A [PackedColorArray] containing the colors in the palette. + + + diff --git a/doc/classes/ColorPicker.xml b/doc/classes/ColorPicker.xml index a0bcb17caff..e6b71115203 100644 --- a/doc/classes/ColorPicker.xml +++ b/doc/classes/ColorPicker.xml @@ -183,6 +183,9 @@ The icon for color preset drop down menu when folded. + + The icon for color preset option menu. + The indicator used to signalize that the color value is outside the 0-1 range. diff --git a/doc/classes/EditorInspector.xml b/doc/classes/EditorInspector.xml index 0b56060bda7..8c7e935dd23 100644 --- a/doc/classes/EditorInspector.xml +++ b/doc/classes/EditorInspector.xml @@ -26,6 +26,19 @@ Gets the path of the currently selected property. + + + + + + + + + + + Creates a property editor that can be used by plugin UI to edit the specified property of an [param object]. + + diff --git a/doc/classes/EditorProperty.xml b/doc/classes/EditorProperty.xml index 4ff541f72df..2a9e4088a6b 100644 --- a/doc/classes/EditorProperty.xml +++ b/doc/classes/EditorProperty.xml @@ -29,6 +29,12 @@ If any of the controls added can gain keyboard focus, add it here. This ensures that focus will be restored if the inspector is refreshed. + + + + Draw property as not selected. Used by the inspector. + + @@ -51,6 +57,19 @@ Gets the edited property. If your editor is for a single property (added via [method EditorInspectorPlugin._parse_property]), then this will return the property. + + + + Returns [code]true[/code] if property is drawn as selected. Used by the inspector. + + + + + + + Draw property as selected. Used by the inspector. + + @@ -58,6 +77,21 @@ Puts the [param editor] control below the property label. The control must be previously added using [method Node.add_child]. + + + + + Used by the inspector, set to a control that will be used as a reference to calculate the size of the label. + + + + + + + + Assigns object and property to edit. + + @@ -84,9 +118,18 @@ Set this property to change the label (if you want to show one). + + Space distribution ratio between the label and the editing field. + Used by the inspector, set to [code]true[/code] when the property is read-only. + + Used by the inspector, set to [code]true[/code] when the property is selectable. + + + Used by the inspector, set to [code]true[/code] when the property is using folding. + diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml index 747260745eb..701f8974e5a 100644 --- a/doc/classes/EditorSettings.xml +++ b/doc/classes/EditorSettings.xml @@ -398,6 +398,9 @@ The inertia to use when panning in the 3D editor. Higher values make the camera start and stop slower, which looks smoother but adds latency. + + The mouse sensitivity to use when panning in the 3D editor. + The inertia to use when zooming in the 3D editor. Higher values make the camera start and stop slower, which looks smoother but adds latency. diff --git a/doc/classes/PackedFloat32Array.xml b/doc/classes/PackedFloat32Array.xml index d421993b8fd..237b659a522 100644 --- a/doc/classes/PackedFloat32Array.xml +++ b/doc/classes/PackedFloat32Array.xml @@ -192,7 +192,7 @@ - Returns a copy of the data converted to a [PackedByteArray], where each element have been encoded as 4 bytes. + Returns a copy of the data converted to a [PackedByteArray], where each element has been encoded as 4 bytes. The size of the new array will be [code]float32_array.size() * 4[/code]. diff --git a/doc/classes/PackedFloat64Array.xml b/doc/classes/PackedFloat64Array.xml index 4622d632586..56cc08d8c40 100644 --- a/doc/classes/PackedFloat64Array.xml +++ b/doc/classes/PackedFloat64Array.xml @@ -193,7 +193,7 @@ - Returns a copy of the data converted to a [PackedByteArray], where each element have been encoded as 8 bytes. + Returns a copy of the data converted to a [PackedByteArray], where each element has been encoded as 8 bytes. The size of the new array will be [code]float64_array.size() * 8[/code]. diff --git a/doc/classes/PackedInt32Array.xml b/doc/classes/PackedInt32Array.xml index 3a3596b2d02..5bb2ea97ef9 100644 --- a/doc/classes/PackedInt32Array.xml +++ b/doc/classes/PackedInt32Array.xml @@ -186,7 +186,7 @@ - Returns a copy of the data converted to a [PackedByteArray], where each element have been encoded as 4 bytes. + Returns a copy of the data converted to a [PackedByteArray], where each element has been encoded as 4 bytes. The size of the new array will be [code]int32_array.size() * 4[/code]. diff --git a/doc/classes/PackedInt64Array.xml b/doc/classes/PackedInt64Array.xml index b82d0de350e..2ed85489588 100644 --- a/doc/classes/PackedInt64Array.xml +++ b/doc/classes/PackedInt64Array.xml @@ -187,7 +187,7 @@ - Returns a copy of the data converted to a [PackedByteArray], where each element have been encoded as 8 bytes. + Returns a copy of the data converted to a [PackedByteArray], where each element has been encoded as 8 bytes. The size of the new array will be [code]int64_array.size() * 8[/code]. diff --git a/doc/classes/ShaderIncludeDB.xml b/doc/classes/ShaderIncludeDB.xml new file mode 100644 index 00000000000..a431eabf4e8 --- /dev/null +++ b/doc/classes/ShaderIncludeDB.xml @@ -0,0 +1,33 @@ + + + + Internal database of built in shader include files. + + + This object contains shader fragments from Godot's internal shaders. These can be used when access to internal uniform buffers and/or internal functions is required for instance when composing compositor effects or compute shaders. Only fragments for the current rendering device are loaded. + + + + + + + + + Returns the code for the built-in shader fragment. You can also access this in your shader code through [code]#include "filename"[/code]. + + + + + + + Returns [code]true[/code] if an include file with this name exists. + + + + + + Returns a list of built-in include files that are currently registered. + + + + diff --git a/doc/classes/SurfaceTool.xml b/doc/classes/SurfaceTool.xml index 8f0760a0ab7..8e44146d95e 100644 --- a/doc/classes/SurfaceTool.xml +++ b/doc/classes/SurfaceTool.xml @@ -147,7 +147,7 @@ - Generates a tangent vector for each vertex. Requires that each vertex have UVs and normals set already (see [method generate_normals]). + Generates a tangent vector for each vertex. Requires that each vertex already has UVs and normals set (see [method generate_normals]). diff --git a/drivers/d3d12/rendering_device_driver_d3d12.cpp b/drivers/d3d12/rendering_device_driver_d3d12.cpp index cc711977c58..e6d85d81fa7 100644 --- a/drivers/d3d12/rendering_device_driver_d3d12.cpp +++ b/drivers/d3d12/rendering_device_driver_d3d12.cpp @@ -6146,6 +6146,16 @@ uint64_t RenderingDeviceDriverD3D12::limit_get(Limit p_limit) { switch (p_limit) { case LIMIT_MAX_BOUND_UNIFORM_SETS: return safe_unbounded; + case LIMIT_MAX_TEXTURE_ARRAY_LAYERS: + return D3D12_REQ_TEXTURE2D_ARRAY_AXIS_DIMENSION; + case LIMIT_MAX_TEXTURE_SIZE_1D: + return D3D12_REQ_TEXTURE1D_U_DIMENSION; + case LIMIT_MAX_TEXTURE_SIZE_2D: + return D3D12_REQ_TEXTURE2D_U_OR_V_DIMENSION; + case LIMIT_MAX_TEXTURE_SIZE_3D: + return D3D12_REQ_TEXTURE3D_U_V_OR_W_DIMENSION; + case LIMIT_MAX_TEXTURE_SIZE_CUBE: + return D3D12_REQ_TEXTURECUBE_DIMENSION; case LIMIT_MAX_TEXTURES_PER_SHADER_STAGE: return device_limits.max_srvs_per_shader_stage; case LIMIT_MAX_UNIFORM_BUFFER_SIZE: diff --git a/drivers/gles3/storage/light_storage.cpp b/drivers/gles3/storage/light_storage.cpp index 07d07f727ca..a3d06ae6b01 100644 --- a/drivers/gles3/storage/light_storage.cpp +++ b/drivers/gles3/storage/light_storage.cpp @@ -1407,7 +1407,7 @@ bool LightStorage::shadow_atlas_update_light(RID p_atlas, RID p_light_instance, old_shadow = old_key & SHADOW_INDEX_MASK; // Only re-allocate if a better option is available, and enough time has passed. - should_realloc = shadow_atlas->quadrants[old_quadrant].subdivision != (uint32_t)best_subdiv && (shadow_atlas->quadrants[old_quadrant].shadows[old_shadow].alloc_tick - tick > shadow_atlas_realloc_tolerance_msec); + should_realloc = shadow_atlas->quadrants[old_quadrant].subdivision != (uint32_t)best_subdiv && (tick - shadow_atlas->quadrants[old_quadrant].shadows[old_shadow].alloc_tick > shadow_atlas_realloc_tolerance_msec); should_redraw = shadow_atlas->quadrants[old_quadrant].shadows[old_shadow].version != p_light_version; if (!should_realloc) { diff --git a/drivers/gles3/storage/mesh_storage.cpp b/drivers/gles3/storage/mesh_storage.cpp index 917515bbdbd..1c1edb3b970 100644 --- a/drivers/gles3/storage/mesh_storage.cpp +++ b/drivers/gles3/storage/mesh_storage.cpp @@ -846,6 +846,7 @@ void MeshStorage::mesh_clear(RID p_mesh) { mesh->surface_count = 0; mesh->material_cache.clear(); mesh->has_bone_weights = false; + mesh->aabb = AABB(); mesh->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_MESH); for (Mesh *E : mesh->shadow_owners) { diff --git a/drivers/unix/os_unix.cpp b/drivers/unix/os_unix.cpp index 78e33c17919..7dae5d7cd6f 100644 --- a/drivers/unix/os_unix.cpp +++ b/drivers/unix/os_unix.cpp @@ -951,13 +951,13 @@ String OS_Unix::get_environment(const String &p_var) const { } void OS_Unix::set_environment(const String &p_var, const String &p_value) const { - ERR_FAIL_COND_MSG(p_var.is_empty() || p_var.contains("="), vformat("Invalid environment variable name '%s', cannot be empty or include '='.", p_var)); + ERR_FAIL_COND_MSG(p_var.is_empty() || p_var.contains_char('='), vformat("Invalid environment variable name '%s', cannot be empty or include '='.", p_var)); int err = setenv(p_var.utf8().get_data(), p_value.utf8().get_data(), /* overwrite: */ 1); ERR_FAIL_COND_MSG(err != 0, vformat("Failed setting environment variable '%s', the system is out of memory.", p_var)); } void OS_Unix::unset_environment(const String &p_var) const { - ERR_FAIL_COND_MSG(p_var.is_empty() || p_var.contains("="), vformat("Invalid environment variable name '%s', cannot be empty or include '='.", p_var)); + ERR_FAIL_COND_MSG(p_var.is_empty() || p_var.contains_char('='), vformat("Invalid environment variable name '%s', cannot be empty or include '='.", p_var)); unsetenv(p_var.utf8().get_data()); } diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp index 47e1f26ff6c..37458151b14 100644 --- a/editor/connections_dialog.cpp +++ b/editor/connections_dialog.cpp @@ -525,7 +525,7 @@ void ConnectDialog::set_dst_node(Node *p_node) { StringName ConnectDialog::get_dst_method_name() const { String txt = dst_method->get_text(); - if (txt.contains("(")) { + if (txt.contains_char('(')) { txt = txt.left(txt.find_char('(')).strip_edges(); } return txt; @@ -909,7 +909,7 @@ ConnectDialog::~ConnectDialog() { Control *ConnectionsDockTree::make_custom_tooltip(const String &p_text) const { // If it's not a doc tooltip, fallback to the default one. - if (p_text.contains("::")) { + if (p_text.is_empty() || p_text.contains("::")) { return nullptr; } diff --git a/editor/debugger/debug_adapter/debug_adapter_parser.cpp b/editor/debugger/debug_adapter/debug_adapter_parser.cpp index 13f0802261a..fd7e352c6c1 100644 --- a/editor/debugger/debug_adapter/debug_adapter_parser.cpp +++ b/editor/debugger/debug_adapter/debug_adapter_parser.cpp @@ -361,7 +361,7 @@ Dictionary DebugAdapterParser::req_setBreakpoints(const Dictionary &p_params) co } // If path contains \, it's a Windows path, so we need to convert it to /, and make the drive letter uppercase - if (source.path.contains("\\")) { + if (source.path.contains_char('\\')) { source.path = source.path.replace("\\", "/"); source.path = source.path.substr(0, 1).to_upper() + source.path.substr(1); } diff --git a/editor/debugger/debug_adapter/debug_adapter_parser.h b/editor/debugger/debug_adapter/debug_adapter_parser.h index db63085f0e9..9ab01717a5d 100644 --- a/editor/debugger/debug_adapter/debug_adapter_parser.h +++ b/editor/debugger/debug_adapter/debug_adapter_parser.h @@ -49,7 +49,7 @@ class DebugAdapterParser : public Object { _FORCE_INLINE_ bool is_valid_path(const String &p_path) const { // If path contains \, it's a Windows path, so we need to convert it to /, and check as case-insensitive. - if (p_path.contains("\\")) { + if (p_path.contains_char('\\')) { String project_path = ProjectSettings::get_singleton()->get_resource_path(); String path = p_path.replace("\\", "/"); return path.containsn(project_path); diff --git a/editor/directory_create_dialog.cpp b/editor/directory_create_dialog.cpp index 885fe94e4df..4ff9b7cef2a 100644 --- a/editor/directory_create_dialog.cpp +++ b/editor/directory_create_dialog.cpp @@ -69,8 +69,8 @@ String DirectoryCreateDialog::_validate_path(const String &p_path) const { return TTR("Folder name cannot be empty."); } } - if (part.contains("\\") || part.contains(":") || part.contains("*") || - part.contains("|") || part.contains(">") || part.ends_with(".") || part.ends_with(" ")) { + if (part.contains_char('\\') || part.contains_char(':') || part.contains_char('*') || + part.contains_char('|') || part.contains_char('>') || part.ends_with(".") || part.ends_with(" ")) { if (is_file) { return TTR("File name contains invalid characters."); } else { @@ -103,7 +103,7 @@ void DirectoryCreateDialog::_on_dir_path_changed() { const String error = _validate_path(path); if (error.is_empty()) { - if (path.contains("/")) { + if (path.contains_char('/')) { if (mode == MODE_DIRECTORY) { validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Using slashes in folder names will create subfolders recursively."), EditorValidationPanel::MSG_OK); } else { diff --git a/editor/editor_feature_profile.cpp b/editor/editor_feature_profile.cpp index b0df84da5b7..51c68487876 100644 --- a/editor/editor_feature_profile.cpp +++ b/editor/editor_feature_profile.cpp @@ -474,7 +474,7 @@ void EditorFeatureProfileManager::_erase_selected_profile() { void EditorFeatureProfileManager::_create_new_profile() { String name = new_profile_name->get_text().strip_edges(); - if (!name.is_valid_filename() || name.contains(".")) { + if (!name.is_valid_filename() || name.contains_char('.')) { EditorNode::get_singleton()->show_warning(TTR("Profile must be a valid filename and must not contain '.'")); return; } diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp index 23d3508a8dd..4054441ceb3 100644 --- a/editor/editor_file_system.cpp +++ b/editor/editor_file_system.cpp @@ -386,7 +386,7 @@ void EditorFileSystem::_scan_filesystem() { FileCache fc; fc.type = split[1]; - if (fc.type.contains("/")) { + if (fc.type.contains_char('/')) { fc.type = split[1].get_slice("/", 0); fc.resource_script_class = split[1].get_slice("/", 1); } diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index 0c6bbf6edbd..137d685cb04 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -322,7 +322,7 @@ void EditorHelp::_class_desc_select(const String &p_select) { } } - if (link.contains(".")) { + if (link.contains_char('.')) { const int class_end = link.find_char('.'); emit_signal(SNAME("go_to_help"), topic + ":" + link.left(class_end) + ":" + link.substr(class_end + 1)); } @@ -367,7 +367,7 @@ static void _add_type_to_rt(const String &p_type, const String &p_enum, bool p_i bool is_enum_type = !p_enum.is_empty(); bool is_bitfield = p_is_bitfield && is_enum_type; - bool can_ref = !p_type.contains("*") || is_enum_type; + bool can_ref = !p_type.contains_char('*') || is_enum_type; String link_t = p_type; // For links in metadata String display_t; // For display purposes. @@ -2554,7 +2554,7 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, const C p_rt->push_meta("@" + link_tag + " " + link_target, underline_mode); if (link_tag == "member" && - ((!link_target.contains(".") && (p_class == "ProjectSettings" || p_class == "EditorSettings")) || + ((!link_target.contains_char('.') && (p_class == "ProjectSettings" || p_class == "EditorSettings")) || link_target.begins_with("ProjectSettings.") || link_target.begins_with("EditorSettings."))) { // Special formatting for both ProjectSettings and EditorSettings. String prefix; @@ -3667,7 +3667,7 @@ void EditorHelpBit::_meta_clicked(const String &p_select) { return; } - if (link.contains(".")) { + if (link.contains_char('.')) { const int class_end = link.find_char('.'); _go_to_help(topic + ":" + link.left(class_end) + ":" + link.substr(class_end + 1)); } else { diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index aff24ab17f0..97a91575d6e 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -71,6 +71,12 @@ bool EditorInspector::_property_path_matches(const String &p_property_path, cons return false; } +String EditorProperty::get_tooltip_string(const String &p_string) const { + // Trim to 100 characters to prevent the tooltip from being too long. + constexpr int TOOLTIP_MAX_LENGTH = 100; + return p_string.left(TOOLTIP_MAX_LENGTH).strip_edges() + String((p_string.length() > TOOLTIP_MAX_LENGTH) ? "..." : ""); +} + Size2 EditorProperty::get_minimum_size() const { Size2 ms; Ref font = get_theme_font(SceneStringName(font), SNAME("Tree")); @@ -277,7 +283,7 @@ void EditorProperty::_notification(int p_what) { } else { color = get_theme_color(is_read_only() ? SNAME("readonly_color") : SNAME("property_color")); } - if (label.contains(".")) { + if (label.contains_char('.')) { // FIXME: Move this to the project settings editor, as this is only used // for project settings feature tag overrides. color.a = 0.5; @@ -1092,6 +1098,21 @@ void EditorProperty::_bind_methods() { ClassDB::bind_method(D_METHOD("add_focusable", "control"), &EditorProperty::add_focusable); ClassDB::bind_method(D_METHOD("set_bottom_editor", "editor"), &EditorProperty::set_bottom_editor); + ClassDB::bind_method(D_METHOD("set_selectable", "selectable"), &EditorProperty::set_selectable); + ClassDB::bind_method(D_METHOD("is_selectable"), &EditorProperty::is_selectable); + + ClassDB::bind_method(D_METHOD("set_use_folding", "use_folding"), &EditorProperty::set_use_folding); + ClassDB::bind_method(D_METHOD("is_using_folding"), &EditorProperty::is_using_folding); + + ClassDB::bind_method(D_METHOD("set_name_split_ratio", "ratio"), &EditorProperty::set_name_split_ratio); + ClassDB::bind_method(D_METHOD("get_name_split_ratio"), &EditorProperty::get_name_split_ratio); + + ClassDB::bind_method(D_METHOD("deselect"), &EditorProperty::deselect); + ClassDB::bind_method(D_METHOD("is_selected"), &EditorProperty::is_selected); + ClassDB::bind_method(D_METHOD("select", "focusable"), &EditorProperty::select, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("set_object_and_property", "object", "property"), &EditorProperty::set_object_and_property); + ClassDB::bind_method(D_METHOD("set_label_reference", "control"), &EditorProperty::set_label_reference); + ClassDB::bind_method(D_METHOD("emit_changed", "property", "value", "field", "changing"), &EditorProperty::emit_changed, DEFVAL(StringName()), DEFVAL(false)); ADD_PROPERTY(PropertyInfo(Variant::STRING, "label"), "set_label", "get_label"); @@ -1101,6 +1122,9 @@ void EditorProperty::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_warning"), "set_draw_warning", "is_draw_warning"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "keying"), "set_keying", "is_keying"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deletable"), "set_deletable", "is_deletable"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selectable"), "set_selectable", "is_selectable"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_folding"), "set_use_folding", "is_using_folding"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "name_split_ratio"), "set_name_split_ratio", "get_name_split_ratio"); ADD_SIGNAL(MethodInfo("property_changed", PropertyInfo(Variant::STRING_NAME, "property"), PropertyInfo(Variant::NIL, "value", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT), PropertyInfo(Variant::STRING_NAME, "field"), PropertyInfo(Variant::BOOL, "changing"))); ADD_SIGNAL(MethodInfo("multiple_properties_changed", PropertyInfo(Variant::PACKED_STRING_ARRAY, "properties"), PropertyInfo(Variant::ARRAY, "value"))); @@ -3173,7 +3197,7 @@ void EditorInspector::update_tree() { } // Get the property label's string. - String name_override = (path.contains("/")) ? path.substr(path.rfind_char('/') + 1) : path; + String name_override = (path.contains_char('/')) ? path.substr(path.rfind_char('/') + 1) : path; String feature_tag; { const int dot = name_override.find_char('.'); @@ -3322,7 +3346,7 @@ void EditorInspector::update_tree() { array_element_prefix = class_name_components[0]; editor_inspector_array = memnew(EditorInspectorArray(all_read_only)); - String array_label = path.contains("/") ? path.substr(path.rfind_char('/') + 1) : path; + String array_label = path.contains_char('/') ? path.substr(path.rfind_char('/') + 1) : path; array_label = EditorPropertyNameProcessor::get_singleton()->process_name(property_label_string, property_name_style, p.name, doc_name); int page = per_array_page.has(array_element_prefix) ? per_array_page[array_element_prefix] : 0; editor_inspector_array->setup_with_move_element_function(object, array_label, array_element_prefix, page, c, use_folding); @@ -4630,6 +4654,8 @@ void EditorInspector::_bind_methods() { ClassDB::bind_method("get_selected_path", &EditorInspector::get_selected_path); ClassDB::bind_method("get_edited_object", &EditorInspector::get_edited_object); + ClassDB::bind_static_method("EditorInspector", D_METHOD("instantiate_property_editor", "object", "type", "path", "hint", "hint_text", "usage", "wide"), &EditorInspector::instantiate_property_editor, DEFVAL(false)); + ADD_SIGNAL(MethodInfo("property_selected", PropertyInfo(Variant::STRING, "property"))); ADD_SIGNAL(MethodInfo("property_keyed", PropertyInfo(Variant::STRING, "property"), PropertyInfo(Variant::NIL, "value", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT), PropertyInfo(Variant::BOOL, "advance"))); ADD_SIGNAL(MethodInfo("property_deleted", PropertyInfo(Variant::STRING, "property"))); diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h index 63cbaf84273..672bbe534e2 100644 --- a/editor/editor_inspector.h +++ b/editor/editor_inspector.h @@ -162,6 +162,8 @@ class EditorProperty : public Container { public: void emit_changed(const StringName &p_property, const Variant &p_value, const StringName &p_field = StringName(), bool p_changing = false); + String get_tooltip_string(const String &p_string) const; + virtual Size2 get_minimum_size() const override; void set_label(const String &p_label); @@ -172,7 +174,10 @@ class EditorProperty : public Container { Object *get_edited_object(); StringName get_edited_property() const; - inline Variant get_edited_property_value() const { return object->get(property); } + inline Variant get_edited_property_value() const { + ERR_FAIL_NULL_V(object, Variant()); + return object->get(property); + } EditorInspector *get_parent_inspector() const; void set_doc_path(const String &p_doc_path); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 87a60567f99..ed586216bbe 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -1429,7 +1429,7 @@ void EditorNode::save_resource_as(const Ref &p_resource, const String if (p_resource->get_path().is_resource_file()) { file->set_current_file(p_resource->get_path().get_file()); } else { - if (extensions.size()) { + if (!preferred.is_empty()) { String resource_name_snake_case = p_resource->get_class().to_snake_case(); file->set_current_file("new_" + resource_name_snake_case + "." + preferred.front()->get().to_lower()); } else { @@ -1438,18 +1438,15 @@ void EditorNode::save_resource_as(const Ref &p_resource, const String } } else if (!p_resource->get_path().is_empty()) { file->set_current_path(p_resource->get_path()); - if (extensions.size()) { - String ext = p_resource->get_path().get_extension().to_lower(); + if (!extensions.is_empty()) { + const String ext = p_resource->get_path().get_extension().to_lower(); if (extensions.find(ext) == nullptr) { file->set_current_path(p_resource->get_path().replacen("." + ext, "." + extensions.front()->get())); } } - } else if (preferred.size()) { - String existing; - if (extensions.size()) { - String resource_name_snake_case = p_resource->get_class().to_snake_case(); - existing = "new_" + resource_name_snake_case + "." + preferred.front()->get().to_lower(); - } + } else if (!preferred.is_empty()) { + const String resource_name_snake_case = p_resource->get_class().to_snake_case(); + const String existing = "new_" + resource_name_snake_case + "." + preferred.front()->get().to_lower(); file->set_current_path(existing); } file->set_title(TTR("Save Resource As...")); @@ -3446,6 +3443,11 @@ void EditorNode::_update_file_menu_closed() { file_menu->set_item_disabled(file_menu->get_item_index(FILE_OPEN_PREV), false); } +void EditorNode::_palette_quick_open_dialog() { + quick_open_color_palette->popup_dialog({ "ColorPalette" }, palette_file_selected_callback); + quick_open_color_palette->set_title(TTR("Quick Open Color Palette...")); +} + void EditorNode::replace_resources_in_object(Object *p_object, const Vector> &p_source_resources, const Vector> &p_target_resource) { List pi; p_object->get_property_list(&pi); @@ -3905,6 +3907,10 @@ void EditorNode::setup_color_picker(ColorPicker *p_picker) { p_picker->set_color_mode((ColorPicker::ColorModeType)default_color_mode); p_picker->set_picker_shape((ColorPicker::PickerShapeType)picker_shape); + + p_picker->set_quick_open_callback(callable_mp(this, &EditorNode::_palette_quick_open_dialog)); + p_picker->set_palette_saved_callback(callable_mp(EditorFileSystem::get_singleton(), &EditorFileSystem::update_file)); + palette_file_selected_callback = callable_mp(p_picker, &ColorPicker::_quick_open_palette_file_selected); } bool EditorNode::is_scene_open(const String &p_path) { @@ -7175,6 +7181,7 @@ EditorNode::EditorNode() { main_menu = memnew(MenuBar); main_menu->set_mouse_filter(Control::MOUSE_FILTER_STOP); title_bar->add_child(main_menu); + main_menu->set_v_size_flags(Control::SIZE_SHRINK_CENTER); main_menu->set_theme_type_variation("MainMenuBar"); main_menu->set_start_index(0); // Main menu, add to the start of global menu. main_menu->set_prefer_global_menu(global_menu); @@ -7874,6 +7881,9 @@ EditorNode::EditorNode() { quick_open_dialog = memnew(EditorQuickOpenDialog); gui_base->add_child(quick_open_dialog); + quick_open_color_palette = memnew(EditorQuickOpenDialog); + gui_base->add_child(quick_open_color_palette); + _update_recent_scenes(); set_process_shortcut_input(true); diff --git a/editor/editor_node.h b/editor/editor_node.h index 4bf892579f8..30412ddd005 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -266,6 +266,7 @@ class EditorNode : public Node { EditorPluginList *editor_plugins_force_input_forwarding = nullptr; EditorPluginList *editor_plugins_force_over = nullptr; EditorPluginList *editor_plugins_over = nullptr; + EditorQuickOpenDialog *quick_open_color_palette = nullptr; EditorResourcePreview *resource_preview = nullptr; EditorSelection *editor_selection = nullptr; EditorSettingsDialog *editor_settings_dialog = nullptr; @@ -415,6 +416,7 @@ class EditorNode : public Node { Timer *editor_layout_save_delay_timer = nullptr; Timer *scan_changes_timer = nullptr; Button *distraction_free = nullptr; + Callable palette_file_selected_callback; EditorBottomPanel *bottom_panel = nullptr; @@ -536,6 +538,7 @@ class EditorNode : public Node { void _export_as_menu_option(int p_idx); void _update_file_menu_opened(); void _update_file_menu_closed(); + void _palette_quick_open_dialog(); void _remove_plugin_from_enabled(const String &p_name); void _plugin_over_edit(EditorPlugin *p_plugin, Object *p_object); diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index b7eeb1e3963..8e13233b59e 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -93,6 +93,10 @@ void EditorPropertyText::_text_changed(const String &p_string) { return; } + // Set tooltip so that the full text is displayed in a tooltip if hovered. + // This is useful when using a narrow inspector, as the text can be trimmed otherwise. + text->set_tooltip_text(get_tooltip_string(text->get_text())); + if (string_name) { emit_changed(get_edited_property(), StringName(p_string)); } else { @@ -106,6 +110,7 @@ void EditorPropertyText::update_property() { if (text->get_text() != s) { int caret = text->get_caret_column(); text->set_text(s); + text->set_tooltip_text(get_tooltip_string(s)); text->set_caret_column(caret); } text->set_editable(!is_read_only()); @@ -152,10 +157,14 @@ void EditorPropertyMultilineText::_set_read_only(bool p_read_only) { void EditorPropertyMultilineText::_big_text_changed() { text->set_text(big_text->get_text()); + // Set tooltip so that the full text is displayed in a tooltip if hovered. + // This is useful when using a narrow inspector, as the text can be trimmed otherwise. + text->set_tooltip_text(get_tooltip_string(big_text->get_text())); emit_changed(get_edited_property(), big_text->get_text(), "", true); } void EditorPropertyMultilineText::_text_changed() { + text->set_tooltip_text(get_tooltip_string(text->get_text())); emit_changed(get_edited_property(), text->get_text(), "", true); } @@ -184,6 +193,7 @@ void EditorPropertyMultilineText::update_property() { String t = get_edited_property_value(); if (text->get_text() != t) { text->set_text(t); + text->set_tooltip_text(get_tooltip_string(t)); if (big_text && big_text->is_visible_in_tree()) { big_text->set_text(t); } @@ -829,7 +839,7 @@ void EditorPropertyLayersGrid::_rename_operation_confirm() { if (new_name.length() == 0) { EditorNode::get_singleton()->show_warning(TTR("No name provided.")); return; - } else if (new_name.contains("/") || new_name.contains("\\") || new_name.contains(":")) { + } else if (new_name.contains_char('/') || new_name.contains_char('\\') || new_name.contains_char(':')) { EditorNode::get_singleton()->show_warning(TTR("Name contains invalid characters.")); return; } @@ -2865,7 +2875,7 @@ void EditorPropertyNodePath::update_property() { const Node *target_node = base_node->get_node(p); ERR_FAIL_NULL(target_node); - if (String(target_node->get_name()).contains("@")) { + if (String(target_node->get_name()).contains_char('@')) { assign->set_button_icon(Ref()); assign->set_text(p); return; diff --git a/editor/editor_property_name_processor.cpp b/editor/editor_property_name_processor.cpp index 1c6b90d5d24..48c5a57b5ba 100644 --- a/editor/editor_property_name_processor.cpp +++ b/editor/editor_property_name_processor.cpp @@ -237,6 +237,7 @@ EditorPropertyNameProcessor::EditorPropertyNameProcessor() { capitalize_string_remaps["linuxbsd"] = "Linux/*BSD"; capitalize_string_remaps["lod"] = "LOD"; capitalize_string_remaps["lods"] = "LODs"; + capitalize_string_remaps["loongarch64"] = "loongarch64"; capitalize_string_remaps["lowpass"] = "Low-pass"; capitalize_string_remaps["macos"] = "macOS"; capitalize_string_remaps["mb"] = "(MB)"; // Unit. diff --git a/editor/editor_sectioned_inspector.cpp b/editor/editor_sectioned_inspector.cpp index 88e49dc3255..50e30e9b2f2 100644 --- a/editor/editor_sectioned_inspector.cpp +++ b/editor/editor_sectioned_inspector.cpp @@ -110,7 +110,7 @@ class SectionedInspectorFilter : public Object { if (pi.name.begins_with(section + "/")) { pi.name = pi.name.replace_first(section + "/", ""); - if (!allow_sub && pi.name.contains("/")) { + if (!allow_sub && pi.name.contains_char('/')) { continue; } p_list->push_back(pi); @@ -249,7 +249,7 @@ void SectionedInspector::update_category_list() { continue; } - if (pi.name.contains(":") || pi.name == "script" || pi.name == "resource_name" || pi.name == "resource_path" || pi.name == "resource_local_to_scene" || pi.name.begins_with("_global_script")) { + if (pi.name.contains_char(':') || pi.name == "script" || pi.name == "resource_name" || pi.name == "resource_path" || pi.name == "resource_local_to_scene" || pi.name.begins_with("_global_script")) { continue; } diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index 8aba8c47152..125aeb27d43 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -829,7 +829,8 @@ void EditorSettings::_load_defaults(Ref p_extra_config) { _initial_set("editors/3d/navigation/warped_mouse_panning", true, true); // 3D: Navigation feel - EDITOR_SETTING_BASIC(Variant::FLOAT, PROPERTY_HINT_RANGE, "editors/3d/navigation_feel/orbit_sensitivity", 0.25, "0.01,2,0.001") + EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "editors/3d/navigation_feel/orbit_sensitivity", 0.25, "0.01,20,0.001") + EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "editors/3d/navigation_feel/translation_sensitivity", 1.0, "0.01,20,0.001") EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "editors/3d/navigation_feel/orbit_inertia", 0.0, "0,1,0.001") EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "editors/3d/navigation_feel/translation_inertia", 0.05, "0,1,0.001") EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "editors/3d/navigation_feel/zoom_inertia", 0.05, "0,1,0.001") diff --git a/editor/editor_string_names.h b/editor/editor_string_names.h index 44fc7f485cc..220cdacc779 100644 --- a/editor/editor_string_names.h +++ b/editor/editor_string_names.h @@ -36,9 +36,7 @@ #include "core/string/string_name.h" class EditorStringNames { - static EditorStringNames *singleton; - - EditorStringNames(); + inline static EditorStringNames *singleton = nullptr; public: static void create() { singleton = memnew(EditorStringNames); } @@ -49,10 +47,10 @@ class EditorStringNames { _FORCE_INLINE_ static EditorStringNames *get_singleton() { return singleton; } - StringName Editor; - StringName EditorFonts; - StringName EditorIcons; - StringName EditorStyles; + const StringName Editor = StaticCString::create("Editor"); + const StringName EditorFonts = StaticCString::create("EditorFonts"); + const StringName EditorIcons = StaticCString::create("EditorIcons"); + const StringName EditorStyles = StaticCString::create("EditorStyles"); }; #define EditorStringName(m_name) EditorStringNames::get_singleton()->m_name diff --git a/editor/export/export_template_manager.cpp b/editor/export/export_template_manager.cpp index 1f2f9b068f1..117f4d51b87 100644 --- a/editor/export/export_template_manager.cpp +++ b/editor/export/export_template_manager.cpp @@ -440,6 +440,13 @@ bool ExportTemplateManager::_install_file_selected(const String &p_file, bool p_ } String file = String::utf8(fname); + + // Skip the __MACOSX directory created by macOS's built-in file zipper. + if (file.begins_with("__MACOSX")) { + ret = unzGoToNextFile(pkg); + continue; + } + if (file.ends_with("version.txt")) { Vector uncomp_data; uncomp_data.resize(info.uncompressed_size); @@ -515,7 +522,8 @@ bool ExportTemplateManager::_install_file_selected(const String &p_file, bool p_ String file = file_path.get_file(); - if (file.size() == 0) { + // Skip the __MACOSX directory created by macOS's built-in file zipper. + if (file.is_empty() || file.begins_with("__MACOSX")) { ret = unzGoToNextFile(pkg); continue; } diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index 137d86d6bb6..9152058811a 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -247,6 +247,8 @@ void FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory } subdirectory_item->set_selectable(0, true); subdirectory_item->set_metadata(0, lpath); + folder_map[lpath] = subdirectory_item; + if (!p_select_in_favorites && (current_path == lpath || ((display_mode != DISPLAY_MODE_TREE_ONLY) && current_path.get_base_dir() == lpath))) { subdirectory_item->select(0); // Keep select an item when re-created a tree @@ -373,6 +375,7 @@ void FileSystemDock::_update_tree(const Vector &p_uncollapsed_paths, boo tree_update_id++; updating_tree = true; TreeItem *root = tree->create_item(); + folder_map.clear(); // Handles the favorites. favorites_item = tree->create_item(root); @@ -704,19 +707,21 @@ void FileSystemDock::_set_current_path_line_edit_text(const String &p_path) { } void FileSystemDock::_navigate_to_path(const String &p_path, bool p_select_in_favorites) { + bool is_directory = false; if (p_path == "Favorites") { current_path = p_path; } else { String target_path = p_path; // If the path is a file, do not only go to the directory in the tree, also select the file in the file list. if (target_path.ends_with("/")) { - target_path = target_path.substr(0, target_path.length() - 1); + target_path = target_path.trim_suffix("/"); } Ref da = DirAccess::create(DirAccess::ACCESS_RESOURCES); if (da->file_exists(p_path)) { current_path = target_path; } else if (da->dir_exists(p_path)) { current_path = target_path + "/"; + is_directory = true; } else { ERR_FAIL_MSG(vformat("Cannot navigate to '%s' as it has not been found in the file system!", p_path)); } @@ -725,17 +730,56 @@ void FileSystemDock::_navigate_to_path(const String &p_path, bool p_select_in_fa _set_current_path_line_edit_text(current_path); _push_to_history(); - _update_tree(get_uncollapsed_paths(), false, p_select_in_favorites, true); + const String file_name = is_directory ? p_path.trim_suffix("/").get_file() + "/" : p_path.get_file(); + bool found = false; + + TreeItem **base_dir_ptr; + { + const String base_dir = current_path.get_base_dir(); + if (base_dir == "res://") { + base_dir_ptr = folder_map.getptr(base_dir); + } else if (is_directory) { + base_dir_ptr = folder_map.getptr(base_dir.get_base_dir() + "/"); + } else { + base_dir_ptr = folder_map.getptr(base_dir + "/"); + } + } + + if (base_dir_ptr) { + TreeItem *directory = *base_dir_ptr; + { + TreeItem *entry = directory->get_first_child(); + while (entry) { + if (entry->get_metadata(0).operator String().ends_with(file_name)) { + tree->deselect_all(); + entry->select(0); + found = true; + break; + } + entry = entry->get_next(); + } + } + + while (directory) { + directory->set_collapsed(false); + directory = directory->get_parent(); + } + } + + if (!found) { + return; + } + + tree->ensure_cursor_is_visible(); if (display_mode != DISPLAY_MODE_TREE_ONLY) { _update_file_list(false); // Reset the scroll for a directory. - if (p_path.ends_with("/")) { + if (is_directory) { files->get_v_scroll_bar()->set_value(0); } } - String file_name = p_path.get_file(); if (!file_name.is_empty()) { for (int i = 0; i < files->get_item_count(); i++) { if (files->get_item_text(i) == file_name) { @@ -1761,7 +1805,7 @@ void FileSystemDock::_rename_operation_confirm() { if (new_name.length() == 0) { EditorNode::get_singleton()->show_warning(TTR("No name provided.")); rename_error = true; - } else if (new_name.contains("/") || new_name.contains("\\") || new_name.contains(":")) { + } else if (new_name.contains_char('/') || new_name.contains_char('\\') || new_name.contains_char(':')) { EditorNode::get_singleton()->show_warning(TTR("Name contains invalid characters.")); rename_error = true; } else if (new_name[0] == '.') { @@ -2224,7 +2268,7 @@ void FileSystemDock::_file_option(int p_option, const Vector &p_selected test_args.push_back("command -v " + terminal_emulator); const Error err = OS::get_singleton()->execute("bash", test_args, &pipe); // Check if a path to the terminal executable exists. - if (err == OK && pipe.contains("/")) { + if (err == OK && pipe.contains_char('/')) { chosen_terminal_emulator = terminal_emulator; break; } else if (err == ERR_CANT_FORK) { diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h index 54dc47dda8d..ecdb9fa9aec 100644 --- a/editor/filesystem_dock.h +++ b/editor/filesystem_dock.h @@ -135,6 +135,7 @@ class FileSystemDock : public VBoxContainer { CONVERT_BASE_ID = 1000, }; + HashMap folder_map; HashMap folder_colors; Dictionary assigned_folder_colors; diff --git a/editor/gui/editor_file_dialog.cpp b/editor/gui/editor_file_dialog.cpp index 6498a533ad9..63e9bcc627a 100644 --- a/editor/gui/editor_file_dialog.cpp +++ b/editor/gui/editor_file_dialog.cpp @@ -46,7 +46,6 @@ #include "scene/gui/check_box.h" #include "scene/gui/grid_container.h" #include "scene/gui/label.h" -#include "scene/gui/margin_container.h" #include "scene/gui/option_button.h" #include "scene/gui/separator.h" #include "scene/gui/split_container.h" @@ -1402,11 +1401,8 @@ void EditorFileDialog::set_file_mode(FileMode p_mode) { item_list->set_select_mode(ItemList::SELECT_SINGLE); } - if (can_create_dir) { - makedir->show(); - } else { - makedir->hide(); - } + makedir_sep->set_visible(can_create_dir); + makedir->set_visible(can_create_dir); } EditorFileDialog::FileMode EditorFileDialog::get_file_mode() const { @@ -1573,6 +1569,8 @@ void EditorFileDialog::_select_drive(int p_idx) { void EditorFileDialog::_update_drives(bool p_select) { int dc = dir_access->get_drive_count(); if (dc == 0 || access != ACCESS_FILESYSTEM) { + shortcuts_container->hide(); + drives_container->hide(); drives->hide(); } else { drives->clear(); @@ -1581,6 +1579,8 @@ void EditorFileDialog::_update_drives(bool p_select) { dp->remove_child(drives); } dp = dir_access->drives_are_shortcuts() ? shortcuts_container : drives_container; + shortcuts_container->set_visible(dir_access->drives_are_shortcuts()); + drives_container->set_visible(!dir_access->drives_are_shortcuts()); dp->add_child(drives); drives->show(); @@ -1770,6 +1770,9 @@ void EditorFileDialog::_update_favorites() { recent->deselect_all(); } } + + fav_up->set_disabled(current_favorite < 1); + fav_down->set_disabled(current_favorite == -1 || favorited_paths.size() - 1 <= current_favorite); } void EditorFileDialog::_favorite_pressed() { @@ -2356,7 +2359,8 @@ EditorFileDialog::EditorFileDialog() { drives->connect(SceneStringName(item_selected), callable_mp(this, &EditorFileDialog::_select_drive)); pathhb->add_child(drives); - pathhb->add_child(memnew(VSeparator)); + makedir_sep = memnew(VSeparator); + pathhb->add_child(makedir_sep); makedir = memnew(Button); makedir->set_theme_type_variation("FlatButton"); @@ -2475,7 +2479,8 @@ EditorFileDialog::EditorFileDialog() { lower_hb->add_child(memnew(VSeparator)); file_sort_button = memnew(MenuButton); - file_sort_button->set_flat(true); + file_sort_button->set_flat(false); + file_sort_button->set_theme_type_variation("FlatMenuButton"); file_sort_button->set_tooltip_text(TTR("Sort files")); show_search_filter_button = memnew(Button); diff --git a/editor/gui/editor_file_dialog.h b/editor/gui/editor_file_dialog.h index 1c86e882c47..7c03a08f9a7 100644 --- a/editor/gui/editor_file_dialog.h +++ b/editor/gui/editor_file_dialog.h @@ -46,6 +46,7 @@ class MenuButton; class OptionButton; class PopupMenu; class TextureRect; +class VSeparator; class EditorFileDialog : public ConfirmationDialog { GDCLASS(EditorFileDialog, ConfirmationDialog); @@ -91,6 +92,7 @@ class EditorFileDialog : public ConfirmationDialog { ConfirmationDialog *makedialog = nullptr; LineEdit *makedirname = nullptr; + VSeparator *makedir_sep = nullptr; Button *makedir = nullptr; Access access = ACCESS_RESOURCES; diff --git a/editor/gui/scene_tree_editor.cpp b/editor/gui/scene_tree_editor.cpp index 8f5f21e56d2..295ec2a678d 100644 --- a/editor/gui/scene_tree_editor.cpp +++ b/editor/gui/scene_tree_editor.cpp @@ -761,7 +761,7 @@ bool SceneTreeEditor::_item_matches_all_terms(TreeItem *p_item, const PackedStri const String &term = p_terms[i]; // Recognize special filter. - if (term.contains(":") && !term.get_slicec(':', 0).is_empty()) { + if (term.contains_char(':') && !term.get_slicec(':', 0).is_empty()) { String parameter = term.get_slicec(':', 0); String argument = term.get_slicec(':', 1); diff --git a/editor/import/3d/collada.cpp b/editor/import/3d/collada.cpp index 3cb45ccd27f..cd26855e3ab 100644 --- a/editor/import/3d/collada.cpp +++ b/editor/import/3d/collada.cpp @@ -1810,10 +1810,10 @@ void Collada::_parse_animation(XMLParser &p_parser) { } } - if (target.contains("/")) { //transform component + if (target.contains_char('/')) { //transform component track.target = target.get_slicec('/', 0); track.param = target.get_slicec('/', 1); - if (track.param.contains(".")) { + if (track.param.contains_char('.')) { track.component = track.param.get_slice(".", 1).to_upper(); } track.param = track.param.get_slice(".", 0); diff --git a/editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp b/editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp index d9bd9ff6e20..b574d0f08c8 100644 --- a/editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp +++ b/editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp @@ -241,7 +241,7 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory List anims; ap->get_animation_list(&anims); for (const StringName &name : anims) { - if (String(name).contains("/")) { + if (String(name).contains_char('/')) { continue; // Avoid animation library which may be created by importer dynamically. } diff --git a/editor/plugins/animation_blend_tree_editor_plugin.cpp b/editor/plugins/animation_blend_tree_editor_plugin.cpp index 1fb43b8c638..32d3f0b00fc 100644 --- a/editor/plugins/animation_blend_tree_editor_plugin.cpp +++ b/editor/plugins/animation_blend_tree_editor_plugin.cpp @@ -1070,7 +1070,7 @@ void AnimationNodeBlendTreeEditor::_node_renamed(const String &p_text, Refget_parent() == tree->get_root()) { diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp index 11e3daa2e4e..06fd3536baa 100644 --- a/editor/plugins/animation_player_editor_plugin.cpp +++ b/editor/plugins/animation_player_editor_plugin.cpp @@ -506,7 +506,7 @@ void AnimationPlayerEditor::_animation_rename() { String selected_name = animation->get_item_text(selected); // Remove library prefix if present. - if (selected_name.contains("/")) { + if (selected_name.contains_char('/')) { selected_name = selected_name.get_slice("/", 1); } @@ -539,7 +539,7 @@ void AnimationPlayerEditor::_animation_remove_confirmed() { ERR_FAIL_COND(al.is_null()); // For names of form lib_name/anim_name, remove library name prefix. - if (current.contains("/")) { + if (current.contains_char('/')) { current = current.get_slice("/", 1); } EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); @@ -628,7 +628,7 @@ void AnimationPlayerEditor::_animation_name_edited() { // Extract library prefix if present. String new_library_prefix = ""; - if (current.contains("/")) { + if (current.contains_char('/')) { new_library_prefix = current.get_slice("/", 0) + "/"; current = current.get_slice("/", 1); } @@ -1342,7 +1342,7 @@ void AnimationPlayerEditor::_animation_duplicate() { break; } - if (new_name.contains("/")) { + if (new_name.contains_char('/')) { // Discard library prefix. new_name = new_name.get_slice("/", 1); } diff --git a/editor/plugins/animation_state_machine_editor.cpp b/editor/plugins/animation_state_machine_editor.cpp index ac80099db12..e3f701e6f02 100644 --- a/editor/plugins/animation_state_machine_editor.cpp +++ b/editor/plugins/animation_state_machine_editor.cpp @@ -1620,7 +1620,7 @@ void AnimationNodeStateMachineEditor::_open_editor(const String &p_name) { void AnimationNodeStateMachineEditor::_name_edited(const String &p_text) { const String &new_name = p_text; - ERR_FAIL_COND(new_name.is_empty() || new_name.contains(".") || new_name.contains("/")); + ERR_FAIL_COND(new_name.is_empty() || new_name.contains_char('.') || new_name.contains_char('/')); if (new_name == prev_name) { return; // Nothing to do. diff --git a/editor/plugins/gdextension_export_plugin.h b/editor/plugins/gdextension_export_plugin.h index 6f2e9aaaf40..62d40566757 100644 --- a/editor/plugins/gdextension_export_plugin.h +++ b/editor/plugins/gdextension_export_plugin.h @@ -75,6 +75,7 @@ void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p all_archs.insert("ppc32"); all_archs.insert("ppc64"); all_archs.insert("wasm32"); + all_archs.insert("loongarch64"); all_archs.insert("universal"); HashSet archs; diff --git a/editor/plugins/gizmos/reflection_probe_gizmo_plugin.cpp b/editor/plugins/gizmos/reflection_probe_gizmo_plugin.cpp index 4a53c3c6b47..5f45f4fda68 100644 --- a/editor/plugins/gizmos/reflection_probe_gizmo_plugin.cpp +++ b/editor/plugins/gizmos/reflection_probe_gizmo_plugin.cpp @@ -49,7 +49,7 @@ ReflectionProbeGizmoPlugin::ReflectionProbeGizmoPlugin() { gizmo_color.a = 0.5; create_material("reflection_internal_material", gizmo_color); - gizmo_color.a = 0.1; + gizmo_color.a = 0.025; create_material("reflection_probe_solid_material", gizmo_color); create_icon_material("reflection_probe_icon", EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("GizmoReflectionProbe"), EditorStringName(EditorIcons))); @@ -167,22 +167,17 @@ void ReflectionProbeGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { aabb.position = -size / 2; aabb.size = size; - for (int i = 0; i < 8; i++) { - Vector3 ep = aabb.get_endpoint(i); - internal_lines.push_back(probe->get_origin_offset()); - internal_lines.push_back(ep); - } - Vector handles = helper->box_get_handles(probe->get_size()); - for (int i = 0; i < 3; i++) { - Vector3 orig_handle = probe->get_origin_offset(); - orig_handle[i] -= 0.25; - lines.push_back(orig_handle); - handles.push_back(orig_handle); + if (probe->get_origin_offset() != Vector3(0.0, 0.0, 0.0)) { + for (int i = 0; i < 3; i++) { + Vector3 orig_handle = probe->get_origin_offset(); + orig_handle[i] -= 0.25; + lines.push_back(orig_handle); - orig_handle[i] += 0.5; - lines.push_back(orig_handle); + orig_handle[i] += 0.5; + lines.push_back(orig_handle); + } } Ref material = get_material("reflection_probe_material", p_gizmo); diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index 54278b436ec..89384c02019 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -2503,8 +2503,9 @@ Node3DEditorViewport::NavigationMode Node3DEditorViewport::_get_nav_mode_from_sh void Node3DEditorViewport::_nav_pan(Ref p_event, const Vector2 &p_relative) { const NavigationScheme nav_scheme = (NavigationScheme)EDITOR_GET("editors/3d/navigation/navigation_scheme").operator int(); + const real_t translation_sensitivity = EDITOR_GET("editors/3d/navigation_feel/translation_sensitivity"); - real_t pan_speed = 1 / 150.0; + real_t pan_speed = translation_sensitivity / 150.0; if (p_event.is_valid() && nav_scheme == NAVIGATION_MAYA && p_event->is_shift_pressed()) { pan_speed *= 10; } diff --git a/editor/plugins/resource_preloader_editor_plugin.cpp b/editor/plugins/resource_preloader_editor_plugin.cpp index 4aa94bc4df5..223568b5b90 100644 --- a/editor/plugins/resource_preloader_editor_plugin.cpp +++ b/editor/plugins/resource_preloader_editor_plugin.cpp @@ -115,7 +115,7 @@ void ResourcePreloaderEditor::_item_edited() { return; } - if (new_name.is_empty() || new_name.contains("\\") || new_name.contains("/") || preloader->has_resource(new_name)) { + if (new_name.is_empty() || new_name.contains_char('\\') || new_name.contains_char('/') || preloader->has_resource(new_name)) { s->set_text(0, old_name); return; } diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index 35e5d69b450..83c1194a6d4 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -185,7 +185,7 @@ void EditorStandardSyntaxHighlighter::_update_cache() { if (E.usage & PROPERTY_USAGE_CATEGORY || E.usage & PROPERTY_USAGE_GROUP || E.usage & PROPERTY_USAGE_SUBGROUP) { continue; } - if (prop_name.contains("/")) { + if (prop_name.contains_char('/')) { continue; } highlighter->add_member_keyword_color(prop_name, member_variable_color); diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index 0314782f0e6..b59102ee417 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -1638,7 +1638,7 @@ void ScriptTextEditor::_edit_option_toggle_inline_comment() { script->get_language()->get_comment_delimiters(&comment_delimiters); for (const String &script_delimiter : comment_delimiters) { - if (!script_delimiter.contains(" ")) { + if (!script_delimiter.contains_char(' ')) { delimiter = script_delimiter; break; } diff --git a/editor/project_converter_3_to_4.cpp b/editor/project_converter_3_to_4.cpp index 0dc9947fe58..c6049ead3d0 100644 --- a/editor/project_converter_3_to_4.cpp +++ b/editor/project_converter_3_to_4.cpp @@ -1679,7 +1679,7 @@ void ProjectConverter3To4::process_gdscript_line(String &line, const RegExContai } // -- \t.func() -> \tsuper.func() Object - if (line.contains("(") && line.contains(".")) { + if (line.contains_char('(') && line.contains_char('.')) { line = reg_container.reg_super.sub(line, "$1super.$2", true); // TODO, not sure if possible, but for now this broke String text e.g. "Chosen .gitignore" -> "Chosen super.gitignore" } diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index 331f3dacd2e..ac60e4015b9 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -836,7 +836,7 @@ void ProjectManager::_set_new_tag_name(const String p_name) { return; } - if (p_name.contains(" ")) { + if (p_name.contains_char(' ')) { tag_error->set_text(TTR("Tag name can't contain spaces.")); return; } diff --git a/editor/project_manager/project_dialog.cpp b/editor/project_manager/project_dialog.cpp index 7b772ede099..bdc89beacdc 100644 --- a/editor/project_manager/project_dialog.cpp +++ b/editor/project_manager/project_dialog.cpp @@ -134,6 +134,13 @@ void ProjectDialog::_validate_path() { ERR_FAIL_COND_MSG(ret != UNZ_OK, "Failed to get current file info."); String name = String::utf8(fname); + + // Skip the __MACOSX directory created by macOS's built-in file zipper. + if (name.begins_with("__MACOSX")) { + ret = unzGoToNextFile(pkg); + continue; + } + if (name.get_file() == "project.godot") { break; // ret == UNZ_OK. } @@ -606,6 +613,13 @@ void ProjectDialog::ok_pressed() { ERR_FAIL_COND_MSG(ret != UNZ_OK, "Failed to get current file info."); String name = String::utf8(fname); + + // Skip the __MACOSX directory created by macOS's built-in file zipper. + if (name.begins_with("__MACOSX")) { + ret = unzGoToNextFile(pkg); + continue; + } + if (name.get_file() == "project.godot") { zip_root = name.get_base_dir(); break; @@ -638,7 +652,15 @@ void ProjectDialog::ok_pressed() { ret = unzGetCurrentFileInfo(pkg, &info, fname, 16384, nullptr, 0, nullptr, 0); ERR_FAIL_COND_MSG(ret != UNZ_OK, "Failed to get current file info."); - String rel_path = String::utf8(fname).trim_prefix(zip_root); + String name = String::utf8(fname); + + // Skip the __MACOSX directory created by macOS's built-in file zipper. + if (name.begins_with("__MACOSX")) { + ret = unzGoToNextFile(pkg); + continue; + } + + String rel_path = name.trim_prefix(zip_root); if (rel_path.is_empty()) { // Root. } else if (rel_path.ends_with("/")) { // Directory. Ref da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); diff --git a/editor/project_manager/project_list.cpp b/editor/project_manager/project_list.cpp index 5d909d51e66..99a2d4deaca 100644 --- a/editor/project_manager/project_list.cpp +++ b/editor/project_manager/project_list.cpp @@ -352,7 +352,7 @@ const char *ProjectList::SIGNAL_PROJECT_ASK_OPEN = "project_ask_open"; // Helpers. bool ProjectList::project_feature_looks_like_version(const String &p_feature) { - return p_feature.contains(".") && p_feature.substr(0, 3).is_numeric(); + return p_feature.contains_char('.') && p_feature.substr(0, 3).is_numeric(); } // Notifications. @@ -583,7 +583,7 @@ void ProjectList::sort_projects() { bool item_visible = true; if (!_search_term.is_empty()) { String search_path; - if (search_term.contains("/")) { + if (search_term.contains_char('/')) { // Search path will match the whole path search_path = item.path; } else { diff --git a/editor/project_settings_editor.cpp b/editor/project_settings_editor.cpp index 9849f04ee62..42f3f3c86be 100644 --- a/editor/project_settings_editor.cpp +++ b/editor/project_settings_editor.cpp @@ -270,7 +270,7 @@ void ProjectSettingsEditor::shortcut_input(const Ref &p_event) { String ProjectSettingsEditor::_get_setting_name() const { String name = property_box->get_text().strip_edges(); - if (!name.begins_with("_") && !name.contains("/")) { + if (!name.begins_with("_") && !name.contains_char('/')) { name = "global/" + name; } return name; diff --git a/editor/property_selector.cpp b/editor/property_selector.cpp index 484c62ce6d4..4e8b071ce23 100644 --- a/editor/property_selector.cpp +++ b/editor/property_selector.cpp @@ -260,7 +260,7 @@ void PropertySelector::_update_search() { TreeItem *item = search_options->create_item(category ? category : root); String desc; - if (mi.name.contains(":")) { + if (mi.name.contains_char(':')) { desc = mi.name.get_slice(":", 1) + " "; mi.name = mi.name.get_slice(":", 0); } else if (mi.return_val.type != Variant::NIL) { @@ -280,7 +280,7 @@ void PropertySelector::_update_search() { if (arg_itr->type == Variant::NIL) { desc += ": Variant"; - } else if (arg_itr->name.contains(":")) { + } else if (arg_itr->name.contains_char(':')) { desc += vformat(": %s", arg_itr->name.get_slice(":", 1)); arg_itr->name = arg_itr->name.get_slice(":", 0); } else { diff --git a/editor/script_create_dialog.cpp b/editor/script_create_dialog.cpp index 79e87800db5..ad3bb74882a 100644 --- a/editor/script_create_dialog.cpp +++ b/editor/script_create_dialog.cpp @@ -746,7 +746,7 @@ ScriptLanguage::ScriptTemplate ScriptCreateDialog::_parse_template(const ScriptL List comment_delimiters; p_language->get_comment_delimiters(&comment_delimiters); for (const String &script_delimiter : comment_delimiters) { - if (!script_delimiter.contains(" ")) { + if (!script_delimiter.contains_char(' ')) { meta_delimiter = script_delimiter; break; } diff --git a/editor/shader_create_dialog.cpp b/editor/shader_create_dialog.cpp index 8ce121d74a4..cb5e27f7f61 100644 --- a/editor/shader_create_dialog.cpp +++ b/editor/shader_create_dialog.cpp @@ -294,7 +294,7 @@ void ShaderCreateDialog::_type_changed(int p_language) { String extension = ""; if (!path.is_empty()) { - if (path.contains(".")) { + if (path.contains_char('.')) { extension = path.get_extension(); } if (extension.length() == 0) { diff --git a/editor/themes/editor_fonts.cpp b/editor/themes/editor_fonts.cpp index 1ca3cc79dfc..451d6160106 100644 --- a/editor/themes/editor_fonts.cpp +++ b/editor/themes/editor_fonts.cpp @@ -171,7 +171,7 @@ void editor_register_fonts(const Ref &p_theme) { } TypedArray fallbacks; - Ref arabic_font = load_internal_font(_font_NotoNaskhArabicUI_Regular, _font_NotoNaskhArabicUI_Regular_size, font_hinting, font_antialiasing, true, font_subpixel_positioning, font_disable_embedded_bitmaps, false, &fallbacks); + Ref arabic_font = load_internal_font(_font_Vazirmatn_Regular, _font_Vazirmatn_Regular_size, font_hinting, font_antialiasing, true, font_subpixel_positioning, font_disable_embedded_bitmaps, false, &fallbacks); Ref bengali_font = load_internal_font(_font_NotoSansBengaliUI_Regular, _font_NotoSansBengaliUI_Regular_size, font_hinting, font_antialiasing, true, font_subpixel_positioning, font_disable_embedded_bitmaps, false, &fallbacks); Ref devanagari_font = load_internal_font(_font_NotoSansDevanagariUI_Regular, _font_NotoSansDevanagariUI_Regular_size, font_hinting, font_antialiasing, true, font_subpixel_positioning, font_disable_embedded_bitmaps, false, &fallbacks); Ref georgian_font = load_internal_font(_font_NotoSansGeorgian_Regular, _font_NotoSansGeorgian_Regular_size, font_hinting, font_antialiasing, true, font_subpixel_positioning, font_disable_embedded_bitmaps, false, &fallbacks); @@ -194,7 +194,7 @@ void editor_register_fonts(const Ref &p_theme) { Ref default_font_bold_msdf = load_internal_font(_font_NotoSans_Bold, _font_NotoSans_Bold_size, font_hinting, font_antialiasing, true, font_subpixel_positioning, font_disable_embedded_bitmaps, font_allow_msdf); TypedArray fallbacks_bold; - Ref arabic_font_bold = load_internal_font(_font_NotoNaskhArabicUI_Bold, _font_NotoNaskhArabicUI_Bold_size, font_hinting, font_antialiasing, true, font_subpixel_positioning, font_disable_embedded_bitmaps, false, &fallbacks_bold); + Ref arabic_font_bold = load_internal_font(_font_Vazirmatn_Bold, _font_Vazirmatn_Bold_size, font_hinting, font_antialiasing, true, font_subpixel_positioning, font_disable_embedded_bitmaps, false, &fallbacks_bold); Ref bengali_font_bold = load_internal_font(_font_NotoSansBengaliUI_Bold, _font_NotoSansBengaliUI_Bold_size, font_hinting, font_antialiasing, true, font_subpixel_positioning, font_disable_embedded_bitmaps, false, &fallbacks_bold); Ref devanagari_font_bold = load_internal_font(_font_NotoSansDevanagariUI_Bold, _font_NotoSansDevanagariUI_Bold_size, font_hinting, font_antialiasing, true, font_subpixel_positioning, font_disable_embedded_bitmaps, false, &fallbacks_bold); Ref georgian_font_bold = load_internal_font(_font_NotoSansGeorgian_Bold, _font_NotoSansGeorgian_Bold_size, font_hinting, font_antialiasing, true, font_subpixel_positioning, font_disable_embedded_bitmaps, false, &fallbacks_bold); diff --git a/main/main.cpp b/main/main.cpp index 3d6ae81f501..a1257e4a26b 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -1292,7 +1292,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph if (N) { String vm = N->get(); - if (!vm.contains("x")) { // invalid parameter format + if (!vm.contains_char('x')) { // invalid parameter format OS::get_singleton()->print("Invalid resolution '%s', it should be e.g. '1280x720'.\n", vm.utf8().get_data()); @@ -1335,7 +1335,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph if (N) { String vm = N->get(); - if (!vm.contains(",")) { // invalid parameter format + if (!vm.contains_char(',')) { // invalid parameter format OS::get_singleton()->print("Invalid position '%s', it should be e.g. '80,128'.\n", vm.utf8().get_data()); @@ -1834,7 +1834,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph // 'project.godot' file which will only be available through the network if this is enabled if (!remotefs.is_empty()) { int port; - if (remotefs.contains(":")) { + if (remotefs.contains_char(':')) { port = remotefs.get_slicec(':', 1).to_int(); remotefs = remotefs.get_slicec(':', 0); } else { @@ -3277,7 +3277,7 @@ Error Main::setup2(bool p_show_boot_logo) { // Dummy text driver cannot draw any text, making the editor unusable if selected. continue; } - if (!text_driver_options.is_empty() && !text_driver_options.contains(",")) { + if (!text_driver_options.is_empty() && !text_driver_options.contains_char(',')) { // Not the first option; add a comma before it as a separator for the property hint. text_driver_options += ","; } diff --git a/misc/extension_api_validation/4.3-stable.expected b/misc/extension_api_validation/4.3-stable.expected index c2b54b1e689..1bbe720eb94 100644 --- a/misc/extension_api_validation/4.3-stable.expected +++ b/misc/extension_api_validation/4.3-stable.expected @@ -205,3 +205,13 @@ GH-99481 Validate extension JSON: Error: Field 'classes/RichTextLabel/methods/push_meta/arguments': size changed value in new API, from 2 to 3. Optional argument added to set tooltip. Compatibility method registered. + + +GH-98443 +-------- +Validate extension JSON: Error: Field 'classes/Control/properties/offset_bottom': type changed value in new API, from "int" to "float". +Validate extension JSON: Error: Field 'classes/Control/properties/offset_left': type changed value in new API, from "int" to "float". +Validate extension JSON: Error: Field 'classes/Control/properties/offset_right': type changed value in new API, from "int" to "float". +Validate extension JSON: Error: Field 'classes/Control/properties/offset_top': type changed value in new API, from "int" to "float". + +Property type changed to float to match the actual internal API and documentation. diff --git a/misc/scripts/header_guards.py b/misc/scripts/header_guards.py index 58d5f66633e..77b58c09f0a 100755 --- a/misc/scripts/header_guards.py +++ b/misc/scripts/header_guards.py @@ -36,11 +36,16 @@ break if HEADER_CHECK_OFFSET < 0: + invalid.append(file) continue HEADER_BEGIN_OFFSET = HEADER_CHECK_OFFSET + 1 HEADER_END_OFFSET = len(lines) - 1 + if HEADER_BEGIN_OFFSET >= HEADER_END_OFFSET: + invalid.append(file) + continue + split = file.split("/") # Already in posix-format. prefix = "" diff --git a/modules/basis_universal/SCsub b/modules/basis_universal/SCsub index e2014523854..b8cce67f897 100644 --- a/modules/basis_universal/SCsub +++ b/modules/basis_universal/SCsub @@ -12,46 +12,49 @@ thirdparty_obj = [] # Not unbundled so far since not widespread as shared library thirdparty_dir = "#thirdparty/basis_universal/" + +# Only build the encoder for editor builds +basisu_encoder = env.editor_build + # Sync list with upstream CMakeLists.txt -encoder_sources = [ - "3rdparty/android_astc_decomp.cpp", - "basisu_astc_hdr_enc.cpp", - "basisu_backend.cpp", - "basisu_basis_file.cpp", - "basisu_bc7enc.cpp", - "basisu_opencl.cpp", - "basisu_comp.cpp", - "basisu_enc.cpp", - "basisu_etc.cpp", - "basisu_frontend.cpp", - "basisu_gpu_texture.cpp", - "basisu_kernels_sse.cpp", - "basisu_pvrtc1_4.cpp", - "basisu_resampler.cpp", - "basisu_resample_filters.cpp", - "basisu_ssim.cpp", - "basisu_uastc_enc.cpp", - "pvpngreader.cpp", -] -encoder_sources = [thirdparty_dir + "encoder/" + file for file in encoder_sources] +if basisu_encoder: + encoder_sources = [ + "3rdparty/android_astc_decomp.cpp", + "basisu_astc_hdr_enc.cpp", + "basisu_backend.cpp", + "basisu_basis_file.cpp", + "basisu_bc7enc.cpp", + "basisu_opencl.cpp", + "basisu_comp.cpp", + "basisu_enc.cpp", + "basisu_etc.cpp", + "basisu_frontend.cpp", + "basisu_gpu_texture.cpp", + "basisu_kernels_sse.cpp", + "basisu_pvrtc1_4.cpp", + "basisu_resampler.cpp", + "basisu_resample_filters.cpp", + "basisu_ssim.cpp", + "basisu_uastc_enc.cpp", + "pvpngreader.cpp", + ] + encoder_sources = [thirdparty_dir + "encoder/" + file for file in encoder_sources] + transcoder_sources = [thirdparty_dir + "transcoder/basisu_transcoder.cpp"] # Treat Basis headers as system headers to avoid raising warnings. Not supported on MSVC. if not env.msvc: - env_basisu.Append( - CPPFLAGS=["-isystem", Dir(thirdparty_dir).path, "-isystem", Dir("#thirdparty/jpeg-compressor").path] - ) + env_basisu.Append(CPPFLAGS=["-isystem", Dir(thirdparty_dir).path]) else: - env_basisu.Prepend(CPPPATH=[thirdparty_dir, "#thirdparty/jpeg-compressor"]) + env_basisu.Prepend(CPPPATH=[thirdparty_dir]) + +if basisu_encoder: + env_basisu.Prepend(CPPPATH=["#thirdparty/jpeg-compressor"]) + env_basisu.Prepend(CPPPATH=["#thirdparty/tinyexr"]) if env["builtin_zstd"]: env_basisu.Prepend(CPPPATH=["#thirdparty/zstd"]) -env_basisu.Prepend(CPPPATH=["#thirdparty/tinyexr"]) - -if env.dev_build: - env_basisu.Append(CPPDEFINES=[("BASISU_DEVEL_MESSAGES", 1), ("BASISD_ENABLE_DEBUG_FLAGS", 1)]) - env_thirdparty = env_basisu.Clone() env_thirdparty.disable_warnings() @@ -74,9 +77,9 @@ env_thirdparty.Append( ] ) -if env.editor_build: - env_thirdparty.Append(CPPDEFINES=["BASISU_NO_IMG_LOADERS"]) +if basisu_encoder: env_thirdparty.add_source_files(thirdparty_obj, encoder_sources) + env_thirdparty.add_source_files(thirdparty_obj, transcoder_sources) env.modules_sources += thirdparty_obj diff --git a/modules/basis_universal/config.py b/modules/basis_universal/config.py index 6a67f2c77cb..0f723cbb6b5 100644 --- a/modules/basis_universal/config.py +++ b/modules/basis_universal/config.py @@ -1,5 +1,6 @@ def can_build(env, platform): - env.module_add_dependencies("basis_universal", ["jpg"]) + if env.editor_build: # Encoder dependencies + env.module_add_dependencies("basis_universal", ["jpg", "tinyexr"]) return True diff --git a/modules/camera/config.py b/modules/camera/config.py index 7b368d21936..fa229ef2f5a 100644 --- a/modules/camera/config.py +++ b/modules/camera/config.py @@ -1,4 +1,8 @@ def can_build(env, platform): + import sys + + if sys.platform.startswith("freebsd"): + return False return platform == "macos" or platform == "windows" or platform == "linuxbsd" diff --git a/modules/csg/csg_shape.cpp b/modules/csg/csg_shape.cpp index eedc031992a..f307bd3aa43 100644 --- a/modules/csg/csg_shape.cpp +++ b/modules/csg/csg_shape.cpp @@ -32,6 +32,9 @@ #include "csg_shape.h" +#ifdef DEV_ENABLED +#include "core/io/json.h" +#endif // DEV_ENABLED #include "core/math/geometry_2d.h" #include @@ -144,6 +147,7 @@ bool CSGShape3D::is_root_shape() const { return !parent_shape; } +#ifndef DISABLE_DEPRECATED void CSGShape3D::set_snap(float p_snap) { if (snap == p_snap) { return; @@ -156,6 +160,7 @@ void CSGShape3D::set_snap(float p_snap) { float CSGShape3D::get_snap() const { return snap; } +#endif // DISABLE_DEPRECATED void CSGShape3D::_make_dirty(bool p_parent_removing) { if ((p_parent_removing || is_root_shape()) && !dirty) { @@ -234,13 +239,111 @@ static void _unpack_manifold( r_mesh_merge->_regen_face_aabbs(); } +// Errors matching `thirdparty/manifold/include/manifold/manifold.h`. +static String manifold_error_to_string(const manifold::Manifold::Error &p_error) { + switch (p_error) { + case manifold::Manifold::Error::NoError: + return "No Error"; + case manifold::Manifold::Error::NonFiniteVertex: + return "Non Finite Vertex"; + case manifold::Manifold::Error::NotManifold: + return "Not Manifold"; + case manifold::Manifold::Error::VertexOutOfBounds: + return "Vertex Out Of Bounds"; + case manifold::Manifold::Error::PropertiesWrongLength: + return "Properties Wrong Length"; + case manifold::Manifold::Error::MissingPositionProperties: + return "Missing Position Properties"; + case manifold::Manifold::Error::MergeVectorsDifferentLengths: + return "Merge Vectors Different Lengths"; + case manifold::Manifold::Error::MergeIndexOutOfBounds: + return "Merge Index Out Of Bounds"; + case manifold::Manifold::Error::TransformWrongLength: + return "Transform Wrong Length"; + case manifold::Manifold::Error::RunIndexWrongLength: + return "Run Index Wrong Length"; + case manifold::Manifold::Error::FaceIDWrongLength: + return "Face ID Wrong Length"; + case manifold::Manifold::Error::InvalidConstruction: + return "Invalid Construction"; + default: + return "Unknown Error"; + } +} + +#ifdef DEV_ENABLED +static String _export_meshgl_as_json(const manifold::MeshGL64 &p_mesh) { + Dictionary mesh_dict; + mesh_dict["numProp"] = p_mesh.numProp; + + Array vert_properties; + for (const double &val : p_mesh.vertProperties) { + vert_properties.append(val); + } + mesh_dict["vertProperties"] = vert_properties; + + Array tri_verts; + for (const uint64_t &val : p_mesh.triVerts) { + tri_verts.append(val); + } + mesh_dict["triVerts"] = tri_verts; + + Array merge_from_vert; + for (const uint64_t &val : p_mesh.mergeFromVert) { + merge_from_vert.append(val); + } + mesh_dict["mergeFromVert"] = merge_from_vert; + + Array merge_to_vert; + for (const uint64_t &val : p_mesh.mergeToVert) { + merge_to_vert.append(val); + } + mesh_dict["mergeToVert"] = merge_to_vert; + + Array run_index; + for (const uint64_t &val : p_mesh.runIndex) { + run_index.append(val); + } + mesh_dict["runIndex"] = run_index; + + Array run_original_id; + for (const uint32_t &val : p_mesh.runOriginalID) { + run_original_id.append(val); + } + mesh_dict["runOriginalID"] = run_original_id; + + Array run_transform; + for (const double &val : p_mesh.runTransform) { + run_transform.append(val); + } + mesh_dict["runTransform"] = run_transform; + + Array face_id; + for (const uint64_t &val : p_mesh.faceID) { + face_id.append(val); + } + mesh_dict["faceID"] = face_id; + + Array halfedge_tangent; + for (const double &val : p_mesh.halfedgeTangent) { + halfedge_tangent.append(val); + } + mesh_dict["halfedgeTangent"] = halfedge_tangent; + + mesh_dict["tolerance"] = p_mesh.tolerance; + + String json_string = JSON::stringify(mesh_dict); + return json_string; +} +#endif // DEV_ENABLED + static void _pack_manifold( const CSGBrush *const p_mesh_merge, manifold::Manifold &r_manifold, HashMap> &p_mesh_materials, - float p_snap) { + CSGShape3D *p_csg_shape) { ERR_FAIL_NULL_MSG(p_mesh_merge, "p_mesh_merge is null"); - + ERR_FAIL_NULL_MSG(p_csg_shape, "p_shape is null"); HashMap> faces_by_material; for (int face_i = 0; face_i < p_mesh_merge->faces.size(); face_i++) { const CSGBrush::Face &face = p_mesh_merge->faces[face_i]; @@ -248,7 +351,6 @@ static void _pack_manifold( } manifold::MeshGL64 mesh; - mesh.tolerance = p_snap; mesh.numProp = MANIFOLD_PROPERTY_MAX; mesh.runOriginalID.reserve(faces_by_material.size()); mesh.runIndex.reserve(faces_by_material.size() + 1); @@ -293,12 +395,22 @@ static void _pack_manifold( } // runIndex needs an explicit end value. mesh.runIndex.push_back(mesh.triVerts.size()); + mesh.tolerance = 2 * FLT_EPSILON; ERR_FAIL_COND_MSG(mesh.vertProperties.size() % mesh.numProp != 0, "Invalid vertex properties size."); mesh.Merge(); +#ifdef DEV_ENABLED + print_verbose(_export_meshgl_as_json(mesh)); +#endif // DEV_ENABLED r_manifold = manifold::Manifold(mesh); - manifold::Manifold::Error err = r_manifold.Status(); - if (err != manifold::Manifold::Error::NoError) { - print_error(String("Manifold creation from mesh failed:" + itos((int)err))); + manifold::Manifold::Error error = r_manifold.Status(); + if (error == manifold::Manifold::Error::NoError) { + return; + } + if (p_csg_shape->get_owner()) { + NodePath path = p_csg_shape->get_owner()->get_path_to(p_csg_shape, true); + print_error(vformat("CSGShape3D manifold creation from mesh failed at %s: %s.", path, manifold_error_to_string(error))); + } else { + print_error(vformat("CSGShape3D manifold creation from mesh failed at .: %s.", manifold_error_to_string(error))); } } @@ -332,7 +444,7 @@ CSGBrush *CSGShape3D::_get_brush() { CSGBrush *n = _build_brush(); HashMap> mesh_materials; manifold::Manifold root_manifold; - _pack_manifold(n, root_manifold, mesh_materials, get_snap()); + _pack_manifold(n, root_manifold, mesh_materials, this); manifold::OpType current_op = ManifoldOperation::convert_csg_op(get_operation()); std::vector manifolds; manifolds.push_back(root_manifold); @@ -348,7 +460,7 @@ CSGBrush *CSGShape3D::_get_brush() { CSGBrush transformed_brush; transformed_brush.copy_from(*child_brush, child->get_transform()); manifold::Manifold child_manifold; - _pack_manifold(&transformed_brush, child_manifold, mesh_materials, get_snap()); + _pack_manifold(&transformed_brush, child_manifold, mesh_materials, child); manifold::OpType child_operation = ManifoldOperation::convert_csg_op(child->get_operation()); if (child_operation != current_op) { manifold::Manifold result = manifold::Manifold::BatchBoolean(manifolds, current_op); @@ -836,8 +948,10 @@ void CSGShape3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_operation", "operation"), &CSGShape3D::set_operation); ClassDB::bind_method(D_METHOD("get_operation"), &CSGShape3D::get_operation); +#ifndef DISABLE_DEPRECATED ClassDB::bind_method(D_METHOD("set_snap", "snap"), &CSGShape3D::set_snap); ClassDB::bind_method(D_METHOD("get_snap"), &CSGShape3D::get_snap); +#endif // DISABLE_DEPRECATED ClassDB::bind_method(D_METHOD("set_use_collision", "operation"), &CSGShape3D::set_use_collision); ClassDB::bind_method(D_METHOD("is_using_collision"), &CSGShape3D::is_using_collision); @@ -866,7 +980,9 @@ void CSGShape3D::_bind_methods() { ClassDB::bind_method(D_METHOD("bake_collision_shape"), &CSGShape3D::bake_collision_shape); ADD_PROPERTY(PropertyInfo(Variant::INT, "operation", PROPERTY_HINT_ENUM, "Union,Intersection,Subtraction"), "set_operation", "get_operation"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "snap", PROPERTY_HINT_RANGE, "0.000001,1,0.000001,suffix:m"), "set_snap", "get_snap"); +#ifndef DISABLE_DEPRECATED + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "snap", PROPERTY_HINT_RANGE, "0.000001,1,0.000001,suffix:m", PROPERTY_USAGE_NONE), "set_snap", "get_snap"); +#endif // DISABLE_DEPRECATED ADD_PROPERTY(PropertyInfo(Variant::BOOL, "calculate_tangents"), "set_calculate_tangents", "is_calculating_tangents"); ADD_GROUP("Collision", "collision_"); @@ -1180,14 +1296,22 @@ CSGBrush *CSGSphere3D::_build_brush() { const double longitude_step = Math_TAU / radial_segments; int face = 0; for (int i = 0; i < rings; i++) { - double latitude0 = latitude_step * i + Math_TAU / 4; - double cos0 = Math::cos(latitude0); - double sin0 = Math::sin(latitude0); + double cos0 = 0; + double sin0 = 1; + if (i > 0) { + double latitude0 = latitude_step * i + Math_TAU / 4; + cos0 = Math::cos(latitude0); + sin0 = Math::sin(latitude0); + } double v0 = double(i) / rings; - double latitude1 = latitude_step * (i + 1) + Math_TAU / 4; - double cos1 = Math::cos(latitude1); - double sin1 = Math::sin(latitude1); + double cos1 = 0; + double sin1 = -1; + if (i < rings - 1) { + double latitude1 = latitude_step * (i + 1) + Math_TAU / 4; + cos1 = Math::cos(latitude1); + sin1 = Math::sin(latitude1); + } double v1 = double(i + 1) / rings; for (int j = 0; j < radial_segments; j++) { diff --git a/modules/csg/csg_shape.h b/modules/csg/csg_shape.h index 81c2e531113..3aeb61ce653 100644 --- a/modules/csg/csg_shape.h +++ b/modules/csg/csg_shape.h @@ -157,8 +157,10 @@ class CSGShape3D : public GeometryInstance3D { void set_collision_priority(real_t p_priority); real_t get_collision_priority() const; +#ifndef DISABLE_DEPRECATED void set_snap(float p_snap); float get_snap() const; +#endif // DISABLE_DEPRECATED void set_calculate_tangents(bool p_calculate_tangents); bool is_calculating_tangents() const; diff --git a/modules/csg/doc_classes/CSGShape3D.xml b/modules/csg/doc_classes/CSGShape3D.xml index ac62d8dd83d..8ea471e62d8 100644 --- a/modules/csg/doc_classes/CSGShape3D.xml +++ b/modules/csg/doc_classes/CSGShape3D.xml @@ -89,8 +89,8 @@ The operation that is performed on this shape. This is ignored for the first CSG child node as the operation is between this node and the previous child of this nodes parent. - - Snap makes the mesh vertices snap to a given distance so that the faces of two meshes can be perfectly aligned. A lower value results in greater precision but may be harder to adjust. The top-level CSG shape's snap value is used for the entire CSG tree. + + This property does nothing. Adds a collision shape to the physics engine for our CSG shape. This will always act like a static body. Note that the collision shape is still active even if the CSG shape itself is hidden. See also [member collision_mask] and [member collision_priority]. diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index 0355119442a..b9b35e5d197 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -726,7 +726,7 @@ [/codeblock] [b]Note:[/b] Only the script can have a custom icon. Inner classes are not supported. [b]Note:[/b] As annotations describe their subject, the [annotation @icon] annotation must be placed before the class definition and inheritance. - [b]Note:[/b] Unlike other annotations, the argument of the [annotation @icon] annotation must be a string literal (constant expressions are not supported). + [b]Note:[/b] Unlike most other annotations, the argument of the [annotation @icon] annotation must be a string literal (constant expressions are not supported). @@ -794,6 +794,33 @@ @warning_ignore("unreachable_code") print("unreachable") [/codeblock] + See also [annotation @warning_ignore_start] and [annotation @warning_ignore_restore]. + + + + + + + Stops ignoring the listed warning types after [annotation @warning_ignore_start]. Ignoring the specified warning types will be reset to Project Settings. This annotation can be omitted to ignore the warning types until the end of the file. + [b]Note:[/b] Unlike most other annotations, arguments of the [annotation @warning_ignore_restore] annotation must be string literals (constant expressions are not supported). + + + + + + + Starts ignoring the listed warning types until the end of the file or the [annotation @warning_ignore_restore] annotation with the given warning type. + [codeblock] + func test(): + var a = 1 # Warning (if enabled in the Project Settings). + @warning_ignore_start("unused_variable") + var b = 2 # No warning. + var c = 3 # No warning. + @warning_ignore_restore("unused_variable") + var d = 4 # Warning (if enabled in the Project Settings). + [/codeblock] + [b]Note:[/b] To suppress a single warning, use [annotation @warning_ignore] instead. + [b]Note:[/b] Unlike most other annotations, arguments of the [annotation @warning_ignore_start] annotation must be string literals (constant expressions are not supported). diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp index 3856a64e9dd..7f6886a2868 100644 --- a/modules/gdscript/editor/gdscript_highlighter.cpp +++ b/modules/gdscript/editor/gdscript_highlighter.cpp @@ -817,7 +817,7 @@ void GDScriptSyntaxHighlighter::_update_cache() { if (E.usage & PROPERTY_USAGE_CATEGORY || E.usage & PROPERTY_USAGE_GROUP || E.usage & PROPERTY_USAGE_SUBGROUP) { continue; } - if (prop_name.contains("/")) { + if (prop_name.contains_char('/')) { continue; } member_keywords[prop_name] = member_variable_color; diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 803b859edc6..dce4d853eb4 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -964,8 +964,11 @@ static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_a } } break; } - } else if (p_annotation->name == SNAME("@warning_ignore")) { + } else if (p_annotation->name == SNAME("@warning_ignore") || p_annotation->name == SNAME("@warning_ignore_start") || p_annotation->name == SNAME("@warning_ignore_restore")) { for (int warning_code = 0; warning_code < GDScriptWarning::WARNING_MAX; warning_code++) { + if (warning_code == GDScriptWarning::RENAMED_IN_GODOT_4_HINT) { + continue; + } ScriptLanguage::CodeCompletionOption warning(GDScriptWarning::get_name_from_code((GDScriptWarning::Code)warning_code).to_lower(), ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); warning.insert_text = warning.display.quote(p_quote_style); r_result.insert(warning.display, warning); @@ -1212,7 +1215,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base if (E.usage & (PROPERTY_USAGE_CATEGORY | PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SUBGROUP | PROPERTY_USAGE_INTERNAL)) { continue; } - if (E.name.contains("/")) { + if (E.name.contains_char('/')) { continue; } int location = p_recursion_depth + _get_property_location(scr, E.name); @@ -1303,7 +1306,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base if (E.usage & (PROPERTY_USAGE_CATEGORY | PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SUBGROUP | PROPERTY_USAGE_INTERNAL)) { continue; } - if (E.name.contains("/")) { + if (E.name.contains_char('/')) { continue; } int location = p_recursion_depth + _get_property_location(type, E.name); @@ -1385,7 +1388,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base if (E.usage & (PROPERTY_USAGE_CATEGORY | PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SUBGROUP | PROPERTY_USAGE_INTERNAL)) { continue; } - if (!String(E.name).contains("/")) { + if (!String(E.name).contains_char('/')) { ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER, location); if (base_type.kind == GDScriptParser::DataType::ENUM) { // Sort enum members in their declaration order. @@ -2721,7 +2724,7 @@ static bool _guess_method_return_type_from_base(GDScriptParser::CompletionContex } static void _find_enumeration_candidates(GDScriptParser::CompletionContext &p_context, const String &p_enum_hint, HashMap &r_result) { - if (!p_enum_hint.contains(".")) { + if (!p_enum_hint.contains_char('.')) { // Global constant or in the current class. StringName current_enum = p_enum_hint; if (p_context.current_class && p_context.current_class->has_member(current_enum) && p_context.current_class->get_member(current_enum).type == GDScriptParser::ClassNode::Member::ENUM) { @@ -3476,7 +3479,7 @@ ::Error GDScriptLanguage::complete_code(const String &p_code, const String &p_pa for (const MethodInfo &mi : virtual_methods) { String method_hint = mi.name; - if (method_hint.contains(":")) { + if (method_hint.contains_char(':')) { method_hint = method_hint.get_slice(":", 0); } method_hint += "("; @@ -3486,7 +3489,7 @@ ::Error GDScriptLanguage::complete_code(const String &p_code, const String &p_pa method_hint += ", "; } String arg = arg_itr->name; - if (arg.contains(":")) { + if (arg.contains_char(':')) { arg = arg.substr(0, arg.find_char(':')); } method_hint += arg; @@ -3544,7 +3547,7 @@ ::Error GDScriptLanguage::complete_code(const String &p_code, const String &p_pa if (path_needs_quote) { // Ignore quote_style and just use double quotes for paths with apostrophes. // Double quotes don't need to be checked because they're not valid in node and property names. - opt = opt.quote(opt.contains("'") ? "\"" : quote_style); // Handle user preference. + opt = opt.quote(opt.contains_char('\'') ? "\"" : quote_style); // Handle user preference. } ScriptLanguage::CodeCompletionOption option(opt, ScriptLanguage::CODE_COMPLETION_KIND_NODE_PATH); options.insert(option.display, option); diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index eebabe9fe05..04d988129c7 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -96,10 +96,11 @@ bool GDScriptParser::annotation_exists(const String &p_annotation_name) const { GDScriptParser::GDScriptParser() { // Register valid annotations. if (unlikely(valid_annotations.is_empty())) { + // Script annotations. register_annotation(MethodInfo("@tool"), AnnotationInfo::SCRIPT, &GDScriptParser::tool_annotation); register_annotation(MethodInfo("@icon", PropertyInfo(Variant::STRING, "icon_path")), AnnotationInfo::SCRIPT, &GDScriptParser::icon_annotation); register_annotation(MethodInfo("@static_unload"), AnnotationInfo::SCRIPT, &GDScriptParser::static_unload_annotation); - + // Onready annotation. register_annotation(MethodInfo("@onready"), AnnotationInfo::VARIABLE, &GDScriptParser::onready_annotation); // Export annotations. register_annotation(MethodInfo("@export"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations); @@ -130,13 +131,18 @@ GDScriptParser::GDScriptParser() { register_annotation(MethodInfo("@export_group", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::STRING, "prefix")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations, varray("")); register_annotation(MethodInfo("@export_subgroup", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::STRING, "prefix")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations, varray("")); // Warning annotations. - register_annotation(MethodInfo("@warning_ignore", PropertyInfo(Variant::STRING, "warning")), AnnotationInfo::CLASS_LEVEL | AnnotationInfo::STATEMENT, &GDScriptParser::warning_annotations, varray(), true); + register_annotation(MethodInfo("@warning_ignore", PropertyInfo(Variant::STRING, "warning")), AnnotationInfo::CLASS_LEVEL | AnnotationInfo::STATEMENT, &GDScriptParser::warning_ignore_annotation, varray(), true); + register_annotation(MethodInfo("@warning_ignore_start", PropertyInfo(Variant::STRING, "warning")), AnnotationInfo::STANDALONE, &GDScriptParser::warning_ignore_region_annotations, varray(), true); + register_annotation(MethodInfo("@warning_ignore_restore", PropertyInfo(Variant::STRING, "warning")), AnnotationInfo::STANDALONE, &GDScriptParser::warning_ignore_region_annotations, varray(), true); // Networking. register_annotation(MethodInfo("@rpc", PropertyInfo(Variant::STRING, "mode"), PropertyInfo(Variant::STRING, "sync"), PropertyInfo(Variant::STRING, "transfer_mode"), PropertyInfo(Variant::INT, "transfer_channel")), AnnotationInfo::FUNCTION, &GDScriptParser::rpc_annotation, varray("authority", "call_remote", "unreliable", 0)); } #ifdef DEBUG_ENABLED is_ignoring_warnings = !(bool)GLOBAL_GET("debug/gdscript/warnings/enable"); + for (int i = 0; i < GDScriptWarning::WARNING_MAX; i++) { + warning_ignore_start_lines[i] = INT_MAX; + } #endif #ifdef TOOLS_ENABLED @@ -216,6 +222,9 @@ void GDScriptParser::apply_pending_warnings() { if (warning_ignored_lines[pw.code].has(pw.source->start_line)) { continue; } + if (warning_ignore_start_lines[pw.code] <= pw.source->start_line) { + continue; + } GDScriptWarning warning; warning.code = pw.code; @@ -627,7 +636,7 @@ void GDScriptParser::parse_program() { } else if (annotation->applies_to(AnnotationInfo::SCRIPT)) { PUSH_PENDING_ANNOTATIONS_TO_HEAD; if (annotation->name == SNAME("@tool") || annotation->name == SNAME("@icon")) { - // Some annotations need to be resolved in the parser. + // Some annotations need to be resolved and applied in the parser. annotation->apply(this, head, nullptr); // `head->outer == nullptr`. } else { head->annotations.push_back(annotation); @@ -642,8 +651,10 @@ void GDScriptParser::parse_program() { // so we stop looking for script-level stuff. can_have_class_or_extends = false; break; + } else if (annotation->name == SNAME("@warning_ignore_start") || annotation->name == SNAME("@warning_ignore_restore")) { + // Some annotations need to be resolved and applied in the parser. + annotation->apply(this, nullptr, nullptr); } else { - // For potential non-group standalone annotations. push_error(R"(Unexpected standalone annotation.)"); } } else { @@ -1032,8 +1043,10 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) { } if (annotation->name == SNAME("@export_category") || annotation->name == SNAME("@export_group") || annotation->name == SNAME("@export_subgroup")) { current_class->add_member_group(annotation); + } else if (annotation->name == SNAME("@warning_ignore_start") || annotation->name == SNAME("@warning_ignore_restore")) { + // Some annotations need to be resolved and applied in the parser. + annotation->apply(this, nullptr, nullptr); } else { - // For potential non-group standalone annotations. push_error(R"(Unexpected standalone annotation.)"); } } else { // `AnnotationInfo::CLASS_LEVEL`. @@ -1898,9 +1911,21 @@ GDScriptParser::Node *GDScriptParser::parse_statement() { break; case GDScriptTokenizer::Token::ANNOTATION: { advance(); - AnnotationNode *annotation = parse_annotation(AnnotationInfo::STATEMENT); + AnnotationNode *annotation = parse_annotation(AnnotationInfo::STATEMENT | AnnotationInfo::STANDALONE); if (annotation != nullptr) { - annotation_stack.push_back(annotation); + if (annotation->applies_to(AnnotationInfo::STANDALONE)) { + if (previous.type != GDScriptTokenizer::Token::NEWLINE) { + push_error(R"(Expected newline after a standalone annotation.)"); + } + if (annotation->name == SNAME("@warning_ignore_start") || annotation->name == SNAME("@warning_ignore_restore")) { + // Some annotations need to be resolved and applied in the parser. + annotation->apply(this, nullptr, nullptr); + } else { + push_error(R"(Unexpected standalone annotation.)"); + } + } else { + annotation_stack.push_back(annotation); + } } break; } @@ -4098,23 +4123,25 @@ bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation) return false; } - // Some annotations need to be resolved in the parser. - if (p_annotation->name == SNAME("@icon")) { - ExpressionNode *argument = p_annotation->arguments[0]; + // Some annotations need to be resolved and applied in the parser. + if (p_annotation->name == SNAME("@icon") || p_annotation->name == SNAME("@warning_ignore_start") || p_annotation->name == SNAME("@warning_ignore_restore")) { + for (int i = 0; i < p_annotation->arguments.size(); i++) { + ExpressionNode *argument = p_annotation->arguments[i]; - if (argument->type != Node::LITERAL) { - push_error(R"(Argument 1 of annotation "@icon" must be a string literal.)", argument); - return false; - } + if (argument->type != Node::LITERAL) { + push_error(vformat(R"(Argument %d of annotation "%s" must be a string literal.)", i + 1, p_annotation->name), argument); + return false; + } - Variant value = static_cast(argument)->value; + Variant value = static_cast(argument)->value; - if (value.get_type() != Variant::STRING) { - push_error(R"(Argument 1 of annotation "@icon" must be a string literal.)", argument); - return false; - } + if (value.get_type() != Variant::STRING) { + push_error(vformat(R"(Argument %d of annotation "%s" must be a string literal.)", i + 1, p_annotation->name), argument); + return false; + } - p_annotation->resolved_arguments.push_back(value); + p_annotation->resolved_arguments.push_back(value); + } } // For other annotations, see `GDScriptAnalyzer::resolve_annotation()`. @@ -4164,6 +4191,17 @@ bool GDScriptParser::icon_annotation(AnnotationNode *p_annotation, Node *p_targe return true; } +bool GDScriptParser::static_unload_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { + ERR_FAIL_COND_V_MSG(p_target->type != Node::CLASS, false, vformat(R"("%s" annotation can only be applied to classes.)", p_annotation->name)); + ClassNode *class_node = static_cast(p_target); + if (class_node->annotated_static_unload) { + push_error(vformat(R"("%s" annotation can only be used once per script.)", p_annotation->name), p_annotation); + return false; + } + class_node->annotated_static_unload = true; + return true; +} + bool GDScriptParser::onready_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { ERR_FAIL_COND_V_MSG(p_target->type != Node::VARIABLE, false, R"("@onready" annotation can only be applied to class variables.)"); @@ -4325,7 +4363,7 @@ bool GDScriptParser::export_annotations(AnnotationNode *p_annotation, Node *p_ta push_error(vformat(R"(Argument %d of annotation "%s" is empty.)", i + 1, p_annotation->name), p_annotation->arguments[i]); return false; } - if (arg_string.contains(",")) { + if (arg_string.contains_char(',')) { push_error(vformat(R"(Argument %d of annotation "%s" contains a comma. Use separate arguments instead.)", i + 1, p_annotation->name), p_annotation->arguments[i]); return false; } @@ -4758,11 +4796,8 @@ bool GDScriptParser::export_group_annotations(AnnotationNode *p_annotation, Node return true; } -bool GDScriptParser::warning_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { -#ifndef DEBUG_ENABLED - // Only available in debug builds. - return true; -#else // DEBUG_ENABLED +bool GDScriptParser::warning_ignore_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { +#ifdef DEBUG_ENABLED if (is_ignoring_warnings) { return true; // We already ignore all warnings, let's optimize it. } @@ -4807,8 +4842,14 @@ bool GDScriptParser::warning_annotations(AnnotationNode *p_annotation, Node *p_t } break; case Node::FUNCTION: { - // `@warning_ignore` on function has a controversial feature that is used in tests. - // It's better not to remove it for now, while there is no way to mass-ignore warnings. + FunctionNode *function = static_cast(p_target); + end_line = function->start_line; + for (int i = 0; i < function->parameters.size(); i++) { + end_line = MAX(end_line, function->parameters[i]->end_line); + if (function->parameters[i]->initializer != nullptr) { + end_line = MAX(end_line, function->parameters[i]->initializer->end_line); + } + } } break; case Node::MATCH_BRANCH: { @@ -4830,6 +4871,48 @@ bool GDScriptParser::warning_annotations(AnnotationNode *p_annotation, Node *p_t } } return !has_error; +#else // !DEBUG_ENABLED + // Only available in debug builds. + return true; +#endif // DEBUG_ENABLED +} + +bool GDScriptParser::warning_ignore_region_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { +#ifdef DEBUG_ENABLED + bool has_error = false; + const bool is_start = p_annotation->name == SNAME("@warning_ignore_start"); + for (const Variant &warning_name : p_annotation->resolved_arguments) { + GDScriptWarning::Code warning_code = GDScriptWarning::get_code_from_name(String(warning_name).to_upper()); + if (warning_code == GDScriptWarning::WARNING_MAX) { + push_error(vformat(R"(Invalid warning name: "%s".)", warning_name), p_annotation); + has_error = true; + continue; + } + if (is_start) { + if (warning_ignore_start_lines[warning_code] != INT_MAX) { + push_error(vformat(R"(Warning "%s" is already being ignored by "@warning_ignore_start" at line %d.)", String(warning_name).to_upper(), warning_ignore_start_lines[warning_code]), p_annotation); + has_error = true; + continue; + } + warning_ignore_start_lines[warning_code] = p_annotation->start_line; + } else { + if (warning_ignore_start_lines[warning_code] == INT_MAX) { + push_error(vformat(R"(Warning "%s" is not being ignored by "@warning_ignore_start".)", String(warning_name).to_upper()), p_annotation); + has_error = true; + continue; + } + const int start_line = warning_ignore_start_lines[warning_code]; + const int end_line = MAX(start_line, p_annotation->start_line); // Prevent infinite loop. + for (int i = start_line; i <= end_line; i++) { + warning_ignored_lines[warning_code].insert(i); + } + warning_ignore_start_lines[warning_code] = INT_MAX; + } + } + return !has_error; +#else // !DEBUG_ENABLED + // Only available in debug builds. + return true; #endif // DEBUG_ENABLED } @@ -4894,17 +4977,6 @@ bool GDScriptParser::rpc_annotation(AnnotationNode *p_annotation, Node *p_target return true; } -bool GDScriptParser::static_unload_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { - ERR_FAIL_COND_V_MSG(p_target->type != Node::CLASS, false, vformat(R"("%s" annotation can only be applied to classes.)", p_annotation->name)); - ClassNode *class_node = static_cast(p_target); - if (class_node->annotated_static_unload) { - push_error(vformat(R"("%s" annotation can only be used once per script.)", p_annotation->name), p_annotation); - return false; - } - class_node->annotated_static_unload = true; - return true; -} - GDScriptParser::DataType GDScriptParser::SuiteNode::Local::get_datatype() const { switch (type) { case CONSTANT: diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 3d85cbd6fe8..686f3193052 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -1360,6 +1360,7 @@ class GDScriptParser { List warnings; List pending_warnings; HashSet warning_ignored_lines[GDScriptWarning::WARNING_MAX]; + int warning_ignore_start_lines[GDScriptWarning::WARNING_MAX]; HashSet unsafe_lines; #endif @@ -1508,6 +1509,7 @@ class GDScriptParser { void clear_unused_annotations(); bool tool_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); bool icon_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); + bool static_unload_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); bool onready_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); template bool export_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); @@ -1516,9 +1518,9 @@ class GDScriptParser { bool export_tool_button_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); template bool export_group_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); - bool warning_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); + bool warning_ignore_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); + bool warning_ignore_region_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); bool rpc_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); - bool static_unload_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); // Statements. Node *parse_statement(); VariableNode *parse_variable(bool p_is_static); diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index 5732a3372bb..1e552e44443 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -69,7 +69,7 @@ lsp::Position GodotPosition::to_lsp(const Vector &p_lines) const { res.character = column - 1; String pos_line = p_lines[res.line]; - if (pos_line.contains("\t")) { + if (pos_line.contains_char('\t')) { int tab_size = get_indent_size(); int in_col = 1; diff --git a/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd b/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd index d0f895d7846..eb4ec1c38ac 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd @@ -13,7 +13,7 @@ func param_inferred(param := variant()) -> void: print(param) func return_untyped(): return variant() func return_typed() -> Variant: return variant() -@warning_ignore("unused_variable", "inference_on_variant") +@warning_ignore_start("unused_variable", "inference_on_variant") func test() -> void: var weak = variant() var typed: Variant = variant() @@ -32,4 +32,4 @@ func test() -> void: if typed != null: pass if typed is Node: pass - print('ok') + print("ok") diff --git a/modules/gdscript/tests/scripts/analyzer/features/type_test_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/type_test_usage.gd index ee30f01dfb8..6999a1612ac 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/type_test_usage.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/type_test_usage.gd @@ -123,4 +123,4 @@ func test(): Utils.check((const_null is A) == false) Utils.check(is_instance_of(const_null, A) == false) - print('ok') + print("ok") diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd index eb53d0a7004..f69698112cb 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd @@ -20,9 +20,7 @@ class Members: Utils.check(str(two) == '[486]') return true - -@warning_ignore("unsafe_method_access") -@warning_ignore("return_value_discarded") +@warning_ignore_start('unsafe_method_access', 'return_value_discarded') func test(): var untyped_basic = [459] Utils.check(str(untyped_basic) == '[459]') @@ -207,7 +205,7 @@ func test(): var a := A.new() var typed_natives: Array[RefCounted] = [a] - var typed_scripts = Array(typed_natives, TYPE_OBJECT, "RefCounted", A) + var typed_scripts = Array(typed_natives, TYPE_OBJECT, 'RefCounted', A) Utils.check(typed_scripts[0] == a) diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd index c9ab368f45a..9bb077aacc5 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd @@ -21,9 +21,7 @@ class Members: return true -@warning_ignore("unsafe_method_access") -@warning_ignore("assert_always_true") -@warning_ignore("return_value_discarded") +@warning_ignore_start("unsafe_method_access", "return_value_discarded") func test(): var untyped_basic = { 459: 954 } Utils.check(str(untyped_basic) == '{ 459: 954 }') diff --git a/modules/gdscript/tests/scripts/analyzer/features/vararg_call.gd b/modules/gdscript/tests/scripts/analyzer/features/vararg_call.gd index d444250f1ea..b77bb35405b 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/vararg_call.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/vararg_call.gd @@ -1,6 +1,6 @@ signal ok() -@warning_ignore("return_value_discarded") +@warning_ignore_start("return_value_discarded") func test(): - ok.connect(func(): print('ok')) - emit_signal(&'ok') + ok.connect(func(): print("ok")) + emit_signal(&"ok") diff --git a/modules/gdscript/tests/scripts/analyzer/features/warning_ignore_targets.out b/modules/gdscript/tests/scripts/analyzer/features/warning_ignore_targets.out index bf870408111..bf92bd6ede7 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/warning_ignore_targets.out +++ b/modules/gdscript/tests/scripts/analyzer/features/warning_ignore_targets.out @@ -1,6 +1,7 @@ GDTEST_OK ~~ WARNING at line 3: (CONFUSABLE_IDENTIFIER) The identifier "my_vАr" has misleading characters and might be confused with something else. ~~ WARNING at line 8: (NARROWING_CONVERSION) Narrowing conversion (float is converted to int and loses precision). +~~ WARNING at line 14: (NARROWING_CONVERSION) Narrowing conversion (float is converted to int and loses precision). ~~ WARNING at line 19: (NARROWING_CONVERSION) Narrowing conversion (float is converted to int and loses precision). ~~ WARNING at line 24: (NARROWING_CONVERSION) Narrowing conversion (float is converted to int and loses precision). ~~ WARNING at line 27: (CONFUSABLE_IDENTIFIER) The identifier "_my_vАr" has misleading characters and might be confused with something else. diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd b/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd index 6a16ae6bcc0..0624f3fd379 100644 --- a/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd +++ b/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd @@ -4,11 +4,10 @@ extends ShadowingBase var member: int = 0 var print_debug := 'print_debug' -@warning_ignore("shadowed_global_identifier") +@warning_ignore('shadowed_global_identifier') var print := 'print' -@warning_ignore("unused_variable") -@warning_ignore("unused_local_constant") +@warning_ignore_start('unused_variable', 'unused_local_constant') func test(): var Array := 'Array' var Node := 'Node' diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out b/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out index 0b11103a801..702361f8a39 100644 --- a/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out +++ b/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out @@ -1,13 +1,13 @@ GDTEST_OK ~~ WARNING at line 6: (SHADOWED_GLOBAL_IDENTIFIER) The variable "print_debug" has the same name as a built-in function. -~~ WARNING at line 13: (SHADOWED_GLOBAL_IDENTIFIER) The variable "Array" has the same name as a built-in type. -~~ WARNING at line 14: (SHADOWED_GLOBAL_IDENTIFIER) The variable "Node" has the same name as a native class. -~~ WARNING at line 15: (SHADOWED_GLOBAL_IDENTIFIER) The variable "is_same" has the same name as a built-in function. -~~ WARNING at line 16: (SHADOWED_GLOBAL_IDENTIFIER) The variable "sqrt" has the same name as a built-in function. -~~ WARNING at line 17: (SHADOWED_VARIABLE) The local variable "member" is shadowing an already-declared variable at line 4 in the current class. -~~ WARNING at line 18: (SHADOWED_VARIABLE_BASE_CLASS) The local variable "reference" is shadowing an already-declared method in the base class "RefCounted". -~~ WARNING at line 19: (SHADOWED_GLOBAL_IDENTIFIER) The variable "ShadowedClass" has the same name as a global class defined in "shadowning.gd". -~~ WARNING at line 20: (SHADOWED_VARIABLE_BASE_CLASS) The local variable "base_variable_member" is shadowing an already-declared variable at line 4 in the base class "ShadowingBase". -~~ WARNING at line 21: (SHADOWED_VARIABLE_BASE_CLASS) The local constant "base_function_member" is shadowing an already-declared function at line 6 in the base class "ShadowingBase". -~~ WARNING at line 22: (SHADOWED_VARIABLE_BASE_CLASS) The local variable "base_const_member" is shadowing an already-declared constant at line 3 in the base class "ShadowingBase". +~~ WARNING at line 12: (SHADOWED_GLOBAL_IDENTIFIER) The variable "Array" has the same name as a built-in type. +~~ WARNING at line 13: (SHADOWED_GLOBAL_IDENTIFIER) The variable "Node" has the same name as a native class. +~~ WARNING at line 14: (SHADOWED_GLOBAL_IDENTIFIER) The variable "is_same" has the same name as a built-in function. +~~ WARNING at line 15: (SHADOWED_GLOBAL_IDENTIFIER) The variable "sqrt" has the same name as a built-in function. +~~ WARNING at line 16: (SHADOWED_VARIABLE) The local variable "member" is shadowing an already-declared variable at line 4 in the current class. +~~ WARNING at line 17: (SHADOWED_VARIABLE_BASE_CLASS) The local variable "reference" is shadowing an already-declared method in the base class "RefCounted". +~~ WARNING at line 18: (SHADOWED_GLOBAL_IDENTIFIER) The variable "ShadowedClass" has the same name as a global class defined in "shadowning.gd". +~~ WARNING at line 19: (SHADOWED_VARIABLE_BASE_CLASS) The local variable "base_variable_member" is shadowing an already-declared variable at line 4 in the base class "ShadowingBase". +~~ WARNING at line 20: (SHADOWED_VARIABLE_BASE_CLASS) The local constant "base_function_member" is shadowing an already-declared function at line 6 in the base class "ShadowingBase". +~~ WARNING at line 21: (SHADOWED_VARIABLE_BASE_CLASS) The local variable "base_const_member" is shadowing an already-declared constant at line 3 in the base class "ShadowingBase". warn diff --git a/modules/gdscript/tests/scripts/parser/errors/warning_ignore_extra_start.gd b/modules/gdscript/tests/scripts/parser/errors/warning_ignore_extra_start.gd new file mode 100644 index 00000000000..788fbc53d71 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/warning_ignore_extra_start.gd @@ -0,0 +1,5 @@ +@warning_ignore_start("unreachable_code") +@warning_ignore_start("unreachable_code") + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/parser/errors/warning_ignore_extra_start.out b/modules/gdscript/tests/scripts/parser/errors/warning_ignore_extra_start.out new file mode 100644 index 00000000000..1d02f0eaa01 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/warning_ignore_extra_start.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Warning "UNREACHABLE_CODE" is already being ignored by "@warning_ignore_start" at line 1. diff --git a/modules/gdscript/tests/scripts/parser/errors/warning_ignore_restore_without_start.gd b/modules/gdscript/tests/scripts/parser/errors/warning_ignore_restore_without_start.gd new file mode 100644 index 00000000000..3580a1bf90b --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/warning_ignore_restore_without_start.gd @@ -0,0 +1,4 @@ +@warning_ignore_restore("unreachable_code") + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/parser/errors/warning_ignore_restore_without_start.out b/modules/gdscript/tests/scripts/parser/errors/warning_ignore_restore_without_start.out new file mode 100644 index 00000000000..8b0fc7d43eb --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/warning_ignore_restore_without_start.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Warning "UNREACHABLE_CODE" is not being ignored by "@warning_ignore_start". diff --git a/modules/gdscript/tests/scripts/parser/features/arrays_dictionaries_nested_const.gd b/modules/gdscript/tests/scripts/parser/features/arrays_dictionaries_nested_const.gd index a34cc26e67a..05988377a2a 100644 --- a/modules/gdscript/tests/scripts/parser/features/arrays_dictionaries_nested_const.gd +++ b/modules/gdscript/tests/scripts/parser/features/arrays_dictionaries_nested_const.gd @@ -1,6 +1,6 @@ # https://github.com/godotengine/godot/issues/50285 -@warning_ignore("unused_local_constant") +@warning_ignore_start("unused_local_constant") func test(): const CONST_INNER_DICTIONARY = { "key": true } const CONST_NESTED_DICTIONARY_OLD_WORKAROUND = { diff --git a/modules/gdscript/tests/scripts/parser/features/signal_declaration.gd b/modules/gdscript/tests/scripts/parser/features/signal_declaration.gd index d02f82d4174..23f55620171 100644 --- a/modules/gdscript/tests/scripts/parser/features/signal_declaration.gd +++ b/modules/gdscript/tests/scripts/parser/features/signal_declaration.gd @@ -1,3 +1,5 @@ +@warning_ignore_start("unused_signal") + # No parentheses. signal a @@ -17,12 +19,5 @@ signal d( # With type hints. signal e(a: int, b: Variant, c: Node) -func no_exec(): - a.emit() - b.emit() - c.emit() - d.emit() - e.emit() - func test(): print("Ok") diff --git a/modules/gdscript/tests/scripts/parser/features/warning_ignore_regions.gd b/modules/gdscript/tests/scripts/parser/features/warning_ignore_regions.gd new file mode 100644 index 00000000000..ffdeae1763a --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/warning_ignore_regions.gd @@ -0,0 +1,26 @@ +@warning_ignore_start("unreachable_code", "narrowing_conversion") + +var _a = 1 +@warning_ignore_start("unused_private_class_variable") +var _b = 2 +var _c = 3 +@warning_ignore_restore("unused_private_class_variable") +var _d = 4 + +func test(): + return + + var a = 1 + @warning_ignore_start("unused_variable") + var b = 2 + var c = 3 + @warning_ignore_restore("unused_variable") + var d = 4 + + var _x: int = 1.0 + @warning_ignore_restore("narrowing_conversion") + var _y: int = 1.0 + +func test_2(): + return + print(42) diff --git a/modules/gdscript/tests/scripts/parser/features/warning_ignore_regions.out b/modules/gdscript/tests/scripts/parser/features/warning_ignore_regions.out new file mode 100644 index 00000000000..11f7263ed8e --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/warning_ignore_regions.out @@ -0,0 +1,6 @@ +GDTEST_OK +~~ WARNING at line 3: (UNUSED_PRIVATE_CLASS_VARIABLE) The class variable "_a" is declared but never used in the class. +~~ WARNING at line 8: (UNUSED_PRIVATE_CLASS_VARIABLE) The class variable "_d" is declared but never used in the class. +~~ WARNING at line 13: (UNUSED_VARIABLE) The local variable "a" is declared but never used in the block. If this is intended, prefix it with an underscore: "_a". +~~ WARNING at line 18: (UNUSED_VARIABLE) The local variable "d" is declared but never used in the block. If this is intended, prefix it with an underscore: "_d". +~~ WARNING at line 22: (NARROWING_CONVERSION) Narrowing conversion (float is converted to int and loses precision). diff --git a/modules/gdscript/tests/scripts/runtime/features/assign_operator.gd b/modules/gdscript/tests/scripts/runtime/features/assign_operator.gd index 2a9fe851ef0..0baa16f6b7c 100644 --- a/modules/gdscript/tests/scripts/runtime/features/assign_operator.gd +++ b/modules/gdscript/tests/scripts/runtime/features/assign_operator.gd @@ -1,6 +1,6 @@ # https://github.com/godotengine/godot/issues/75832 -@warning_ignore("narrowing_conversion") +@warning_ignore_start("narrowing_conversion") func test(): var hf := 2.0 var sf = 2.0 diff --git a/modules/gdscript/tests/scripts/runtime/features/member_info.gd b/modules/gdscript/tests/scripts/runtime/features/member_info.gd index 4ce53aa395e..fb99cf5de6a 100644 --- a/modules/gdscript/tests/scripts/runtime/features/member_info.gd +++ b/modules/gdscript/tests/scripts/runtime/features/member_info.gd @@ -56,6 +56,7 @@ func test_func_hard_int() -> int: return 1 func test_func_args_1(_a: int, _b: Array[int], _c: Dictionary[int, int], _d: int = 1, _e = 2): pass func test_func_args_2(_a = 1, _b = _a, _c = [2], _d = 3): pass +@warning_ignore_start("unused_signal") signal test_signal_1() signal test_signal_2(a: Variant, b) signal test_signal_3(a: int, b: Array[int], c: Dictionary[int, int]) @@ -64,16 +65,7 @@ signal test_signal_5(a: MyEnum, b: Array[MyEnum], c: Dictionary[MyEnum, MyEnum]) signal test_signal_6(a: Resource, b: Array[Resource], c: Dictionary[Resource, Resource]) signal test_signal_7(a: TestMemberInfo, b: Array[TestMemberInfo], c: Dictionary[TestMemberInfo, TestMemberInfo]) signal test_signal_8(a: MyClass, b: Array[MyClass], c: Dictionary[MyClass, MyClass]) - -func no_exec(): - test_signal_1.emit() - test_signal_2.emit() - test_signal_3.emit() - test_signal_4.emit() - test_signal_5.emit() - test_signal_6.emit() - test_signal_7.emit() - test_signal_8.emit() +@warning_ignore_restore("unused_signal") func test(): var script: Script = get_script() diff --git a/modules/gltf/editor/editor_scene_exporter_gltf_settings.cpp b/modules/gltf/editor/editor_scene_exporter_gltf_settings.cpp index 7e460e4c28d..b35ed42e47e 100644 --- a/modules/gltf/editor/editor_scene_exporter_gltf_settings.cpp +++ b/modules/gltf/editor/editor_scene_exporter_gltf_settings.cpp @@ -36,7 +36,7 @@ const uint32_t PROP_EDITOR_SCRIPT_VAR = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_S bool EditorSceneExporterGLTFSettings::_set(const StringName &p_name, const Variant &p_value) { String name_str = String(p_name); - if (name_str.contains("/")) { + if (name_str.contains_char('/')) { return _set_extension_setting(name_str, p_value); } if (p_name == StringName("image_format")) { @@ -57,7 +57,7 @@ bool EditorSceneExporterGLTFSettings::_set(const StringName &p_name, const Varia bool EditorSceneExporterGLTFSettings::_get(const StringName &p_name, Variant &r_ret) const { String name_str = String(p_name); - if (name_str.contains("/")) { + if (name_str.contains_char('/')) { return _get_extension_setting(name_str, r_ret); } if (p_name == StringName("image_format")) { diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index 735a2a5af86..130d243c4ab 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -4080,7 +4080,7 @@ Error GLTFDocument::_parse_images(Ref p_state, const String &p_base_p if (uri.begins_with("data:")) { // Embedded data using base64. data = _parse_base64_uri(uri); // mimeType is optional, but if we have it defined in the URI, let's use it. - if (mime_type.is_empty() && uri.contains(";")) { + if (mime_type.is_empty() && uri.contains_char(';')) { // Trim "data:" prefix which is 5 characters long, and end at ";base64". mime_type = uri.substr(5, uri.find(";base64") - 5); } diff --git a/modules/interactive_music/audio_stream_interactive.cpp b/modules/interactive_music/audio_stream_interactive.cpp index 2e79c6a6aeb..b251e383ae7 100644 --- a/modules/interactive_music/audio_stream_interactive.cpp +++ b/modules/interactive_music/audio_stream_interactive.cpp @@ -693,9 +693,14 @@ void AudioStreamPlaybackInteractive::_queue(int p_to_clip_index, bool p_is_auto_ src_fade_wait = beat_sec - remainder; } break; case AudioStreamInteractive::TRANSITION_FROM_TIME_NEXT_BAR: { - float bar_sec = beat_sec * from_state.stream->get_bar_beats(); - float remainder = Math::fmod(current_pos, bar_sec); - src_fade_wait = bar_sec - remainder; + if (from_state.stream->get_bar_beats() > 0) { + float bar_sec = beat_sec * from_state.stream->get_bar_beats(); + float remainder = Math::fmod(current_pos, bar_sec); + src_fade_wait = bar_sec - remainder; + } else { + // Stream does not have a number of beats per bar - avoid NaN, and play immediately. + src_fade_wait = 0; + } } break; case AudioStreamInteractive::TRANSITION_FROM_TIME_END: { float end = from_state.stream->get_beat_count() > 0 ? float(from_state.stream->get_beat_count() * beat_sec) : from_state.stream->get_length(); diff --git a/modules/interactive_music/audio_stream_interactive.h b/modules/interactive_music/audio_stream_interactive.h index 4c703a3ca9c..5b647c565e4 100644 --- a/modules/interactive_music/audio_stream_interactive.h +++ b/modules/interactive_music/audio_stream_interactive.h @@ -102,7 +102,7 @@ class AudioStreamInteractive : public AudioStream { TransitionFromTime from_time = TRANSITION_FROM_TIME_NEXT_BEAT; TransitionToTime to_time = TRANSITION_TO_TIME_START; FadeMode fade_mode = FADE_AUTOMATIC; - int fade_beats = 1; + float fade_beats = 1; bool use_filler_clip = false; int filler_clip = 0; bool hold_previous = false; diff --git a/modules/interactive_music/audio_stream_synchronized.cpp b/modules/interactive_music/audio_stream_synchronized.cpp index 3d8ba77753c..3ceedb21ab9 100644 --- a/modules/interactive_music/audio_stream_synchronized.cpp +++ b/modules/interactive_music/audio_stream_synchronized.cpp @@ -101,6 +101,18 @@ int AudioStreamSynchronized::get_beat_count() const { return max_beats; } +int AudioStreamSynchronized::get_bar_beats() const { + for (int i = 0; i < stream_count; i++) { + if (audio_streams[i].is_valid()) { + int bar_beats = audio_streams[i]->get_bar_beats(); + if (bar_beats != 0) { + return bar_beats; + } + } + } + return 0; +} + bool AudioStreamSynchronized::has_loop() const { for (int i = 0; i < stream_count; i++) { if (audio_streams[i].is_valid()) { diff --git a/modules/interactive_music/audio_stream_synchronized.h b/modules/interactive_music/audio_stream_synchronized.h index eba89fdb1d5..d2ecb3b6537 100644 --- a/modules/interactive_music/audio_stream_synchronized.h +++ b/modules/interactive_music/audio_stream_synchronized.h @@ -56,6 +56,7 @@ class AudioStreamSynchronized : public AudioStream { public: virtual double get_bpm() const override; virtual int get_beat_count() const override; + virtual int get_bar_beats() const override; virtual bool has_loop() const override; void set_stream_count(int p_count); int get_stream_count() const; diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 937048d000f..966a366ef5b 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -2346,9 +2346,7 @@ bool CSharpScript::can_instantiate() const { } StringName CSharpScript::get_instance_base_type() const { - StringName native_name; - GDMonoCache::managed_callbacks.ScriptManagerBridge_GetScriptNativeName(this, &native_name); - return native_name; + return type_info.native_base_name; } CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_is_ref_counted, Callable::CallError &r_error) { diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h index a02d7e426cb..770b1ecfb4e 100644 --- a/modules/mono/csharp_script.h +++ b/modules/mono/csharp_script.h @@ -70,6 +70,11 @@ class CSharpScript : public Script { */ String class_name; + /** + * Name of the native class this script derives from. + */ + StringName native_base_name; + /** * Path to the icon that will be used for this class by the editor. */ diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 5d7e346453f..263b2c6e10b 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -879,7 +879,7 @@ void BindingsGenerator::_append_text_method(StringBuilder &p_output, const TypeI } void BindingsGenerator::_append_text_member(StringBuilder &p_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector &p_link_target_parts) { - if (p_link_target.contains("/")) { + if (p_link_target.contains_char('/')) { // Properties with '/' (slash) in the name are not declared in C#, so there is nothing to reference. _append_text_undeclared(p_output, p_link_target); } else if (!p_target_itype || !p_target_itype->is_object_type) { @@ -1160,7 +1160,7 @@ void BindingsGenerator::_append_xml_method(StringBuilder &p_xml_output, const Ty } void BindingsGenerator::_append_xml_member(StringBuilder &p_xml_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector &p_link_target_parts) { - if (p_link_target.contains("/")) { + if (p_link_target.contains_char('/')) { // Properties with '/' (slash) in the name are not declared in C#, so there is nothing to reference. _append_xml_undeclared(p_xml_output, p_link_target); } else if (!p_target_itype || !p_target_itype->is_object_type) { @@ -3872,7 +3872,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() { continue; } - if (property.name.contains("/")) { + if (property.name.contains_char('/')) { // Ignore properties with '/' (slash) in the name. These are only meant for use in the inspector. continue; } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs index fb1d32c0cb3..6d3724b11ed 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs @@ -1060,6 +1060,22 @@ private static godot_variant ToVariantFunc(in Array godotArray) => private static Array FromVariantFunc(in godot_variant variant) => VariantUtils.ConvertToArray(variant); + private void SetTypedForUnderlyingArray() + { + Marshaling.GetTypedCollectionParameterInfo(out var elemVariantType, out var elemClassName, out var elemScriptRef); + + var self = (godot_array)NativeValue; + + using (elemScriptRef) + { + NativeFuncs.godotsharp_array_set_typed( + ref self, + (uint)elemVariantType, + elemClassName, + elemScriptRef); + } + } + static unsafe Array() { VariantUtils.GenericConversion>.ToVariantCb = &ToVariantFunc; @@ -1083,6 +1099,7 @@ internal ref godot_array.movable NativeValue public Array() { _underlyingArray = new Array(); + SetTypedForUnderlyingArray(); } /// @@ -1099,6 +1116,7 @@ public Array(IEnumerable collection) throw new ArgumentNullException(nameof(collection)); _underlyingArray = new Array(); + SetTypedForUnderlyingArray(); foreach (T element in collection) Add(element); @@ -1118,6 +1136,7 @@ public Array(T[] array) throw new ArgumentNullException(nameof(array)); _underlyingArray = new Array(); + SetTypedForUnderlyingArray(); foreach (T element in array) Add(element); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs index 91d49854c73..93a3f52f7db 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs @@ -182,18 +182,7 @@ internal static unsafe void GetScriptNativeName(IntPtr scriptPtr, godot_string_n return; } - var native = GodotObject.InternalGetClassNativeBase(scriptType); - - var field = native.GetField("NativeName", BindingFlags.DeclaredOnly | BindingFlags.Static | - BindingFlags.Public | BindingFlags.NonPublic); - - if (field == null) - { - *outRes = default; - return; - } - - var nativeName = (StringName?)field.GetValue(null); + var nativeName = GodotObject.InternalGetClassNativeBaseName(scriptType); if (nativeName == null) { @@ -658,10 +647,14 @@ internal static godot_bool TryReloadRegisteredScriptWithClass(IntPtr scriptPtr) private static unsafe void GetScriptTypeInfo(Type scriptType, godot_csharp_type_info* outTypeInfo) { - Type native = GodotObject.InternalGetClassNativeBase(scriptType); - godot_string className = Marshaling.ConvertStringToNative(ReflectionUtils.ConstructTypeName(scriptType)); + StringName? nativeBase = GodotObject.InternalGetClassNativeBaseName(scriptType); + + godot_string_name nativeBaseName = nativeBase != null + ? NativeFuncs.godotsharp_string_name_new_copy((godot_string_name)nativeBase.NativeValue) + : default; + bool isTool = scriptType.IsDefined(typeof(ToolAttribute), inherit: false); // If the type is nested and the parent type is a tool script, @@ -686,6 +679,7 @@ private static unsafe void GetScriptTypeInfo(Type scriptType, godot_csharp_type_ godot_string iconPath = Marshaling.ConvertStringToNative(iconAttr?.Path); outTypeInfo->ClassName = className; + outTypeInfo->NativeBaseName = nativeBaseName; outTypeInfo->IconPath = iconPath; outTypeInfo->IsTool = isTool.ToGodotBool(); outTypeInfo->IsGlobalClass = isGlobalClass.ToGodotBool(); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs index db6961fd12a..8c3dec1cbf7 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs @@ -496,6 +496,27 @@ private static godot_variant ToVariantFunc(in Dictionary godotDict private static Dictionary FromVariantFunc(in godot_variant variant) => VariantUtils.ConvertToDictionary(variant); + private void SetTypedForUnderlyingDictionary() + { + Marshaling.GetTypedCollectionParameterInfo(out var keyVariantType, out var keyClassName, out var keyScriptRef); + Marshaling.GetTypedCollectionParameterInfo(out var valueVariantType, out var valueClassName, out var valueScriptRef); + + var self = (godot_dictionary)NativeValue; + + using (keyScriptRef) + using (valueScriptRef) + { + NativeFuncs.godotsharp_dictionary_set_typed( + ref self, + (uint)keyVariantType, + keyClassName, + keyScriptRef, + (uint)valueVariantType, + valueClassName, + valueScriptRef); + } + } + static unsafe Dictionary() { VariantUtils.GenericConversion>.ToVariantCb = &ToVariantFunc; @@ -519,6 +540,7 @@ internal ref godot_dictionary.movable NativeValue public Dictionary() { _underlyingDict = new Dictionary(); + SetTypedForUnderlyingDictionary(); } /// @@ -535,6 +557,7 @@ public Dictionary(IDictionary dictionary) throw new ArgumentNullException(nameof(dictionary)); _underlyingDict = new Dictionary(); + SetTypedForUnderlyingDictionary(); foreach (KeyValuePair entry in dictionary) Add(entry.Key, entry.Value); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs index b8e464d4d05..be9ade85010 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.Diagnostics; +using System.Reflection; using System.Runtime.InteropServices; using Godot.Bridge; using Godot.NativeInterop; @@ -13,6 +15,8 @@ public partial class GodotObject : IDisposable private bool _disposed; private static readonly Type _cachedType = typeof(GodotObject); + private static readonly Dictionary _nativeNames = new Dictionary(); + internal IntPtr NativePtr; private bool _memoryOwn; @@ -191,16 +195,56 @@ public SignalAwaiter ToSignal(GodotObject source, StringName signal) return new SignalAwaiter(source, signal, this); } + internal static bool IsNativeClass(Type t) + { + if (ReferenceEquals(t.Assembly, typeof(GodotObject).Assembly)) + { + return true; + } + + if (ReflectionUtils.IsEditorHintCached) + { + return t.Assembly.GetName().Name == "GodotSharpEditor"; + } + + return false; + } + internal static Type InternalGetClassNativeBase(Type t) { - var name = t.Assembly.GetName().Name; + while (!IsNativeClass(t)) + { + Debug.Assert(t.BaseType is not null, "Script types must derive from a native Redot type."); + + t = t.BaseType; + } + + return t; + } + + internal static StringName? InternalGetClassNativeBaseName(Type t) + { + if (_nativeNames.TryGetValue(t, out var name)) + { + return name; + } + + var baseType = InternalGetClassNativeBase(t); + + if (_nativeNames.TryGetValue(baseType, out name)) + { + return name; + } + + var field = baseType.GetField("NativeName", + BindingFlags.DeclaredOnly | BindingFlags.Static | + BindingFlags.Public | BindingFlags.NonPublic); - if (name == "GodotSharp" || name == "GodotSharpEditor") - return t; + name = field?.GetValue(null) as StringName; - Debug.Assert(t.BaseType is not null, "Script types must derive from a native Redot type."); + _nativeNames[baseType] = name; - return InternalGetClassNativeBase(t.BaseType); + return name; } // ReSharper disable once VirtualMemberNeverOverridden.Global diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs index 7e5c01d0f88..555aebcd831 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs @@ -109,6 +109,7 @@ public Godot.Variant.Type Expected public ref struct godot_csharp_type_info { private godot_string _className; + private godot_string_name _nativeBaseName; private godot_string _iconPath; private godot_bool _isTool; private godot_bool _isGlobalClass; @@ -122,6 +123,12 @@ public godot_string ClassName set => _className = value; } + public godot_string_name NativeBaseName + { + readonly get => _nativeBaseName; + set => _nativeBaseName = value; + } + public godot_string IconPath { readonly get => _iconPath; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs index fc68b119322..54efaad3c84 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs @@ -199,6 +199,44 @@ internal static Variant.Type ConvertManagedTypeToVariantType(Type type, out bool return Variant.Type.Nil; } + internal static void GetTypedCollectionParameterInfo( + out Variant.Type variantType, + out godot_string_name className, + out godot_ref script) + { + variantType = ConvertManagedTypeToVariantType(typeof(T), out _); + + if (variantType != Variant.Type.Object) + { + className = default; + script = default; + return; + } + + godot_ref scriptRef = default; + + if (!GodotObject.IsNativeClass(typeof(T))) + { + unsafe + { + Godot.Bridge.ScriptManagerBridge.GetOrLoadOrCreateScriptForType(typeof(T), &scriptRef); + } + + // Don't call GodotObject.InternalGetClassNativeBaseName here! + // godot_dictionary_set_typed and godot_array_set_typed will call CSharpScript::get_instance_base_type + // when a script is passed, because this is better for performance than using reflection to find the + // native base type. + className = default; + } + else + { + StringName? nativeBaseName = GodotObject.InternalGetClassNativeBaseName(typeof(T)); + className = nativeBaseName != null ? (godot_string_name)nativeBaseName.NativeValue : default; + } + + script = scriptRef; + } + // String public static unsafe godot_string ConvertStringToNative(string? p_mono_string) diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs index 6a643833f6a..47998c8017c 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs @@ -402,6 +402,14 @@ public static partial void public static partial void godotsharp_array_make_read_only(ref godot_array p_self); + public static partial void godotsharp_array_set_typed( + ref godot_array p_self, + uint p_elem_type, + in godot_string_name p_elem_class_name, + in godot_ref p_elem_script); + + public static partial godot_bool godotsharp_array_is_typed(ref godot_array p_self); + public static partial void godotsharp_array_max(ref godot_array p_self, out godot_variant r_value); public static partial void godotsharp_array_min(ref godot_array p_self, out godot_variant r_value); @@ -463,6 +471,31 @@ public static partial godot_bool godotsharp_dictionary_remove_key(ref godot_dict public static partial void godotsharp_dictionary_make_read_only(ref godot_dictionary p_self); + public static partial void godotsharp_dictionary_set_typed( + ref godot_dictionary p_self, + uint p_key_type, + in godot_string_name p_key_class_name, + in godot_ref p_key_script, + uint p_value_type, + in godot_string_name p_value_class_name, + in godot_ref p_value_script); + + public static partial godot_bool godotsharp_dictionary_is_typed_key(ref godot_dictionary p_self); + + public static partial godot_bool godotsharp_dictionary_is_typed_value(ref godot_dictionary p_self); + + public static partial uint godotsharp_dictionary_get_typed_key_builtin(ref godot_dictionary p_self); + + public static partial uint godotsharp_dictionary_get_typed_value_builtin(ref godot_dictionary p_self); + + public static partial void godotsharp_dictionary_get_typed_key_class_name(ref godot_dictionary p_self, out godot_string_name r_dest); + + public static partial void godotsharp_dictionary_get_typed_value_class_name(ref godot_dictionary p_self, out godot_string_name r_dest); + + public static partial void godotsharp_dictionary_get_typed_key_script(ref godot_dictionary p_self, out godot_variant r_dest); + + public static partial void godotsharp_dictionary_get_typed_value_script(ref godot_dictionary p_self, out godot_variant r_dest); + public static partial void godotsharp_dictionary_to_string(ref godot_dictionary p_self, out godot_string r_str); // StringExtensions diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/ReflectionUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/ReflectionUtils.cs index 27989b5c813..e88155edb84 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/ReflectionUtils.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/ReflectionUtils.cs @@ -12,12 +12,12 @@ internal class ReflectionUtils { private static readonly HashSet? _tupleTypeSet; private static readonly Dictionary? _builtinTypeNameDictionary; - private static readonly bool _isEditorHintCached; + internal static readonly bool IsEditorHintCached; static ReflectionUtils() { - _isEditorHintCached = Engine.IsEditorHint(); - if (!_isEditorHintCached) + IsEditorHintCached = Engine.IsEditorHint(); + if (!IsEditorHintCached) { return; } @@ -66,7 +66,7 @@ static ReflectionUtils() public static string ConstructTypeName(Type type) { - if (!_isEditorHintCached) + if (!IsEditorHintCached) { return type.Name; } diff --git a/modules/mono/glue/runtime_interop.cpp b/modules/mono/glue/runtime_interop.cpp index b4bbdc7cd14..4031230428f 100644 --- a/modules/mono/glue/runtime_interop.cpp +++ b/modules/mono/glue/runtime_interop.cpp @@ -1100,6 +1100,20 @@ void godotsharp_array_make_read_only(Array *p_self) { p_self->make_read_only(); } +void godotsharp_array_set_typed(Array *p_self, uint32_t p_elem_type, const StringName *p_elem_class_name, const Ref *p_elem_script) { + Variant elem_script_variant; + StringName elem_class_name = *p_elem_class_name; + if (p_elem_script && p_elem_script->is_valid()) { + elem_script_variant = Variant(p_elem_script->ptr()); + elem_class_name = p_elem_script->ptr()->get_instance_base_type(); + } + p_self->set_typed(p_elem_type, elem_class_name, p_elem_script->ptr()); +} + +bool godotsharp_array_is_typed(const Array *p_self) { + return p_self->is_typed(); +} + void godotsharp_array_max(const Array *p_self, Variant *r_value) { *r_value = p_self->max(); } @@ -1209,6 +1223,54 @@ void godotsharp_dictionary_make_read_only(Dictionary *p_self) { p_self->make_read_only(); } +void godotsharp_dictionary_set_typed(Dictionary *p_self, uint32_t p_key_type, const StringName *p_key_class_name, const Ref *p_key_script, uint32_t p_value_type, const StringName *p_value_class_name, const Ref *p_value_script) { + Variant key_script_variant; + StringName key_class_name = *p_key_class_name; + if (p_key_script && p_key_script->is_valid()) { + key_script_variant = Variant(p_key_script->ptr()); + key_class_name = p_key_script->ptr()->get_instance_base_type(); + } + Variant value_script_variant; + StringName value_class_name = *p_value_class_name; + if (p_value_script && p_value_script->is_valid()) { + value_script_variant = Variant(p_value_script->ptr()); + value_class_name = p_value_script->ptr()->get_instance_base_type(); + } + p_self->set_typed(p_key_type, key_class_name, p_key_script->ptr(), p_value_type, value_class_name, p_value_script->ptr()); +} + +bool godotsharp_dictionary_is_typed_key(const Dictionary *p_self) { + return p_self->is_typed_key(); +} + +bool godotsharp_dictionary_is_typed_value(const Dictionary *p_self) { + return p_self->is_typed_value(); +} + +uint32_t godotsharp_dictionary_get_typed_key_builtin(const Dictionary *p_self) { + return p_self->get_typed_key_builtin(); +} + +uint32_t godotsharp_dictionary_get_typed_value_builtin(const Dictionary *p_self) { + return p_self->get_typed_value_builtin(); +} + +void godotsharp_dictionary_get_typed_key_class_name(const Dictionary *p_self, StringName *r_dest) { + memnew_placement(r_dest, StringName(p_self->get_typed_key_class_name())); +} + +void godotsharp_dictionary_get_typed_value_class_name(const Dictionary *p_self, StringName *r_dest) { + memnew_placement(r_dest, StringName(p_self->get_typed_value_class_name())); +} + +void godotsharp_dictionary_get_typed_key_script(const Dictionary *p_self, Variant *r_dest) { + memnew_placement(r_dest, Variant(p_self->get_typed_key_script())); +} + +void godotsharp_dictionary_get_typed_value_script(const Dictionary *p_self, Variant *r_dest) { + memnew_placement(r_dest, Variant(p_self->get_typed_value_script())); +} + void godotsharp_dictionary_to_string(const Dictionary *p_self, String *r_str) { *r_str = Variant(*p_self).operator String(); } @@ -1587,6 +1649,8 @@ static const void *unmanaged_callbacks[]{ (void *)godotsharp_array_insert, (void *)godotsharp_array_last_index_of, (void *)godotsharp_array_make_read_only, + (void *)godotsharp_array_set_typed, + (void *)godotsharp_array_is_typed, (void *)godotsharp_array_max, (void *)godotsharp_array_min, (void *)godotsharp_array_pick_random, @@ -1612,6 +1676,15 @@ static const void *unmanaged_callbacks[]{ (void *)godotsharp_dictionary_recursive_equal, (void *)godotsharp_dictionary_remove_key, (void *)godotsharp_dictionary_make_read_only, + (void *)godotsharp_dictionary_set_typed, + (void *)godotsharp_dictionary_is_typed_key, + (void *)godotsharp_dictionary_is_typed_value, + (void *)godotsharp_dictionary_get_typed_key_builtin, + (void *)godotsharp_dictionary_get_typed_value_builtin, + (void *)godotsharp_dictionary_get_typed_key_class_name, + (void *)godotsharp_dictionary_get_typed_value_class_name, + (void *)godotsharp_dictionary_get_typed_key_script, + (void *)godotsharp_dictionary_get_typed_value_script, (void *)godotsharp_dictionary_to_string, (void *)godotsharp_string_simplify_path, (void *)godotsharp_string_to_camel_case, diff --git a/modules/navigation/nav_map.cpp b/modules/navigation/nav_map.cpp index 5c41935dcda..7cb48a4d966 100644 --- a/modules/navigation/nav_map.cpp +++ b/modules/navigation/nav_map.cpp @@ -950,14 +950,18 @@ void NavMap::_sync_dirty_map_update_requests() { void NavMap::_sync_dirty_avoidance_update_requests() { // Sync NavAgents. - agents_dirty = sync_dirty_requests.agents.first(); + if (!agents_dirty) { + agents_dirty = sync_dirty_requests.agents.first(); + } for (SelfList *element = sync_dirty_requests.agents.first(); element; element = element->next()) { element->self()->sync(); } sync_dirty_requests.agents.clear(); // Sync NavObstacles. - obstacles_dirty = sync_dirty_requests.obstacles.first(); + if (!obstacles_dirty) { + obstacles_dirty = sync_dirty_requests.obstacles.first(); + } for (SelfList *element = sync_dirty_requests.obstacles.first(); element; element = element->next()) { element->self()->sync(); } diff --git a/modules/openxr/SCsub b/modules/openxr/SCsub index 48c87bcd59c..edfe0bbfad6 100644 --- a/modules/openxr/SCsub +++ b/modules/openxr/SCsub @@ -1,6 +1,8 @@ #!/usr/bin/env python from misc.utility.scons_hints import * +import sys + Import("env") Import("env_modules") @@ -29,7 +31,9 @@ elif env["platform"] == "linuxbsd": env_openxr.AppendUnique(CPPDEFINES=["XR_USE_PLATFORM_EGL"]) # FIXME: Review what needs to be set for Android and macOS. - env_openxr.AppendUnique(CPPDEFINES=["HAVE_SECURE_GETENV"]) + # FreeBSD uses non-standard getenv functions. + if not sys.platform.startswith("freebsd"): + env_openxr.AppendUnique(CPPDEFINES=["HAVE_SECURE_GETENV"]) elif env["platform"] == "windows": env_openxr.AppendUnique(CPPDEFINES=["XR_OS_WINDOWS", "NOMINMAX", "XR_USE_PLATFORM_WIN32"]) elif env["platform"] == "macos": diff --git a/modules/openxr/scene/openxr_composition_layer.cpp b/modules/openxr/scene/openxr_composition_layer.cpp index 9cffc61b52c..aefd68c690e 100644 --- a/modules/openxr/scene/openxr_composition_layer.cpp +++ b/modules/openxr/scene/openxr_composition_layer.cpp @@ -445,7 +445,7 @@ void OpenXRCompositionLayer::_get_property_list(List *p_property_l for (const PropertyInfo &pinfo : extension_properties) { StringName prop_name = pinfo.name; - if (!String(prop_name).contains("/")) { + if (!String(prop_name).contains_char('/')) { WARN_PRINT_ONCE(vformat("Discarding OpenXRCompositionLayer property name '%s' from extension because it doesn't contain a '/'.")); continue; } diff --git a/modules/raycast/raycast_occlusion_cull.cpp b/modules/raycast/raycast_occlusion_cull.cpp index f3302ed3e54..3ea6611982e 100644 --- a/modules/raycast/raycast_occlusion_cull.cpp +++ b/modules/raycast/raycast_occlusion_cull.cpp @@ -82,23 +82,22 @@ void RaycastOcclusionCull::RaycastHZBuffer::resize(const Size2i &p_size) { memset(camera_ray_masks.ptr(), ~0, camera_rays_tile_count * TILE_RAYS * sizeof(uint32_t)); } -void RaycastOcclusionCull::RaycastHZBuffer::update_camera_rays(const Transform3D &p_cam_transform, const Projection &p_cam_projection, bool p_cam_orthogonal) { +void RaycastOcclusionCull::RaycastHZBuffer::update_camera_rays(const Transform3D &p_cam_transform, const Vector3 &p_near_bottom_left, const Vector2 &p_near_extents, real_t p_z_far, bool p_cam_orthogonal) { CameraRayThreadData td; td.thread_count = WorkerThreadPool::get_singleton()->get_thread_count(); - td.z_near = p_cam_projection.get_z_near(); - td.z_far = p_cam_projection.get_z_far() * 1.05f; + td.z_near = -p_near_bottom_left.z; + td.z_far = p_z_far * 1.05f; td.camera_pos = p_cam_transform.origin; td.camera_dir = -p_cam_transform.basis.get_column(2); td.camera_orthogonal = p_cam_orthogonal; // Calculate the world coordinates of the viewport. - Vector2 viewport_half = p_cam_projection.get_viewport_half_extents(); - td.pixel_corner = p_cam_transform.xform(Vector3(-viewport_half.x, -viewport_half.y, -p_cam_projection.get_z_near())); - Vector3 top_corner_world = p_cam_transform.xform(Vector3(-viewport_half.x, viewport_half.y, -p_cam_projection.get_z_near())); - Vector3 left_corner_world = p_cam_transform.xform(Vector3(viewport_half.x, -viewport_half.y, -p_cam_projection.get_z_near())); + td.pixel_corner = p_cam_transform.xform(p_near_bottom_left); + Vector3 top_corner_world = p_cam_transform.xform(p_near_bottom_left + Vector3(0, p_near_extents.y, 0)); + Vector3 right_corner_world = p_cam_transform.xform(p_near_bottom_left + Vector3(p_near_extents.x, 0, 0)); - td.pixel_u_interp = left_corner_world - td.pixel_corner; + td.pixel_u_interp = right_corner_world - td.pixel_corner; td.pixel_v_interp = top_corner_world - td.pixel_corner; debug_tex_range = td.z_far; @@ -368,7 +367,7 @@ void RaycastOcclusionCull::Scenario::_transform_vertices_thread(uint32_t p_threa } void RaycastOcclusionCull::Scenario::_transform_vertices_range(const Vector3 *p_read, float *p_write, const Transform3D &p_xform, int p_from, int p_to) { - float *floats_w = p_write; + float *floats_w = p_write + 3 * p_from; for (int i = p_from; i < p_to; i++) { const Vector3 p = p_xform.xform(p_read[i]); floats_w[0] = p.x; @@ -528,14 +527,14 @@ void RaycastOcclusionCull::buffer_set_size(RID p_buffer, const Vector2i &p_size) buffers[p_buffer].resize(p_size); } -Projection RaycastOcclusionCull::_jitter_projection(const Projection &p_cam_projection, const Size2i &p_viewport_size) { +Vector2 RaycastOcclusionCull::_jitter_half_extents(const Vector2 &p_half_extents, const Size2i &p_viewport_size) { if (!_jitter_enabled) { - return p_cam_projection; + return p_half_extents; } // Prevent divide by zero when using NULL viewport. if ((p_viewport_size.x <= 0) || (p_viewport_size.y <= 0)) { - return p_cam_projection; + return p_half_extents; } int32_t frame = Engine::get_singleton()->get_frames_drawn(); @@ -572,16 +571,16 @@ Projection RaycastOcclusionCull::_jitter_projection(const Projection &p_cam_proj } break; } - // The multiplier here determines the divergence from center, - // and is to some extent a balancing act. - // Higher divergence gives fewer false hidden, but more false shown. + jitter *= Vector2(p_half_extents.x / (float)p_viewport_size.x, p_half_extents.y / (float)p_viewport_size.y); + + // The multiplier here determines the jitter magnitude in pixels. + // It seems like a value of 0.66 matches well the above jittering pattern as it generates subpixel samples at 0, 1/3 and 2/3 + // Higher magnitude gives fewer false hidden, but more false shown. // False hidden is obvious to viewer, false shown is not. // False shown can lower percentage that are occluded, and therefore performance. - jitter *= Vector2(1 / (float)p_viewport_size.x, 1 / (float)p_viewport_size.y) * 0.9f; + jitter *= 0.66f; - Projection correction; - correction.add_jitter_offset(jitter); - return correction * p_cam_projection; + return p_half_extents + jitter; } void RaycastOcclusionCull::buffer_update(RID p_buffer, const Transform3D &p_cam_transform, const Projection &p_cam_projection, bool p_cam_orthogonal) { @@ -598,9 +597,11 @@ void RaycastOcclusionCull::buffer_update(RID p_buffer, const Transform3D &p_cam_ Scenario &scenario = scenarios[buffer.scenario_rid]; scenario.update(); - Projection jittered_proj = _jitter_projection(p_cam_projection, buffer.get_occlusion_buffer_size()); + Vector2 viewport_half = p_cam_projection.get_viewport_half_extents(); + Vector2 jitter_viewport_half = _jitter_half_extents(viewport_half, buffer.get_occlusion_buffer_size()); + Vector3 near_bottom_left = Vector3(-jitter_viewport_half.x, -jitter_viewport_half.y, -p_cam_projection.get_z_near()); - buffer.update_camera_rays(p_cam_transform, jittered_proj, p_cam_orthogonal); + buffer.update_camera_rays(p_cam_transform, near_bottom_left, 2 * viewport_half, p_cam_projection.get_z_far(), p_cam_orthogonal); scenario.raycast(buffer.camera_rays, buffer.camera_ray_masks.ptr(), buffer.camera_rays_tile_count); buffer.sort_rays(-p_cam_transform.basis.get_column(2), p_cam_orthogonal); diff --git a/modules/raycast/raycast_occlusion_cull.h b/modules/raycast/raycast_occlusion_cull.h index 821db1ea509..fc9e55388fe 100644 --- a/modules/raycast/raycast_occlusion_cull.h +++ b/modules/raycast/raycast_occlusion_cull.h @@ -78,7 +78,7 @@ class RaycastOcclusionCull : public RendererSceneOcclusionCull { virtual void clear() override; virtual void resize(const Size2i &p_size) override; void sort_rays(const Vector3 &p_camera_dir, bool p_orthogonal); - void update_camera_rays(const Transform3D &p_cam_transform, const Projection &p_cam_projection, bool p_cam_orthogonal); + void update_camera_rays(const Transform3D &p_cam_transform, const Vector3 &p_near_bottom_left, const Vector2 &p_near_extents, real_t p_z_far, bool p_cam_orthogonal); ~RaycastHZBuffer(); }; @@ -168,7 +168,7 @@ class RaycastOcclusionCull : public RendererSceneOcclusionCull { bool _jitter_enabled = false; void _init_embree(); - Projection _jitter_projection(const Projection &p_cam_projection, const Size2i &p_viewport_size); + Vector2 _jitter_half_extents(const Vector2 &p_half_extents, const Size2i &p_viewport_size); public: virtual bool is_occluder(RID p_rid) override; diff --git a/modules/svg/SCsub b/modules/svg/SCsub index af8f6c14f4e..83321cf4493 100644 --- a/modules/svg/SCsub +++ b/modules/svg/SCsub @@ -14,7 +14,6 @@ thirdparty_dir = "#thirdparty/thorvg/" thirdparty_sources = [ # common "src/common/tvgCompressor.cpp", - "src/common/tvgLines.cpp", "src/common/tvgMath.cpp", "src/common/tvgStr.cpp", # SVG parser @@ -52,6 +51,7 @@ thirdparty_sources = [ "src/renderer/sw_engine/tvgSwImage.cpp", "src/renderer/sw_engine/tvgSwMath.cpp", "src/renderer/sw_engine/tvgSwMemPool.cpp", + "src/renderer/sw_engine/tvgSwPostEffect.cpp", "src/renderer/sw_engine/tvgSwRaster.cpp", "src/renderer/sw_engine/tvgSwRenderer.cpp", "src/renderer/sw_engine/tvgSwRle.cpp", diff --git a/modules/text_server_adv/thorvg_svg_in_ot.cpp b/modules/text_server_adv/thorvg_svg_in_ot.cpp index 84d6bc12b42..bcd857d49e1 100644 --- a/modules/text_server_adv/thorvg_svg_in_ot.cpp +++ b/modules/text_server_adv/thorvg_svg_in_ot.cpp @@ -99,7 +99,7 @@ FT_Error tvg_svg_in_ot_preset_slot(FT_GlyphSlot p_slot, FT_Bool p_cache, FT_Poin if (parser->has_attribute("id")) { const String &gl_name = parser->get_named_attribute_value("id"); if (gl_name.begins_with("glyph")) { - int dot_pos = gl_name.find("."); + int dot_pos = gl_name.find_char('.'); int64_t gl_idx = gl_name.substr(5, (dot_pos > 0) ? dot_pos - 5 : -1).to_int(); if (p_slot->glyph_index != gl_idx) { parser->skip_section(); diff --git a/modules/text_server_fb/thorvg_svg_in_ot.cpp b/modules/text_server_fb/thorvg_svg_in_ot.cpp index 84d6bc12b42..bcd857d49e1 100644 --- a/modules/text_server_fb/thorvg_svg_in_ot.cpp +++ b/modules/text_server_fb/thorvg_svg_in_ot.cpp @@ -99,7 +99,7 @@ FT_Error tvg_svg_in_ot_preset_slot(FT_GlyphSlot p_slot, FT_Bool p_cache, FT_Poin if (parser->has_attribute("id")) { const String &gl_name = parser->get_named_attribute_value("id"); if (gl_name.begins_with("glyph")) { - int dot_pos = gl_name.find("."); + int dot_pos = gl_name.find_char('.'); int64_t gl_idx = gl_name.substr(5, (dot_pos > 0) ? dot_pos - 5 : -1).to_int(); if (p_slot->glyph_index != gl_idx) { parser->skip_section(); diff --git a/platform/linuxbsd/detect.py b/platform/linuxbsd/detect.py index 96bd56fb8e7..270542f3e14 100644 --- a/platform/linuxbsd/detect.py +++ b/platform/linuxbsd/detect.py @@ -73,7 +73,7 @@ def get_flags(): def configure(env: "SConsEnvironment"): # Validate arch. - supported_arches = ["x86_32", "x86_64", "arm32", "arm64", "rv64", "ppc32", "ppc64"] + supported_arches = ["x86_32", "x86_64", "arm32", "arm64", "rv64", "ppc32", "ppc64", "loongarch64"] validate_arch(env["arch"], get_name(), supported_arches) ## Build type diff --git a/platform/linuxbsd/doc_classes/EditorExportPlatformLinuxBSD.xml b/platform/linuxbsd/doc_classes/EditorExportPlatformLinuxBSD.xml index a44c86202e2..7b50950646d 100644 --- a/platform/linuxbsd/doc_classes/EditorExportPlatformLinuxBSD.xml +++ b/platform/linuxbsd/doc_classes/EditorExportPlatformLinuxBSD.xml @@ -11,7 +11,7 @@ Application executable architecture. - Supported architectures: [code]x86_32[/code], [code]x86_64[/code], [code]arm64[/code], [code]arm32[/code], [code]rv64[/code], [code]ppc64[/code], and [code]ppc32[/code]. + Supported architectures: [code]x86_32[/code], [code]x86_64[/code], [code]arm64[/code], [code]arm32[/code], [code]rv64[/code], [code]ppc64[/code], [code]ppc32[/code], and [code]loongarch64[/code]. Official export templates include [code]x86_32[/code] and [code]x86_64[/code] binaries only. diff --git a/platform/linuxbsd/export/export_plugin.cpp b/platform/linuxbsd/export/export_plugin.cpp index 1d612d21e4b..03d164a5e59 100644 --- a/platform/linuxbsd/export/export_plugin.cpp +++ b/platform/linuxbsd/export/export_plugin.cpp @@ -182,7 +182,7 @@ bool EditorExportPlatformLinuxBSD::get_export_option_visibility(const EditorExpo void EditorExportPlatformLinuxBSD::get_export_options(List *r_options) const { EditorExportPlatformPC::get_export_options(r_options); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "binary_format/architecture", PROPERTY_HINT_ENUM, "x86_64,x86_32,arm64,arm32,rv64,ppc64,ppc32"), "x86_64")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "binary_format/architecture", PROPERTY_HINT_ENUM, "x86_64,x86_32,arm64,arm32,rv64,ppc64,ppc32,loongarch64"), "x86_64")); String run_script = "#!/usr/bin/env bash\n" "export DISPLAY=:0\n" @@ -284,6 +284,8 @@ String EditorExportPlatformLinuxBSD::_get_exe_arch(const String &p_path) const { return "arm64"; case 0x00f3: return "rv64"; + case 0x0102: + return "loongarch64"; default: return "unknown"; } diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp index ac01f029b3a..d9207973860 100644 --- a/platform/linuxbsd/os_linuxbsd.cpp +++ b/platform/linuxbsd/os_linuxbsd.cpp @@ -186,7 +186,7 @@ String OS_LinuxBSD::get_processor_name() const { while (!f->eof_reached()) { const String line = f->get_line(); - if (line.contains("model name")) { + if (line.to_lower().contains("model name")) { return line.split(":")[1].strip_edges(); } } diff --git a/platform/linuxbsd/platform_linuxbsd_builders.py b/platform/linuxbsd/platform_linuxbsd_builders.py index 46fa1947e87..1298a416e0c 100644 --- a/platform/linuxbsd/platform_linuxbsd_builders.py +++ b/platform/linuxbsd/platform_linuxbsd_builders.py @@ -5,6 +5,6 @@ def make_debug_linuxbsd(target, source, env): dst = str(target[0]) - os.system("objcopy --only-keep-debug {0} {0}.debugsymbols".format(dst)) - os.system("strip --strip-debug --strip-unneeded {0}".format(dst)) - os.system("objcopy --add-gnu-debuglink={0}.debugsymbols {0}".format(dst)) + os.system('objcopy --only-keep-debug "{0}" "{0}.debugsymbols"'.format(dst)) + os.system('strip --strip-debug --strip-unneeded "{0}"'.format(dst)) + os.system('objcopy --add-gnu-debuglink="{0}.debugsymbols" "{0}"'.format(dst)) diff --git a/platform/linuxbsd/wayland/wayland_thread.cpp b/platform/linuxbsd/wayland/wayland_thread.cpp index 705b09bd923..659372edb5a 100644 --- a/platform/linuxbsd/wayland/wayland_thread.cpp +++ b/platform/linuxbsd/wayland/wayland_thread.cpp @@ -34,8 +34,12 @@ #ifdef WAYLAND_ENABLED -// FIXME: Does this cause issues with *BSDs? +#ifdef __FreeBSD__ +#include +#else +// Assume Linux. #include +#endif // For the actual polling thread. #include diff --git a/platform/windows/export/export_plugin.cpp b/platform/windows/export/export_plugin.cpp index 1127b31ee25..4f13bc5b063 100644 --- a/platform/windows/export/export_plugin.cpp +++ b/platform/windows/export/export_plugin.cpp @@ -351,7 +351,7 @@ String EditorExportPlatformWindows::get_export_option_warning(const EditorExport PackedStringArray version_array = file_version.split(".", false); if (version_array.size() != 4 || !version_array[0].is_valid_int() || !version_array[1].is_valid_int() || !version_array[2].is_valid_int() || - !version_array[3].is_valid_int() || file_version.contains("-")) { + !version_array[3].is_valid_int() || file_version.contains_char('-')) { return TTR("Invalid file version."); } } @@ -361,7 +361,7 @@ String EditorExportPlatformWindows::get_export_option_warning(const EditorExport PackedStringArray version_array = product_version.split(".", false); if (version_array.size() != 4 || !version_array[0].is_valid_int() || !version_array[1].is_valid_int() || !version_array[2].is_valid_int() || - !version_array[3].is_valid_int() || product_version.contains("-")) { + !version_array[3].is_valid_int() || product_version.contains_char('-')) { return TTR("Invalid product version."); } } diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index c2fa0c81ba0..2d7e3d9fe95 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -1729,7 +1729,7 @@ String OS_Windows::get_environment(const String &p_var) const { } void OS_Windows::set_environment(const String &p_var, const String &p_value) const { - ERR_FAIL_COND_MSG(p_var.is_empty() || p_var.contains("="), vformat("Invalid environment variable name '%s', cannot be empty or include '='.", p_var)); + ERR_FAIL_COND_MSG(p_var.is_empty() || p_var.contains_char('='), vformat("Invalid environment variable name '%s', cannot be empty or include '='.", p_var)); Char16String var = p_var.utf16(); Char16String value = p_value.utf16(); ERR_FAIL_COND_MSG(var.length() + value.length() + 2 > 32767, vformat("Invalid definition for environment variable '%s', cannot exceed 32767 characters.", p_var)); @@ -1737,7 +1737,7 @@ void OS_Windows::set_environment(const String &p_var, const String &p_value) con } void OS_Windows::unset_environment(const String &p_var) const { - ERR_FAIL_COND_MSG(p_var.is_empty() || p_var.contains("="), vformat("Invalid environment variable name '%s', cannot be empty or include '='.", p_var)); + ERR_FAIL_COND_MSG(p_var.is_empty() || p_var.contains_char('='), vformat("Invalid environment variable name '%s', cannot be empty or include '='.", p_var)); SetEnvironmentVariableW((LPCWSTR)(p_var.utf16().get_data()), nullptr); // Null to delete. } @@ -2280,9 +2280,14 @@ void OS_Windows::add_frame_delay(bool p_can_draw) { target_ticks += dynamic_delay; uint64_t current_ticks = get_ticks_usec(); - // The minimum sleep resolution on windows is 1 ms on most systems. - if (current_ticks < (target_ticks - delay_resolution)) { - delay_usec((target_ticks - delay_resolution) - current_ticks); + if (target_ticks > current_ticks + delay_resolution) { + uint64_t delay_time = target_ticks - current_ticks - delay_resolution; + // Make sure we always sleep for a multiple of delay_resolution to avoid overshooting. + // Refer to: https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-sleep#remarks + delay_time = (delay_time / delay_resolution) * delay_resolution; + if (delay_time > 0) { + delay_usec(delay_time); + } } // Busy wait for the remainder of time. while (get_ticks_usec() < target_ticks) { diff --git a/platform_methods.py b/platform_methods.py index 201df3c0b54..c8646a40226 100644 --- a/platform_methods.py +++ b/platform_methods.py @@ -16,7 +16,7 @@ } # CPU architecture options. -architectures = ["x86_32", "x86_64", "arm32", "arm64", "rv64", "ppc32", "ppc64", "wasm32"] +architectures = ["x86_32", "x86_64", "arm32", "arm64", "rv64", "ppc32", "ppc64", "wasm32", "loongarch64"] architecture_aliases = { "x86": "x86_32", "x64": "x86_64", @@ -31,6 +31,7 @@ "ppcle": "ppc32", "ppc": "ppc32", "ppc64le": "ppc64", + "loong64": "loongarch64", } diff --git a/scene/2d/navigation_region_2d.cpp b/scene/2d/navigation_region_2d.cpp index cf28fd2227d..c293a7b7260 100644 --- a/scene/2d/navigation_region_2d.cpp +++ b/scene/2d/navigation_region_2d.cpp @@ -166,16 +166,14 @@ void NavigationRegion2D::_notification(int p_what) { case NOTIFICATION_VISIBILITY_CHANGED: { #ifdef DEBUG_ENABLED - if (debug_instance_rid.is_valid()) { - RS::get_singleton()->canvas_item_set_visible(debug_instance_rid, is_visible_in_tree()); - } + _set_debug_visibile(is_visible_in_tree()); #endif // DEBUG_ENABLED } break; case NOTIFICATION_EXIT_TREE: { _region_exit_navigation_map(); #ifdef DEBUG_ENABLED - _free_debug(); + _set_debug_visibile(false); #endif // DEBUG_ENABLED } break; @@ -210,6 +208,13 @@ void NavigationRegion2D::set_navigation_polygon(const Ref &p_ if (navigation_polygon.is_valid()) { navigation_polygon->connect_changed(callable_mp(this, &NavigationRegion2D::_navigation_polygon_changed)); } + +#ifdef DEBUG_ENABLED + if (navigation_polygon.is_null()) { + _set_debug_visibile(false); + } +#endif // DEBUG_ENABLED + _navigation_polygon_changed(); update_configuration_warnings(); @@ -394,6 +399,12 @@ NavigationRegion2D::~NavigationRegion2D() { #ifdef DEBUG_ENABLED NavigationServer2D::get_singleton()->disconnect(SNAME("map_changed"), callable_mp(this, &NavigationRegion2D::_navigation_map_changed)); NavigationServer2D::get_singleton()->disconnect(SNAME("navigation_debug_changed"), callable_mp(this, &NavigationRegion2D::_navigation_debug_changed)); + if (debug_instance_rid.is_valid()) { + RS::get_singleton()->free(debug_instance_rid); + } + if (debug_mesh_rid.is_valid()) { + RS::get_singleton()->free(debug_mesh_rid); + } #endif // DEBUG_ENABLED } @@ -437,7 +448,7 @@ void NavigationRegion2D::_region_update_transform() { #ifdef DEBUG_ENABLED void NavigationRegion2D::_update_debug_mesh() { if (!is_inside_tree()) { - _free_debug(); + _set_debug_visibile(false); return; } @@ -632,17 +643,11 @@ void NavigationRegion2D::_update_debug_baking_rect() { #endif // DEBUG_ENABLED #ifdef DEBUG_ENABLED -void NavigationRegion2D::_free_debug() { +void NavigationRegion2D::_set_debug_visibile(bool p_visible) { RenderingServer *rs = RenderingServer::get_singleton(); ERR_FAIL_NULL(rs); if (debug_instance_rid.is_valid()) { - rs->canvas_item_clear(debug_instance_rid); - rs->free(debug_instance_rid); - debug_instance_rid = RID(); - } - if (debug_mesh_rid.is_valid()) { - rs->free(debug_mesh_rid); - debug_mesh_rid = RID(); + RS::get_singleton()->canvas_item_set_visible(debug_instance_rid, p_visible); } } #endif // DEBUG_ENABLED diff --git a/scene/2d/navigation_region_2d.h b/scene/2d/navigation_region_2d.h index 7c27524fa98..79c9e1f8703 100644 --- a/scene/2d/navigation_region_2d.h +++ b/scene/2d/navigation_region_2d.h @@ -59,7 +59,7 @@ class NavigationRegion2D : public Node2D { bool debug_mesh_dirty = true; - void _free_debug(); + void _set_debug_visibile(bool p_visible); void _update_debug_mesh(); void _update_debug_edge_connections_mesh(); void _update_debug_baking_rect(); diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp index 044abc48383..7ccb6d69a78 100644 --- a/scene/3d/skeleton_3d.cpp +++ b/scene/3d/skeleton_3d.cpp @@ -610,7 +610,7 @@ uint64_t Skeleton3D::get_version() const { } int Skeleton3D::add_bone(const String &p_name) { - ERR_FAIL_COND_V_MSG(p_name.is_empty() || p_name.contains(":") || p_name.contains("/"), -1, vformat("Bone name cannot be empty or contain ':' or '/'.", p_name)); + ERR_FAIL_COND_V_MSG(p_name.is_empty() || p_name.contains_char(':') || p_name.contains_char('/'), -1, vformat("Bone name cannot be empty or contain ':' or '/'.", p_name)); ERR_FAIL_COND_V_MSG(name_to_bone_index.has(p_name), -1, vformat("Skeleton3D \"%s\" already has a bone with name \"%s\".", to_string(), p_name)); Bone b; diff --git a/scene/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp index d4a16ccb10e..2b9017d08a1 100644 --- a/scene/animation/animation_blend_tree.cpp +++ b/scene/animation/animation_blend_tree.cpp @@ -1439,7 +1439,7 @@ void AnimationNodeBlendTree::add_node(const StringName &p_name, Ref &p_animat Error AnimationMixer::add_animation_library(const StringName &p_name, const Ref &p_animation_library) { ERR_FAIL_COND_V(p_animation_library.is_null(), ERR_INVALID_PARAMETER); #ifdef DEBUG_ENABLED - ERR_FAIL_COND_V_MSG(String(p_name).contains("/") || String(p_name).contains(":") || String(p_name).contains(",") || String(p_name).contains("["), ERR_INVALID_PARAMETER, "Invalid animation name: " + String(p_name) + "."); + ERR_FAIL_COND_V_MSG(String(p_name).contains_char('/') || String(p_name).contains_char(':') || String(p_name).contains_char(',') || String(p_name).contains_char('['), ERR_INVALID_PARAMETER, "Invalid animation name: " + String(p_name) + "."); #endif int insert_pos = 0; @@ -358,7 +358,7 @@ void AnimationMixer::rename_animation_library(const StringName &p_name, const St return; } #ifdef DEBUG_ENABLED - ERR_FAIL_COND_MSG(String(p_new_name).contains("/") || String(p_new_name).contains(":") || String(p_new_name).contains(",") || String(p_new_name).contains("["), "Invalid animation library name: " + String(p_new_name) + "."); + ERR_FAIL_COND_MSG(String(p_new_name).contains_char('/') || String(p_new_name).contains_char(':') || String(p_new_name).contains_char(',') || String(p_new_name).contains_char('['), "Invalid animation library name: " + String(p_new_name) + "."); #endif bool found = false; diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp index c318a5ff058..9019ad3e3ac 100644 --- a/scene/animation/animation_node_state_machine.cpp +++ b/scene/animation/animation_node_state_machine.cpp @@ -56,7 +56,7 @@ AnimationNodeStateMachineTransition::AdvanceMode AnimationNodeStateMachineTransi void AnimationNodeStateMachineTransition::set_advance_condition(const StringName &p_condition) { String cs = p_condition; - ERR_FAIL_COND(cs.contains("/") || cs.contains(":")); + ERR_FAIL_COND(cs.contains_char('/') || cs.contains_char(':')); advance_condition = p_condition; if (!cs.is_empty()) { advance_condition_name = "conditions/" + cs; @@ -1259,7 +1259,7 @@ bool AnimationNodeStateMachine::is_parameter_read_only(const StringName &p_param void AnimationNodeStateMachine::add_node(const StringName &p_name, Ref p_node, const Vector2 &p_position) { ERR_FAIL_COND(states.has(p_name)); ERR_FAIL_COND(p_node.is_null()); - ERR_FAIL_COND(String(p_name).contains("/")); + ERR_FAIL_COND(String(p_name).contains_char('/')); State state_new; state_new.node = p_node; @@ -1278,7 +1278,7 @@ void AnimationNodeStateMachine::add_node(const StringName &p_name, Ref p_node) { ERR_FAIL_COND(states.has(p_name) == false); ERR_FAIL_COND(p_node.is_null()); - ERR_FAIL_COND(String(p_name).contains("/")); + ERR_FAIL_COND(String(p_name).contains_char('/')); { Ref node = states[p_name].node; diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp index 355bd73027c..48881b5bed8 100644 --- a/scene/animation/animation_tree.cpp +++ b/scene/animation/animation_tree.cpp @@ -327,7 +327,7 @@ bool AnimationNode::add_input(const String &p_name) { // Root nodes can't add inputs. ERR_FAIL_COND_V(Object::cast_to(this) != nullptr, false); Input input; - ERR_FAIL_COND_V(p_name.contains(".") || p_name.contains("/"), false); + ERR_FAIL_COND_V(p_name.contains_char('.') || p_name.contains_char('/'), false); input.name = p_name; inputs.push_back(input); emit_changed(); @@ -342,7 +342,7 @@ void AnimationNode::remove_input(int p_index) { bool AnimationNode::set_input_name(int p_input, const String &p_name) { ERR_FAIL_INDEX_V(p_input, inputs.size(), false); - ERR_FAIL_COND_V(p_name.contains(".") || p_name.contains("/"), false); + ERR_FAIL_COND_V(p_name.contains_char('.') || p_name.contains_char('/'), false); inputs.write[p_input].name = p_name; emit_changed(); return true; diff --git a/scene/debugger/scene_debugger.cpp b/scene/debugger/scene_debugger.cpp index 6f92b3e0ff8..24b1893bb59 100644 --- a/scene/debugger/scene_debugger.cpp +++ b/scene/debugger/scene_debugger.cpp @@ -1244,13 +1244,6 @@ void RuntimeNodeSelect::_setup(const Dictionary &p_settings) { root->connect(SceneStringName(window_input), callable_mp(this, &RuntimeNodeSelect::_root_window_input)); root->connect("size_changed", callable_mp(this, &RuntimeNodeSelect::_queue_selection_update), CONNECT_DEFERRED); - selection_list = memnew(PopupMenu); - selection_list->set_theme(ThemeDB::get_singleton()->get_default_theme()); - selection_list->set_auto_translate_mode(Node::AUTO_TRANSLATE_MODE_DISABLED); - selection_list->set_force_native(true); - selection_list->connect("index_pressed", callable_mp(this, &RuntimeNodeSelect::_items_popup_index_pressed).bind(selection_list)); - selection_list->connect("popup_hide", callable_mp(Object::cast_to(root), &Node::remove_child).bind(selection_list)); - panner.instantiate(); panner->set_callbacks(callable_mp(this, &RuntimeNodeSelect::_pan_callback), callable_mp(this, &RuntimeNodeSelect::_zoom_callback)); @@ -1358,9 +1351,9 @@ void RuntimeNodeSelect::_set_camera_override_enabled(bool p_enabled) { void RuntimeNodeSelect::_root_window_input(const Ref &p_event) { Window *root = SceneTree::get_singleton()->get_root(); - if (node_select_type == NODE_TYPE_NONE || selection_list->is_visible()) { + if (node_select_type == NODE_TYPE_NONE || (selection_list && selection_list->is_visible())) { // Workaround for platforms that don't allow subwindows. - if (selection_list->is_visible() && selection_list->is_embedded()) { + if (selection_list && selection_list->is_visible() && selection_list->is_embedded()) { root->set_disable_input_override(false); selection_list->push_input(p_event); callable_mp(root->get_viewport(), &Viewport::set_disable_input_override).call_deferred(true); @@ -1532,21 +1525,9 @@ void RuntimeNodeSelect::_click_point() { message.append(items[0].item->get_instance_id()); EngineDebugger::get_singleton()->send_message("remote_node_clicked", message); } else if (list_shortcut_pressed || node_select_mode == SELECT_MODE_LIST) { - if (!selection_list->is_inside_tree()) { - root->add_child(selection_list); + if (!selection_list) { + _open_selection_list(items, pos); } - - selection_list->clear(); - for (const SelectResult &I : items) { - selection_list->add_item(I.item->get_name()); - selection_list->set_item_metadata(-1, I.item); - } - - selection_list->set_position(selection_list->is_embedded() ? pos : selection_position + root->get_position()); - selection_list->reset_size(); - selection_list->popup(); - // FIXME: Ugly hack that stops the popup from hiding when the button is released. - selection_list->call_deferred(SNAME("set_position"), selection_list->get_position() + Point2(1, 0)); } } @@ -1738,6 +1719,35 @@ void RuntimeNodeSelect::_clear_selection() { #endif // _3D_DISABLED } +void RuntimeNodeSelect::_open_selection_list(const Vector &p_items, const Point2 &p_pos) { + Window *root = SceneTree::get_singleton()->get_root(); + + selection_list = memnew(PopupMenu); + selection_list->set_theme(ThemeDB::get_singleton()->get_default_theme()); + selection_list->set_auto_translate_mode(Node::AUTO_TRANSLATE_MODE_DISABLED); + selection_list->set_force_native(true); + selection_list->connect("index_pressed", callable_mp(this, &RuntimeNodeSelect::_items_popup_index_pressed).bind(selection_list)); + selection_list->connect("popup_hide", callable_mp(this, &RuntimeNodeSelect::_close_selection_list)); + + root->add_child(selection_list); + + for (const SelectResult &I : p_items) { + selection_list->add_item(I.item->get_name()); + selection_list->set_item_metadata(-1, I.item); + } + + selection_list->set_position(selection_list->is_embedded() ? p_pos : selection_position + root->get_position()); + selection_list->reset_size(); + selection_list->popup(); + // FIXME: Ugly hack that stops the popup from hiding when the button is released. + selection_list->call_deferred(SNAME("set_position"), selection_list->get_position() + Point2(1, 0)); +} + +void RuntimeNodeSelect::_close_selection_list() { + selection_list->queue_free(); + selection_list = nullptr; +} + void RuntimeNodeSelect::_set_selection_visible(bool p_visible) { selection_visible = p_visible; diff --git a/scene/debugger/scene_debugger.h b/scene/debugger/scene_debugger.h index ff4fe22e66b..560e7f1cddf 100644 --- a/scene/debugger/scene_debugger.h +++ b/scene/debugger/scene_debugger.h @@ -297,6 +297,9 @@ class RuntimeNodeSelect : public Object { void _clear_selection(); void _set_selection_visible(bool p_visible); + void _open_selection_list(const Vector &p_items, const Point2 &p_pos); + void _close_selection_list(); + void _find_canvas_items_at_pos(const Point2 &p_pos, Node *p_node, Vector &r_items, const Transform2D &p_parent_xform = Transform2D(), const Transform2D &p_canvas_xform = Transform2D()); void _pan_callback(Vector2 p_scroll_vec, Ref p_event); void _zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref p_event); diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index 2bdb7ffa5b4..63e0eff07c6 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -35,6 +35,7 @@ #include "core/io/image.h" #include "scene/gui/aspect_ratio_container.h" #include "scene/gui/color_mode.h" +#include "scene/gui/file_dialog.h" #include "scene/gui/grid_container.h" #include "scene/gui/label.h" #include "scene/gui/line_edit.h" @@ -45,6 +46,7 @@ #include "scene/gui/slider.h" #include "scene/gui/spin_box.h" #include "scene/gui/texture_rect.h" +#include "scene/resources/color_palette.h" #include "scene/resources/image_texture.h" #include "scene/resources/style_box_flat.h" #include "scene/resources/style_box_texture.h" @@ -90,6 +92,7 @@ void ColorPicker::_notification(int p_what) { _update_drop_down_arrow(btn_preset->is_pressed(), btn_preset); _update_drop_down_arrow(btn_recent_preset->is_pressed(), btn_recent_preset); btn_add_preset->set_button_icon(theme_cache.add_preset); + menu_btn->set_button_icon(theme_cache.menu_option); btn_pick->set_custom_minimum_size(Size2(28 * theme_cache.base_scale, 0)); btn_shape->set_custom_minimum_size(Size2(28 * theme_cache.base_scale, 0)); @@ -498,6 +501,15 @@ void ColorPicker::set_editor_settings(Object *p_editor_settings) { _update_presets(); _update_recent_presets(); } + +void ColorPicker::set_quick_open_callback(const Callable &p_file_selected) { + quick_open_callback = p_file_selected; +} + +void ColorPicker::set_palette_saved_callback(const Callable &p_palette_saved) { + palette_saved_callback = p_palette_saved; +} + #endif HSlider *ColorPicker::get_slider(int p_idx) { @@ -676,16 +688,34 @@ void ColorPicker::_update_presets() { #ifdef TOOLS_ENABLED if (editor_settings) { - // Rebuild swatch color buttons, keeping the add-preset button in the first position. - for (int i = 1; i < preset_container->get_child_count(); i++) { - preset_container->get_child(i)->queue_free(); - } - for (const Color &preset : preset_cache) { - _add_preset_button(preset_size, preset); + String cached_name = editor_settings->call(SNAME("get_project_metadata"), "color_picker", "palette_name", String()); + palette_path = editor_settings->call(SNAME("get_project_metadata"), "color_picker", "palette_path", String()); + bool palette_edited = editor_settings->call(SNAME("get_project_metadata"), "color_picker", "palette_edited", false); + if (!cached_name.is_empty()) { + palette_name->set_text(cached_name); + if (btn_preset->is_pressed() && !presets.is_empty()) { + palette_name->show(); + } + + if (palette_edited) { + palette_name->set_text(vformat("%s*", palette_name->get_text().replace("*", ""))); + palette_name->set_tooltip_text(ETR("The changes to this palette have not been saved to a file.")); + } } - _notification(NOTIFICATION_VISIBILITY_CHANGED); } #endif + + // Rebuild swatch color buttons, keeping the add-preset button in the first position. + for (int i = 1; i < preset_container->get_child_count(); i++) { + preset_container->get_child(i)->queue_free(); + } + + presets = preset_cache; + for (const Color &preset : preset_cache) { + _add_preset_button(preset_size, preset); + } + + _notification(NOTIFICATION_VISIBILITY_CHANGED); } void ColorPicker::_update_recent_presets() { @@ -794,6 +824,107 @@ void ColorPicker::_add_recent_preset_button(int p_size, const Color &p_color) { btn_preset_new->connect(SceneStringName(toggled), callable_mp(this, &ColorPicker::_recent_preset_pressed).bind(btn_preset_new)); } +void ColorPicker::_load_palette() { + List extensions; + ResourceLoader::get_recognized_extensions_for_type("ColorPalette", &extensions); + + file_dialog->set_title(RTR("Load Color Palette")); + file_dialog->clear_filters(); + for (const String &K : extensions) { + file_dialog->add_filter("*." + K); + } + + file_dialog->set_file_mode(FileDialog::FILE_MODE_OPEN_FILE); + file_dialog->set_current_file(""); + file_dialog->popup_centered_ratio(); +} + +void ColorPicker::_save_palette(bool p_is_save_as) { + if (!p_is_save_as && !palette_path.is_empty()) { + file_dialog->set_file_mode(FileDialog::FILE_MODE_SAVE_FILE); + _palette_file_selected(palette_path); + return; + } else { + List extensions; + ResourceLoader::get_recognized_extensions_for_type("ColorPalette", &extensions); + + file_dialog->set_title(RTR("Save Color Palette")); + file_dialog->clear_filters(); + for (const String &K : extensions) { + file_dialog->add_filter("*." + K); + } + + file_dialog->set_file_mode(FileDialog::FILE_MODE_SAVE_FILE); + file_dialog->set_current_file("new_palette.tres"); + file_dialog->popup_centered_ratio(); + } +} + +void ColorPicker::_quick_open_palette_file_selected(const String &p_path) { + if (!file_dialog) { + file_dialog = memnew(FileDialog); + add_child(file_dialog, false, INTERNAL_MODE_FRONT); + file_dialog->force_parent_owned(); + file_dialog->connect("file_selected", callable_mp(this, &ColorPicker::_palette_file_selected)); + file_dialog->set_access(FileDialog::ACCESS_FILESYSTEM); + file_dialog->set_current_dir(Engine::get_singleton()->is_editor_hint() ? "res://" : "user://"); + } + file_dialog->set_file_mode(FileDialog::FILE_MODE_OPEN_FILE); + _palette_file_selected(p_path); +} + +void ColorPicker::_palette_file_selected(const String &p_path) { + switch (file_dialog->get_file_mode()) { + case FileDialog::FileMode::FILE_MODE_OPEN_FILE: { + Ref palette = ResourceLoader::load(p_path, "", ResourceFormatLoader::CACHE_MODE_IGNORE); + ERR_FAIL_COND_MSG(palette.is_null(), vformat("Cannot open color palette file for reading at: %s", p_path)); + preset_cache.clear(); + presets.clear(); + + PackedColorArray saved_presets = palette->get_colors(); + for (const Color &saved_preset : saved_presets) { + preset_cache.push_back(saved_preset); + presets.push_back(saved_preset); + } + +#ifdef TOOLS_ENABLED + if (editor_settings) { + const StringName set_project_metadata = SNAME("set_project_metadata"); + editor_settings->call(set_project_metadata, "color_picker", "presets", saved_presets); + editor_settings->call(set_project_metadata, "color_picker", "palette_edited", false); + } +#endif + } break; + case FileDialog::FileMode::FILE_MODE_SAVE_FILE: { + ColorPalette *palette = memnew(ColorPalette); + palette->set_colors(get_presets()); + Error error = ResourceSaver::save(palette, p_path); + ERR_FAIL_COND_MSG(error != Error::OK, vformat("Cannot open color palette file for writing at: %s", p_path)); +#ifdef TOOLS_ENABLED + if (palette_saved_callback.is_valid()) { + palette_saved_callback.call_deferred(p_path); + } +#endif // TOOLS_ENABLED + } break; + default: + break; + } + + palette_name->set_text(p_path.get_file().get_basename()); + palette_name->set_tooltip_text(""); + palette_name->show(); + palette_path = p_path; + btn_preset->set_pressed(true); +#ifdef TOOLS_ENABLED + if (editor_settings) { + editor_settings->call(SNAME("set_project_metadata"), "color_picker", "palette_name", palette_name->get_text()); + editor_settings->call(SNAME("set_project_metadata"), "color_picker", "palette_path", palette_path); + editor_settings->call(SNAME("set_project_metadata"), "color_picker", "palette_edited", false); + } +#endif + _update_presets(); +} + void ColorPicker::_show_hide_preset(const bool &p_is_btn_pressed, Button *p_btn_preset, Container *p_preset_container) { if (p_is_btn_pressed) { p_preset_container->show(); @@ -801,6 +932,11 @@ void ColorPicker::_show_hide_preset(const bool &p_is_btn_pressed, Button *p_btn_ p_preset_container->hide(); } _update_drop_down_arrow(p_is_btn_pressed, p_btn_preset); + + palette_name->hide(); + if (btn_preset->is_pressed() && !palette_name->get_text().is_empty()) { + palette_name->show(); + } } void ColorPicker::_update_drop_down_arrow(const bool &p_is_btn_pressed, Button *p_btn_preset) { @@ -877,10 +1013,17 @@ void ColorPicker::add_preset(const Color &p_color) { _add_preset_button(_get_preset_size(), p_color); } + if (!palette_name->get_text().is_empty()) { + palette_name->set_text(vformat("%s*", palette_name->get_text().trim_suffix("*"))); + palette_name->set_tooltip_text(ETR("The changes to this palette have not been saved to a file.")); + } + #ifdef TOOLS_ENABLED if (editor_settings) { PackedColorArray arr_to_save = get_presets(); - editor_settings->call(SNAME("set_project_metadata"), "color_picker", "presets", arr_to_save); + const StringName set_project_metadata = SNAME("set_project_metadata"); + editor_settings->call(set_project_metadata, "color_picker", "presets", arr_to_save); + editor_settings->call(set_project_metadata, "color_picker", "palette_edited", true); } #endif } @@ -921,10 +1064,22 @@ void ColorPicker::erase_preset(const Color &p_color) { } } + palette_name->set_text(vformat("%s*", palette_name->get_text().replace("*", ""))); + palette_name->set_tooltip_text(ETR("The changes to this palette have not been saved to a file.")); + if (presets.is_empty()) { + palette_name->set_text(""); + palette_path = String(); + palette_name->hide(); + } + #ifdef TOOLS_ENABLED if (editor_settings) { PackedColorArray arr_to_save = get_presets(); - editor_settings->call(SNAME("set_project_metadata"), "color_picker", "presets", arr_to_save); + const StringName set_project_metadata = SNAME("set_project_metadata"); + editor_settings->call(set_project_metadata, "color_picker", "presets", arr_to_save); + editor_settings->call(set_project_metadata, "color_picker", "palette_edited", true); + editor_settings->call(set_project_metadata, "color_picker", "palette_name", palette_name->get_text()); + editor_settings->call(set_project_metadata, "color_picker", "palette_path", palette_path); } #endif } @@ -1537,6 +1692,106 @@ void ColorPicker::_pick_finished() { set_process_internal(false); } +void ColorPicker::_update_menu_items() { + if (!options_menu) { + options_menu = memnew(PopupMenu); + add_child(options_menu, false, INTERNAL_MODE_FRONT); + options_menu->force_parent_owned(); + options_menu->connect("id_pressed", callable_mp(this, &ColorPicker::_options_menu_cbk)); + } + + options_menu->clear(); + options_menu->reset_size(); + + if (!presets.is_empty()) { + options_menu->add_icon_item(get_theme_icon(SNAME("save"), SNAME("FileDialog")), RTR("Save"), static_cast(MenuOption::MENU_SAVE)); + options_menu->set_item_tooltip(-1, ETR("Save the current color palette to reuse later.")); + } + if (!palette_path.is_empty()) { + options_menu->add_icon_item(get_theme_icon(SNAME("save"), SNAME("FileDialog")), RTR("Save As"), static_cast(MenuOption::MENU_SAVE_AS)); + options_menu->set_item_tooltip(-1, ETR("Save the current color palette as a new to reuse later.")); + } + options_menu->add_icon_item(get_theme_icon(SNAME("load"), SNAME("FileDialog")), RTR("Load"), static_cast(MenuOption::MENU_LOAD)); + options_menu->set_item_tooltip(-1, ETR("Load existing color palette.")); + + if (Engine::get_singleton()->is_editor_hint()) { + options_menu->add_icon_item(get_theme_icon(SNAME("load"), SNAME("FileDialog")), RTR("Quick Load"), static_cast(MenuOption::MENU_QUICKLOAD)); + options_menu->set_item_tooltip(-1, ETR("Load existing color palette.")); + } + + if (!presets.is_empty()) { + options_menu->add_icon_item(get_theme_icon(SNAME("clear"), SNAME("FileDialog")), RTR("Clear"), static_cast(MenuOption::MENU_CLEAR)); + options_menu->set_item_tooltip(-1, ETR("Clear the currently loaded color palettes in the picker.")); + } +} + +void ColorPicker::_update_menu() { + _update_menu_items(); + Rect2 gt = menu_btn->get_screen_rect(); + menu_btn->reset_size(); + int min_size = menu_btn->get_minimum_size().width; + Vector2 popup_pos = gt.get_end() - Vector2(min_size, 0); + options_menu->set_position(popup_pos); + options_menu->popup(); +} + +void ColorPicker::_options_menu_cbk(int p_which) { + if (!file_dialog) { + file_dialog = memnew(FileDialog); + add_child(file_dialog, false, INTERNAL_MODE_FRONT); + file_dialog->force_parent_owned(); + file_dialog->connect("file_selected", callable_mp(this, &ColorPicker::_palette_file_selected)); + file_dialog->set_access(FileDialog::ACCESS_FILESYSTEM); + file_dialog->set_current_dir(Engine::get_singleton()->is_editor_hint() ? "res://" : "user://"); + } + + MenuOption option = static_cast(p_which); + switch (option) { + case MenuOption::MENU_SAVE: + _save_palette(false); + break; + case MenuOption::MENU_SAVE_AS: + _save_palette(true); + break; + case MenuOption::MENU_LOAD: + _load_palette(); + break; + +#ifdef TOOLS_ENABLED + case MenuOption::MENU_QUICKLOAD: + if (quick_open_callback.is_valid()) { + file_dialog->set_file_mode(FileDialog::FILE_MODE_OPEN_FILE); + quick_open_callback.call_deferred(); + } + break; +#endif // TOOLS_ENABLED + case MenuOption::MENU_CLEAR: { + PackedColorArray colors = get_presets(); + for (Color c : colors) { + erase_preset(c); + } + + palette_name->set_text(""); + palette_name->set_tooltip_text(""); + palette_path = String(); + btn_preset->set_pressed(false); + +#ifdef TOOLS_ENABLED + if (editor_settings) { + editor_settings->call(SNAME("set_project_metadata"), "color_picker", "palette_name", palette_name->get_text()); + editor_settings->call(SNAME("set_project_metadata"), "color_picker", "palette_path", palette_path); + editor_settings->call(SNAME("set_project_metadata"), "color_picker", "palette_edited", false); + } +#endif // TOOLS_ENABLED + + } + + break; + default: + break; + } +} + void ColorPicker::_pick_button_pressed_legacy() { if (!is_inside_tree()) { return; @@ -1792,6 +2047,7 @@ void ColorPicker::_bind_methods() { BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, ColorPicker, center_slider_grabbers); + BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, ColorPicker, menu_option); BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, ColorPicker, screen_picker); BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, ColorPicker, expanded_arrow); BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, ColorPicker, folded_arrow); @@ -1985,13 +2241,34 @@ ColorPicker::ColorPicker() { preset_group.instantiate(); - btn_preset = memnew(Button(ETR("Swatches"))); + HBoxContainer *palette_box = memnew(HBoxContainer); + palette_box->set_h_size_flags(SIZE_EXPAND_FILL); + real_vbox->add_child(palette_box); + + btn_preset = memnew(Button); + btn_preset->set_text("Swatches"); btn_preset->set_flat(true); btn_preset->set_toggle_mode(true); btn_preset->set_focus_mode(FOCUS_NONE); btn_preset->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT); btn_preset->connect(SceneStringName(toggled), callable_mp(this, &ColorPicker::_show_hide_preset).bind(btn_preset, preset_container)); - real_vbox->add_child(btn_preset); + palette_box->add_child(btn_preset); + + HBoxContainer *padding_box = memnew(HBoxContainer); + padding_box->set_h_size_flags(SIZE_EXPAND_FILL); + palette_box->add_child(padding_box); + + menu_btn = memnew(Button); + menu_btn->set_flat(true); + menu_btn->set_tooltip_text(ETR("Show all options available.")); + menu_btn->set_focus_mode(FOCUS_NONE); + menu_btn->connect(SceneStringName(pressed), callable_mp(this, &ColorPicker::_update_menu)); + palette_box->add_child(menu_btn); + + palette_name = memnew(Label); + palette_name->hide(); + palette_name->set_mouse_filter(MOUSE_FILTER_PASS); + real_vbox->add_child(palette_name); real_vbox->add_child(preset_container); diff --git a/scene/gui/color_picker.h b/scene/gui/color_picker.h index ec7b22a64f7..357a56b7cbd 100644 --- a/scene/gui/color_picker.h +++ b/scene/gui/color_picker.h @@ -51,6 +51,7 @@ class PopupMenu; class SpinBox; class StyleBoxFlat; class TextureRect; +class FileDialog; class ColorPresetButton : public BaseButton { GDCLASS(ColorPresetButton, BaseButton); @@ -108,6 +109,14 @@ class ColorPicker : public VBoxContainer { static const int SLIDER_COUNT = 4; private: + enum class MenuOption { + MENU_SAVE, + MENU_SAVE_AS, + MENU_LOAD, + MENU_QUICKLOAD, + MENU_CLEAR, + }; + static inline Ref wheel_shader; static inline Ref circle_shader; static inline Ref circle_ok_color_shader; @@ -131,6 +140,9 @@ class ColorPicker : public VBoxContainer { Label *picker_preview_label = nullptr; Ref picker_preview_style_box; Color picker_color; + FileDialog *file_dialog = nullptr; + Button *menu_btn = nullptr; + PopupMenu *options_menu = nullptr; MarginContainer *internal_margin = nullptr; Control *uv_edit = nullptr; @@ -146,6 +158,8 @@ class ColorPicker : public VBoxContainer { HBoxContainer *recent_preset_hbc = nullptr; Button *btn_add_preset = nullptr; Button *btn_pick = nullptr; + Label *palette_name = nullptr; + String palette_path; Button *btn_preset = nullptr; Button *btn_recent_preset = nullptr; PopupMenu *shape_popup = nullptr; @@ -161,6 +175,10 @@ class ColorPicker : public VBoxContainer { ColorPresetButton *selected_recent_preset = nullptr; Ref preset_group; Ref recent_preset_group; +#ifdef TOOLS_ENABLED + Callable quick_open_callback; + Callable palette_saved_callback; +#endif // TOOLS_ENABLED OptionButton *mode_option_button = nullptr; @@ -228,6 +246,7 @@ class ColorPicker : public VBoxContainer { bool center_slider_grabbers = true; + Ref menu_option; Ref screen_picker; Ref expanded_arrow; Ref folded_arrow; @@ -282,6 +301,10 @@ class ColorPicker : public VBoxContainer { void _html_focus_exit(); void _pick_button_pressed(); void _pick_finished(); + void _update_menu_items(); + void _update_menu(); + void _options_menu_cbk(int p_which); + // Legacy color picking. void _pick_button_pressed_legacy(); void _picker_texture_input(const Ref &p_event); @@ -289,6 +312,8 @@ class ColorPicker : public VBoxContainer { inline int _get_preset_size(); void _add_preset_button(int p_size, const Color &p_color); void _add_recent_preset_button(int p_size, const Color &p_color); + void _save_palette(bool p_is_save_as); + void _load_palette(); void _show_hide_preset(const bool &p_is_btn_pressed, Button *p_btn_preset, Container *p_preset_container); void _update_drop_down_arrow(const bool &p_is_btn_pressed, Button *p_btn_preset); @@ -308,6 +333,8 @@ class ColorPicker : public VBoxContainer { public: #ifdef TOOLS_ENABLED void set_editor_settings(Object *p_editor_settings); + void set_quick_open_callback(const Callable &p_file_selected); + void set_palette_saved_callback(const Callable &p_palette_saved); #endif HSlider *get_slider(int idx); Vector get_active_slider_values(); @@ -325,6 +352,8 @@ class ColorPicker : public VBoxContainer { Color get_pick_color() const; void set_old_color(const Color &p_color); Color get_old_color() const; + void _quick_open_palette_file_selected(const String &p_path); + void _palette_file_selected(const String &p_path); void set_display_old_color(bool p_enabled); bool is_displaying_old_color() const; diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index 4e657c86974..dc412cb0e85 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -3604,10 +3604,10 @@ void Control::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anchor_bottom", PROPERTY_HINT_RANGE, "0,1,0.001,or_less,or_greater"), "_set_anchor", "get_anchor", SIDE_BOTTOM); ADD_SUBGROUP_INDENT("Anchor Offsets", "offset_", 1); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "offset_left", PROPERTY_HINT_RANGE, "-4096,4096,suffix:px"), "set_offset", "get_offset", SIDE_LEFT); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "offset_top", PROPERTY_HINT_RANGE, "-4096,4096,suffix:px"), "set_offset", "get_offset", SIDE_TOP); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "offset_right", PROPERTY_HINT_RANGE, "-4096,4096,suffix:px"), "set_offset", "get_offset", SIDE_RIGHT); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "offset_bottom", PROPERTY_HINT_RANGE, "-4096,4096,suffix:px"), "set_offset", "get_offset", SIDE_BOTTOM); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "offset_left", PROPERTY_HINT_RANGE, "-4096,4096,1,or_less,or_greater,suffix:px"), "set_offset", "get_offset", SIDE_LEFT); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "offset_top", PROPERTY_HINT_RANGE, "-4096,4096,1,or_less,or_greater,suffix:px"), "set_offset", "get_offset", SIDE_TOP); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "offset_right", PROPERTY_HINT_RANGE, "-4096,4096,1,or_less,or_greater,suffix:px"), "set_offset", "get_offset", SIDE_RIGHT); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "offset_bottom", PROPERTY_HINT_RANGE, "-4096,4096,1,or_less,or_greater,suffix:px"), "set_offset", "get_offset", SIDE_BOTTOM); ADD_SUBGROUP_INDENT("Grow Direction", "grow_", 1); ADD_PROPERTY(PropertyInfo(Variant::INT, "grow_horizontal", PROPERTY_HINT_ENUM, "Left,Right,Both"), "set_h_grow_direction", "get_h_grow_direction"); diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index c3705b173c6..9cb9cd55876 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -366,7 +366,8 @@ float RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Reflines[p_line]; MutexLock lock(l.text_buf->get_mutex()); - l.offset.x = _find_margin(l.from, p_base_font, p_base_font_size) + l.prefix_width; + l.indent = _find_margin(l.from, p_base_font, p_base_font_size) + l.prefix_width; + l.offset.x = l.indent; l.text_buf->set_width(p_width - l.offset.x); PackedFloat32Array tab_stops = _find_tab_stops(l.from); @@ -503,7 +504,8 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref } // Add indent. - l.offset.x = _find_margin(l.from, p_base_font, p_base_font_size) + l.prefix_width; + l.indent = _find_margin(l.from, p_base_font, p_base_font_size) + l.prefix_width; + l.offset.x = l.indent; l.text_buf->set_width(p_width - l.offset.x); l.text_buf->set_alignment(_find_alignment(l.from)); l.text_buf->set_direction(_find_direction(l.from)); @@ -627,8 +629,8 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref t_char_count += cell_ch; remaining_characters -= cell_ch; - table->columns[column].min_width = MAX(table->columns[column].min_width, ceil(frame->lines[i].text_buf->get_size().x)); - table->columns[column].max_width = MAX(table->columns[column].max_width, ceil(frame->lines[i].text_buf->get_non_wrapped_size().x)); + table->columns[column].min_width = MAX(table->columns[column].min_width, frame->lines[i].indent + ceil(frame->lines[i].text_buf->get_size().x)); + table->columns[column].max_width = MAX(table->columns[column].max_width, frame->lines[i].indent + ceil(frame->lines[i].text_buf->get_non_wrapped_size().x)); } idx++; } @@ -979,6 +981,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o if (frame->lines.size() != 0 && row < row_count) { Vector2 coff = frame->lines[0].offset; + coff.x -= frame->lines[0].indent; if (rtl) { coff.x = rect.size.width - table->columns[col].width - coff.x; } @@ -2550,6 +2553,10 @@ int RichTextLabel::_find_margin(Item *p_item, const Ref &p_base_font, int float margin = 0.0; while (item) { + if (item->type == ITEM_FRAME) { + break; + } + if (item->type == ITEM_INDENT) { Ref font = p_base_font; int font_size = p_base_font_size; @@ -4297,7 +4304,6 @@ void RichTextLabel::append_text(const String &p_bbcode) { parsing_bbcode.store(true); int pos = 0; - int indent_level = 0; bool in_bold = false; bool in_italics = false; @@ -4379,7 +4385,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { in_italics = false; } if ((tag_stack.front()->get() == "indent") || (tag_stack.front()->get() == "ol") || (tag_stack.front()->get() == "ul")) { - indent_level--; + current_frame->indent_level--; } if (!tag_ok) { @@ -4652,44 +4658,44 @@ void RichTextLabel::append_text(const String &p_bbcode) { pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag == "ul") { - indent_level++; - push_list(indent_level, LIST_DOTS, false); + current_frame->indent_level++; + push_list(current_frame->indent_level, LIST_DOTS, false); pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag.begins_with("ul bullet=")) { String bullet = _get_tag_value(tag); - indent_level++; - push_list(indent_level, LIST_DOTS, false, bullet); + current_frame->indent_level++; + push_list(current_frame->indent_level, LIST_DOTS, false, bullet); pos = brk_end + 1; tag_stack.push_front("ul"); } else if ((tag == "ol") || (tag == "ol type=1")) { - indent_level++; - push_list(indent_level, LIST_NUMBERS, false); + current_frame->indent_level++; + push_list(current_frame->indent_level, LIST_NUMBERS, false); pos = brk_end + 1; tag_stack.push_front("ol"); } else if (tag == "ol type=a") { - indent_level++; - push_list(indent_level, LIST_LETTERS, false); + current_frame->indent_level++; + push_list(current_frame->indent_level, LIST_LETTERS, false); pos = brk_end + 1; tag_stack.push_front("ol"); } else if (tag == "ol type=A") { - indent_level++; - push_list(indent_level, LIST_LETTERS, true); + current_frame->indent_level++; + push_list(current_frame->indent_level, LIST_LETTERS, true); pos = brk_end + 1; tag_stack.push_front("ol"); } else if (tag == "ol type=i") { - indent_level++; - push_list(indent_level, LIST_ROMAN, false); + current_frame->indent_level++; + push_list(current_frame->indent_level, LIST_ROMAN, false); pos = brk_end + 1; tag_stack.push_front("ol"); } else if (tag == "ol type=I") { - indent_level++; - push_list(indent_level, LIST_ROMAN, true); + current_frame->indent_level++; + push_list(current_frame->indent_level, LIST_ROMAN, true); pos = brk_end + 1; tag_stack.push_front("ol"); } else if (tag == "indent") { - indent_level++; - push_indent(indent_level); + current_frame->indent_level++; + push_indent(current_frame->indent_level); pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag.begins_with("lang=")) { diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index acd38b76eb4..92ad6a15043 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -155,6 +155,7 @@ class RichTextLabel : public Control { Color dc_ol_color; Vector2 offset; + float indent = 0.0; int char_offset = 0; int char_count = 0; @@ -207,6 +208,7 @@ class RichTextLabel : public Control { Size2 min_size_over = Size2(-1, -1); Size2 max_size_over = Size2(-1, -1); Rect2 padding; + int indent_level = 0; ItemFrame() { type = ITEM_FRAME; diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index a745c6531e4..49111920055 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -1997,7 +1997,8 @@ void Viewport::_gui_input_event(Ref p_event) { Control *drag_preview = _gui_get_drag_preview(); if (drag_preview) { - drag_preview->set_position(mpos); + Vector2 pos = drag_preview->get_canvas_transform().affine_inverse().xform(mpos); + drag_preview->set_position(pos); } gui.drag_mouse_over = section_root->gui.target_control; diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 76d7065f6a5..f09f1937222 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -115,6 +115,7 @@ #include "scene/resources/bone_map.h" #include "scene/resources/camera_attributes.h" #include "scene/resources/camera_texture.h" +#include "scene/resources/color_palette.h" #include "scene/resources/compositor.h" #include "scene/resources/compressed_texture.h" #include "scene/resources/curve_texture.h" @@ -971,6 +972,7 @@ void register_scene_types() { GDREGISTER_CLASS(FontFile); GDREGISTER_CLASS(FontVariation); GDREGISTER_CLASS(SystemFont); + GDREGISTER_CLASS(ColorPalette); GDREGISTER_CLASS(Curve); diff --git a/scene/resources/3d/primitive_meshes.cpp b/scene/resources/3d/primitive_meshes.cpp index 56951170dd6..9460dbf0d10 100644 --- a/scene/resources/3d/primitive_meshes.cpp +++ b/scene/resources/3d/primitive_meshes.cpp @@ -445,15 +445,25 @@ void CapsuleMesh::create_mesh_array(Array &p_arr, const float radius, const floa v = j; v /= (rings + 1); - w = sin(0.5 * Math_PI * v); - y = radius * cos(0.5 * Math_PI * v); + if (j == (rings + 1)) { + w = 1.0; + y = 0.0; + } else { + w = Math::sin(0.5 * Math_PI * v); + y = Math::cos(0.5 * Math_PI * v) * radius; + } for (i = 0; i <= radial_segments; i++) { u = i; u /= radial_segments; - x = -sin(u * Math_TAU); - z = cos(u * Math_TAU); + if (i == radial_segments) { + x = 0.0; + z = 1.0; + } else { + x = -Math::sin(u * Math_TAU); + z = Math::cos(u * Math_TAU); + } Vector3 p = Vector3(x * radius * w, y, -z * radius * w); points.push_back(p + Vector3(0.0, 0.5 * height - radius, 0.0)); @@ -494,8 +504,13 @@ void CapsuleMesh::create_mesh_array(Array &p_arr, const float radius, const floa u = i; u /= radial_segments; - x = -sin(u * Math_TAU); - z = cos(u * Math_TAU); + if (i == radial_segments) { + x = 0.0; + z = 1.0; + } else { + x = -Math::sin(u * Math_TAU); + z = Math::cos(u * Math_TAU); + } Vector3 p = Vector3(x * radius, y, -z * radius); points.push_back(p); @@ -529,24 +544,33 @@ void CapsuleMesh::create_mesh_array(Array &p_arr, const float radius, const floa v = j; v /= (rings + 1); - v += 1.0; - w = sin(0.5 * Math_PI * v); - y = radius * cos(0.5 * Math_PI * v); + if (j == (rings + 1)) { + w = 0.0; + y = -radius; + } else { + w = Math::cos(0.5 * Math_PI * v); + y = -Math::sin(0.5 * Math_PI * v) * radius; + } for (i = 0; i <= radial_segments; i++) { u = i; u /= radial_segments; - x = -sin(u * Math_TAU); - z = cos(u * Math_TAU); + if (i == radial_segments) { + x = 0.0; + z = 1.0; + } else { + x = -Math::sin(u * Math_TAU); + z = Math::cos(u * Math_TAU); + } Vector3 p = Vector3(x * radius * w, y, -z * radius * w); points.push_back(p + Vector3(0.0, -0.5 * height + radius, 0.0)); normals.push_back(p.normalized()); ADD_TANGENT(-z, 0.0, -x, 1.0) - uvs.push_back(Vector2(u, twothirds + ((v - 1.0) * onethird))); + uvs.push_back(Vector2(u, twothirds + v * onethird)); if (p_add_uv2) { - uv2s.push_back(Vector2(u * radial_h, radial_v + height_v + ((v - 1.0) * radial_v))); + uv2s.push_back(Vector2(u * radial_h, radial_v + height_v + v * radial_v)); } point++; @@ -1076,8 +1100,13 @@ void CylinderMesh::create_mesh_array(Array &p_arr, float top_radius, float botto u = i; u /= radial_segments; - x = sin(u * Math_TAU); - z = cos(u * Math_TAU); + if (i == radial_segments) { + x = 0.0; + z = 1.0; + } else { + x = Math::sin(u * Math_TAU); + z = Math::cos(u * Math_TAU); + } Vector3 p = Vector3(x * radius, y, z * radius); points.push_back(p); @@ -1128,8 +1157,13 @@ void CylinderMesh::create_mesh_array(Array &p_arr, float top_radius, float botto float r = i; r /= radial_segments; - x = sin(r * Math_TAU); - z = cos(r * Math_TAU); + if (i == radial_segments) { + x = 0.0; + z = 1.0; + } else { + x = Math::sin(r * Math_TAU); + z = Math::cos(r * Math_TAU); + } u = ((x + 1.0) * 0.25); v = 0.5 + ((z + 1.0) * 0.25); @@ -1170,8 +1204,13 @@ void CylinderMesh::create_mesh_array(Array &p_arr, float top_radius, float botto float r = i; r /= radial_segments; - x = sin(r * Math_TAU); - z = cos(r * Math_TAU); + if (i == radial_segments) { + x = 0.0; + z = 1.0; + } else { + x = Math::sin(r * Math_TAU); + z = Math::cos(r * Math_TAU); + } u = 0.5 + ((x + 1.0) * 0.25); v = 1.0 - ((z + 1.0) * 0.25); @@ -1936,15 +1975,25 @@ void SphereMesh::create_mesh_array(Array &p_arr, float radius, float height, int float w; v /= (rings + 1); - w = sin(Math_PI * v); - y = scale * cos(Math_PI * v); + if (j == (rings + 1)) { + w = 0.0; + y = -scale; + } else { + w = Math::sin(Math_PI * v); + y = Math::cos(Math_PI * v) * scale; + } for (i = 0; i <= radial_segments; i++) { float u = i; u /= radial_segments; - x = sin(u * Math_TAU); - z = cos(u * Math_TAU); + if (i == radial_segments) { + x = 0.0; + z = 1.0; + } else { + x = Math::sin(u * Math_TAU); + z = Math::cos(u * Math_TAU); + } if (is_hemisphere && y < 0.0) { points.push_back(Vector3(x * radius * w, 0.0, z * radius * w)); @@ -2146,13 +2195,13 @@ void TorusMesh::_create_mesh_array(Array &p_arr) const { float inci = float(i) / rings; float angi = inci * Math_TAU; - Vector2 normali = Vector2(-Math::sin(angi), -Math::cos(angi)); + Vector2 normali = (i == rings) ? Vector2(0.0, -1.0) : Vector2(-Math::sin(angi), -Math::cos(angi)); for (int j = 0; j <= ring_segments; j++) { float incj = float(j) / ring_segments; float angj = incj * Math_TAU; - Vector2 normalj = Vector2(-Math::cos(angj), Math::sin(angj)); + Vector2 normalj = (j == ring_segments) ? Vector2(-1.0, 0.0) : Vector2(-Math::cos(angj), Math::sin(angj)); Vector2 normalk = normalj * radius + Vector2(min_radius + radius, 0); float offset_h = 0.5 * (1.0 - normalj.x) * delta_h; @@ -2161,7 +2210,7 @@ void TorusMesh::_create_mesh_array(Array &p_arr) const { points.push_back(Vector3(normali.x * normalk.x, normalk.y, normali.y * normalk.x)); normals.push_back(Vector3(normali.x * normalj.x, normalj.y, normali.y * normalj.x)); - ADD_TANGENT(-Math::cos(angi), 0.0, Math::sin(angi), 1.0); + ADD_TANGENT(normali.y, 0.0, -normali.x, 1.0); uvs.push_back(Vector2(inci, incj)); if (_add_uv2) { uv2s.push_back(Vector2(offset_h + inci * adj_h, incj * height_v)); @@ -2436,8 +2485,12 @@ void TubeTrailMesh::_create_mesh_array(Array &p_arr) const { if (curve.is_valid() && curve->get_point_count() > 0) { r *= curve->sample_baked(v); } - float x = sin(u * Math_TAU); - float z = cos(u * Math_TAU); + float x = 0.0; + float z = 1.0; + if (i < radial_steps) { + x = Math::sin(u * Math_TAU); + z = Math::cos(u * Math_TAU); + } Vector3 p = Vector3(x * r, y, z * r); points.push_back(p); @@ -2505,8 +2558,12 @@ void TubeTrailMesh::_create_mesh_array(Array &p_arr) const { float r = i; r /= radial_steps; - float x = sin(r * Math_TAU); - float z = cos(r * Math_TAU); + float x = 0.0; + float z = 1.0; + if (i < radial_steps) { + x = Math::sin(r * Math_TAU); + z = Math::cos(r * Math_TAU); + } float u = ((x + 1.0) * 0.25); float v = 0.5 + ((z + 1.0) * 0.25); @@ -2570,8 +2627,12 @@ void TubeTrailMesh::_create_mesh_array(Array &p_arr) const { float r = i; r /= radial_steps; - float x = sin(r * Math_TAU); - float z = cos(r * Math_TAU); + float x = 0.0; + float z = 1.0; + if (i < radial_steps) { + x = Math::sin(r * Math_TAU); + z = Math::cos(r * Math_TAU); + } float u = 0.5 + ((x + 1.0) * 0.25); float v = 1.0 - ((z + 1.0) * 0.25); diff --git a/scene/resources/SCsub b/scene/resources/SCsub index e9adf769812..5f0d9c892e3 100644 --- a/scene/resources/SCsub +++ b/scene/resources/SCsub @@ -7,7 +7,13 @@ Import("env") thirdparty_obj = [] -thirdparty_sources = "#thirdparty/misc/mikktspace.c" +thirdparty_dir = "#thirdparty/misc/" +thirdparty_sources = [ + "mikktspace.c", + "qoa.c" +] + +thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] env_thirdparty = env.Clone() env_thirdparty.disable_warnings() diff --git a/scene/resources/animation_library.cpp b/scene/resources/animation_library.cpp index 0fba9db429a..8701e85518b 100644 --- a/scene/resources/animation_library.cpp +++ b/scene/resources/animation_library.cpp @@ -35,11 +35,11 @@ #include "scene/scene_string_names.h" bool AnimationLibrary::is_valid_animation_name(const String &p_name) { - return !(p_name.is_empty() || p_name.contains("/") || p_name.contains(":") || p_name.contains(",") || p_name.contains("[")); + return !(p_name.is_empty() || p_name.contains_char('/') || p_name.contains_char(':') || p_name.contains_char(',') || p_name.contains_char('[')); } bool AnimationLibrary::is_valid_library_name(const String &p_name) { - return !(p_name.contains("/") || p_name.contains(":") || p_name.contains(",") || p_name.contains("[")); + return !(p_name.contains_char('/') || p_name.contains_char(':') || p_name.contains_char(',') || p_name.contains_char('[')); } String AnimationLibrary::validate_library_name(const String &p_name) { diff --git a/scene/resources/audio_stream_wav.cpp b/scene/resources/audio_stream_wav.cpp index 502ba9a5744..76739921259 100644 --- a/scene/resources/audio_stream_wav.cpp +++ b/scene/resources/audio_stream_wav.cpp @@ -1144,13 +1144,13 @@ Ref AudioStreamWAV::load_from_buffer(const Vector &p_fi is16 = false; } - Vector pcm_data; + Vector dst_data; AudioStreamWAV::Format dst_format; if (compression == 1) { dst_format = AudioStreamWAV::FORMAT_IMA_ADPCM; if (format_channels == 1) { - _compress_ima_adpcm(data, pcm_data); + _compress_ima_adpcm(data, dst_data); } else { //byte interleave Vector left; @@ -1172,9 +1172,9 @@ Ref AudioStreamWAV::load_from_buffer(const Vector &p_fi _compress_ima_adpcm(right, bright); int dl = bleft.size(); - pcm_data.resize(dl * 2); + dst_data.resize(dl * 2); - uint8_t *w = pcm_data.ptrw(); + uint8_t *w = dst_data.ptrw(); const uint8_t *rl = bleft.ptr(); const uint8_t *rr = bright.ptr(); @@ -1184,16 +1184,24 @@ Ref AudioStreamWAV::load_from_buffer(const Vector &p_fi } } + } else if (compression == 2) { + dst_format = AudioStreamWAV::FORMAT_QOA; + + qoa_desc desc = {}; + desc.samplerate = rate; + desc.samples = frames; + desc.channels = format_channels; + + _compress_qoa(data, dst_data, &desc); } else { dst_format = is16 ? AudioStreamWAV::FORMAT_16_BITS : AudioStreamWAV::FORMAT_8_BITS; - bool enforce16 = is16 || compression == 2; - pcm_data.resize(data.size() * (enforce16 ? 2 : 1)); + dst_data.resize(data.size() * (is16 ? 2 : 1)); { - uint8_t *w = pcm_data.ptrw(); + uint8_t *w = dst_data.ptrw(); int ds = data.size(); for (int i = 0; i < ds; i++) { - if (enforce16) { + if (is16) { int16_t v = CLAMP(data[i] * 32768, -32768, 32767); encode_uint16(v, &w[i * 2]); } else { @@ -1204,26 +1212,6 @@ Ref AudioStreamWAV::load_from_buffer(const Vector &p_fi } } - Vector dst_data; - if (compression == 2) { - dst_format = AudioStreamWAV::FORMAT_QOA; - qoa_desc desc = {}; - uint32_t qoa_len = 0; - - desc.samplerate = rate; - desc.samples = frames; - desc.channels = format_channels; - - void *encoded = qoa_encode((short *)pcm_data.ptr(), &desc, &qoa_len); - if (encoded) { - dst_data.resize(qoa_len); - memcpy(dst_data.ptrw(), encoded, qoa_len); - QOA_FREE(encoded); - } - } else { - dst_data = pcm_data; - } - Ref sample; sample.instantiate(); sample->set_data(dst_data); diff --git a/scene/resources/audio_stream_wav.h b/scene/resources/audio_stream_wav.h index 8a705f0c3c3..054bb6a13c6 100644 --- a/scene/resources/audio_stream_wav.h +++ b/scene/resources/audio_stream_wav.h @@ -33,9 +33,6 @@ #ifndef AUDIO_STREAM_WAV_H #define AUDIO_STREAM_WAV_H -#define QOA_IMPLEMENTATION -#define QOA_NO_STDIO - #include "servers/audio/audio_stream.h" #include "thirdparty/misc/qoa.h" @@ -275,6 +272,34 @@ class AudioStreamWAV : public AudioStream { } } + static void _compress_qoa(const Vector &p_data, Vector &dst_data, qoa_desc *p_desc) { + uint32_t frames_len = (p_desc->samples + QOA_FRAME_LEN - 1) / QOA_FRAME_LEN * (QOA_LMS_LEN * 4 * p_desc->channels + 8); + uint32_t slices_len = (p_desc->samples + QOA_SLICE_LEN - 1) / QOA_SLICE_LEN * 8 * p_desc->channels; + dst_data.resize(8 + frames_len + slices_len); + + for (uint32_t c = 0; c < p_desc->channels; c++) { + memset(p_desc->lms[c].history, 0, sizeof(p_desc->lms[c].history)); + memset(p_desc->lms[c].weights, 0, sizeof(p_desc->lms[c].weights)); + p_desc->lms[c].weights[2] = -(1 << 13); + p_desc->lms[c].weights[3] = (1 << 14); + } + + LocalVector data16; + data16.resize(QOA_FRAME_LEN * p_desc->channels); + + uint8_t *dst_ptr = dst_data.ptrw(); + dst_ptr += qoa_encode_header(p_desc, dst_data.ptrw()); + + uint32_t frame_len = QOA_FRAME_LEN; + for (uint32_t s = 0; s < p_desc->samples; s += frame_len) { + frame_len = MIN(frame_len, p_desc->samples - s); + for (uint32_t i = 0; i < frame_len * p_desc->channels; i++) { + data16[i] = CLAMP(p_data[s * p_desc->channels + i] * 32767.0, -32768, 32767); + } + dst_ptr += qoa_encode_frame(data16.ptr(), p_desc, frame_len, dst_ptr); + } + } + AudioStreamWAV(); ~AudioStreamWAV(); }; diff --git a/core/core_string_names.cpp b/scene/resources/color_palette.cpp similarity index 57% rename from core/core_string_names.cpp rename to scene/resources/color_palette.cpp index 081e3f57d7d..412a945b599 100644 --- a/core/core_string_names.cpp +++ b/scene/resources/color_palette.cpp @@ -1,5 +1,5 @@ /**************************************************************************/ -/* core_string_names.cpp */ +/* color_palette.cpp */ /**************************************************************************/ /* This file is part of: */ /* REDOT ENGINE */ @@ -30,48 +30,19 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -#include "core_string_names.h" +#include "color_palette.h" -CoreStringNames *CoreStringNames::singleton = nullptr; +void ColorPalette::set_colors(const PackedColorArray &p_colors) { + colors = p_colors; +} -CoreStringNames::CoreStringNames() : - free_(StaticCString::create("free")), - changed(StaticCString::create("changed")), - script(StaticCString::create("script")), - script_changed(StaticCString::create("script_changed")), - _iter_init(StaticCString::create("_iter_init")), - _iter_next(StaticCString::create("_iter_next")), - _iter_get(StaticCString::create("_iter_get")), - get_rid(StaticCString::create("get_rid")), - _to_string(StaticCString::create("_to_string")), - _custom_features(StaticCString::create("_custom_features")), +PackedColorArray ColorPalette::get_colors() const { + return colors; +} - x(StaticCString::create("x")), - y(StaticCString::create("y")), - z(StaticCString::create("z")), - w(StaticCString::create("w")), - r(StaticCString::create("r")), - g(StaticCString::create("g")), - b(StaticCString::create("b")), - a(StaticCString::create("a")), - position(StaticCString::create("position")), - size(StaticCString::create("size")), - end(StaticCString::create("end")), - basis(StaticCString::create("basis")), - origin(StaticCString::create("origin")), - normal(StaticCString::create("normal")), - d(StaticCString::create("d")), - h(StaticCString::create("h")), - s(StaticCString::create("s")), - v(StaticCString::create("v")), - r8(StaticCString::create("r8")), - g8(StaticCString::create("g8")), - b8(StaticCString::create("b8")), - a8(StaticCString::create("a8")), +void ColorPalette::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_colors", "colors"), &ColorPalette::set_colors); + ClassDB::bind_method(D_METHOD("get_colors"), &ColorPalette::get_colors); - call(StaticCString::create("call")), - call_deferred(StaticCString::create("call_deferred")), - bind(StaticCString::create("bind")), - notification(StaticCString::create("notification")), - property_list_changed(StaticCString::create("property_list_changed")) { + ADD_PROPERTY(PropertyInfo(Variant::PACKED_COLOR_ARRAY, "colors"), "set_colors", "get_colors"); } diff --git a/editor/editor_string_names.cpp b/scene/resources/color_palette.h similarity index 84% rename from editor/editor_string_names.cpp rename to scene/resources/color_palette.h index d250e80b096..9df6e06ffee 100644 --- a/editor/editor_string_names.cpp +++ b/scene/resources/color_palette.h @@ -1,5 +1,5 @@ /**************************************************************************/ -/* editor_string_names.cpp */ +/* color_palette.h */ /**************************************************************************/ /* This file is part of: */ /* REDOT ENGINE */ @@ -30,13 +30,23 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -#include "editor_string_names.h" +#ifndef COLOR_PALETTE_H +#define COLOR_PALETTE_H -EditorStringNames *EditorStringNames::singleton = nullptr; +#include "core/io/resource.h" -EditorStringNames::EditorStringNames() { - Editor = StaticCString::create("Editor"); - EditorFonts = StaticCString::create("EditorFonts"); - EditorIcons = StaticCString::create("EditorIcons"); - EditorStyles = StaticCString::create("EditorStyles"); -} +class ColorPalette : public Resource { + GDCLASS(ColorPalette, Resource) + +private: + PackedColorArray colors; + +protected: + static void _bind_methods(); + +public: + void set_colors(const PackedColorArray &p_colors); + PackedColorArray get_colors() const; +}; + +#endif // COLOR_PALETTE_H diff --git a/scene/resources/theme.cpp b/scene/resources/theme.cpp index 7f83cdd3c9a..2258986f441 100644 --- a/scene/resources/theme.cpp +++ b/scene/resources/theme.cpp @@ -39,7 +39,7 @@ bool Theme::_set(const StringName &p_name, const Variant &p_value) { String sname = p_name; - if (sname.contains("/")) { + if (sname.contains_char('/')) { String type = sname.get_slicec('/', 1); String theme_type = sname.get_slicec('/', 0); String prop_name = sname.get_slicec('/', 2); @@ -71,7 +71,7 @@ bool Theme::_set(const StringName &p_name, const Variant &p_value) { bool Theme::_get(const StringName &p_name, Variant &r_ret) const { String sname = p_name; - if (sname.contains("/")) { + if (sname.contains_char('/')) { String type = sname.get_slicec('/', 1); String theme_type = sname.get_slicec('/', 0); String prop_name = sname.get_slicec('/', 2); diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp index 7d81cf30b27..9173736d769 100644 --- a/scene/resources/visual_shader.cpp +++ b/scene/resources/visual_shader.cpp @@ -4149,7 +4149,7 @@ String VisualShaderNodeOutput::generate_code(Shader::Mode p_mode, VisualShader:: if (ports[idx].mode == shader_mode && ports[idx].shader_type == shader_type) { if (!p_input_vars[count].is_empty()) { String s = ports[idx].string; - if (s.contains(":")) { + if (s.contains_char(':')) { shader_code += " " + s.get_slicec(':', 0) + " = " + p_input_vars[count] + "." + s.get_slicec(':', 1) + ";\n"; } else { shader_code += " " + s + " = " + p_input_vars[count] + ";\n"; diff --git a/scene/scene_string_names.cpp b/scene/scene_string_names.cpp deleted file mode 100644 index 1ad6ff93466..00000000000 --- a/scene/scene_string_names.cpp +++ /dev/null @@ -1,150 +0,0 @@ -/**************************************************************************/ -/* scene_string_names.cpp */ -/**************************************************************************/ -/* This file is part of: */ -/* REDOT ENGINE */ -/* https://redotengine.org */ -/**************************************************************************/ -/* Copyright (c) 2024-present Redot Engine contributors */ -/* (see REDOT_AUTHORS.md) */ -/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ -/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/**************************************************************************/ - -#include "scene_string_names.h" - -SceneStringNames *SceneStringNames::singleton = nullptr; - -SceneStringNames::SceneStringNames() { - resized = StaticCString::create("resized"); - draw = StaticCString::create("draw"); - hidden = StaticCString::create("hidden"); - visibility_changed = StaticCString::create("visibility_changed"); - input_event = StaticCString::create("input_event"); - shader = StaticCString::create("shader"); - tree_entered = StaticCString::create("tree_entered"); - tree_exiting = StaticCString::create("tree_exiting"); - tree_exited = StaticCString::create("tree_exited"); - ready = StaticCString::create("ready"); - item_rect_changed = StaticCString::create("item_rect_changed"); - size_flags_changed = StaticCString::create("size_flags_changed"); - minimum_size_changed = StaticCString::create("minimum_size_changed"); - sleeping_state_changed = StaticCString::create("sleeping_state_changed"); - - finished = StaticCString::create("finished"); - animation_finished = StaticCString::create("animation_finished"); - animation_changed = StaticCString::create("animation_changed"); - animation_started = StaticCString::create("animation_started"); - RESET = StaticCString::create("RESET"); - - pose_updated = StaticCString::create("pose_updated"); - skeleton_updated = StaticCString::create("skeleton_updated"); - bone_enabled_changed = StaticCString::create("bone_enabled_changed"); - show_rest_only_changed = StaticCString::create("show_rest_only_changed"); - - mouse_entered = StaticCString::create("mouse_entered"); - mouse_exited = StaticCString::create("mouse_exited"); - mouse_shape_entered = StaticCString::create("mouse_shape_entered"); - mouse_shape_exited = StaticCString::create("mouse_shape_exited"); - - focus_entered = StaticCString::create("focus_entered"); - focus_exited = StaticCString::create("focus_exited"); - - pre_sort_children = StaticCString::create("pre_sort_children"); - sort_children = StaticCString::create("sort_children"); - - body_shape_entered = StaticCString::create("body_shape_entered"); - body_entered = StaticCString::create("body_entered"); - body_shape_exited = StaticCString::create("body_shape_exited"); - body_exited = StaticCString::create("body_exited"); - - area_shape_entered = StaticCString::create("area_shape_entered"); - area_shape_exited = StaticCString::create("area_shape_exited"); - - update = StaticCString::create("update"); - updated = StaticCString::create("updated"); - - _ready = StaticCString::create("_ready"); - - screen_entered = StaticCString::create("screen_entered"); - screen_exited = StaticCString::create("screen_exited"); - - gui_input = StaticCString::create("gui_input"); - - _spatial_editor_group = StaticCString::create("_spatial_editor_group"); - _request_gizmo = StaticCString::create("_request_gizmo"); - - offset = StaticCString::create("offset"); - rotation_mode = StaticCString::create("rotation_mode"); - rotate = StaticCString::create("rotate"); - h_offset = StaticCString::create("h_offset"); - v_offset = StaticCString::create("v_offset"); - - area_entered = StaticCString::create("area_entered"); - area_exited = StaticCString::create("area_exited"); - - line_separation = StaticCString::create("line_separation"); - font = StaticCString::create("font"); - font_size = StaticCString::create("font_size"); - font_color = StaticCString::create("font_color"); - - frame_changed = StaticCString::create("frame_changed"); - texture_changed = StaticCString::create("texture_changed"); - - autoplay = StaticCString::create("autoplay"); - blend_times = StaticCString::create("blend_times"); - speed = StaticCString::create("speed"); - - node_configuration_warning_changed = StaticCString::create("node_configuration_warning_changed"); - - output = StaticCString::create("output"); - - path_pp = NodePath(".."); - - // Audio bus name. - Master = StaticCString::create("Master"); - - default_ = StaticCString::create("default"); - - window_input = StaticCString::create("window_input"); - - theme_changed = StaticCString::create("theme_changed"); - - shader_overrides_group = StaticCString::create("_shader_overrides_group_"); - shader_overrides_group_active = StaticCString::create("_shader_overrides_group_active_"); - - _custom_type_script = StaticCString::create("_custom_type_script"); - - pressed = StaticCString::create("pressed"); - id_pressed = StaticCString::create("id_pressed"); - toggled = StaticCString::create("toggled"); - - panel = StaticCString::create("panel"); - - item_selected = StaticCString::create("item_selected"); - - confirmed = StaticCString::create("confirmed"); - - text_changed = StaticCString::create("text_changed"); - text_submitted = StaticCString::create("text_submitted"); - value_changed = StaticCString::create("value_changed"); -} diff --git a/scene/scene_string_names.h b/scene/scene_string_names.h index 7bd28c30b46..6f4a372b270 100644 --- a/scene/scene_string_names.h +++ b/scene/scene_string_names.h @@ -37,129 +37,121 @@ #include "core/string/string_name.h" class SceneStringNames { - friend void register_scene_types(); - friend void unregister_scene_types(); - - static SceneStringNames *singleton; + inline static SceneStringNames *singleton = nullptr; +public: static void create() { singleton = memnew(SceneStringNames); } static void free() { memdelete(singleton); singleton = nullptr; } - SceneStringNames(); - -public: _FORCE_INLINE_ static SceneStringNames *get_singleton() { return singleton; } - StringName resized; - StringName draw; - StringName hidden; - StringName visibility_changed; - StringName input_event; - StringName gui_input; - StringName item_rect_changed; - StringName shader; - StringName tree_entered; - StringName tree_exiting; - StringName tree_exited; - StringName ready; - StringName size_flags_changed; - StringName minimum_size_changed; - StringName sleeping_state_changed; - StringName update; - StringName updated; - - StringName line_separation; - StringName font; - StringName font_size; - StringName font_color; - - StringName mouse_entered; - StringName mouse_exited; - StringName mouse_shape_entered; - StringName mouse_shape_exited; - StringName focus_entered; - StringName focus_exited; - - StringName pre_sort_children; - StringName sort_children; - - StringName finished; - StringName animation_finished; - StringName animation_changed; - StringName animation_started; - StringName RESET; - - StringName pose_updated; - StringName skeleton_updated; - StringName bone_enabled_changed; - StringName show_rest_only_changed; - - StringName body_shape_entered; - StringName body_entered; - StringName body_shape_exited; - StringName body_exited; - - StringName area_shape_entered; - StringName area_shape_exited; - - StringName _ready; - - StringName screen_entered; - StringName screen_exited; - - StringName _spatial_editor_group; - StringName _request_gizmo; - - StringName offset; - StringName rotation_mode; - StringName rotate; - StringName v_offset; - StringName h_offset; - - StringName area_entered; - StringName area_exited; - - StringName frame_changed; - StringName texture_changed; - - StringName autoplay; - StringName blend_times; - StringName speed; - - NodePath path_pp; - - StringName default_; // "default", conflict with C++ keyword. - - StringName node_configuration_warning_changed; - - StringName output; - - StringName Master; - - StringName window_input; - - StringName theme_changed; - StringName shader_overrides_group; - StringName shader_overrides_group_active; - - StringName _custom_type_script; - - StringName pressed; - StringName id_pressed; - StringName toggled; - - StringName panel; - - StringName item_selected; - - StringName confirmed; - - StringName text_changed; - StringName text_submitted; - StringName value_changed; + const StringName resized = StaticCString::create("resized"); + const StringName draw = StaticCString::create("draw"); + const StringName hidden = StaticCString::create("hidden"); + const StringName visibility_changed = StaticCString::create("visibility_changed"); + + const StringName input_event = StaticCString::create("input_event"); + const StringName gui_input = StaticCString::create("gui_input"); + const StringName window_input = StaticCString::create("window_input"); + + const StringName tree_entered = StaticCString::create("tree_entered"); + const StringName tree_exiting = StaticCString::create("tree_exiting"); + const StringName tree_exited = StaticCString::create("tree_exited"); + const StringName ready = StaticCString::create("ready"); + const StringName _ready = StaticCString::create("_ready"); + + const StringName item_rect_changed = StaticCString::create("item_rect_changed"); + const StringName size_flags_changed = StaticCString::create("size_flags_changed"); + const StringName minimum_size_changed = StaticCString::create("minimum_size_changed"); + const StringName sleeping_state_changed = StaticCString::create("sleeping_state_changed"); + const StringName node_configuration_warning_changed = StaticCString::create("node_configuration_warning_changed"); + const StringName update = StaticCString::create("update"); + const StringName updated = StaticCString::create("updated"); + + const StringName line_separation = StaticCString::create("line_separation"); + const StringName font = StaticCString::create("font"); + const StringName font_size = StaticCString::create("font_size"); + const StringName font_color = StaticCString::create("font_color"); + + const StringName mouse_entered = StaticCString::create("mouse_entered"); + const StringName mouse_exited = StaticCString::create("mouse_exited"); + const StringName mouse_shape_entered = StaticCString::create("mouse_shape_entered"); + const StringName mouse_shape_exited = StaticCString::create("mouse_shape_exited"); + const StringName focus_entered = StaticCString::create("focus_entered"); + const StringName focus_exited = StaticCString::create("focus_exited"); + + const StringName pre_sort_children = StaticCString::create("pre_sort_children"); + const StringName sort_children = StaticCString::create("sort_children"); + + const StringName finished = StaticCString::create("finished"); + const StringName animation_finished = StaticCString::create("animation_finished"); + const StringName animation_changed = StaticCString::create("animation_changed"); + const StringName animation_started = StaticCString::create("animation_started"); + const StringName RESET = StaticCString::create("RESET"); + + const StringName pose_updated = StaticCString::create("pose_updated"); + const StringName skeleton_updated = StaticCString::create("skeleton_updated"); + const StringName bone_enabled_changed = StaticCString::create("bone_enabled_changed"); + const StringName show_rest_only_changed = StaticCString::create("show_rest_only_changed"); + + const StringName body_shape_entered = StaticCString::create("body_shape_entered"); + const StringName body_entered = StaticCString::create("body_entered"); + const StringName body_shape_exited = StaticCString::create("body_shape_exited"); + const StringName body_exited = StaticCString::create("body_exited"); + + const StringName area_shape_entered = StaticCString::create("area_shape_entered"); + const StringName area_shape_exited = StaticCString::create("area_shape_exited"); + + const StringName screen_entered = StaticCString::create("screen_entered"); + const StringName screen_exited = StaticCString::create("screen_exited"); + + const StringName _spatial_editor_group = StaticCString::create("_spatial_editor_group"); + const StringName _request_gizmo = StaticCString::create("_request_gizmo"); + + const StringName offset = StaticCString::create("offset"); + const StringName rotation_mode = StaticCString::create("rotation_mode"); + const StringName rotate = StaticCString::create("rotate"); + const StringName h_offset = StaticCString::create("h_offset"); + const StringName v_offset = StaticCString::create("v_offset"); + + const StringName area_entered = StaticCString::create("area_entered"); + const StringName area_exited = StaticCString::create("area_exited"); + + const StringName frame_changed = StaticCString::create("frame_changed"); + const StringName texture_changed = StaticCString::create("texture_changed"); + + const StringName autoplay = StaticCString::create("autoplay"); + const StringName blend_times = StaticCString::create("blend_times"); + const StringName speed = StaticCString::create("speed"); + + const NodePath path_pp = NodePath(".."); + + const StringName default_ = StaticCString::create("default"); // default would conflict with C++ keyword. + const StringName output = StaticCString::create("output"); + + const StringName Master = StaticCString::create("Master"); // Audio bus name. + + const StringName theme_changed = StaticCString::create("theme_changed"); + const StringName shader = StaticCString::create("shader"); + const StringName shader_overrides_group = StaticCString::create("_shader_overrides_group_"); + const StringName shader_overrides_group_active = StaticCString::create("_shader_overrides_group_active_"); + + const StringName _custom_type_script = StaticCString::create("_custom_type_script"); + + const StringName pressed = StaticCString::create("pressed"); + const StringName id_pressed = StaticCString::create("id_pressed"); + const StringName toggled = StaticCString::create("toggled"); + + const StringName panel = StaticCString::create("panel"); + const StringName item_selected = StaticCString::create("item_selected"); + const StringName confirmed = StaticCString::create("confirmed"); + + const StringName text_changed = StaticCString::create("text_changed"); + const StringName text_submitted = StaticCString::create("text_submitted"); + const StringName value_changed = StaticCString::create("value_changed"); }; #define SceneStringName(m_name) SceneStringNames::get_singleton()->m_name diff --git a/scene/theme/default_theme.cpp b/scene/theme/default_theme.cpp index a006c18d7ec..43b0eb62585 100644 --- a/scene/theme/default_theme.cpp +++ b/scene/theme/default_theme.cpp @@ -695,6 +695,9 @@ void fill_default_theme(Ref &theme, const Ref &default_font, const // File Dialog + theme->set_icon("load", "FileDialog", icons["load"]); + theme->set_icon("save", "FileDialog", icons["save"]); + theme->set_icon("clear", "FileDialog", icons["clear"]); theme->set_icon("parent_folder", "FileDialog", icons["folder_up"]); theme->set_icon("back_folder", "FileDialog", icons["arrow_left"]); theme->set_icon("forward_folder", "FileDialog", icons["arrow_right"]); @@ -1036,6 +1039,7 @@ void fill_default_theme(Ref &theme, const Ref &default_font, const theme->set_constant("label_width", "ColorPicker", Math::round(10 * scale)); theme->set_constant("center_slider_grabbers", "ColorPicker", 1); + theme->set_icon("menu_option", "ColorPicker", icons["tabs_menu_hl"]); theme->set_icon("folded_arrow", "ColorPicker", icons["arrow_right"]); theme->set_icon("expanded_arrow", "ColorPicker", icons["arrow_down"]); theme->set_icon("screen_picker", "ColorPicker", icons["color_picker_pipette"]); diff --git a/scene/theme/icons/clear.svg b/scene/theme/icons/clear.svg new file mode 100644 index 00000000000..d809d62e467 --- /dev/null +++ b/scene/theme/icons/clear.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/scene/theme/icons/load.svg b/scene/theme/icons/load.svg new file mode 100644 index 00000000000..6f738a8a7e8 --- /dev/null +++ b/scene/theme/icons/load.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/scene/theme/icons/save.svg b/scene/theme/icons/save.svg new file mode 100644 index 00000000000..ab8ffff89c7 --- /dev/null +++ b/scene/theme/icons/save.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/servers/audio/effects/audio_effect_record.cpp b/servers/audio/effects/audio_effect_record.cpp index a9328ed7018..d01b8d615aa 100644 --- a/servers/audio/effects/audio_effect_record.cpp +++ b/servers/audio/effects/audio_effect_record.cpp @@ -252,6 +252,12 @@ Ref AudioEffectRecord::get_recording() const { w[i * 2 + 0] = rl[i]; w[i * 2 + 1] = rr[i]; } + } else if (dst_format == AudioStreamWAV::FORMAT_QOA) { + qoa_desc desc = {}; + desc.samples = current_instance->recording_data.size() / 2; + desc.samplerate = AudioServer::get_singleton()->get_mix_rate(); + desc.channels = 2; + AudioStreamWAV::_compress_qoa(current_instance->recording_data, dst_data, &desc); } else { ERR_PRINT("Format not implemented."); } diff --git a/servers/register_server_types.cpp b/servers/register_server_types.cpp index 0b9663d3fe0..403b5ddeb44 100644 --- a/servers/register_server_types.cpp +++ b/servers/register_server_types.cpp @@ -72,6 +72,7 @@ #include "rendering/renderer_rd/uniform_set_cache_rd.h" #include "rendering/rendering_device.h" #include "rendering/rendering_device_binds.h" +#include "rendering/shader_include_db.h" #include "rendering/storage/render_data.h" #include "rendering/storage/render_scene_buffers.h" #include "rendering/storage/render_scene_data.h" @@ -212,6 +213,7 @@ void register_server_types() { } GDREGISTER_ABSTRACT_CLASS(RenderingDevice); + GDREGISTER_CLASS(ShaderIncludeDB); GDREGISTER_CLASS(RDTextureFormat); GDREGISTER_CLASS(RDTextureView); GDREGISTER_CLASS(RDAttachmentFormat); diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp index d9680078f4d..0156982690d 100644 --- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp +++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp @@ -4644,7 +4644,7 @@ void RenderForwardClustered::mesh_generate_pipelines(RID p_mesh, bool p_backgrou void *mesh_surface = mesh_storage->mesh_get_surface(p_mesh, i); void *mesh_surface_shadow = mesh_surface; SceneShaderForwardClustered::MaterialData *material = static_cast(material_storage->material_get_data(materials[i], RendererRD::MaterialStorage::SHADER_TYPE_3D)); - if (material == nullptr) { + if (material == nullptr || !material->shader_data->is_valid()) { continue; } diff --git a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp index 44f4013ded2..1662142aa4a 100644 --- a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp +++ b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp @@ -145,7 +145,14 @@ void SceneShaderForwardClustered::ShaderData::set_code(const String &p_code) { MutexLock lock(SceneShaderForwardClustered::singleton_mutex); Error err = SceneShaderForwardClustered::singleton->compiler.compile(RS::SHADER_SPATIAL, code, &actions, path, gen_code); - ERR_FAIL_COND_MSG(err != OK, "Shader compilation failed."); + + if (err != OK) { + if (version.is_valid()) { + SceneShaderForwardClustered::singleton->shader.version_free(version); + version = RID(); + } + ERR_FAIL_MSG("Shader compilation failed."); + } if (version.is_null()) { version = SceneShaderForwardClustered::singleton->shader.version_create(); diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp index 28ff7c586d3..c20fabf0118 100644 --- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp +++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp @@ -296,7 +296,7 @@ void RenderForwardMobile::mesh_generate_pipelines(RID p_mesh, bool p_background_ void *mesh_surface = mesh_storage->mesh_get_surface(p_mesh, i); void *mesh_surface_shadow = mesh_surface; SceneShaderForwardMobile::MaterialData *material = static_cast(material_storage->material_get_data(materials[i], RendererRD::MaterialStorage::SHADER_TYPE_3D)); - if (material == nullptr) { + if (material == nullptr || !material->shader_data->is_valid()) { continue; } @@ -1007,13 +1007,20 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color { base_specialization.use_directional_soft_shadows = p_render_data->directional_light_count > 0 ? p_render_data->directional_light_soft_shadows : false; base_specialization.directional_lights = p_render_data->directional_light_count; + base_specialization.directional_light_blend_splits = light_storage->get_directional_light_blend_splits(p_render_data->directional_light_count); if (!is_environment(p_render_data->environment) || !environment_get_fog_enabled(p_render_data->environment)) { base_specialization.disable_fog = true; - } - - if (p_render_data->environment.is_valid() && environment_get_fog_mode(p_render_data->environment) == RS::EnvironmentFogMode::ENV_FOG_MODE_DEPTH) { - base_specialization.use_depth_fog = true; + base_specialization.use_fog_aerial_perspective = false; + base_specialization.use_fog_sun_scatter = false; + base_specialization.use_fog_height_density = false; + base_specialization.use_depth_fog = false; + } else { + base_specialization.disable_fog = false; + base_specialization.use_fog_aerial_perspective = environment_get_fog_aerial_perspective(p_render_data->environment) > 0.0; + base_specialization.use_fog_sun_scatter = environment_get_fog_sun_scatter(p_render_data->environment) > 0.001; + base_specialization.use_fog_height_density = abs(environment_get_fog_height_density(p_render_data->environment)) >= 0.0001; + base_specialization.use_depth_fog = p_render_data->environment.is_valid() && environment_get_fog_mode(p_render_data->environment) == RS::EnvironmentFogMode::ENV_FOG_MODE_DEPTH; } base_specialization.scene_use_ambient_cubemap = use_ambient_cubemap; diff --git a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp index e5d208d3a59..8f2f64b7b2f 100644 --- a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp +++ b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp @@ -145,7 +145,14 @@ void SceneShaderForwardMobile::ShaderData::set_code(const String &p_code) { MutexLock lock(SceneShaderForwardMobile::singleton_mutex); Error err = SceneShaderForwardMobile::singleton->compiler.compile(RS::SHADER_SPATIAL, code, &actions, path, gen_code); - ERR_FAIL_COND_MSG(err != OK, "Shader compilation failed."); + + if (err != OK) { + if (version.is_valid()) { + SceneShaderForwardMobile::singleton->shader.version_free(version); + version = RID(); + } + ERR_FAIL_MSG("Shader compilation failed."); + } if (version.is_null()) { version = SceneShaderForwardMobile::singleton->shader.version_create(); @@ -240,6 +247,7 @@ void SceneShaderForwardMobile::ShaderData::_create_pipeline(PipelineKey p_pipeli "SPEC PACKED #0:", p_pipeline_key.shader_specialization.packed_0, "SPEC PACKED #1:", p_pipeline_key.shader_specialization.packed_1, "SPEC PACKED #2:", p_pipeline_key.shader_specialization.packed_2, + "SPEC PACKED #3:", p_pipeline_key.shader_specialization.packed_3, "RENDER PASS:", p_pipeline_key.render_pass, "WIREFRAME:", p_pipeline_key.wireframe); #endif @@ -330,7 +338,12 @@ void SceneShaderForwardMobile::ShaderData::_create_pipeline(PipelineKey p_pipeli specialization_constants.push_back(sc); sc.constant_id = 2; - sc.float_value = p_pipeline_key.shader_specialization.packed_2; + sc.int_value = p_pipeline_key.shader_specialization.packed_2; + sc.type = RD::PIPELINE_SPECIALIZATION_CONSTANT_TYPE_INT; + specialization_constants.push_back(sc); + + sc.constant_id = 3; + sc.float_value = p_pipeline_key.shader_specialization.packed_3; sc.type = RD::PIPELINE_SPECIALIZATION_CONSTANT_TYPE_FLOAT; specialization_constants.push_back(sc); diff --git a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.h b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.h index ea9ee072261..d93a4c15b77 100644 --- a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.h +++ b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.h @@ -69,6 +69,9 @@ class SceneShaderForwardMobile { uint32_t projector_use_mipmaps : 1; uint32_t disable_fog : 1; uint32_t use_depth_fog : 1; + uint32_t use_fog_aerial_perspective : 1; + uint32_t use_fog_sun_scatter : 1; + uint32_t use_fog_height_density : 1; uint32_t use_lightmap_bicubic_filter : 1; uint32_t multimesh : 1; uint32_t multimesh_format_2d : 1; @@ -77,7 +80,7 @@ class SceneShaderForwardMobile { uint32_t scene_use_ambient_cubemap : 1; uint32_t scene_use_reflection_cubemap : 1; uint32_t scene_roughness_limiter_enabled : 1; - uint32_t padding : 5; + uint32_t padding_0 : 2; uint32_t soft_shadow_samples : 6; uint32_t penumbra_shadow_samples : 6; }; @@ -99,9 +102,18 @@ class SceneShaderForwardMobile { uint32_t packed_1; }; + union { + struct { + uint32_t directional_light_blend_splits : 8; + uint32_t padding_1 : 24; + }; + + uint32_t packed_2; + }; + union { float luminance_multiplier; - float packed_2; + float packed_3; }; }; @@ -113,6 +125,10 @@ class SceneShaderForwardMobile { uint32_t packed_0; }; + + uint32_t padding_1; + uint32_t padding_2; + uint32_t padding_3; }; struct ShaderData : public RendererRD::MaterialStorage::ShaderData { diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp index 5bb2d9f0157..445f42ebceb 100644 --- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp @@ -1519,7 +1519,13 @@ void RendererCanvasRenderRD::CanvasShaderData::set_code(const String &p_code) { MutexLock lock(canvas_singleton->shader.mutex); Error err = canvas_singleton->shader.compiler.compile(RS::SHADER_CANVAS_ITEM, code, &actions, path, gen_code); - ERR_FAIL_COND_MSG(err != OK, "Shader compilation failed."); + if (err != OK) { + if (version.is_valid()) { + canvas_singleton->shader.canvas_shader.version_free(version); + version = RID(); + } + ERR_FAIL_MSG("Shader compilation failed."); + } uses_screen_texture_mipmaps = gen_code.uses_screen_texture_mipmaps; uses_screen_texture = gen_code.uses_screen_texture; @@ -1595,9 +1601,13 @@ uint64_t RendererCanvasRenderRD::CanvasShaderData::get_vertex_input_mask(ShaderV } bool RendererCanvasRenderRD::CanvasShaderData::is_valid() const { - RendererCanvasRenderRD *canvas_singleton = static_cast(RendererCanvasRender::singleton); - MutexLock lock(canvas_singleton->shader.mutex); - return canvas_singleton->shader.canvas_shader.version_is_valid(version); + if (version.is_valid()) { + RendererCanvasRenderRD *canvas_singleton = static_cast(RendererCanvasRender::singleton); + MutexLock lock(canvas_singleton->shader.mutex); + return canvas_singleton->shader.canvas_shader.version_is_valid(version); + } else { + return false; + } } RendererCanvasRenderRD::CanvasShaderData::CanvasShaderData() { @@ -2913,11 +2923,12 @@ void RendererCanvasRenderRD::_render_batch(RD::DrawListID p_draw_list, CanvasSha const RID *uniform_set = rid_set_to_uniform_set.getptr(key); if (uniform_set == nullptr) { - state.batch_texture_uniforms.write[0] = RD::Uniform(RD::UNIFORM_TYPE_TEXTURE, 0, p_batch->tex_info->diffuse); - state.batch_texture_uniforms.write[1] = RD::Uniform(RD::UNIFORM_TYPE_TEXTURE, 1, p_batch->tex_info->normal); - state.batch_texture_uniforms.write[2] = RD::Uniform(RD::UNIFORM_TYPE_TEXTURE, 2, p_batch->tex_info->specular); - state.batch_texture_uniforms.write[3] = RD::Uniform(RD::UNIFORM_TYPE_SAMPLER, 3, p_batch->tex_info->sampler); - state.batch_texture_uniforms.write[4] = RD::Uniform(RD::UNIFORM_TYPE_STORAGE_BUFFER, 4, state.canvas_instance_data_buffers[state.current_data_buffer_index].instance_buffers[p_batch->instance_buffer_index]); + RD::Uniform *uniform_ptrw = state.batch_texture_uniforms.ptrw(); + uniform_ptrw[0] = RD::Uniform(RD::UNIFORM_TYPE_TEXTURE, 0, p_batch->tex_info->diffuse); + uniform_ptrw[1] = RD::Uniform(RD::UNIFORM_TYPE_TEXTURE, 1, p_batch->tex_info->normal); + uniform_ptrw[2] = RD::Uniform(RD::UNIFORM_TYPE_TEXTURE, 2, p_batch->tex_info->specular); + uniform_ptrw[3] = RD::Uniform(RD::UNIFORM_TYPE_SAMPLER, 3, p_batch->tex_info->sampler); + uniform_ptrw[4] = RD::Uniform(RD::UNIFORM_TYPE_STORAGE_BUFFER, 4, state.canvas_instance_data_buffers[state.current_data_buffer_index].instance_buffers[p_batch->instance_buffer_index]); RID rid = RD::get_singleton()->uniform_set_create(state.batch_texture_uniforms, shader.default_version_rd_shader, BATCH_UNIFORM_SET); ERR_FAIL_COND_MSG(rid.is_null(), "Failed to create uniform set for batch."); diff --git a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp index 36b43d94e27..e4e34101e8c 100644 --- a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp @@ -37,9 +37,13 @@ #include "core/os/os.h" #include "renderer_compositor_rd.h" #include "servers/rendering/renderer_rd/environment/fog.h" +#include "servers/rendering/renderer_rd/shaders/decal_data_inc.glsl.gen.h" +#include "servers/rendering/renderer_rd/shaders/light_data_inc.glsl.gen.h" +#include "servers/rendering/renderer_rd/shaders/scene_data_inc.glsl.gen.h" #include "servers/rendering/renderer_rd/storage_rd/material_storage.h" #include "servers/rendering/renderer_rd/storage_rd/texture_storage.h" #include "servers/rendering/rendering_server_default.h" +#include "servers/rendering/shader_include_db.h" #include "servers/rendering/storage/camera_attributes_storage.h" void get_vogel_disk(float *r_kernel, int p_sample_count) { @@ -1454,6 +1458,13 @@ void RendererSceneRenderRD::init() { /* Forward ID */ forward_id_storage = create_forward_id_storage(); + /* Register the include files we make available by default to our users */ + { + ShaderIncludeDB::register_built_in_include_file("godot/decal_data_inc.glsl", decal_data_inc_shader_glsl); + ShaderIncludeDB::register_built_in_include_file("godot/light_data_inc.glsl", light_data_inc_shader_glsl); + ShaderIncludeDB::register_built_in_include_file("godot/scene_data_inc.glsl", scene_data_inc_shader_glsl); + } + /* SKY SHADER */ sky.init(); diff --git a/servers/rendering/renderer_rd/shader_rd.cpp b/servers/rendering/renderer_rd/shader_rd.cpp index 27310fee4c5..fd8f0afabc4 100644 --- a/servers/rendering/renderer_rd/shader_rd.cpp +++ b/servers/rendering/renderer_rd/shader_rd.cpp @@ -39,6 +39,7 @@ #include "core/version.h" #include "renderer_compositor_rd.h" #include "servers/rendering/rendering_device.h" +#include "servers/rendering/shader_include_db.h" #include "thirdparty/misc/smolv.h" #define ENABLE_SHADER_CACHE 1 @@ -48,7 +49,8 @@ void ShaderRD::_add_stage(const char *p_code, StageType p_stage_type) { String text; - for (int i = 0; i < lines.size(); i++) { + int line_count = lines.size(); + for (int i = 0; i < line_count; i++) { const String &l = lines[i]; bool push_chunk = false; @@ -80,6 +82,35 @@ void ShaderRD::_add_stage(const char *p_code, StageType p_stage_type) { chunk.type = StageTemplate::Chunk::TYPE_CODE; push_chunk = true; chunk.code = l.replace_first("#CODE", String()).replace(":", "").strip_edges().to_upper(); + } else if (l.begins_with("#include ")) { + String include_file = l.replace("#include ", "").strip_edges(); + if (include_file[0] == '"') { + int end_pos = include_file.find_char('"', 1); + if (end_pos >= 0) { + include_file = include_file.substr(1, end_pos - 1); + + String include_code = ShaderIncludeDB::get_built_in_include_file(include_file); + if (!include_code.is_empty()) { + // Add these lines into our parse list so we parse them as well. + Vector include_lines = include_code.split("\n"); + + for (int j = include_lines.size() - 1; j >= 0; j--) { + lines.insert(i + 1, include_lines[j]); + } + + line_count = lines.size(); + } else { + // Add it in as is. + text += l + "\n"; + } + } else { + // Add it in as is. + text += l + "\n"; + } + } else { + // Add it in as is. + text += l + "\n"; + } } else { text += l + "\n"; } diff --git a/servers/rendering/renderer_rd/shaders/SCsub b/servers/rendering/renderer_rd/shaders/SCsub index e102b839b59..f1b6710383d 100644 --- a/servers/rendering/renderer_rd/shaders/SCsub +++ b/servers/rendering/renderer_rd/shaders/SCsub @@ -4,16 +4,20 @@ from misc.utility.scons_hints import * Import("env") if "RD_GLSL" in env["BUILDERS"]: - # find all include files + # find just the include files gl_include_files = [str(f) for f in Glob("*_inc.glsl")] - # find all shader code(all glsl files excluding our include files) + # find all shader code (all glsl files excluding our include files) glsl_files = [str(f) for f in Glob("*.glsl") if str(f) not in gl_include_files] # make sure we recompile shaders if include files change env.Depends([f + ".gen.h" for f in glsl_files], gl_include_files + ["#glsl_builders.py"]) - # compile shaders + # compile include files + for glsl_file in gl_include_files: + env.GLSL_HEADER(glsl_file) + + # compile RD shader for glsl_file in glsl_files: env.RD_GLSL(glsl_file) diff --git a/servers/rendering/renderer_rd/shaders/canvas_uniforms_inc.glsl b/servers/rendering/renderer_rd/shaders/canvas_uniforms_inc.glsl index da582ec1b46..f5a806fbca3 100644 --- a/servers/rendering/renderer_rd/shaders/canvas_uniforms_inc.glsl +++ b/servers/rendering/renderer_rd/shaders/canvas_uniforms_inc.glsl @@ -37,7 +37,7 @@ struct InstanceData { #endif vec2 color_texture_pixel_size; - uint lights[4]; + uvec4 lights; }; //1 means enabled, 2+ means trails in use diff --git a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl index 1e1b6d89371..52b3d5d1e38 100644 --- a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl +++ b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl @@ -2369,11 +2369,7 @@ void fragment_shader(in SceneData scene_data) { continue; // Statically baked light and object uses lightmap, skip } - float shadow = light_process_omni_shadow(light_index, vertex, normal, scene_data.taa_frame_count); - - shadow = blur_shadow(shadow); - - light_process_omni(light_index, vertex, view, normal, vertex_ddx, vertex_ddy, f0, orms, shadow, albedo, alpha, screen_uv, + light_process_omni(light_index, vertex, view, normal, vertex_ddx, vertex_ddy, f0, orms, scene_data.taa_frame_count, albedo, alpha, screen_uv, #ifdef LIGHT_BACKLIGHT_USED backlight, #endif @@ -2441,11 +2437,7 @@ void fragment_shader(in SceneData scene_data) { continue; // Statically baked light and object uses lightmap, skip } - float shadow = light_process_spot_shadow(light_index, vertex, normal, scene_data.taa_frame_count); - - shadow = blur_shadow(shadow); - - light_process_spot(light_index, vertex, view, normal, vertex_ddx, vertex_ddy, f0, orms, shadow, albedo, alpha, screen_uv, + light_process_spot(light_index, vertex, view, normal, vertex_ddx, vertex_ddy, f0, orms, scene_data.taa_frame_count, albedo, alpha, screen_uv, #ifdef LIGHT_BACKLIGHT_USED backlight, #endif diff --git a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl index 0cb34557ea8..144b994a52c 100644 --- a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl +++ b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl @@ -768,7 +768,7 @@ layout(location = 0) out mediump vec4 frag_color; vec4 fog_process(vec3 vertex) { vec3 fog_color = scene_data_block.data.fog_light_color; - if (scene_data_block.data.fog_aerial_perspective > 0.0) { + if (sc_use_fog_aerial_perspective()) { vec3 sky_fog_color = vec3(0.0); vec3 cube_view = scene_data_block.data.radiance_inverse_xform * vertex; // mip_level always reads from the second mipmap and higher so the fog is always slightly blurred @@ -784,7 +784,7 @@ vec4 fog_process(vec3 vertex) { fog_color = mix(fog_color, sky_fog_color, scene_data_block.data.fog_aerial_perspective); } - if (scene_data_block.data.fog_sun_scatter > 0.001) { + if (sc_use_fog_sun_scatter()) { vec4 sun_scatter = vec4(0.0); float sun_total = 0.0; vec3 view = normalize(vertex); @@ -806,7 +806,7 @@ vec4 fog_process(vec3 vertex) { fog_amount = 1 - exp(min(0.0, -length(vertex) * scene_data_block.data.fog_density)); } - if (abs(scene_data_block.data.fog_height_density) >= 0.0001) { + if (sc_use_fog_height_density()) { float y = (scene_data_block.data.inv_view_matrix * vec4(vertex, 1.0)).y; float y_dist = y - scene_data_block.data.fog_height; @@ -1497,9 +1497,11 @@ void main() { pssm_coord /= pssm_coord.w; - shadow = sample_directional_pcf_shadow(directional_shadow_atlas, scene_data.directional_shadow_pixel_size * directional_lights.data[i].soft_shadow_scale * (blur_factor + (1.0 - blur_factor) * float(directional_lights.data[i].blend_splits)), pssm_coord, scene_data.taa_frame_count); + bool blend_split = sc_directional_light_blend_split(i); + float blend_split_weight = blend_split ? 1.0f : 0.0f; + shadow = sample_directional_pcf_shadow(directional_shadow_atlas, scene_data.directional_shadow_pixel_size * directional_lights.data[i].soft_shadow_scale * (blur_factor + (1.0 - blur_factor) * blend_split_weight), pssm_coord, scene_data.taa_frame_count); - if (directional_lights.data[i].blend_splits) { + if (blend_split) { float pssm_blend; float blur_factor2; @@ -1531,7 +1533,7 @@ void main() { pssm_coord /= pssm_coord.w; - float shadow2 = sample_directional_pcf_shadow(directional_shadow_atlas, scene_data.directional_shadow_pixel_size * directional_lights.data[i].soft_shadow_scale * (blur_factor2 + (1.0 - blur_factor2) * float(directional_lights.data[i].blend_splits)), pssm_coord, scene_data.taa_frame_count); + float shadow2 = sample_directional_pcf_shadow(directional_shadow_atlas, scene_data.directional_shadow_pixel_size * directional_lights.data[i].soft_shadow_scale * (blur_factor2 + (1.0 - blur_factor2) * blend_split_weight), pssm_coord, scene_data.taa_frame_count); shadow = mix(shadow, shadow2, pssm_blend); } @@ -1622,13 +1624,7 @@ void main() { uvec2 omni_indices = instances.data[draw_call.instance_index].omni_lights; for (uint i = 0; i < sc_omni_lights(); i++) { uint light_index = (i > 3) ? ((omni_indices.y >> ((i - 4) * 8)) & 0xFF) : ((omni_indices.x >> (i * 8)) & 0xFF); - - float shadow = light_process_omni_shadow(light_index, vertex, normal, scene_data.taa_frame_count); - - shadow = blur_shadow(shadow); - - // Fragment lighting - light_process_omni(light_index, vertex, view, normal, vertex_ddx, vertex_ddy, f0, orms, shadow, albedo, alpha, screen_uv, + light_process_omni(light_index, vertex, view, normal, vertex_ddx, vertex_ddy, f0, orms, scene_data.taa_frame_count, albedo, alpha, screen_uv, #ifdef LIGHT_BACKLIGHT_USED backlight, #endif @@ -1656,12 +1652,7 @@ void main() { uvec2 spot_indices = instances.data[draw_call.instance_index].spot_lights; for (uint i = 0; i < sc_spot_lights(); i++) { uint light_index = (i > 3) ? ((spot_indices.y >> ((i - 4) * 8)) & 0xFF) : ((spot_indices.x >> (i * 8)) & 0xFF); - - float shadow = light_process_spot_shadow(light_index, vertex, normal, scene_data.taa_frame_count); - - shadow = blur_shadow(shadow); - - light_process_spot(light_index, vertex, view, normal, vertex_ddx, vertex_ddy, f0, orms, shadow, albedo, alpha, screen_uv, + light_process_spot(light_index, vertex, view, normal, vertex_ddx, vertex_ddy, f0, orms, scene_data.taa_frame_count, albedo, alpha, screen_uv, #ifdef LIGHT_BACKLIGHT_USED backlight, #endif diff --git a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile_inc.glsl b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile_inc.glsl index 2cc86482f6f..5e4719c4988 100644 --- a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile_inc.glsl +++ b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile_inc.glsl @@ -23,8 +23,12 @@ layout(push_constant, std430) uniform DrawCall { #ifdef UBERSHADER uint sc_packed_0; uint sc_packed_1; - float sc_packed_2; + uint sc_packed_2; + float sc_packed_3; uint uc_packed_0; + uint uc_padding_1; + uint uc_padding_2; + uint uc_padding_3; #endif } draw_call; @@ -46,10 +50,14 @@ uint sc_packed_1() { return draw_call.sc_packed_1; } -float sc_packed_2() { +uint sc_packed_2() { return draw_call.sc_packed_2; } +float sc_packed_3() { + return draw_call.sc_packed_3; +} + uint uc_cull_mode() { return (draw_call.uc_packed_0 >> 0) & 3U; } @@ -59,7 +67,8 @@ uint uc_cull_mode() { // Pull the constants from the pipeline's specialization constants. layout(constant_id = 0) const uint pso_sc_packed_0 = 0; layout(constant_id = 1) const uint pso_sc_packed_1 = 0; -layout(constant_id = 2) const float pso_sc_packed_2 = 2.0; +layout(constant_id = 2) const uint pso_sc_packed_2 = 0; +layout(constant_id = 3) const float pso_sc_packed_3 = 2.0; uint sc_packed_0() { return pso_sc_packed_0; @@ -69,10 +78,14 @@ uint sc_packed_1() { return pso_sc_packed_1; } -float sc_packed_2() { +uint sc_packed_2() { return pso_sc_packed_2; } +float sc_packed_3() { + return pso_sc_packed_3; +} + #endif bool sc_use_light_projector() { @@ -103,38 +116,50 @@ bool sc_use_depth_fog() { return ((sc_packed_0() >> 6) & 1U) != 0; } -bool sc_use_lightmap_bicubic_filter() { +bool sc_use_fog_aerial_perspective() { return ((sc_packed_0() >> 7) & 1U) != 0; } -bool sc_multimesh() { +bool sc_use_fog_sun_scatter() { return ((sc_packed_0() >> 8) & 1U) != 0; } -bool sc_multimesh_format_2d() { +bool sc_use_fog_height_density() { return ((sc_packed_0() >> 9) & 1U) != 0; } -bool sc_multimesh_has_color() { +bool sc_use_lightmap_bicubic_filter() { return ((sc_packed_0() >> 10) & 1U) != 0; } -bool sc_multimesh_has_custom_data() { +bool sc_multimesh() { return ((sc_packed_0() >> 11) & 1U) != 0; } -bool sc_scene_use_ambient_cubemap() { +bool sc_multimesh_format_2d() { return ((sc_packed_0() >> 12) & 1U) != 0; } -bool sc_scene_use_reflection_cubemap() { +bool sc_multimesh_has_color() { return ((sc_packed_0() >> 13) & 1U) != 0; } -bool sc_scene_roughness_limiter_enabled() { +bool sc_multimesh_has_custom_data() { return ((sc_packed_0() >> 14) & 1U) != 0; } +bool sc_scene_use_ambient_cubemap() { + return ((sc_packed_0() >> 15) & 1U) != 0; +} + +bool sc_scene_use_reflection_cubemap() { + return ((sc_packed_0() >> 16) & 1U) != 0; +} + +bool sc_scene_roughness_limiter_enabled() { + return ((sc_packed_0() >> 17) & 1U) != 0; +} + uint sc_soft_shadow_samples() { return (sc_packed_0() >> 20) & 63U; } @@ -171,8 +196,12 @@ uint sc_decals() { return (sc_packed_1() >> 28) & 15U; } +bool sc_directional_light_blend_split(uint i) { + return ((sc_packed_2() >> i) & 1U) != 0; +} + float sc_luminance_multiplier() { - return sc_packed_2(); + return sc_packed_3(); } /* Set 0: Base Pass (never changes) */ diff --git a/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl b/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl index 1e8fc7eab48..f58304aa8d3 100644 --- a/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl +++ b/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl @@ -59,16 +59,14 @@ void light_compute(vec3 N, vec3 L, vec3 V, float A, vec3 light_color, bool is_di vec3 B, vec3 T, float anisotropy, #endif inout vec3 diffuse_light, inout vec3 specular_light) { - vec4 orms_unpacked = unpackUnorm4x8(orms); - float roughness = orms_unpacked.y; float metallic = orms_unpacked.z; #if defined(LIGHT_CODE_USED) - // light is written by the light shader - + // Light is written by the user shader. mat4 inv_view_matrix = scene_data_block.data.inv_view_matrix; + mat4 read_view_matrix = scene_data_block.data.view_matrix; #ifdef USING_MOBILE_RENDERER mat4 read_model_matrix = instances.data[draw_call.instance_index].transform; @@ -76,183 +74,159 @@ void light_compute(vec3 N, vec3 L, vec3 V, float A, vec3 light_color, bool is_di mat4 read_model_matrix = instances.data[instance_index_interp].transform; #endif - mat4 read_view_matrix = scene_data_block.data.view_matrix; - #undef projection_matrix #define projection_matrix scene_data_block.data.projection_matrix #undef inv_projection_matrix #define inv_projection_matrix scene_data_block.data.inv_projection_matrix vec2 read_viewport_size = scene_data_block.data.viewport_size; - vec3 normal = N; vec3 light = L; vec3 view = V; #CODE : LIGHT +#else // !LIGHT_CODE_USED + float NdotL = min(A + dot(N, L), 1.0); + float cNdotV = max(dot(N, V), 1e-4); +#ifdef LIGHT_TRANSMITTANCE_USED + { +#ifdef SSS_MODE_SKIN + float scale = 8.25 / transmittance_depth; + float d = scale * abs(transmittance_z); + float dd = -d * d; + vec3 profile = vec3(0.233, 0.455, 0.649) * exp(dd / 0.0064) + + vec3(0.1, 0.336, 0.344) * exp(dd / 0.0484) + + vec3(0.118, 0.198, 0.0) * exp(dd / 0.187) + + vec3(0.113, 0.007, 0.007) * exp(dd / 0.567) + + vec3(0.358, 0.004, 0.0) * exp(dd / 1.99) + + vec3(0.078, 0.0, 0.0) * exp(dd / 7.41); + + diffuse_light += profile * transmittance_color.a * light_color * clamp(transmittance_boost - NdotL, 0.0, 1.0) * (1.0 / M_PI); #else - float NdotL = min(A + dot(N, L), 1.0); - float cNdotL = max(NdotL, 0.0); // clamped NdotL - float NdotV = dot(N, V); - float cNdotV = max(NdotV, 1e-4); - -#if defined(DIFFUSE_BURLEY) || defined(SPECULAR_SCHLICK_GGX) || defined(LIGHT_CLEARCOAT_USED) - vec3 H = normalize(V + L); + float scale = 8.25 / transmittance_depth; + float d = scale * abs(transmittance_z); + float dd = -d * d; + diffuse_light += exp(dd) * transmittance_color.rgb * transmittance_color.a * light_color * clamp(transmittance_boost - NdotL, 0.0, 1.0) * (1.0 / M_PI); #endif + } +#endif //LIGHT_TRANSMITTANCE_USED -#if defined(SPECULAR_SCHLICK_GGX) - float cNdotH = clamp(A + dot(N, H), 0.0, 1.0); +#if defined(LIGHT_RIM_USED) + // Epsilon min to prevent pow(0, 0) singularity which results in undefined behavior. + float rim_light = pow(max(1e-4, 1.0 - cNdotV), max(0.0, (1.0 - roughness) * 16.0)); + diffuse_light += rim_light * rim * mix(vec3(1.0), albedo, rim_tint) * light_color; #endif + // We skip checking on attenuation on directional lights to avoid a branch that is not as beneficial for directional lights as the other ones. + const float EPSILON = 1e-3f; + if (is_directional || attenuation > EPSILON) { + float cNdotL = max(NdotL, 0.0); +#if defined(DIFFUSE_BURLEY) || defined(SPECULAR_SCHLICK_GGX) || defined(LIGHT_CLEARCOAT_USED) + vec3 H = normalize(V + L); +#endif #if defined(DIFFUSE_BURLEY) || defined(SPECULAR_SCHLICK_GGX) || defined(LIGHT_CLEARCOAT_USED) - float cLdotH = clamp(A + dot(L, H), 0.0, 1.0); + float cLdotH = clamp(A + dot(L, H), 0.0, 1.0); #endif +#if defined(LIGHT_CLEARCOAT_USED) + // Clearcoat ignores normal_map, use vertex normal instead + float ccNdotL = max(min(A + dot(vertex_normal, L), 1.0), 0.0); + float ccNdotH = clamp(A + dot(vertex_normal, H), 0.0, 1.0); + float ccNdotV = max(dot(vertex_normal, V), 1e-4); + +#if !defined(SPECULAR_SCHLICK_GGX) + float cLdotH5 = SchlickFresnel(cLdotH); +#endif + float Dr = D_GGX(ccNdotH, mix(0.001, 0.1, clearcoat_roughness)); + float Gr = 0.25 / (cLdotH * cLdotH); + float Fr = mix(.04, 1.0, cLdotH5); + float clearcoat_specular_brdf_NL = clearcoat * Gr * Fr * Dr * cNdotL; + + specular_light += clearcoat_specular_brdf_NL * light_color * attenuation * specular_amount; + + // TODO: Clearcoat adds light to the scene right now (it is non-energy conserving), both diffuse and specular need to be scaled by (1.0 - FR) + // but to do so we need to rearrange this entire function +#endif // LIGHT_CLEARCOAT_USED - if (metallic < 1.0) { - float diffuse_brdf_NL; // BRDF times N.L for calculating diffuse radiance + if (metallic < 1.0) { + float diffuse_brdf_NL; // BRDF times N.L for calculating diffuse radiance #if defined(DIFFUSE_LAMBERT_WRAP) - // Energy conserving lambert wrap shader. - // https://web.archive.org/web/20210228210901/http://blog.stevemcauley.com/2011/12/03/energy-conserving-wrapped-diffuse/ - diffuse_brdf_NL = max(0.0, (NdotL + roughness) / ((1.0 + roughness) * (1.0 + roughness))) * (1.0 / M_PI); + // Energy conserving lambert wrap shader. + // https://web.archive.org/web/20210228210901/http://blog.stevemcauley.com/2011/12/03/energy-conserving-wrapped-diffuse/ + diffuse_brdf_NL = max(0.0, (NdotL + roughness) / ((1.0 + roughness) * (1.0 + roughness))) * (1.0 / M_PI); #elif defined(DIFFUSE_TOON) - diffuse_brdf_NL = smoothstep(-roughness, max(roughness, 0.01), NdotL) * (1.0 / M_PI); + diffuse_brdf_NL = smoothstep(-roughness, max(roughness, 0.01), NdotL) * (1.0 / M_PI); #elif defined(DIFFUSE_BURLEY) - - { - float FD90_minus_1 = 2.0 * cLdotH * cLdotH * roughness - 0.5; - float FdV = 1.0 + FD90_minus_1 * SchlickFresnel(cNdotV); - float FdL = 1.0 + FD90_minus_1 * SchlickFresnel(cNdotL); - diffuse_brdf_NL = (1.0 / M_PI) * FdV * FdL * cNdotL; - /* - float energyBias = mix(roughness, 0.0, 0.5); - float energyFactor = mix(roughness, 1.0, 1.0 / 1.51); - float fd90 = energyBias + 2.0 * VoH * VoH * roughness; - float f0 = 1.0; - float lightScatter = f0 + (fd90 - f0) * pow(1.0 - cNdotL, 5.0); - float viewScatter = f0 + (fd90 - f0) * pow(1.0 - cNdotV, 5.0); - - diffuse_brdf_NL = lightScatter * viewScatter * energyFactor; - */ - } + { + float FD90_minus_1 = 2.0 * cLdotH * cLdotH * roughness - 0.5; + float FdV = 1.0 + FD90_minus_1 * SchlickFresnel(cNdotV); + float FdL = 1.0 + FD90_minus_1 * SchlickFresnel(cNdotL); + diffuse_brdf_NL = (1.0 / M_PI) * FdV * FdL * cNdotL; + } #else - // lambert - diffuse_brdf_NL = cNdotL * (1.0 / M_PI); + // lambert + diffuse_brdf_NL = cNdotL * (1.0 / M_PI); #endif - diffuse_light += light_color * diffuse_brdf_NL * attenuation; + diffuse_light += light_color * diffuse_brdf_NL * attenuation; #if defined(LIGHT_BACKLIGHT_USED) - diffuse_light += light_color * (vec3(1.0 / M_PI) - diffuse_brdf_NL) * backlight * attenuation; -#endif - -#if defined(LIGHT_RIM_USED) - // Epsilon min to prevent pow(0, 0) singularity which results in undefined behavior. - float rim_light = pow(max(1e-4, 1.0 - cNdotV), max(0.0, (1.0 - roughness) * 16.0)); - diffuse_light += rim_light * rim * mix(vec3(1.0), albedo, rim_tint) * light_color; -#endif - -#ifdef LIGHT_TRANSMITTANCE_USED - - { -#ifdef SSS_MODE_SKIN - float scale = 8.25 / transmittance_depth; - float d = scale * abs(transmittance_z); - float dd = -d * d; - vec3 profile = vec3(0.233, 0.455, 0.649) * exp(dd / 0.0064) + - vec3(0.1, 0.336, 0.344) * exp(dd / 0.0484) + - vec3(0.118, 0.198, 0.0) * exp(dd / 0.187) + - vec3(0.113, 0.007, 0.007) * exp(dd / 0.567) + - vec3(0.358, 0.004, 0.0) * exp(dd / 1.99) + - vec3(0.078, 0.0, 0.0) * exp(dd / 7.41); - - diffuse_light += profile * transmittance_color.a * light_color * clamp(transmittance_boost - NdotL, 0.0, 1.0) * (1.0 / M_PI); -#else - - float scale = 8.25 / transmittance_depth; - float d = scale * abs(transmittance_z); - float dd = -d * d; - diffuse_light += exp(dd) * transmittance_color.rgb * transmittance_color.a * light_color * clamp(transmittance_boost - NdotL, 0.0, 1.0) * (1.0 / M_PI); + diffuse_light += light_color * (vec3(1.0 / M_PI) - diffuse_brdf_NL) * backlight * attenuation; #endif } -#else - -#endif //LIGHT_TRANSMITTANCE_USED - } - - if (roughness > 0.0) { // FIXME: roughness == 0 should not disable specular light entirely - - // D + if (roughness > 0.0) { +#if defined(SPECULAR_SCHLICK_GGX) + float cNdotH = clamp(A + dot(N, H), 0.0, 1.0); +#endif + // Apply specular light. + // FIXME: roughness == 0 should not disable specular light entirely #if defined(SPECULAR_TOON) - - vec3 R = normalize(-reflect(L, N)); - float RdotV = dot(R, V); - float mid = 1.0 - roughness; - mid *= mid; - float intensity = smoothstep(mid - roughness * 0.5, mid + roughness * 0.5, RdotV) * mid; - diffuse_light += light_color * intensity * attenuation * specular_amount; // write to diffuse_light, as in toon shading you generally want no reflection + vec3 R = normalize(-reflect(L, N)); + float RdotV = dot(R, V); + float mid = 1.0 - roughness; + mid *= mid; + float intensity = smoothstep(mid - roughness * 0.5, mid + roughness * 0.5, RdotV) * mid; + diffuse_light += light_color * intensity * attenuation * specular_amount; // write to diffuse_light, as in toon shading you generally want no reflection #elif defined(SPECULAR_DISABLED) - // none.. + // Do nothing. #elif defined(SPECULAR_SCHLICK_GGX) - // shlick+ggx as default - float alpha_ggx = roughness * roughness; + // shlick+ggx as default + float alpha_ggx = roughness * roughness; #if defined(LIGHT_ANISOTROPY_USED) - - float aspect = sqrt(1.0 - anisotropy * 0.9); - float ax = alpha_ggx / aspect; - float ay = alpha_ggx * aspect; - float XdotH = dot(T, H); - float YdotH = dot(B, H); - float D = D_GGX_anisotropic(cNdotH, ax, ay, XdotH, YdotH); - float G = V_GGX_anisotropic(ax, ay, dot(T, V), dot(T, L), dot(B, V), dot(B, L), cNdotV, cNdotL); + float aspect = sqrt(1.0 - anisotropy * 0.9); + float ax = alpha_ggx / aspect; + float ay = alpha_ggx * aspect; + float XdotH = dot(T, H); + float YdotH = dot(B, H); + float D = D_GGX_anisotropic(cNdotH, ax, ay, XdotH, YdotH); + float G = V_GGX_anisotropic(ax, ay, dot(T, V), dot(T, L), dot(B, V), dot(B, L), cNdotV, cNdotL); #else // LIGHT_ANISOTROPY_USED - float D = D_GGX(cNdotH, alpha_ggx); - float G = V_GGX(cNdotL, cNdotV, alpha_ggx); + float D = D_GGX(cNdotH, alpha_ggx); + float G = V_GGX(cNdotL, cNdotV, alpha_ggx); #endif // LIGHT_ANISOTROPY_USED // F - float cLdotH5 = SchlickFresnel(cLdotH); - // Calculate Fresnel using specular occlusion term from Filament: - // https://google.github.io/filament/Filament.html#lighting/occlusion/specularocclusion - float f90 = clamp(dot(f0, vec3(50.0 * 0.33)), metallic, 1.0); - vec3 F = f0 + (f90 - f0) * cLdotH5; - - vec3 specular_brdf_NL = cNdotL * D * F * G; - - specular_light += specular_brdf_NL * light_color * attenuation * specular_amount; -#endif - -#if defined(LIGHT_CLEARCOAT_USED) - // Clearcoat ignores normal_map, use vertex normal instead - float ccNdotL = max(min(A + dot(vertex_normal, L), 1.0), 0.0); - float ccNdotH = clamp(A + dot(vertex_normal, H), 0.0, 1.0); - float ccNdotV = max(dot(vertex_normal, V), 1e-4); - -#if !defined(SPECULAR_SCHLICK_GGX) - float cLdotH5 = SchlickFresnel(cLdotH); + float cLdotH5 = SchlickFresnel(cLdotH); + // Calculate Fresnel using specular occlusion term from Filament: + // https://google.github.io/filament/Filament.html#lighting/occlusion/specularocclusion + float f90 = clamp(dot(f0, vec3(50.0 * 0.33)), metallic, 1.0); + vec3 F = f0 + (f90 - f0) * cLdotH5; + vec3 specular_brdf_NL = cNdotL * D * F * G; + specular_light += specular_brdf_NL * light_color * attenuation * specular_amount; #endif - float Dr = D_GGX(ccNdotH, mix(0.001, 0.1, clearcoat_roughness)); - float Gr = 0.25 / (cLdotH * cLdotH); - float Fr = mix(.04, 1.0, cLdotH5); - float clearcoat_specular_brdf_NL = clearcoat * Gr * Fr * Dr * cNdotL; - - specular_light += clearcoat_specular_brdf_NL * light_color * attenuation * specular_amount; - // TODO: Clearcoat adds light to the scene right now (it is non-energy conserving), both diffuse and specular need to be scaled by (1.0 - FR) - // but to do so we need to rearrange this entire function -#endif // LIGHT_CLEARCOAT_USED - } + } #ifdef USE_SHADOW_TO_OPACITY - alpha = min(alpha, clamp(1.0 - attenuation, 0.0, 1.0)); + alpha = min(alpha, clamp(1.0 - attenuation, 0.0, 1.0)); #endif - -#endif //defined(LIGHT_CODE_USED) + } +#endif // LIGHT_CODE_USED } #ifndef SHADOWS_DISABLED @@ -412,9 +386,43 @@ float get_omni_attenuation(float distance, float inv_range, float decay) { return nd * pow(max(distance, 0.0001), -decay); } -float light_process_omni_shadow(uint idx, vec3 vertex, vec3 normal, float taa_frame_count) { +void light_process_omni(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 vertex_ddx, vec3 vertex_ddy, vec3 f0, uint orms, float taa_frame_count, vec3 albedo, inout float alpha, vec2 screen_uv, +#ifdef LIGHT_BACKLIGHT_USED + vec3 backlight, +#endif +#ifdef LIGHT_TRANSMITTANCE_USED + vec4 transmittance_color, + float transmittance_depth, + float transmittance_boost, +#endif +#ifdef LIGHT_RIM_USED + float rim, float rim_tint, +#endif +#ifdef LIGHT_CLEARCOAT_USED + float clearcoat, float clearcoat_roughness, vec3 vertex_normal, +#endif +#ifdef LIGHT_ANISOTROPY_USED + vec3 binormal, vec3 tangent, float anisotropy, +#endif + inout vec3 diffuse_light, inout vec3 specular_light) { + const float EPSILON = 1e-3f; + + // Omni light attenuation. + vec3 light_rel_vec = omni_lights.data[idx].position - vertex; + float light_length = length(light_rel_vec); + float omni_attenuation = get_omni_attenuation(light_length, omni_lights.data[idx].inv_radius, omni_lights.data[idx].attenuation); + + // Compute size. + float size = 0.0; + if (sc_use_light_soft_shadows() && omni_lights.data[idx].size > 0.0) { + float t = omni_lights.data[idx].size / max(0.001, light_length); + size = max(0.0, 1.0 - 1 / sqrt(1 + t * t)); + } + + float shadow = 1.0; #ifndef SHADOWS_DISABLED - if (omni_lights.data[idx].shadow_opacity > 0.001) { + // Omni light shadow. + if (omni_attenuation > EPSILON && omni_lights.data[idx].shadow_opacity > 0.001) { // there is a shadowmap vec2 texel_size = scene_data_block.data.shadow_atlas_pixel_size; vec4 base_uv_rect = omni_lights.data[idx].atlas_rect; @@ -432,8 +440,6 @@ float light_process_omni_shadow(uint idx, vec3 vertex, vec3 normal, float taa_fr vec3 local_normal = normalize(mat3(omni_lights.data[idx].shadow_matrix) * normal); vec3 normal_bias = local_normal * omni_lights.data[idx].shadow_normal_bias * (1.0 - abs(dot(local_normal, shadow_dir))); - float shadow; - if (sc_use_light_soft_shadows() && omni_lights.data[idx].soft_shadow_size > 0.0) { //soft shadow @@ -539,49 +545,14 @@ float light_process_omni_shadow(uint idx, vec3 vertex, vec3 normal, float taa_fr depth = 1.0 - depth; shadow = mix(1.0, sample_omni_pcf_shadow(shadow_atlas, omni_lights.data[idx].soft_shadow_scale / shadow_sample.z, pos, uv_rect, flip_offset, depth, taa_frame_count), omni_lights.data[idx].shadow_opacity); } - - return shadow; } #endif - return 1.0; -} - -void light_process_omni(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 vertex_ddx, vec3 vertex_ddy, vec3 f0, uint orms, float shadow, vec3 albedo, inout float alpha, vec2 screen_uv, -#ifdef LIGHT_BACKLIGHT_USED - vec3 backlight, -#endif -#ifdef LIGHT_TRANSMITTANCE_USED - vec4 transmittance_color, - float transmittance_depth, - float transmittance_boost, -#endif -#ifdef LIGHT_RIM_USED - float rim, float rim_tint, -#endif -#ifdef LIGHT_CLEARCOAT_USED - float clearcoat, float clearcoat_roughness, vec3 vertex_normal, -#endif -#ifdef LIGHT_ANISOTROPY_USED - vec3 binormal, vec3 tangent, float anisotropy, -#endif - inout vec3 diffuse_light, inout vec3 specular_light) { - vec3 light_rel_vec = omni_lights.data[idx].position - vertex; - float light_length = length(light_rel_vec); - float omni_attenuation = get_omni_attenuation(light_length, omni_lights.data[idx].inv_radius, omni_lights.data[idx].attenuation); - float light_attenuation = omni_attenuation; vec3 color = omni_lights.data[idx].color; - float size_A = 0.0; - - if (sc_use_light_soft_shadows() && omni_lights.data[idx].size > 0.0) { - float t = omni_lights.data[idx].size / max(0.001, light_length); - size_A = max(0.0, 1.0 - 1 / sqrt(1 + t * t)); - } - #ifdef LIGHT_TRANSMITTANCE_USED float transmittance_z = transmittance_depth; //no transmittance by default - transmittance_color.a *= light_attenuation; + transmittance_color.a *= omni_attenuation; #ifndef SHADOWS_DISABLED if (omni_lights.data[idx].shadow_opacity > 0.001) { // Redo shadowmapping, but shrink the model a bit to avoid artifacts. @@ -673,9 +644,8 @@ void light_process_omni(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 v } } - light_attenuation *= shadow; - - light_compute(normal, normalize(light_rel_vec), eye_vec, size_A, color, false, light_attenuation, f0, orms, omni_lights.data[idx].specular_amount, albedo, alpha, screen_uv, + vec3 light_rel_vec_norm = light_rel_vec / light_length; + light_compute(normal, light_rel_vec_norm, eye_vec, size, color, false, omni_attenuation * shadow, f0, orms, omni_lights.data[idx].specular_amount, albedo, alpha, screen_uv, #ifdef LIGHT_BACKLIGHT_USED backlight, #endif @@ -698,15 +668,66 @@ void light_process_omni(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 v specular_light); } -float light_process_spot_shadow(uint idx, vec3 vertex, vec3 normal, float taa_frame_count) { -#ifndef SHADOWS_DISABLED - if (spot_lights.data[idx].shadow_opacity > 0.001) { - vec3 light_rel_vec = spot_lights.data[idx].position - vertex; - float light_length = length(light_rel_vec); - vec3 spot_dir = spot_lights.data[idx].direction; +vec2 normal_to_panorama(vec3 n) { + n = normalize(n); + vec2 panorama_coords = vec2(atan(n.x, n.z), acos(-n.y)); + + if (panorama_coords.x < 0.0) { + panorama_coords.x += M_PI * 2.0; + } + + panorama_coords /= vec2(M_PI * 2.0, M_PI); + return panorama_coords; +} + +void light_process_spot(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 vertex_ddx, vec3 vertex_ddy, vec3 f0, uint orms, float taa_frame_count, vec3 albedo, inout float alpha, vec2 screen_uv, +#ifdef LIGHT_BACKLIGHT_USED + vec3 backlight, +#endif +#ifdef LIGHT_TRANSMITTANCE_USED + vec4 transmittance_color, + float transmittance_depth, + float transmittance_boost, +#endif +#ifdef LIGHT_RIM_USED + float rim, float rim_tint, +#endif +#ifdef LIGHT_CLEARCOAT_USED + float clearcoat, float clearcoat_roughness, vec3 vertex_normal, +#endif +#ifdef LIGHT_ANISOTROPY_USED + vec3 binormal, vec3 tangent, float anisotropy, +#endif + inout vec3 diffuse_light, + inout vec3 specular_light) { + const float EPSILON = 1e-3f; + + // Spot light attenuation. + vec3 light_rel_vec = spot_lights.data[idx].position - vertex; + float light_length = length(light_rel_vec); + vec3 light_rel_vec_norm = light_rel_vec / light_length; + float spot_attenuation = get_omni_attenuation(light_length, spot_lights.data[idx].inv_radius, spot_lights.data[idx].attenuation); + vec3 spot_dir = spot_lights.data[idx].direction; - vec3 shadow_dir = light_rel_vec / light_length; - vec3 normal_bias = normal * light_length * spot_lights.data[idx].shadow_normal_bias * (1.0 - abs(dot(normal, shadow_dir))); + // This conversion to a highp float is crucial to prevent light leaking + // due to precision errors in the following calculations (cone angle is mediump). + highp float cone_angle = spot_lights.data[idx].cone_angle; + float scos = max(dot(-light_rel_vec_norm, spot_dir), cone_angle); + float spot_rim = max(0.0001, (1.0 - scos) / (1.0 - cone_angle)); + spot_attenuation *= 1.0 - pow(spot_rim, spot_lights.data[idx].cone_attenuation); + + // Compute size. + float size = 0.0; + if (sc_use_light_soft_shadows() && spot_lights.data[idx].size > 0.0) { + float t = spot_lights.data[idx].size / max(0.001, light_length); + size = max(0.0, 1.0 - 1 / sqrt(1 + t * t)); + } + + float shadow = 1.0; +#ifndef SHADOWS_DISABLED + // Spot light shadow. + if (spot_attenuation > EPSILON && spot_lights.data[idx].shadow_opacity > 0.001) { + vec3 normal_bias = normal * light_length * spot_lights.data[idx].shadow_normal_bias * (1.0 - abs(dot(normal, light_rel_vec_norm))); //there is a shadowmap vec4 v = vec4(vertex + normal_bias, 1.0); @@ -715,7 +736,6 @@ float light_process_spot_shadow(uint idx, vec3 vertex, vec3 normal, float taa_fr splane.z += spot_lights.data[idx].shadow_bias / (light_length * spot_lights.data[idx].inv_radius); splane /= splane.w; - float shadow; if (sc_use_light_soft_shadows() && spot_lights.data[idx].soft_shadow_size > 0.0) { //soft shadow @@ -772,73 +792,15 @@ float light_process_spot_shadow(uint idx, vec3 vertex, vec3 normal, float taa_fr vec3 shadow_uv = vec3(splane.xy * spot_lights.data[idx].atlas_rect.zw + spot_lights.data[idx].atlas_rect.xy, splane.z); shadow = mix(1.0, sample_pcf_shadow(shadow_atlas, spot_lights.data[idx].soft_shadow_scale * scene_data_block.data.shadow_atlas_pixel_size, shadow_uv, taa_frame_count), spot_lights.data[idx].shadow_opacity); } - - return shadow; } - #endif // SHADOWS_DISABLED - return 1.0; -} - -vec2 normal_to_panorama(vec3 n) { - n = normalize(n); - vec2 panorama_coords = vec2(atan(n.x, n.z), acos(-n.y)); - - if (panorama_coords.x < 0.0) { - panorama_coords.x += M_PI * 2.0; - } - - panorama_coords /= vec2(M_PI * 2.0, M_PI); - return panorama_coords; -} - -void light_process_spot(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 vertex_ddx, vec3 vertex_ddy, vec3 f0, uint orms, float shadow, vec3 albedo, inout float alpha, vec2 screen_uv, -#ifdef LIGHT_BACKLIGHT_USED - vec3 backlight, -#endif -#ifdef LIGHT_TRANSMITTANCE_USED - vec4 transmittance_color, - float transmittance_depth, - float transmittance_boost, -#endif -#ifdef LIGHT_RIM_USED - float rim, float rim_tint, -#endif -#ifdef LIGHT_CLEARCOAT_USED - float clearcoat, float clearcoat_roughness, vec3 vertex_normal, -#endif -#ifdef LIGHT_ANISOTROPY_USED - vec3 binormal, vec3 tangent, float anisotropy, -#endif - inout vec3 diffuse_light, - inout vec3 specular_light) { - vec3 light_rel_vec = spot_lights.data[idx].position - vertex; - float light_length = length(light_rel_vec); - float spot_attenuation = get_omni_attenuation(light_length, spot_lights.data[idx].inv_radius, spot_lights.data[idx].attenuation); - vec3 spot_dir = spot_lights.data[idx].direction; - - // This conversion to a highp float is crucial to prevent light leaking - // due to precision errors in the following calculations (cone angle is mediump). - highp float cone_angle = spot_lights.data[idx].cone_angle; - float scos = max(dot(-normalize(light_rel_vec), spot_dir), cone_angle); - float spot_rim = max(0.0001, (1.0 - scos) / (1.0 - cone_angle)); - - spot_attenuation *= 1.0 - pow(spot_rim, spot_lights.data[idx].cone_attenuation); - float light_attenuation = spot_attenuation; vec3 color = spot_lights.data[idx].color; float specular_amount = spot_lights.data[idx].specular_amount; - float size_A = 0.0; - - if (sc_use_light_soft_shadows() && spot_lights.data[idx].size > 0.0) { - float t = spot_lights.data[idx].size / max(0.001, light_length); - size_A = max(0.0, 1.0 - 1 / sqrt(1 + t * t)); - } - #ifdef LIGHT_TRANSMITTANCE_USED float transmittance_z = transmittance_depth; - transmittance_color.a *= light_attenuation; + transmittance_color.a *= spot_attenuation; #ifndef SHADOWS_DISABLED if (spot_lights.data[idx].shadow_opacity > 0.001) { vec4 splane = (spot_lights.data[idx].shadow_matrix * vec4(vertex - normalize(normal) * spot_lights.data[idx].transmittance_bias, 1.0)); @@ -882,9 +844,8 @@ void light_process_spot(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 v color *= proj.rgb * proj.a; } } - light_attenuation *= shadow; - light_compute(normal, normalize(light_rel_vec), eye_vec, size_A, color, false, light_attenuation, f0, orms, spot_lights.data[idx].specular_amount, albedo, alpha, screen_uv, + light_compute(normal, light_rel_vec_norm, eye_vec, size, color, false, spot_attenuation * shadow, f0, orms, spot_lights.data[idx].specular_amount, albedo, alpha, screen_uv, #ifdef LIGHT_BACKLIGHT_USED backlight, #endif diff --git a/servers/rendering/renderer_rd/storage_rd/light_storage.cpp b/servers/rendering/renderer_rd/storage_rd/light_storage.cpp index 410a6ae5c6a..624e8eab2c0 100644 --- a/servers/rendering/renderer_rd/storage_rd/light_storage.cpp +++ b/servers/rendering/renderer_rd/storage_rd/light_storage.cpp @@ -2330,7 +2330,7 @@ bool LightStorage::shadow_atlas_update_light(RID p_atlas, RID p_light_instance, old_quadrant = (old_key >> QUADRANT_SHIFT) & 0x3; old_shadow = old_key & SHADOW_INDEX_MASK; - should_realloc = shadow_atlas->quadrants[old_quadrant].subdivision != (uint32_t)best_subdiv && (shadow_atlas->quadrants[old_quadrant].shadows[old_shadow].alloc_tick - tick > shadow_atlas_realloc_tolerance_msec); + should_realloc = shadow_atlas->quadrants[old_quadrant].subdivision != (uint32_t)best_subdiv && (tick - shadow_atlas->quadrants[old_quadrant].shadows[old_shadow].alloc_tick > shadow_atlas_realloc_tolerance_msec); should_redraw = shadow_atlas->quadrants[old_quadrant].shadows[old_shadow].version != p_light_version; if (!should_realloc) { diff --git a/servers/rendering/renderer_rd/storage_rd/light_storage.h b/servers/rendering/renderer_rd/storage_rd/light_storage.h index 7cd7d3e3632..a2ae30eeebe 100644 --- a/servers/rendering/renderer_rd/storage_rd/light_storage.h +++ b/servers/rendering/renderer_rd/storage_rd/light_storage.h @@ -801,6 +801,16 @@ class LightStorage : public RendererLightStorage { RID get_spot_light_buffer() { return spot_light_buffer; } RID get_directional_light_buffer() { return directional_light_buffer; } uint32_t get_max_directional_lights() { return max_directional_lights; } + uint32_t get_directional_light_blend_splits(uint32_t p_directional_light_count) const { + uint32_t blend_splits = 0; + for (uint32_t i = 0; i < p_directional_light_count; i++) { + if (directional_lights[i].blend_splits) { + blend_splits |= 1U << i; + } + } + + return blend_splits; + } bool has_directional_shadows(const uint32_t p_directional_light_count) { for (uint32_t i = 0; i < p_directional_light_count; i++) { if (directional_lights[i].shadow_opacity > 0.001) { diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp index 5b01f11abfe..4cea97270bd 100644 --- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp +++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp @@ -853,6 +853,7 @@ void MeshStorage::mesh_clear(RID p_mesh) { mesh->surface_count = 0; mesh->material_cache.clear(); mesh->has_bone_weights = false; + mesh->aabb = AABB(); mesh->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_MESH); for (Mesh *E : mesh->shadow_owners) { diff --git a/servers/rendering/rendering_device.cpp b/servers/rendering/rendering_device.cpp index cdefbf1381a..c955fd800e7 100644 --- a/servers/rendering/rendering_device.cpp +++ b/servers/rendering/rendering_device.cpp @@ -34,6 +34,7 @@ #include "rendering_device.compat.inc" #include "rendering_device_binds.h" +#include "shader_include_db.h" #include "core/config/project_settings.h" #include "core/io/dir_access.h" @@ -191,6 +192,10 @@ void RenderingDevice::_free_dependencies(RID p_id) { } } +/*******************************/ +/**** SHADER INFRASTRUCTURE ****/ +/*******************************/ + void RenderingDevice::shader_set_compile_to_spirv_function(ShaderCompileToSPIRVFunction p_function) { compile_to_spirv_function = p_function; } @@ -213,7 +218,7 @@ Vector RenderingDevice::shader_compile_spirv_from_source(ShaderStage p_ ERR_FAIL_NULL_V(compile_to_spirv_function, Vector()); - return compile_to_spirv_function(p_stage, p_source_code, p_language, r_error, this); + return compile_to_spirv_function(p_stage, ShaderIncludeDB::parse_include_files(p_source_code), p_language, r_error, this); } String RenderingDevice::shader_get_spirv_cache_key() const { diff --git a/servers/rendering/rendering_device_binds.cpp b/servers/rendering/rendering_device_binds.cpp index a86b41996ec..37ecc7f78e8 100644 --- a/servers/rendering/rendering_device_binds.cpp +++ b/servers/rendering/rendering_device_binds.cpp @@ -32,6 +32,8 @@ #include "rendering_device_binds.h" +#include "shader_include_db.h" + Error RDShaderFile::parse_versions_from_text(const String &p_text, const String p_defines, OpenIncludeFunction p_include_func, void *p_include_func_userdata) { ERR_FAIL_NULL_V_MSG( RenderingDevice::get_singleton(), @@ -106,11 +108,11 @@ Error RDShaderFile::parse_versions_from_text(const String &p_text, const String if (reading_versions) { String l = line.strip_edges(); if (!l.is_empty()) { - if (!l.contains("=")) { + if (!l.contains_char('=')) { base_error = "Missing `=` in '" + l + "'. Version syntax is `version = \"\";`."; break; } - if (!l.contains(";")) { + if (!l.contains_char(';')) { // We don't require a semicolon per se, but it's needed for clang-format to handle things properly. base_error = "Missing `;` in '" + l + "'. Version syntax is `version = \"\";`."; break; @@ -146,11 +148,17 @@ Error RDShaderFile::parse_versions_from_text(const String &p_text, const String break; } include = include.substr(1, include.length() - 2).strip_edges(); - String include_text = p_include_func(include, p_include_func_userdata); - if (!include_text.is_empty()) { - stage_code[stage] += "\n" + include_text + "\n"; + + String include_code = ShaderIncludeDB::get_built_in_include_file(include); + if (!include_code.is_empty()) { + stage_code[stage] += "\n" + include_code + "\n"; } else { - base_error = "#include failed for file '" + include + "'"; + String include_text = p_include_func(include, p_include_func_userdata); + if (!include_text.is_empty()) { + stage_code[stage] += "\n" + include_text + "\n"; + } else { + base_error = "#include failed for file '" + include + "'."; + } } } else { base_error = "#include used, but no include function provided."; diff --git a/servers/rendering/shader_compiler.cpp b/servers/rendering/shader_compiler.cpp index a453d532a83..d16fd7633e0 100644 --- a/servers/rendering/shader_compiler.cpp +++ b/servers/rendering/shader_compiler.cpp @@ -183,7 +183,7 @@ static String _mkid(const String &p_id) { static String f2sp0(float p_float) { String num = rtos(p_float); - if (!num.contains(".") && !num.contains("e")) { + if (!num.contains_char('.') && !num.contains_char('e')) { num += ".0"; } return num; diff --git a/servers/rendering/shader_include_db.cpp b/servers/rendering/shader_include_db.cpp new file mode 100644 index 00000000000..054aa405bb4 --- /dev/null +++ b/servers/rendering/shader_include_db.cpp @@ -0,0 +1,117 @@ +/**************************************************************************/ +/* shader_include_db.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* REDOT ENGINE */ +/* https://redotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2024-present Redot Engine contributors */ +/* (see REDOT_AUTHORS.md) */ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "shader_include_db.h" + +HashMap ShaderIncludeDB::built_in_includes; + +void ShaderIncludeDB::_bind_methods() { + ClassDB::bind_static_method("ShaderIncludeDB", D_METHOD("list_built_in_include_files"), &ShaderIncludeDB::list_built_in_include_files); + ClassDB::bind_static_method("ShaderIncludeDB", D_METHOD("has_built_in_include_file", "filename"), &ShaderIncludeDB::has_built_in_include_file); + ClassDB::bind_static_method("ShaderIncludeDB", D_METHOD("get_built_in_include_file", "filename"), &ShaderIncludeDB::get_built_in_include_file); +} + +void ShaderIncludeDB::register_built_in_include_file(const String &p_filename, const String &p_shader_code) { + built_in_includes[p_filename] = p_shader_code; +} + +PackedStringArray ShaderIncludeDB::list_built_in_include_files() { + PackedStringArray ret; + + for (const KeyValue &e : built_in_includes) { + ret.push_back(e.key); + } + + return ret; +} + +bool ShaderIncludeDB::has_built_in_include_file(const String &p_filename) { + return built_in_includes.has(p_filename); +} + +String ShaderIncludeDB::get_built_in_include_file(const String &p_filename) { + const String *ptr = built_in_includes.getptr(p_filename); + + return ptr ? *ptr : String(); +} + +String ShaderIncludeDB::parse_include_files(const String &p_code) { + // Prevent needless processing if we don't have any includes. + if (p_code.find("#include ") == -1) { + return p_code; + } + + const String include = "#include "; + String parsed_code; + + Vector lines = p_code.split("\n"); + int line_count = lines.size(); + for (int i = 0; i < line_count; i++) { + const String &l = lines[i]; + + if (l.begins_with(include)) { + String include_file = l.replace(include, "").strip_edges(); + if (include_file[0] == '"') { + int end_pos = include_file.find_char('"', 1); + if (end_pos >= 0) { + include_file = include_file.substr(1, end_pos - 1); + + String include_code = ShaderIncludeDB::get_built_in_include_file(include_file); + if (!include_code.is_empty()) { + // Add these lines into our parse list so we parse them as well. + Vector include_lines = include_code.split("\n"); + + for (int j = include_lines.size() - 1; j >= 0; j--) { + lines.insert(i + 1, include_lines[j]); + } + + line_count = lines.size(); + } else { + // Just add it back in, this will cause a compile error to alert the user. + parsed_code += l + "\n"; + } + } else { + // Include as is. + parsed_code += l + "\n"; + } + } else { + // Include as is. + parsed_code += l + "\n"; + } + } else { + // Include as is. + parsed_code += l + "\n"; + } + } + + return parsed_code; +} diff --git a/servers/rendering/shader_include_db.h b/servers/rendering/shader_include_db.h new file mode 100644 index 00000000000..adcfb638246 --- /dev/null +++ b/servers/rendering/shader_include_db.h @@ -0,0 +1,55 @@ +/**************************************************************************/ +/* shader_include_db.h */ +/**************************************************************************/ +/* This file is part of: */ +/* REDOT ENGINE */ +/* https://redotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2024-present Redot Engine contributors */ +/* (see REDOT_AUTHORS.md) */ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef SHADER_INCLUDE_DB_H +#define SHADER_INCLUDE_DB_H + +#include "core/object/class_db.h" + +class ShaderIncludeDB : public Object { + GDCLASS(ShaderIncludeDB, Object) + +private: + static HashMap built_in_includes; + +protected: + static void _bind_methods(); + +public: + static void register_built_in_include_file(const String &p_filename, const String &p_shader_code); + static PackedStringArray list_built_in_include_files(); + static bool has_built_in_include_file(const String &p_filename); + static String get_built_in_include_file(const String &p_filename); + static String parse_include_files(const String &p_code); +}; + +#endif // SHADER_INCLUDE_DB_H diff --git a/tests/core/io/test_tcp_server.h b/tests/core/io/test_tcp_server.h index 431143cd8a9..ae53cec43d4 100644 --- a/tests/core/io/test_tcp_server.h +++ b/tests/core/io/test_tcp_server.h @@ -87,8 +87,8 @@ Error poll(Ref p_client) { const uint64_t time = OS::get_singleton()->get_ticks_usec(); Error err = p_client->poll(); while (err != Error::OK && (OS::get_singleton()->get_ticks_usec() - time) < MAX_WAIT_USEC) { - err = p_client->poll(); OS::get_singleton()->delay_usec(SLEEP_DURATION); + err = p_client->poll(); } return err; } @@ -185,8 +185,8 @@ TEST_CASE("[TCPServer] When stopped shouldn't accept new connections") { time = OS::get_singleton()->get_ticks_usec(); Error err = new_client->poll(); while (err != Error::OK && err != Error::ERR_CONNECTION_ERROR && (OS::get_singleton()->get_ticks_usec() - time) < MAX_WAIT_USEC) { - err = new_client->poll(); OS::get_singleton()->delay_usec(SLEEP_DURATION); + err = new_client->poll(); } REQUIRE((err == Error::OK || err == Error::ERR_CONNECTION_ERROR)); StreamPeerTCP::Status status = new_client->get_status(); @@ -212,7 +212,10 @@ TEST_CASE("[TCPServer] Should disconnect client") { server->stop(); CHECK_FALSE(server->is_listening()); - client->put_string(hello_world); + // Reading for a closed connection will print an error. + ERR_PRINT_OFF; + CHECK_EQ(client->get_string(), String()); + ERR_PRINT_ON; REQUIRE_EQ(poll(client), Error::OK); CHECK_EQ(client->get_status(), StreamPeerTCP::STATUS_NONE); } diff --git a/tests/core/string/test_string.h b/tests/core/string/test_string.h index fd9b43a677b..7deb5488bd9 100644 --- a/tests/core/string/test_string.h +++ b/tests/core/string/test_string.h @@ -1689,6 +1689,10 @@ TEST_CASE("[String] Path functions") { for (int i = 0; i < 3; i++) { CHECK(String(file_name[i]).is_valid_filename() == valid[i]); } + + CHECK(String("res://texture.png") == String("res://folder/../folder/../texture.png").simplify_path()); + CHECK(String("res://texture.png") == String("res://folder/sub/../../texture.png").simplify_path()); + CHECK(String("res://../../texture.png") == String("res://../../texture.png").simplify_path()); } TEST_CASE("[String] hash") { diff --git a/tests/scene/test_audio_stream_wav.h b/tests/scene/test_audio_stream_wav.h index 35bb4cf16f6..7143771dcbc 100644 --- a/tests/scene/test_audio_stream_wav.h +++ b/tests/scene/test_audio_stream_wav.h @@ -39,11 +39,6 @@ #include "tests/test_macros.h" -#ifdef TOOLS_ENABLED -#include "core/io/resource_loader.h" -#include "editor/import/resource_importer_wav.h" -#endif - namespace TestAudioStreamWAV { // Default wav rate for test cases. @@ -150,25 +145,8 @@ void run_test(String file_name, AudioStreamWAV::Format data_format, bool stereo, Ref wav_file = FileAccess::open(save_path, FileAccess::READ, &error); REQUIRE(error == OK); -#ifdef TOOLS_ENABLED - // The WAV importer can be used if enabled to check that the saved file is valid. - Ref wav_importer = memnew(ResourceImporterWAV); - - List options_list; - wav_importer->get_import_options("", &options_list); - - HashMap options_map; - for (const ResourceImporter::ImportOption &E : options_list) { - options_map[E.option.name] = E.default_value; - } - // Compressed streams can't be saved, disable compression. - options_map["compress/mode"] = 0; - - REQUIRE(wav_importer->import(0, save_path, save_path, options_map, nullptr) == OK); - - String load_path = save_path + "." + wav_importer->get_save_extension(); - Ref loaded_stream = ResourceLoader::load(load_path, "AudioStreamWAV", ResourceFormatImporter::CACHE_MODE_IGNORE, &error); - REQUIRE(error == OK); + Dictionary options; + Ref loaded_stream = AudioStreamWAV::load_from_file(save_path, options); CHECK(loaded_stream->get_format() == stream->get_format()); CHECK(loaded_stream->get_loop_mode() == stream->get_loop_mode()); @@ -179,7 +157,6 @@ void run_test(String file_name, AudioStreamWAV::Format data_format, bool stereo, CHECK(loaded_stream->get_length() == stream->get_length()); CHECK(loaded_stream->is_monophonic() == stream->is_monophonic()); CHECK(loaded_stream->get_data() == stream->get_data()); -#endif } } diff --git a/tests/scene/test_sky.h b/tests/scene/test_sky.h index 7e507d26d04..308aec931f3 100644 --- a/tests/scene/test_sky.h +++ b/tests/scene/test_sky.h @@ -40,16 +40,17 @@ namespace TestSky { TEST_CASE("[SceneTree][Sky] Constructor") { - Sky *test_sky = memnew(Sky); + Ref test_sky; + test_sky.instantiate(); CHECK(test_sky->get_process_mode() == Sky::PROCESS_MODE_AUTOMATIC); CHECK(test_sky->get_radiance_size() == Sky::RADIANCE_SIZE_256); CHECK(test_sky->get_material().is_null()); - memdelete(test_sky); } TEST_CASE("[SceneTree][Sky] Radiance size setter and getter") { - Sky *test_sky = memnew(Sky); + Ref test_sky; + test_sky.instantiate(); // Check default. CHECK(test_sky->get_radiance_size() == Sky::RADIANCE_SIZE_256); @@ -63,25 +64,25 @@ TEST_CASE("[SceneTree][Sky] Radiance size setter and getter") { ERR_PRINT_ON; CHECK(test_sky->get_radiance_size() == Sky::RADIANCE_SIZE_1024); - - memdelete(test_sky); } TEST_CASE("[SceneTree][Sky] Process mode setter and getter") { - Sky *test_sky = memnew(Sky); + Ref test_sky; + test_sky.instantiate(); // Check default. CHECK(test_sky->get_process_mode() == Sky::PROCESS_MODE_AUTOMATIC); test_sky->set_process_mode(Sky::PROCESS_MODE_INCREMENTAL); CHECK(test_sky->get_process_mode() == Sky::PROCESS_MODE_INCREMENTAL); - - memdelete(test_sky); } TEST_CASE("[SceneTree][Sky] Material setter and getter") { - Sky *test_sky = memnew(Sky); - Ref material = memnew(Material); + Ref test_sky; + test_sky.instantiate(); + + Ref material; + material.instantiate(); SUBCASE("Material passed to the class should remain the same") { test_sky->set_material(material); @@ -94,8 +95,10 @@ TEST_CASE("[SceneTree][Sky] Material setter and getter") { CHECK(test_sky->get_material() == material); } SUBCASE("Material rewrite testing") { - Ref material1 = memnew(Material); - Ref material2 = memnew(Material); + Ref material1; + Ref material2; + material1.instantiate(); + material2.instantiate(); test_sky->set_material(material1); test_sky->set_material(material2); @@ -106,19 +109,22 @@ TEST_CASE("[SceneTree][Sky] Material setter and getter") { } SUBCASE("Assign same material to two skys") { - Sky *sky2 = memnew(Sky); + Ref sky2; + sky2.instantiate(); test_sky->set_material(material); sky2->set_material(material); CHECK_MESSAGE(test_sky->get_material() == sky2->get_material(), "Both skys should have the same material."); - memdelete(sky2); } SUBCASE("Swapping materials between two skys") { - Sky *sky2 = memnew(Sky); - Ref material1 = memnew(Material); - Ref material2 = memnew(Material); + Ref sky2; + sky2.instantiate(); + Ref material1; + Ref material2; + material1.instantiate(); + material2.instantiate(); test_sky->set_material(material1); sky2->set_material(material2); @@ -132,10 +138,85 @@ TEST_CASE("[SceneTree][Sky] Material setter and getter") { CHECK(test_sky->get_material() == material2); CHECK(sky2->get_material() == material1); - memdelete(sky2); } +} + +TEST_CASE("[SceneTree][Sky] Invalid radiance size handling") { + Ref test_sky; + test_sky.instantiate(); + + // Attempt to set an invalid radiance size. + ERR_PRINT_OFF; + test_sky->set_radiance_size(Sky::RADIANCE_SIZE_MAX); + ERR_PRINT_ON; + + // Verify that the radiance size remains unchanged. + CHECK(test_sky->get_radiance_size() == Sky::RADIANCE_SIZE_256); +} + +TEST_CASE("[SceneTree][Sky] Process mode variations") { + Ref test_sky; + test_sky.instantiate(); + + // Test all process modes. + const Sky::ProcessMode process_modes[] = { + Sky::PROCESS_MODE_AUTOMATIC, + Sky::PROCESS_MODE_QUALITY, + Sky::PROCESS_MODE_INCREMENTAL, + Sky::PROCESS_MODE_REALTIME + }; + + for (Sky::ProcessMode mode : process_modes) { + test_sky->set_process_mode(mode); + CHECK(test_sky->get_process_mode() == mode); + } +} + +TEST_CASE("[SceneTree][Sky] Radiance size variations") { + Ref test_sky; + test_sky.instantiate(); + + // Test all radiance sizes except MAX. + const Sky::RadianceSize radiance_sizes[] = { + Sky::RADIANCE_SIZE_32, + Sky::RADIANCE_SIZE_64, + Sky::RADIANCE_SIZE_128, + Sky::RADIANCE_SIZE_256, + Sky::RADIANCE_SIZE_512, + Sky::RADIANCE_SIZE_1024, + Sky::RADIANCE_SIZE_2048 + }; + + for (Sky::RadianceSize size : radiance_sizes) { + test_sky->set_radiance_size(size); + CHECK(test_sky->get_radiance_size() == size); + } +} + +TEST_CASE("[SceneTree][Sky] Null material handling") { + Ref test_sky; + test_sky.instantiate(); + + SUBCASE("Setting null material") { + test_sky->set_material(Ref()); + CHECK(test_sky->get_material().is_null()); + } + + SUBCASE("Overwriting existing material with null") { + Ref material; + material.instantiate(); + test_sky->set_material(material); + test_sky->set_material(Ref()); + + CHECK(test_sky->get_material().is_null()); + } +} - memdelete(test_sky); +TEST_CASE("[SceneTree][Sky] RID generation") { + Ref test_sky; + test_sky.instantiate(); + // Check validity. + CHECK(!test_sky->get_rid().is_valid()); } } // namespace TestSky diff --git a/tests/servers/test_text_server.h b/tests/servers/test_text_server.h index 5fd4b05e4cf..b4dba93fcc7 100644 --- a/tests/servers/test_text_server.h +++ b/tests/servers/test_text_server.h @@ -126,7 +126,7 @@ TEST_SUITE("[TextServer]") { RID font1 = ts->create_font(); ts->font_set_data_ptr(font1, _font_NotoSans_Regular, _font_NotoSans_Regular_size); RID font2 = ts->create_font(); - ts->font_set_data_ptr(font2, _font_NotoNaskhArabicUI_Regular, _font_NotoNaskhArabicUI_Regular_size); + ts->font_set_data_ptr(font2, _font_Vazirmatn_Regular, _font_Vazirmatn_Regular_size); Array font; font.push_back(font1); @@ -182,7 +182,7 @@ TEST_SUITE("[TextServer]") { ts->font_set_data_ptr(font2, _font_NotoSansThai_Regular, _font_NotoSansThai_Regular_size); ts->font_set_allow_system_fallback(font2, false); RID font3 = ts->create_font(); - ts->font_set_data_ptr(font3, _font_NotoNaskhArabicUI_Regular, _font_NotoNaskhArabicUI_Regular_size); + ts->font_set_data_ptr(font3, _font_Vazirmatn_Regular, _font_Vazirmatn_Regular_size); ts->font_set_allow_system_fallback(font3, false); Array font; @@ -505,7 +505,7 @@ TEST_SUITE("[TextServer]") { { U" มีอุปกรณ์\nนี้", { 0, 11, 11, 19, 19, 22 } }, { U"الحمدا لحمدا لحمـــد", { 0, 13, 13, 20 } }, { U" الحمد test", { 0, 15, 15, 19 } }, - { U"الحمـد الرياضي العربي", { 0, 7, 7, 21 } }, + { U"الحمـد الرياضي العربي", { 0, 7, 7, 15, 15, 21 } }, }; for (size_t j = 0; j < sizeof(cases) / sizeof(TestCase); j++) { RID ctx = ts->create_shaped_text(); @@ -586,7 +586,7 @@ TEST_SUITE("[TextServer]") { RID font1 = ts->create_font(); ts->font_set_data_ptr(font1, _font_NotoSans_Regular, _font_NotoSans_Regular_size); RID font2 = ts->create_font(); - ts->font_set_data_ptr(font2, _font_NotoNaskhArabicUI_Regular, _font_NotoNaskhArabicUI_Regular_size); + ts->font_set_data_ptr(font2, _font_Vazirmatn_Regular, _font_Vazirmatn_Regular_size); Array font; font.push_back(font1); diff --git a/thirdparty/README.md b/thirdparty/README.md index 1e11c6c9725..021ba79fb57 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -257,10 +257,6 @@ Files extracted from upstream source: * Upstream: https://github.com/JetBrains/JetBrainsMono * Version: 2.304 (cd5227bd1f61dff3bbd6c814ceaf7ffd95e947d9, 2023) * License: OFL-1.1 -- `NotoNaskhArabicUI*.woff2`: - * Upstream: https://github.com/notofonts/arabic - * Version: 2.014 (133ccaebf922ca080a7eef22998611ac3c242df9, 2022) - * License: OFL-1.1 - `NotoSans*.woff2`: * Upstream: https://github.com/notofonts/latin-greek-cyrillic * Version: 2.012 (9ea0c8d37bff0c0067b03777f40aa04f2bf78f99, 2023) @@ -309,6 +305,10 @@ Files extracted from upstream source: * Upstream: https://fonts.google.com/specimen/Open+Sans * Version: 1.10 (downloaded from Google Fonts in February 2021) * License: Apache 2.0 +- `Vazirmatn*.woff2`: + * Upstream: https://github.com/rastikerdar/vazirmatn + * Version: 33.003 (83629f877e8f084cc07b47030b5d3a0ff06c76ec, 2022) + * License: OFL-1.1 All fonts are converted from the unhinted `.ttf` sources using the `https://github.com/google/woff2` tool. @@ -549,7 +549,7 @@ in the MSVC debugger. ## manifold - Upstream: https://github.com/elalish/manifold -- Version: 3.0.0 (5d127e57fbfb89225a8e905d0d914ccc86c139c8, 2024) +- Version: master (36035428bc32302a9d7c9ee1ecc833fb8394a2a3, 2024) - License: Apache 2.0 File extracted from upstream source: @@ -716,8 +716,8 @@ Collection of single-file libraries used in Godot components. * License: MIT - `qoa.h` * Upstream: https://github.com/phoboslab/qoa - * Version: git (e0c69447d4d3945c3c92ac1751e4cdc9803a8303, 2024) - * Modifications: Added a few modifiers to comply with C++ nature. + * Version: git (a2d927f8ce78a85e903676a33e0f956e53b89f7d, 2024) + * Modifications: Added implementation through `qoa.c`. * License: MIT - `r128.{c,h}` * Upstream: https://github.com/fahickman/r128 @@ -916,7 +916,7 @@ instead of `miniz.h` as an external dependency. ## thorvg - Upstream: https://github.com/thorvg/thorvg -- Version: 0.14.10 (366dcd72850c360b49e841e568fc5a154d7cce9e, 2024) +- Version: 0.15.5 (89ab573acb253567975b2494069c7ee9abc9267c, 2024) - License: MIT Files extracted from upstream source: @@ -924,11 +924,13 @@ Files extracted from upstream source: See `thorvg/update-thorvg.sh` for extraction instructions. Set the version number and run the script. +Patches in the `patches/` directory should be re-applied after updating. + ## ufbx - Upstream: https://github.com/ufbx/ufbx -- Version: 0.14.3 (19bdb7e7ef02eb914d5e7211a3685f50ee6d27e3, 2024) +- Version: 0.15.0 (24eea6f40929fe0f679b7950def378edb003afdb, 2024) - License: MIT Files extracted from upstream source: diff --git a/thirdparty/fonts/LICENSE.Vazirmatn.txt b/thirdparty/fonts/LICENSE.Vazirmatn.txt new file mode 100644 index 00000000000..be66b3824e9 --- /dev/null +++ b/thirdparty/fonts/LICENSE.Vazirmatn.txt @@ -0,0 +1,93 @@ +Copyright 2015 The Vazirmatn Project Authors (https://github.com/rastikerdar/vazirmatn) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/thirdparty/fonts/NotoNaskhArabicUI_Bold.woff2 b/thirdparty/fonts/NotoNaskhArabicUI_Bold.woff2 deleted file mode 100644 index e9a834ac690..00000000000 Binary files a/thirdparty/fonts/NotoNaskhArabicUI_Bold.woff2 and /dev/null differ diff --git a/thirdparty/fonts/NotoNaskhArabicUI_Regular.woff2 b/thirdparty/fonts/NotoNaskhArabicUI_Regular.woff2 deleted file mode 100644 index b0c0aaf7666..00000000000 Binary files a/thirdparty/fonts/NotoNaskhArabicUI_Regular.woff2 and /dev/null differ diff --git a/thirdparty/fonts/Vazirmatn_Bold.woff2 b/thirdparty/fonts/Vazirmatn_Bold.woff2 new file mode 100644 index 00000000000..b9b85b8549c Binary files /dev/null and b/thirdparty/fonts/Vazirmatn_Bold.woff2 differ diff --git a/thirdparty/fonts/Vazirmatn_Regular.woff2 b/thirdparty/fonts/Vazirmatn_Regular.woff2 new file mode 100644 index 00000000000..1f8d6600a7b Binary files /dev/null and b/thirdparty/fonts/Vazirmatn_Regular.woff2 differ diff --git a/thirdparty/linuxbsd_headers/alsa/asoundlib.h b/thirdparty/linuxbsd_headers/alsa/asoundlib.h index a5461943825..598175403cd 100644 --- a/thirdparty/linuxbsd_headers/alsa/asoundlib.h +++ b/thirdparty/linuxbsd_headers/alsa/asoundlib.h @@ -38,7 +38,11 @@ #include #include #include +#ifdef __FreeBSD__ +#include +#else #include +#endif // __FreeBSD__ #ifndef __GNUC__ #define __inline__ inline diff --git a/thirdparty/linuxbsd_headers/alsa/patches/freebsd_endian.diff b/thirdparty/linuxbsd_headers/alsa/patches/freebsd_endian.diff new file mode 100644 index 00000000000..f104d9df854 --- /dev/null +++ b/thirdparty/linuxbsd_headers/alsa/patches/freebsd_endian.diff @@ -0,0 +1,16 @@ +diff --git a/thirdparty/linuxbsd_headers/alsa/asoundlib.h b/thirdparty/linuxbsd_headers/alsa/asoundlib.h +index a546194382..598175403c 100644 +--- a/thirdparty/linuxbsd_headers/alsa/asoundlib.h ++++ b/thirdparty/linuxbsd_headers/alsa/asoundlib.h +@@ -38,7 +38,11 @@ + #include + #include + #include ++#ifdef __FreeBSD__ ++#include ++#else + #include ++#endif // __FreeBSD__ + + #ifndef __GNUC__ + #define __inline__ inline diff --git a/thirdparty/manifold/src/constructors.cpp b/thirdparty/manifold/src/constructors.cpp index 17df744ee96..2b3e528c6bb 100644 --- a/thirdparty/manifold/src/constructors.cpp +++ b/thirdparty/manifold/src/constructors.cpp @@ -436,7 +436,7 @@ Manifold Manifold::Compose(const std::vector& manifolds) { for (const auto& manifold : manifolds) { children.push_back(manifold.pNode_->ToLeafNode()); } - return Manifold(std::make_shared(CsgLeafNode::Compose(children))); + return Manifold(CsgLeafNode::Compose(children)); } /** diff --git a/thirdparty/manifold/src/csg_tree.cpp b/thirdparty/manifold/src/csg_tree.cpp index 8f98fe29895..a05fce3aa6b 100644 --- a/thirdparty/manifold/src/csg_tree.cpp +++ b/thirdparty/manifold/src/csg_tree.cpp @@ -19,7 +19,6 @@ #endif #include -#include #include "./boolean3.h" #include "./csg_tree.h" @@ -31,66 +30,10 @@ constexpr int kParallelThreshold = 4096; namespace { using namespace manifold; -struct Transform4x3 { - mat3x4 transform; - - vec3 operator()(vec3 position) const { - return transform * vec4(position, 1.0); - } -}; - -struct UpdateHalfedge { - const int nextVert; - const int nextEdge; - const int nextFace; - - Halfedge operator()(Halfedge edge) { - edge.startVert += nextVert; - edge.endVert += nextVert; - edge.pairedHalfedge += nextEdge; - return edge; - } -}; - -struct UpdateTriProp { - const int nextProp; - - ivec3 operator()(ivec3 tri) { - tri += nextProp; - return tri; - } -}; - -struct UpdateMeshIDs { - const int offset; - - TriRef operator()(TriRef ref) { - ref.meshID += offset; - return ref; - } -}; - -struct CheckOverlap { - VecView boxes; - const size_t i; - bool operator()(size_t j) { return boxes[i].DoesOverlap(boxes[j]); } -}; - -using SharedImpl = std::variant, - std::shared_ptr>; -struct GetImplPtr { - const Manifold::Impl *operator()(const SharedImpl &p) { - if (std::holds_alternative>(p)) { - return std::get_if>(&p)->get(); - } else { - return std::get_if>(&p)->get(); - } - }; -}; - struct MeshCompare { - bool operator()(const SharedImpl &a, const SharedImpl &b) { - return GetImplPtr()(a)->NumVert() < GetImplPtr()(b)->NumVert(); + bool operator()(const std::shared_ptr &a, + const std::shared_ptr &b) { + return a->GetImpl()->NumVert() < b->GetImpl()->NumVert(); } }; @@ -99,11 +42,12 @@ namespace manifold { std::shared_ptr CsgNode::Boolean( const std::shared_ptr &second, OpType op) { - if (auto opNode = std::dynamic_pointer_cast(second)) { + if (second->GetNodeType() != CsgNodeType::Leaf) { // "this" is not a CsgOpNode (which overrides Boolean), but if "second" is // and the operation is commutative, we let it built the tree. if ((op == OpType::Add || op == OpType::Intersect)) { - return opNode->Boolean(shared_from_this(), op); + return std::static_pointer_cast(second)->Boolean( + shared_from_this(), op); } } std::vector> children({shared_from_this(), second}); @@ -154,8 +98,6 @@ std::shared_ptr CsgLeafNode::GetImpl() const { return pImpl_; } -mat3x4 CsgLeafNode::GetTransform() const { return transform_; } - std::shared_ptr CsgLeafNode::ToLeafNode() const { return std::make_shared(*this); } @@ -166,10 +108,14 @@ std::shared_ptr CsgLeafNode::Transform(const mat3x4 &m) const { CsgNodeType CsgLeafNode::GetNodeType() const { return CsgNodeType::Leaf; } +std::shared_ptr ImplToLeaf(Manifold::Impl &&impl) { + return std::make_shared(std::make_shared(impl)); +} + /** * Efficient union of a set of pairwise disjoint meshes. */ -Manifold::Impl CsgLeafNode::Compose( +std::shared_ptr CsgLeafNode::Compose( const std::vector> &nodes) { ZoneScoped; double epsilon = -1; @@ -187,7 +133,7 @@ Manifold::Impl CsgLeafNode::Compose( if (node->pImpl_->status_ != Manifold::Error::NoError) { Manifold::Impl impl; impl.status_ = node->pImpl_->status_; - return impl; + return ImplToLeaf(std::move(impl)); } double nodeOldScale = node->pImpl_->bBox_.Scale(); double nodeNewScale = @@ -242,18 +188,30 @@ Manifold::Impl CsgLeafNode::Compose( copy(node->pImpl_->halfedgeTangent_.begin(), node->pImpl_->halfedgeTangent_.end(), combined.halfedgeTangent_.begin() + edgeIndices[i]); - transform( - node->pImpl_->halfedge_.begin(), node->pImpl_->halfedge_.end(), - combined.halfedge_.begin() + edgeIndices[i], - UpdateHalfedge({vertIndices[i], edgeIndices[i], triIndices[i]})); + const int nextVert = vertIndices[i]; + const int nextEdge = edgeIndices[i]; + const int nextFace = triIndices[i]; + transform(node->pImpl_->halfedge_.begin(), + node->pImpl_->halfedge_.end(), + combined.halfedge_.begin() + edgeIndices[i], + [nextVert, nextEdge, nextFace](Halfedge edge) { + edge.startVert += nextVert; + edge.endVert += nextVert; + edge.pairedHalfedge += nextEdge; + return edge; + }); if (numPropOut > 0) { auto start = combined.meshRelation_.triProperties.begin() + triIndices[i]; if (node->pImpl_->NumProp() > 0) { auto &triProp = node->pImpl_->meshRelation_.triProperties; + const int nextProp = propVertIndices[i]; transform(triProp.begin(), triProp.end(), start, - UpdateTriProp({propVertIndices[i]})); + [nextProp](ivec3 tri) { + tri += nextProp; + return tri; + }); const int numProp = node->pImpl_->NumProp(); auto &oldProp = node->pImpl_->meshRelation_.properties; @@ -282,8 +240,11 @@ Manifold::Impl CsgLeafNode::Compose( } else { // no need to apply the transform to the node, just copy the vertices // and face normals and apply transform on the fly + const mat3x4 transform = node->transform_; auto vertPosBegin = TransformIterator( - node->pImpl_->vertPos_.begin(), Transform4x3({node->transform_})); + node->pImpl_->vertPos_.begin(), [&transform](vec3 position) { + return transform * vec4(position, 1.0); + }); mat3 normalTransform = la::inverse(la::transpose(mat3(node->transform_))); auto faceNormalBegin = @@ -311,7 +272,10 @@ Manifold::Impl CsgLeafNode::Compose( transform(node->pImpl_->meshRelation_.triRef.begin(), node->pImpl_->meshRelation_.triRef.end(), combined.meshRelation_.triRef.begin() + triIndices[i], - UpdateMeshIDs({offset})); + [offset](TriRef ref) { + ref.meshID += offset; + return ref; + }); }); for (size_t i = 0; i < nodes.size(); i++) { @@ -327,173 +291,52 @@ Manifold::Impl CsgLeafNode::Compose( combined.SimplifyTopology(); combined.Finish(); combined.IncrementMeshIDs(); - return combined; -} - -CsgOpNode::CsgOpNode() {} - -CsgOpNode::CsgOpNode(const std::vector> &children, - OpType op) - : impl_(Impl{}) { - auto impl = impl_.GetGuard(); - impl->children_ = children; - SetOp(op); -} - -CsgOpNode::CsgOpNode(std::vector> &&children, - OpType op) - : impl_(Impl{}) { - auto impl = impl_.GetGuard(); - impl->children_ = children; - SetOp(op); -} - -std::shared_ptr CsgOpNode::Boolean( - const std::shared_ptr &second, OpType op) { - std::vector> children; - - auto isReused = [](const auto &node) { return node->impl_.UseCount() > 1; }; - - auto copyChildren = [&](const auto &list, const mat3x4 &transform) { - for (const auto &child : list) { - children.push_back(child->Transform(transform)); - } - }; - - auto self = std::dynamic_pointer_cast(shared_from_this()); - if (IsOp(op) && !isReused(self)) { - auto impl = impl_.GetGuard(); - copyChildren(impl->children_, transform_); - } else { - children.push_back(self); - } - - auto secondOp = std::dynamic_pointer_cast(second); - auto canInlineSecondOp = [&]() { - switch (op) { - case OpType::Add: - case OpType::Intersect: - return secondOp->IsOp(op); - case OpType::Subtract: - return secondOp->IsOp(OpType::Add); - default: - return false; - } - }; - - if (secondOp && canInlineSecondOp() && !isReused(secondOp)) { - auto secondImpl = secondOp->impl_.GetGuard(); - copyChildren(secondImpl->children_, secondOp->transform_); - } else { - children.push_back(second); - } - - return std::make_shared(children, op); -} - -std::shared_ptr CsgOpNode::Transform(const mat3x4 &m) const { - auto node = std::make_shared(); - node->impl_ = impl_; - node->transform_ = m * Mat4(transform_); - node->op_ = op_; - return node; -} - -std::shared_ptr CsgOpNode::ToLeafNode() const { - if (cache_ != nullptr) return cache_; - // turn the children into leaf nodes - GetChildren(); - auto impl = impl_.GetGuard(); - auto &children_ = impl->children_; - if (children_.size() > 1) { - switch (op_) { - case CsgNodeType::Union: - BatchUnion(); - break; - case CsgNodeType::Intersection: { - std::vector> impls; - for (auto &child : children_) { - impls.push_back( - std::dynamic_pointer_cast(child)->GetImpl()); - } - children_.clear(); - children_.push_back(std::make_shared( - BatchBoolean(OpType::Intersect, impls))); - break; - }; - case CsgNodeType::Difference: { - // take the lhs out and treat the remaining nodes as the rhs, perform - // union optimization for them - auto lhs = std::dynamic_pointer_cast(children_.front()); - children_.erase(children_.begin()); - BatchUnion(); - auto rhs = std::dynamic_pointer_cast(children_.front()); - children_.clear(); - Boolean3 boolean(*lhs->GetImpl(), *rhs->GetImpl(), OpType::Subtract); - children_.push_back( - std::make_shared(std::make_shared( - boolean.Result(OpType::Subtract)))); - }; - case CsgNodeType::Leaf: - // unreachable - break; - } - } else if (children_.size() == 0) { - return nullptr; - } - // children_ must contain only one CsgLeafNode now, and its Transform will - // give CsgLeafNode as well - cache_ = std::dynamic_pointer_cast( - children_.front()->Transform(transform_)); - return cache_; + return ImplToLeaf(std::move(combined)); } /** * Efficient boolean operation on a set of nodes utilizing commutativity of the * operation. Only supports union and intersection. */ -std::shared_ptr CsgOpNode::BatchBoolean( - OpType operation, - std::vector> &results) { +std::shared_ptr BatchBoolean( + OpType operation, std::vector> &results) { ZoneScoped; - auto getImplPtr = GetImplPtr(); DEBUG_ASSERT(operation != OpType::Subtract, logicErr, "BatchBoolean doesn't support Difference."); // common cases - if (results.size() == 0) return std::make_shared(); - if (results.size() == 1) - return std::make_shared(*results.front()); + if (results.size() == 0) return std::make_shared(); + if (results.size() == 1) return results.front(); if (results.size() == 2) { - Boolean3 boolean(*results[0], *results[1], operation); - return std::make_shared(boolean.Result(operation)); + Boolean3 boolean(*results[0]->GetImpl(), *results[1]->GetImpl(), operation); + return ImplToLeaf(boolean.Result(operation)); } #if (MANIFOLD_PAR == 1) && __has_include() tbb::task_group group; - tbb::concurrent_priority_queue queue(results.size()); + tbb::concurrent_priority_queue, MeshCompare> + queue(results.size()); for (auto result : results) { queue.emplace(result); } results.clear(); std::function process = [&]() { while (queue.size() > 1) { - SharedImpl a, b; + std::shared_ptr a, b; if (!queue.try_pop(a)) continue; if (!queue.try_pop(b)) { queue.push(a); continue; } group.run([&, a, b]() { - Boolean3 boolean(*getImplPtr(a), *getImplPtr(b), operation); - queue.emplace( - std::make_shared(boolean.Result(operation))); + Boolean3 boolean(*a->GetImpl(), *b->GetImpl(), operation); + queue.emplace(ImplToLeaf(boolean.Result(operation))); return group.run(process); }); } }; group.run_and_wait(process); - SharedImpl r; + std::shared_ptr r; queue.try_pop(r); - return *std::get_if>(&r); + return r; #endif // apply boolean operations starting from smaller meshes // the assumption is that boolean operations on smaller meshes is faster, @@ -508,24 +351,23 @@ std::shared_ptr CsgOpNode::BatchBoolean( auto b = std::move(results.back()); results.pop_back(); // boolean operation - Boolean3 boolean(*a, *b, operation); - auto result = std::make_shared(boolean.Result(operation)); + Boolean3 boolean(*a->GetImpl(), *b->GetImpl(), operation); + auto result = ImplToLeaf(boolean.Result(operation)); if (results.size() == 0) { return result; } results.push_back(result); std::push_heap(results.begin(), results.end(), cmpFn); } - return std::make_shared(*results.front()); + return results.front(); } /** * Efficient union operation on a set of nodes by doing Compose as much as * possible. - * Note: Due to some unknown issues with `Compose`, we are now doing - * `BatchBoolean` instead of using `Compose` for non-intersecting manifolds. */ -void CsgOpNode::BatchUnion() const { +std::shared_ptr BatchUnion( + std::vector> &children) { ZoneScoped; // INVARIANT: children_ is a vector of leaf nodes // this kMaxUnionSize is a heuristic to avoid the pairwise disjoint check @@ -533,26 +375,25 @@ void CsgOpNode::BatchUnion() const { // If the number of children exceeded this limit, we will operate on chunks // with size kMaxUnionSize. constexpr size_t kMaxUnionSize = 1000; - auto impl = impl_.GetGuard(); - auto &children_ = impl->children_; - while (children_.size() > 1) { - const size_t start = (children_.size() > kMaxUnionSize) - ? (children_.size() - kMaxUnionSize) + DEBUG_ASSERT(!children.empty(), logicErr, + "BatchUnion should not have empty children"); + while (children.size() > 1) { + const size_t start = (children.size() > kMaxUnionSize) + ? (children.size() - kMaxUnionSize) : 0; Vec boxes; - boxes.reserve(children_.size() - start); - for (size_t i = start; i < children_.size(); i++) { - boxes.push_back(std::dynamic_pointer_cast(children_[i]) - ->GetImpl() - ->bBox_); + boxes.reserve(children.size() - start); + for (size_t i = start; i < children.size(); i++) { + boxes.push_back(children[i]->GetImpl()->bBox_); } // partition the children into a set of disjoint sets // each set contains a set of children that are pairwise disjoint std::vector> disjointSets; for (size_t i = 0; i < boxes.size(); i++) { auto lambda = [&boxes, i](const Vec &set) { - return std::find_if(set.begin(), set.end(), CheckOverlap({boxes, i})) == - set.end(); + return std::find_if(set.begin(), set.end(), [&boxes, i](size_t j) { + return boxes[i].DoesOverlap(boxes[j]); + }) == set.end(); }; auto it = std::find_if(disjointSets.begin(), disjointSets.end(), lambda); if (it == disjointSets.end()) { @@ -562,82 +403,260 @@ void CsgOpNode::BatchUnion() const { } } // compose each set of disjoint children - std::vector> impls; + std::vector> impls; for (auto &set : disjointSets) { if (set.size() == 1) { - impls.push_back( - std::dynamic_pointer_cast(children_[start + set[0]]) - ->GetImpl()); + impls.push_back(children[start + set[0]]); } else { std::vector> tmp; for (size_t j : set) { - tmp.push_back( - std::dynamic_pointer_cast(children_[start + j])); + tmp.push_back(children[start + j]); } - impls.push_back( - std::make_shared(CsgLeafNode::Compose(tmp))); + impls.push_back(CsgLeafNode::Compose(tmp)); } } - children_.erase(children_.begin() + start, children_.end()); - children_.push_back( - std::make_shared(BatchBoolean(OpType::Add, impls))); + children.erase(children.begin() + start, children.end()); + children.push_back(BatchBoolean(OpType::Add, impls)); // move it to the front as we process from the back, and the newly added // child should be quite complicated - std::swap(children_.front(), children_.back()); + std::swap(children.front(), children.back()); } + return children.front(); } -/** - * Flatten the children to a list of leaf nodes and return them. - * If forceToLeafNodes is true, the list will be guaranteed to be a list of leaf - * nodes (i.e. no ops). Otherwise, the list may contain ops. Note that this - * function will not apply the transform to children, as they may be shared with - * other nodes. - */ -std::vector> &CsgOpNode::GetChildren( - bool forceToLeafNodes) const { +CsgOpNode::CsgOpNode() {} + +CsgOpNode::CsgOpNode(const std::vector> &children, + OpType op) + : impl_(Impl{}), op_(op) { auto impl = impl_.GetGuard(); + impl->children_ = children; +} - if (forceToLeafNodes && !impl->forcedToLeafNodes_) { - impl->forcedToLeafNodes_ = true; - for_each(ExecutionPolicy::Par, impl->children_.begin(), - impl->children_.end(), [](auto &child) { - if (child->GetNodeType() != CsgNodeType::Leaf) { - child = child->ToLeafNode(); - } - }); - } - return impl->children_; +std::shared_ptr CsgOpNode::Boolean( + const std::shared_ptr &second, OpType op) { + std::vector> children; + children.push_back(shared_from_this()); + children.push_back(second); + + return std::make_shared(children, op); } -void CsgOpNode::SetOp(OpType op) { - switch (op) { - case OpType::Add: - op_ = CsgNodeType::Union; - break; - case OpType::Subtract: - op_ = CsgNodeType::Difference; - break; - case OpType::Intersect: - op_ = CsgNodeType::Intersection; - break; +std::shared_ptr CsgOpNode::Transform(const mat3x4 &m) const { + auto node = std::make_shared(); + node->impl_ = impl_; + node->transform_ = m * Mat4(transform_); + node->op_ = op_; + return node; +} + +struct CsgStackFrame { + bool finalize; + OpType parent_op; + mat3x4 transform; + std::vector> *destination; + std::shared_ptr op_node; + std::vector> positive_children; + std::vector> negative_children; + + CsgStackFrame(bool finalize, OpType parent_op, mat3x4 transform, + std::vector> *parent, + std::shared_ptr op_node) + : finalize(finalize), + parent_op(parent_op), + transform(transform), + destination(parent), + op_node(op_node) {} +}; + +std::shared_ptr CsgOpNode::ToLeafNode() const { + if (cache_ != nullptr) return cache_; + + // Note: We do need a pointer here to avoid vector pointers from being + // invalidated after pushing elements into the explicit stack. + // It is a `shared_ptr` because we may want to drop the stack frame while + // still referring to some of the elements inside the old frame. + // It is possible to use `unique_ptr`, extending the lifetime of the frame + // when we remove it from the stack, but it is a bit more complicated and + // there is no measurable overhead from using `shared_ptr` here... + std::vector> stack; + // initial node, destination is a nullptr because we don't need to put the + // result anywhere else (except in the cache_). + stack.push_back(std::make_shared( + false, op_, la::identity, nullptr, + std::static_pointer_cast(shared_from_this()))); + + // Instead of actually using recursion in the algorithm, we use an explicit + // stack, do DFS and store the intermediate states into `CsgStackFrame` to + // avoid stack overflow. + // + // Before performing boolean operations, we should make sure that all children + // are `CsgLeafNodes`, i.e. are actual meshes that can be operated on. Hence, + // we do it in two steps: + // 1. Populate `children` (`left_children` and `right_children`, see below) + // If the child is a `CsgOpNode`, we either collapse it or compute its + // boolean operation result. + // 2. Performs boolean after populating the `children` set. + // After a boolean operation is completed, we put the result back to its + // parent's `children` set. + // + // When we populate `children`, we perform collapsing on-the-fly. + // For example, we want to turn `(Union a (Union b c))` into `(Union a b c)`. + // This allows more efficient `BatchBoolean`/`BatchUnion` calls. + // We can do this when the child operation is the same as the parent + // operation, except when the operation is `Subtract` (see below). + // Note that to avoid repeating work, we will not collapse nodes that are + // reused. And in the special case where the children set only contains one + // element, we don't need any operation, so we can collapse that as well. + // Instead of moving `b` and `c` into the parent, and running this collapsing + // check until a fixed point, we remember the `destination` where we should + // put the `CsgLeafNode` into. Normally, the `destination` pointer point to + // the parent `children` set. However, when a child is being collapsed, we + // keep using the old `destination` pointer for the grandchildren. Hence, + // removing a node by collapsing takes O(1) time. We also need to store the + // parent operation type for checking if the node is eligible for collapsing, + // and transform matrix because we need to re-apply the transformation to the + // children. + // + // `Subtract` is handled differently from `Add` and `Intersect`. It is treated + // as two `Add` nodes, `positive_children` and `negative_children`, that + // should be subtracted later. This allows collapsing children `Add` nodes. + // For normal `Add` and `Intersect`, we only use `positive_children`. + // + // `impl->children_` should always contain either the raw set of children or + // the NOT transformed result, while `cache_` should contain the transformed + // result. This is because `impl` can be shared between `CsgOpNode` that + // differ in `transform_`, so we want it to be able to share the result. + // =========================================================================== + // Recursive version (pseudocode only): + // + // void f(CsgOpNode node, OpType parent_op, mat3x4 transform, + // std::vector *destination) { + // auto impl = node->impl_.GetGuard(); + // // can collapse when we have the same operation as the parent and is + // // unique, or when we have only one children. + // const bool canCollapse = (node->op_ == parent_op && IsUnique(node)) || + // impl->children_.size() == 1; + // const mat3x4 transform2 = canCollapse ? transform * node->transform_ + // : la::identity; + // std::vector positive_children, negative_children; + // // for subtract, we pretend the operation is Add for our children. + // auto op = node->op_ == OpType::Subtract ? OpType::Add : node->op_; + // for (size_t i = 0; i < impl->children_.size(); i++) { + // auto child = impl->children_[i]; + // // negative when it is the remaining operands for Subtract + // auto dest = node->op_ == OpType::Subtract && i != 0 ? + // negative_children : positive_children; + // if (canCollapse) dest = destination; + // if (child->GetNodeType() == CsgNodeType::Leaf) + // dest.push_back(child); + // else + // f(child, op, transform2, dest); + // } + // if (canCollapse) return; + // if (node->op_ == OpType::Add) + // impl->children_ = {BatchUnion(positive_children)}; + // else if (node->op_ == OpType::Intersect) + // impl->children_ = {BatchBoolean(Intersect, positive_children)}; + // else // subtract + // impl->children_ = { BatchUnion(positive_children) - + // BatchUnion(negative_children)}; + // // node local transform + // node->cache_ = impl->children_[0].Transform(node.transform); + // // collapsed node transforms + // if (destination) + // destination->push_back(node->cache_->Transform(transform)); + // } + while (!stack.empty()) { + std::shared_ptr frame = stack.back(); + auto impl = frame->op_node->impl_.GetGuard(); + if (frame->finalize) { + switch (frame->op_node->op_) { + case OpType::Add: + impl->children_ = {BatchUnion(frame->positive_children)}; + break; + case OpType::Intersect: { + impl->children_ = { + BatchBoolean(OpType::Intersect, frame->positive_children)}; + break; + }; + case OpType::Subtract: + if (frame->positive_children.empty()) { + // nothing to subtract from, so the result is empty. + impl->children_ = {std::make_shared()}; + } else { + auto positive = BatchUnion(frame->positive_children); + if (frame->negative_children.empty()) { + // nothing to subtract, result equal to the LHS. + impl->children_ = {frame->positive_children[0]}; + } else { + Boolean3 boolean(*positive->GetImpl(), + *BatchUnion(frame->negative_children)->GetImpl(), + OpType::Subtract); + impl->children_ = {ImplToLeaf(boolean.Result(OpType::Subtract))}; + } + } + break; + } + frame->op_node->cache_ = std::static_pointer_cast( + impl->children_[0]->Transform(frame->op_node->transform_)); + if (frame->destination != nullptr) + frame->destination->push_back(std::static_pointer_cast( + frame->op_node->cache_->Transform(frame->transform))); + stack.pop_back(); + } else { + auto add_children = [&stack](std::shared_ptr &node, OpType op, + mat3x4 transform, auto *destination) { + if (node->GetNodeType() == CsgNodeType::Leaf) + destination->push_back(std::static_pointer_cast( + node->Transform(transform))); + else + stack.push_back(std::make_shared( + false, op, transform, destination, + std::static_pointer_cast(node))); + }; + // op_node use_count == 2 because it is both inside one CsgOpNode + // and in our stack. + // if there is only one child, we can also collapse. + const bool canCollapse = frame->destination != nullptr && + ((frame->op_node->op_ == frame->parent_op && + frame->op_node.use_count() <= 2 && + frame->op_node->impl_.UseCount() == 1) || + impl->children_.size() == 1); + if (canCollapse) + stack.pop_back(); + else + frame->finalize = true; + + const mat3x4 transform = + canCollapse ? (frame->transform * Mat4(frame->op_node->transform_)) + : la::identity; + OpType op = frame->op_node->op_ == OpType::Subtract ? OpType::Add + : frame->op_node->op_; + for (size_t i = 0; i < impl->children_.size(); i++) { + auto dest = canCollapse ? frame->destination + : (frame->op_node->op_ == OpType::Subtract && i != 0) + ? &frame->negative_children + : &frame->positive_children; + add_children(impl->children_[i], op, transform, dest); + } + } } + return cache_; } -bool CsgOpNode::IsOp(OpType op) { - switch (op) { +CsgNodeType CsgOpNode::GetNodeType() const { + switch (op_) { case OpType::Add: - return op_ == CsgNodeType::Union; + return CsgNodeType::Union; case OpType::Subtract: - return op_ == CsgNodeType::Difference; + return CsgNodeType::Difference; case OpType::Intersect: - return op_ == CsgNodeType::Intersection; - default: - return false; + return CsgNodeType::Intersection; } + // unreachable... + return CsgNodeType::Leaf; } -mat3x4 CsgOpNode::GetTransform() const { return transform_; } - } // namespace manifold diff --git a/thirdparty/manifold/src/csg_tree.h b/thirdparty/manifold/src/csg_tree.h index f2a2f692afe..5c24dc5a205 100644 --- a/thirdparty/manifold/src/csg_tree.h +++ b/thirdparty/manifold/src/csg_tree.h @@ -27,7 +27,6 @@ class CsgNode : public std::enable_shared_from_this { virtual std::shared_ptr ToLeafNode() const = 0; virtual std::shared_ptr Transform(const mat3x4 &m) const = 0; virtual CsgNodeType GetNodeType() const = 0; - virtual mat3x4 GetTransform() const = 0; virtual std::shared_ptr Boolean( const std::shared_ptr &second, OpType op); @@ -52,9 +51,7 @@ class CsgLeafNode final : public CsgNode { CsgNodeType GetNodeType() const override; - mat3x4 GetTransform() const override; - - static Manifold::Impl Compose( + static std::shared_ptr Compose( const std::vector> &nodes); private: @@ -68,8 +65,6 @@ class CsgOpNode final : public CsgNode { CsgOpNode(const std::vector> &children, OpType op); - CsgOpNode(std::vector> &&children, OpType op); - std::shared_ptr Boolean(const std::shared_ptr &second, OpType op) override; @@ -77,9 +72,7 @@ class CsgOpNode final : public CsgNode { std::shared_ptr ToLeafNode() const override; - CsgNodeType GetNodeType() const override { return op_; } - - mat3x4 GetTransform() const override; + CsgNodeType GetNodeType() const override; private: struct Impl { @@ -87,22 +80,10 @@ class CsgOpNode final : public CsgNode { bool forcedToLeafNodes_ = false; }; mutable ConcurrentSharedPtr impl_ = ConcurrentSharedPtr(Impl{}); - CsgNodeType op_; + OpType op_; mat3x4 transform_ = la::identity; // the following fields are for lazy evaluation, so they are mutable mutable std::shared_ptr cache_ = nullptr; - - void SetOp(OpType); - bool IsOp(OpType op); - - static std::shared_ptr BatchBoolean( - OpType operation, - std::vector> &results); - - void BatchUnion() const; - - std::vector> &GetChildren( - bool forceToLeafNodes = true) const; }; } // namespace manifold diff --git a/thirdparty/manifold/src/impl.cpp b/thirdparty/manifold/src/impl.cpp index 9582b2c1263..59cc0293d6d 100644 --- a/thirdparty/manifold/src/impl.cpp +++ b/thirdparty/manifold/src/impl.cpp @@ -194,13 +194,14 @@ int GetLabels(std::vector& components, } void DedupePropVerts(manifold::Vec& triProp, - const Vec>& vert2vert) { + const Vec>& vert2vert, + size_t numPropVert) { ZoneScoped; std::vector vertLabels; - const int numLabels = GetLabels(vertLabels, vert2vert, vert2vert.size()); + const int numLabels = GetLabels(vertLabels, vert2vert, numPropVert); std::vector label2vert(numLabels); - for (size_t v = 0; v < vert2vert.size(); ++v) label2vert[vertLabels[v]] = v; + for (size_t v = 0; v < numPropVert; ++v) label2vert[vertLabels[v]] = v; for (auto& prop : triProp) for (int i : {0, 1, 2}) prop[i] = label2vert[vertLabels[prop[i]]]; } @@ -343,6 +344,8 @@ void Manifold::Impl::CreateFaces() { const int prop1 = meshRelation_ .triProperties[pairFace][jointNum == 2 ? 0 : jointNum + 1]; + if (prop0 == prop1) return; + bool propEqual = true; for (size_t p = 0; p < numProp; ++p) { if (meshRelation_.properties[numProp * prop0 + p] != @@ -355,7 +358,7 @@ void Manifold::Impl::CreateFaces() { vert2vert[edgeIdx] = std::make_pair(prop0, prop1); } }); - DedupePropVerts(meshRelation_.triProperties, vert2vert); + DedupePropVerts(meshRelation_.triProperties, vert2vert, NumPropVert()); } for_each_n(autoPolicy(halfedge_.size(), 1e4), countAt(0), halfedge_.size(), diff --git a/thirdparty/manifold/include/manifold/iters.h b/thirdparty/manifold/src/iters.h similarity index 100% rename from thirdparty/manifold/include/manifold/iters.h rename to thirdparty/manifold/src/iters.h diff --git a/thirdparty/manifold/src/parallel.h b/thirdparty/manifold/src/parallel.h index 221cefac1bb..a434939494c 100644 --- a/thirdparty/manifold/src/parallel.h +++ b/thirdparty/manifold/src/parallel.h @@ -17,6 +17,7 @@ #pragma once +#include "./iters.h" #if (MANIFOLD_PAR == 1) #include #include @@ -27,7 +28,6 @@ #include #include -#include "manifold/iters.h" namespace manifold { enum class ExecutionPolicy { diff --git a/thirdparty/manifold/src/properties.cpp b/thirdparty/manifold/src/properties.cpp index 32f79e5b601..911133776ab 100644 --- a/thirdparty/manifold/src/properties.cpp +++ b/thirdparty/manifold/src/properties.cpp @@ -157,10 +157,11 @@ struct CheckCCW { "tol = %g, area2 = %g, base2*tol2 = %g\n" "normal = %g, %g, %g\n" "norm = %g, %g, %g\nverts: %d, %d, %d\n", - face, area / base, base, tol, area * area, base2 * tol * tol, - triNormal[face].x, triNormal[face].y, triNormal[face].z, norm.x, - norm.y, norm.z, halfedges[3 * face].startVert, - halfedges[3 * face + 1].startVert, halfedges[3 * face + 2].startVert); + static_cast(face), area / base, base, tol, area * area, + base2 * tol * tol, triNormal[face].x, triNormal[face].y, + triNormal[face].z, norm.x, norm.y, norm.z, + halfedges[3 * face].startVert, halfedges[3 * face + 1].startVert, + halfedges[3 * face + 2].startVert); } #endif return check; diff --git a/thirdparty/misc/patches/qoa-min-fix.patch b/thirdparty/misc/patches/qoa-min-fix.patch deleted file mode 100644 index 6008b5f8bcd..00000000000 --- a/thirdparty/misc/patches/qoa-min-fix.patch +++ /dev/null @@ -1,53 +0,0 @@ -diff --git a/qoa.h b/qoa.h -index cfed266bef..23612bb0bf 100644 ---- a/qoa.h -+++ b/qoa.h -@@ -140,14 +140,14 @@ typedef struct { - #endif - } qoa_desc; - --unsigned int qoa_encode_header(qoa_desc *qoa, unsigned char *bytes); --unsigned int qoa_encode_frame(const short *sample_data, qoa_desc *qoa, unsigned int frame_len, unsigned char *bytes); --void *qoa_encode(const short *sample_data, qoa_desc *qoa, unsigned int *out_len); -+inline unsigned int qoa_encode_header(qoa_desc *qoa, unsigned char *bytes); -+inline unsigned int qoa_encode_frame(const short *sample_data, qoa_desc *qoa, unsigned int frame_len, unsigned char *bytes); -+inline void *qoa_encode(const short *sample_data, qoa_desc *qoa, unsigned int *out_len); - --unsigned int qoa_max_frame_size(qoa_desc *qoa); --unsigned int qoa_decode_header(const unsigned char *bytes, int size, qoa_desc *qoa); --unsigned int qoa_decode_frame(const unsigned char *bytes, unsigned int size, qoa_desc *qoa, short *sample_data, unsigned int *frame_len); --short *qoa_decode(const unsigned char *bytes, int size, qoa_desc *file); -+inline unsigned int qoa_max_frame_size(qoa_desc *qoa); -+inline unsigned int qoa_decode_header(const unsigned char *bytes, int size, qoa_desc *qoa); -+inline unsigned int qoa_decode_frame(const unsigned char *bytes, unsigned int size, qoa_desc *qoa, short *sample_data, unsigned int *frame_len); -+inline short *qoa_decode(const unsigned char *bytes, int size, qoa_desc *file); - - #ifndef QOA_NO_STDIO - -@@ -395,7 +395,7 @@ unsigned int qoa_encode_frame(const short *sample_data, qoa_desc *qoa, unsigned - qoa_uint64_t best_error = -1; - #endif - qoa_uint64_t best_slice = 0; -- qoa_lms_t best_lms; -+ qoa_lms_t best_lms = {}; - int best_scalefactor = 0; - - for (int sfi = 0; sfi < 16; sfi++) { -@@ -500,7 +500,7 @@ void *qoa_encode(const short *sample_data, qoa_desc *qoa, unsigned int *out_len) - num_frames * QOA_LMS_LEN * 4 * qoa->channels + /* 4 * 4 bytes lms state per channel */ - num_slices * 8 * qoa->channels; /* 8 byte slices */ - -- unsigned char *bytes = QOA_MALLOC(encoded_size); -+ unsigned char *bytes = (unsigned char *)QOA_MALLOC(encoded_size); - - for (unsigned int c = 0; c < qoa->channels; c++) { - /* Set the initial LMS weights to {0, 0, -1, 2}. This helps with the -@@ -655,7 +655,7 @@ short *qoa_decode(const unsigned char *bytes, int size, qoa_desc *qoa) { - - /* Calculate the required size of the sample buffer and allocate */ - int total_samples = qoa->samples * qoa->channels; -- short *sample_data = QOA_MALLOC(total_samples * sizeof(short)); -+ short *sample_data = (short *)QOA_MALLOC(total_samples * sizeof(short)); - - unsigned int sample_index = 0; - unsigned int frame_len; diff --git a/thirdparty/misc/qoa.c b/thirdparty/misc/qoa.c new file mode 100644 index 00000000000..7f7d366dfa1 --- /dev/null +++ b/thirdparty/misc/qoa.c @@ -0,0 +1,4 @@ +#define QOA_IMPLEMENTATION +#define QOA_NO_STDIO + +#include "qoa.h" diff --git a/thirdparty/misc/qoa.h b/thirdparty/misc/qoa.h index 23612bb0bfb..f0f44214d81 100644 --- a/thirdparty/misc/qoa.h +++ b/thirdparty/misc/qoa.h @@ -140,14 +140,14 @@ typedef struct { #endif } qoa_desc; -inline unsigned int qoa_encode_header(qoa_desc *qoa, unsigned char *bytes); -inline unsigned int qoa_encode_frame(const short *sample_data, qoa_desc *qoa, unsigned int frame_len, unsigned char *bytes); -inline void *qoa_encode(const short *sample_data, qoa_desc *qoa, unsigned int *out_len); +unsigned int qoa_encode_header(qoa_desc *qoa, unsigned char *bytes); +unsigned int qoa_encode_frame(const short *sample_data, qoa_desc *qoa, unsigned int frame_len, unsigned char *bytes); +void *qoa_encode(const short *sample_data, qoa_desc *qoa, unsigned int *out_len); -inline unsigned int qoa_max_frame_size(qoa_desc *qoa); -inline unsigned int qoa_decode_header(const unsigned char *bytes, int size, qoa_desc *qoa); -inline unsigned int qoa_decode_frame(const unsigned char *bytes, unsigned int size, qoa_desc *qoa, short *sample_data, unsigned int *frame_len); -inline short *qoa_decode(const unsigned char *bytes, int size, qoa_desc *file); +unsigned int qoa_max_frame_size(qoa_desc *qoa); +unsigned int qoa_decode_header(const unsigned char *bytes, int size, qoa_desc *qoa); +unsigned int qoa_decode_frame(const unsigned char *bytes, unsigned int size, qoa_desc *qoa, short *sample_data, unsigned int *frame_len); +short *qoa_decode(const unsigned char *bytes, int size, qoa_desc *file); #ifndef QOA_NO_STDIO @@ -395,7 +395,7 @@ unsigned int qoa_encode_frame(const short *sample_data, qoa_desc *qoa, unsigned qoa_uint64_t best_error = -1; #endif qoa_uint64_t best_slice = 0; - qoa_lms_t best_lms = {}; + qoa_lms_t best_lms; int best_scalefactor = 0; for (int sfi = 0; sfi < 16; sfi++) { @@ -500,7 +500,7 @@ void *qoa_encode(const short *sample_data, qoa_desc *qoa, unsigned int *out_len) num_frames * QOA_LMS_LEN * 4 * qoa->channels + /* 4 * 4 bytes lms state per channel */ num_slices * 8 * qoa->channels; /* 8 byte slices */ - unsigned char *bytes = (unsigned char *)QOA_MALLOC(encoded_size); + unsigned char *bytes = QOA_MALLOC(encoded_size); for (unsigned int c = 0; c < qoa->channels; c++) { /* Set the initial LMS weights to {0, 0, -1, 2}. This helps with the @@ -626,12 +626,14 @@ unsigned int qoa_decode_frame(const unsigned char *bytes, unsigned int size, qoa qoa_uint64_t slice = qoa_read_u64(bytes, &p); int scalefactor = (slice >> 60) & 0xf; + slice <<= 4; + int slice_start = sample_index * channels + c; int slice_end = qoa_clamp(sample_index + QOA_SLICE_LEN, 0, samples) * channels + c; for (int si = slice_start; si < slice_end; si += channels) { int predicted = qoa_lms_predict(&qoa->lms[c]); - int quantized = (slice >> 57) & 0x7; + int quantized = (slice >> 61) & 0x7; int dequantized = qoa_dequant_tab[scalefactor][quantized]; int reconstructed = qoa_clamp_s16(predicted + dequantized); @@ -655,7 +657,7 @@ short *qoa_decode(const unsigned char *bytes, int size, qoa_desc *qoa) { /* Calculate the required size of the sample buffer and allocate */ int total_samples = qoa->samples * qoa->channels; - short *sample_data = (short *)QOA_MALLOC(total_samples * sizeof(short)); + short *sample_data = QOA_MALLOC(total_samples * sizeof(short)); unsigned int sample_index = 0; unsigned int frame_len; diff --git a/thirdparty/thorvg/AUTHORS b/thirdparty/thorvg/AUTHORS index e00e91a6967..a15f3262a82 100644 --- a/thirdparty/thorvg/AUTHORS +++ b/thirdparty/thorvg/AUTHORS @@ -28,6 +28,10 @@ Nattu Adnan Gabor Kiss-Vamosi Lorcán Mc Donagh Lucas Niu -Francisco Ramírez +Francisco Ramírez Abdelrahman Ashraf Neo Xu +Thaddeus Crews +Josh Soref +Elliott Sales de Andrade +Łukasz Pomietło diff --git a/thirdparty/thorvg/inc/config.h b/thirdparty/thorvg/inc/config.h index fc2faca29f9..6df6f52d047 100644 --- a/thirdparty/thorvg/inc/config.h +++ b/thirdparty/thorvg/inc/config.h @@ -15,5 +15,5 @@ // For internal debugging: //#define THORVG_LOG_ENABLED -#define THORVG_VERSION_STRING "0.14.10" +#define THORVG_VERSION_STRING "0.15.5" #endif diff --git a/thirdparty/thorvg/inc/thorvg.h b/thirdparty/thorvg/inc/thorvg.h index 4303092a5ea..1ee898ca6ff 100644 --- a/thirdparty/thorvg/inc/thorvg.h +++ b/thirdparty/thorvg/inc/thorvg.h @@ -157,7 +157,7 @@ enum class FillRule enum class CompositeMethod { None = 0, ///< No composition is applied. - ClipPath, ///< The intersection of the source and the target is determined and only the resulting pixels from the source are rendered. Note that ClipPath only supports the Shape type. + ClipPath, ///< The intersection of the source and the target is determined and only the resulting pixels from the source are rendered. Note that ClipPath only supports the Shape type. @deprecated Use Paint::clip() instead. AlphaMask, ///< Alpha Masking using the compositing target's pixels as an alpha value. InvAlphaMask, ///< Alpha Masking using the complement to the compositing target's pixels as an alpha value. LumaMask, ///< Alpha Masking using the grayscale (0.2125R + 0.7154G + 0.0721*B) of the compositing target's pixels. @since 0.9 @@ -178,24 +178,46 @@ enum class CompositeMethod * * @see Paint::blend() * - * @note Experimental API + * @since 0.15 */ enum class BlendMethod : uint8_t { Normal = 0, ///< Perform the alpha blending(default). S if (Sa == 255), otherwise (Sa * S) + (255 - Sa) * D - Add, ///< Simply adds pixel values of one layer with the other. (S + D) - Screen, ///< The values of the pixels in the two layers are inverted, multiplied, and then inverted again. (S + D) - (S * D) Multiply, ///< Takes the RGB channel values from 0 to 255 of each pixel in the top layer and multiples them with the values for the corresponding pixel from the bottom layer. (S * D) + Screen, ///< The values of the pixels in the two layers are inverted, multiplied, and then inverted again. (S + D) - (S * D) Overlay, ///< Combines Multiply and Screen blend modes. (2 * S * D) if (2 * D < Da), otherwise (Sa * Da) - 2 * (Da - S) * (Sa - D) - Difference, ///< Subtracts the bottom layer from the top layer or the other way around, to always get a non-negative value. (S - D) if (S > D), otherwise (D - S) - Exclusion, ///< The result is twice the product of the top and bottom layers, subtracted from their sum. s + d - (2 * s * d) - SrcOver, ///< Replace the bottom layer with the top layer. Darken, ///< Creates a pixel that retains the smallest components of the top and bottom layer pixels. min(S, D) Lighten, ///< Only has the opposite action of Darken Only. max(S, D) ColorDodge, ///< Divides the bottom layer by the inverted top layer. D / (255 - S) ColorBurn, ///< Divides the inverted bottom layer by the top layer, and then inverts the result. 255 - (255 - D) / S HardLight, ///< The same as Overlay but with the color roles reversed. (2 * S * D) if (S < Sa), otherwise (Sa * Da) - 2 * (Da - S) * (Sa - D) - SoftLight ///< The same as Overlay but with applying pure black or white does not result in pure black or white. (1 - 2 * S) * (D ^ 2) + (2 * S * D) + SoftLight, ///< The same as Overlay but with applying pure black or white does not result in pure black or white. (1 - 2 * S) * (D ^ 2) + (2 * S * D) + Difference, ///< Subtracts the bottom layer from the top layer or the other way around, to always get a non-negative value. (S - D) if (S > D), otherwise (D - S) + Exclusion, ///< The result is twice the product of the top and bottom layers, subtracted from their sum. s + d - (2 * s * d) + Hue, ///< Reserved. Not supported. + Saturation, ///< Reserved. Not supported. + Color, ///< Reserved. Not supported. + Luminosity, ///< Reserved. Not supported. + Add, ///< Simply adds pixel values of one layer with the other. (S + D) + HardMix ///< Reserved. Not supported. +}; + + +/** + * @brief Enumeration that defines methods used for Scene Effects. + * + * This enum provides options to apply various post-processing effects to a scene. + * Scene effects are typically applied to modify the final appearance of a rendered scene, such as blurring. + * + * @see Scene::push(SceneEffect effect, ...) + * + * @note Experimental API + */ +enum class SceneEffect : uint8_t +{ + ClearAll = 0, ///< Reset all previously applied scene effects, restoring the scene to its original state. + GaussianBlur, ///< Apply a blur effect with a Gaussian filter. Param(3) = {sigma(float)[> 0], direction(int)[both: 0 / horizontal: 1 / vertical: 2], border(int)[duplicate: 0 / wrap: 1], quality(int)[0 - 100]} + DropShadow ///< Apply a drop shadow effect with a Gaussian Blur filter. Param(8) = {color_R(int)[0 - 255], color_G(int)[0 - 255], color_B(int)[0 - 255], opacity(int)[0 - 255], angle(float)[0 - 360], distance(float), blur_sigma(float)[> 0], quality(int)[0 - 100]} }; @@ -206,7 +228,29 @@ enum class CanvasEngine { Sw = (1 << 1), ///< CPU rasterizer. Gl = (1 << 2), ///< OpenGL rasterizer. - Wg = (1 << 3), ///< WebGPU rasterizer. (Experimental API) + Wg = (1 << 3), ///< WebGPU rasterizer. @since 0.15 +}; + + +/** + * @brief Enumeration specifying the ThorVG class type value. + * + * ThorVG's drawing objects can return class type values, allowing you to identify the specific class of each object. + * + * @see Paint::type() + * @see Fill::type() + * + * @note Experimental API + */ +enum class Type : uint8_t +{ + Undefined = 0, ///< Unkown class + Shape, ///< Shape class + Scene, ///< Scene class + Picture, ///< Picture class + Text, ///< Text class + LinearGradient = 10, ///< LinearGradient class + RadialGradient ///< RadialGradient class }; @@ -274,7 +318,7 @@ class TVG_API Paint /** * @brief Sets the values by which the object is moved in a two-dimensional space. * - * The origin of the coordinate system is in the upper left corner of the canvas. + * The origin of the coordinate system is in the upper-left corner of the canvas. * The horizontal and vertical axes point to the right and down, respectively. * * @param[in] x The value of the horizontal shift. @@ -312,7 +356,6 @@ class TVG_API Paint * @param[in] o The opacity value in the range [0 ~ 255], where 0 is completely transparent and 255 is opaque. * * @note Setting the opacity with this API may require multiple render pass for composition. It is recommended to avoid changing the opacity if possible. - * @note ClipPath won't use the opacity value. (see: enum class CompositeMethod::ClipPath) */ Result opacity(uint8_t o) noexcept; @@ -324,6 +367,20 @@ class TVG_API Paint */ Result composite(std::unique_ptr target, CompositeMethod method) noexcept; + /** + * @brief Clip the drawing region of the paint object. + * + * This function restricts the drawing area of the paint object to the specified shape's paths. + * + * @param[in] clipper The shape object as the clipper. + * + * @retval Result::NonSupport If the @p clipper type is not Shape. + * + * @note @p clipper only supports the Shape type. + * @note Experimental API + */ + Result clip(std::unique_ptr clipper) noexcept; + /** * @brief Sets the blending method for the paint object. * @@ -386,22 +443,15 @@ class TVG_API Paint CompositeMethod composite(const Paint** target) const noexcept; /** - * @brief Retrieves the current blending method applied to the paint object. + * @brief Returns the ID value of this class. * - * @return The currently set blending method. + * This method can be used to check the current concrete instance type. * - * @note Experimental API - */ - BlendMethod blend() const noexcept; - - /** - * @brief Return the unique id value of the paint instance. - * - * This method can be called for checking the current concrete instance type. + * @return The class type ID of the Paint instance. * - * @return The type id of the Paint instance. + * @since Experimental API */ - uint32_t identifier() const noexcept; + virtual Type type() const noexcept = 0; /** * @brief Unique ID of this instance. @@ -412,6 +462,11 @@ class TVG_API Paint */ uint32_t id = 0; + /** + * @see Paint::type() + */ + TVG_DEPRECATED uint32_t identifier() const noexcept; + _TVG_DECLARE_PRIVATE(Paint); }; @@ -503,13 +558,20 @@ class TVG_API Fill Fill* duplicate() const noexcept; /** - * @brief Return the unique id value of the Fill instance. + * @brief Returns the ID value of this class. + * + * This method can be used to check the current concrete instance type. * - * This method can be called for checking the current concrete instance type. + * @return The class type ID of the Fill instance. * - * @return The type id of the Fill instance. + * @since Experimental API + */ + virtual Type type() const noexcept = 0; + + /** + * @see Fill::type() */ - uint32_t identifier() const noexcept; + TVG_DEPRECATED uint32_t identifier() const noexcept; _TVG_DECLARE_PRIVATE(Fill); }; @@ -538,7 +600,7 @@ class TVG_API Canvas * * This function provides the list of paint nodes, allowing users a direct opportunity to modify the scene tree. * - * @warning Please avoid accessing the paints during Canvas update/draw. You can access them after calling sync(). + * @warning Please avoid accessing the paints during Canvas update/draw. You can access them after calling sync(). * @see Canvas::sync() * * @note Experimental API @@ -614,7 +676,7 @@ class TVG_API Canvas * @warning It's not allowed to change the viewport during Canvas::push() - Canvas::sync() or Canvas::update() - Canvas::sync(). * * @note When resetting the target, the viewport will also be reset to the target size. - * @note Experimental API + * @since 0.15 */ virtual Result viewport(int32_t x, int32_t y, int32_t w, int32_t h) noexcept; @@ -686,13 +748,20 @@ class TVG_API LinearGradient final : public Fill static std::unique_ptr gen() noexcept; /** - * @brief Return the unique id value of this class. + * @brief Returns the ID value of this class. * - * This method can be referred for identifying the LinearGradient class type. + * This method can be used to check the current concrete instance type. * - * @return The type id of the LinearGradient class. + * @return The class type ID of the LinearGradient instance. + * + * @since Experimental API + */ + Type type() const noexcept override; + + /** + * @see LinearGradient::type() */ - static uint32_t identifier() noexcept; + TVG_DEPRECATED static uint32_t identifier() noexcept; _TVG_DECLARE_PRIVATE(LinearGradient); }; @@ -744,13 +813,20 @@ class TVG_API RadialGradient final : public Fill static std::unique_ptr gen() noexcept; /** - * @brief Return the unique id value of this class. + * @brief Returns the ID value of this class. * - * This method can be referred for identifying the RadialGradient class type. + * This method can be used to check the current concrete instance type. * - * @return The type id of the RadialGradient class. + * @return The class type ID of the LinearGradient instance. + * + * @since Experimental API + */ + Type type() const noexcept override; + + /** + * @see RadialGradient::type() */ - static uint32_t identifier() noexcept; + TVG_DEPRECATED static uint32_t identifier() noexcept; _TVG_DECLARE_PRIVATE(RadialGradient); }; @@ -774,11 +850,11 @@ class TVG_API Shape final : public Paint ~Shape(); /** - * @brief Resets the properties of the shape path. + * @brief Resets the shape path. * - * The transformation matrix, the color, the fill and the stroke properties are retained. + * The transformation matrix, color, fill, and stroke properties are retained. * - * @note The memory, where the path data is stored, is not deallocated at this stage for caching effect. + * @note The memory where the path data is stored is not deallocated at this stage to allow for caching. */ Result reset() noexcept; @@ -836,15 +912,15 @@ class TVG_API Shape final : public Paint * The rectangle with rounded corners can be achieved by setting non-zero values to @p rx and @p ry arguments. * The @p rx and @p ry values specify the radii of the ellipse defining the rounding of the corners. * - * The position of the rectangle is specified by the coordinates of its upper left corner - @p x and @p y arguments. + * The position of the rectangle is specified by the coordinates of its upper-left corner - @p x and @p y arguments. * * The rectangle is treated as a new sub-path - it is not connected with the previous sub-path. * * The value of the current point is set to (@p x + @p rx, @p y) - in case @p rx is greater * than @p w/2 the current point is set to (@p x + @p w/2, @p y) * - * @param[in] x The horizontal coordinate of the upper left corner of the rectangle. - * @param[in] y The vertical coordinate of the upper left corner of the rectangle. + * @param[in] x The horizontal coordinate of the upper-left corner of the rectangle. + * @param[in] y The vertical coordinate of the upper-left corner of the rectangle. * @param[in] w The width of the rectangle. * @param[in] h The height of the rectangle. * @param[in] rx The x-axis radius of the ellipse defining the rounded corners of the rectangle. @@ -886,7 +962,7 @@ class TVG_API Shape final : public Paint * * @note Setting @p sweep value greater than 360 degrees, is equivalent to calling appendCircle(cx, cy, radius, radius). */ - Result appendArc(float cx, float cy, float radius, float startAngle, float sweep, bool pie) noexcept; + TVG_DEPRECATED Result appendArc(float cx, float cy, float radius, float startAngle, float sweep, bool pie) noexcept; /** * @brief Appends a given sub-path to the path. @@ -999,7 +1075,6 @@ class TVG_API Shape final : public Paint * @param[in] a The alpha channel value in the range [0 ~ 255], where 0 is completely transparent and 255 is opaque. The default value is 0. * * @note Either a solid color or a gradient fill is applied, depending on what was set as last. - * @note ClipPath won't use the fill values. (see: enum class CompositeMethod::ClipPath) */ Result fill(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 255) noexcept; @@ -1130,18 +1205,6 @@ class TVG_API Shape final : public Paint */ float strokeMiterlimit() const noexcept; - /** - * @brief Gets the trim of the stroke along the defined path segment. - * - * @param[out] begin The starting point of the segment to display along the path. - * @param[out] end Specifies the end of the segment to display along the path. - * - * @return @c true if trimming is applied simultaneously to all paths of the shape, @c false otherwise. - * - * @note Experimental API - */ - bool strokeTrim(float* begin, float* end) const noexcept; - /** * @brief Creates a new Shape object. * @@ -1150,13 +1213,20 @@ class TVG_API Shape final : public Paint static std::unique_ptr gen() noexcept; /** - * @brief Return the unique id value of this class. + * @brief Returns the ID value of this class. * - * This method can be referred for identifying the Shape class type. + * This method can be used to check the current concrete instance type. * - * @return The type id of the Shape class. + * @return The class type ID of the Shape instance. + * + * @since Experimental API */ - static uint32_t identifier() noexcept; + Type type() const noexcept override; + + /** + * @see Shape::type() + */ + TVG_DEPRECATED static uint32_t identifier() noexcept; _TVG_DECLARE_PRIVATE(Shape); }; @@ -1213,7 +1283,7 @@ class TVG_API Picture final : public Paint * @retval Result::InvalidArguments In case no data are provided or the @p size is zero or less. * @retval Result::NonSupport When trying to load a file with an unknown extension. * - * @warning: It's the user responsibility to release the @p data memory. + * @warning It's the user responsibility to release the @p data memory. * * @note If you are unsure about the MIME type, you can provide an empty value like @c "", and thorvg will attempt to figure it out. * @since 0.5 @@ -1251,9 +1321,9 @@ class TVG_API Picture final : public Paint * @param[in] data A pointer to a memory location where the content of the picture raw data is stored. * @param[in] w The width of the image @p data in pixels. * @param[in] h The height of the image @p data in pixels. + * @param[in] premultiplied If @c true, the given image data is alpha-premultiplied. * @param[in] copy If @c true the data are copied into the engine local buffer, otherwise they are not. * - * @note It expects premultiplied alpha data. * @since 0.9 */ Result load(uint32_t* data, uint32_t w, uint32_t h, bool copy) noexcept; @@ -1281,13 +1351,20 @@ class TVG_API Picture final : public Paint static std::unique_ptr gen() noexcept; /** - * @brief Return the unique id value of this class. + * @brief Returns the ID value of this class. * - * This method can be referred for identifying the Picture class type. + * This method can be used to check the current concrete instance type. * - * @return The type id of the Picture class. + * @return The class type ID of the Picture instance. + * + * @since Experimental API + */ + Type type() const noexcept override; + + /** + * @see Picture::type() */ - static uint32_t identifier() noexcept; + TVG_DEPRECATED static uint32_t identifier() noexcept; _TVG_DECLARE_ACCESSOR(Animation); _TVG_DECLARE_PRIVATE(Picture); @@ -1331,9 +1408,9 @@ class TVG_API Scene final : public Paint * * This function provides the list of paint nodes, allowing users a direct opportunity to modify the scene tree. * - * @warning Please avoid accessing the paints during Scene update/draw. You can access them after calling Canvas::sync(). + * @warning Please avoid accessing the paints during Scene update/draw. You can access them after calling Canvas::sync(). * @see Canvas::sync() - * @see Scene::push() + * @see Scene::push(std::unique_ptr paint) * @see Scene::clear() * * @note Experimental API @@ -1352,6 +1429,20 @@ class TVG_API Scene final : public Paint */ Result clear(bool free = true) noexcept; + /** + * @brief Apply a post-processing effect to the scene. + * + * This function adds a specified scene effect, such as clearing all effects or applying a Gaussian blur, + * to the scene after it has been rendered. Multiple effects can be applied in sequence. + * + * @param[in] effect The scene effect to apply. Options are defined in the SceneEffect enum. + * For example, use SceneEffect::GaussianBlur to apply a blur with specific parameters. + * @param[in] ... Additional variadic parameters required for certain effects (e.g., sigma and direction for GaussianBlur). + * + * @note Experimental API + */ + Result push(SceneEffect effect, ...) noexcept; + /** * @brief Creates a new Scene object. * @@ -1360,13 +1451,20 @@ class TVG_API Scene final : public Paint static std::unique_ptr gen() noexcept; /** - * @brief Return the unique id value of this class. + * @brief Returns the ID value of this class. * - * This method can be referred for identifying the Scene class type. + * This method can be used to check the current concrete instance type. * - * @return The type id of the Scene class. + * @return The class type ID of the Scene instance. + * + * @since Experimental API */ - static uint32_t identifier() noexcept; + Type type() const noexcept override; + + /** + * @see Scene::type() + */ + TVG_DEPRECATED static uint32_t identifier() noexcept; _TVG_DECLARE_PRIVATE(Scene); }; @@ -1377,7 +1475,7 @@ class TVG_API Scene final : public Paint * * @brief A class to represent text objects in a graphical context, allowing for rendering and manipulation of unicode text. * - * @note Experimental API + * @since 0.15 */ class TVG_API Text final : public Paint { @@ -1422,7 +1520,7 @@ class TVG_API Text final : public Paint * * @see Text::font() * - * @note Experimental API + * @since 0.15 */ Result fill(uint8_t r, uint8_t g, uint8_t b) noexcept; @@ -1434,9 +1532,9 @@ class TVG_API Text final : public Paint * @param[in] f The unique pointer to the gradient fill. * * @note Either a solid color or a gradient fill is applied, depending on what was set as last. - * @note Experimental API - * * @see Text::font() + * + * @since 0.15 */ Result fill(std::unique_ptr f) noexcept; @@ -1452,9 +1550,9 @@ class TVG_API Text final : public Paint * @retval Result::InvalidArguments In case the @p path is invalid. * @retval Result::NonSupport When trying to load a file with an unknown extension. * - * @note Experimental API - * * @see Text::unload(const std::string& path) + * + * @since 0.15 */ static Result load(const std::string& path) noexcept; @@ -1475,13 +1573,13 @@ class TVG_API Text final : public Paint * @retval Result::NonSupport When trying to load a file with an unsupported extension. * @retval Result::InsufficientCondition If attempting to unload the font data that has not been previously loaded. * - * @warning: It's the user responsibility to release the @p data memory. + * @warning It's the user responsibility to release the @p data memory. * * @note To unload the font data loaded using this API, pass the proper @p name and @c nullptr as @p data. * @note If you are unsure about the MIME type, you can provide an empty value like @c "", and thorvg will attempt to figure it out. - * @note Experimental API - * * @see Text::font(const char* name, float size, const char* style) + * + * @note 0.15 */ static Result load(const char* name, const char* data, uint32_t size, const std::string& mimeType = "ttf", bool copy = false) noexcept; @@ -1495,9 +1593,9 @@ class TVG_API Text final : public Paint * @retval Result::InsufficientCondition Fails if the loader is not initialized. * * @note If the font data is currently in use, it will not be immediately unloaded. - * @note Experimental API - * * @see Text::load(const std::string& path) + * + * @since 0.15 */ static Result unload(const std::string& path) noexcept; @@ -1506,18 +1604,20 @@ class TVG_API Text final : public Paint * * @return A new Text object. * - * @note Experimental API + * @since 0.15 */ static std::unique_ptr gen() noexcept; /** - * @brief Return the unique id value of this class. + * @brief Returns the ID value of this class. + * + * This method can be used to check the current concrete instance type. * - * This method can be referred for identifying the Text class type. + * @return The class type ID of the Text instance. * - * @return The type id of the Text class. + * @since Experimental API */ - static uint32_t identifier() noexcept; + Type type() const noexcept override; _TVG_DECLARE_PRIVATE(Text); }; @@ -1616,8 +1716,6 @@ class TVG_API SwCanvas final : public Canvas * * @brief A class for the rendering graphic elements with a GL raster engine. * - * @warning Please do not use it. This class is not fully supported yet. - * * @since 0.14 */ class TVG_API GlCanvas final : public Canvas @@ -1666,7 +1764,7 @@ class TVG_API GlCanvas final : public Canvas * * @warning Please do not use it. This class is not fully supported yet. * - * @note Experimental API + * @since 0.15 */ class TVG_API WgCanvas final : public Canvas { @@ -1680,6 +1778,7 @@ class TVG_API WgCanvas final : public Canvas * @param[in] surface WGPUSurface, handle to a presentable surface. * @param[in] w The width of the surface. * @param[in] h The height of the surface. + * @param[in] device WGPUDevice, a desired handle for the wgpu device. If it is @c nullptr, ThorVG will assign an appropriate device internally. * * @retval Result::InsufficientCondition if the canvas is performing rendering. Please ensure the canvas is synced. * @retval Result::NonSupport In case the wg engine is not supported. @@ -1689,14 +1788,14 @@ class TVG_API WgCanvas final : public Canvas * @see Canvas::viewport() * @see Canvas::sync() */ - Result target(void* instance, void* surface, uint32_t w, uint32_t h) noexcept; + Result target(void* instance, void* surface, uint32_t w, uint32_t h, void* device = nullptr) noexcept; /** * @brief Creates a new WgCanvas object. * * @return A new WgCanvas object. * - * @note Experimental API + * @since 0.15 */ static std::unique_ptr gen() noexcept; @@ -1752,7 +1851,7 @@ class TVG_API Initializer final * * @return The version of the engine in the format major.minor.micro, or a @p nullptr in case of an internal error. * - * @note Experimental API + * @since 0.15 */ static const char* version(uint32_t* major, uint32_t* minor, uint32_t* micro) noexcept; @@ -1857,6 +1956,7 @@ class TVG_API Animation * @note Animation allows a range from 0.0 to 1.0. @p end should not be higher than @p begin. * @note If a marker has been specified, its range will be disregarded. * @see LottieAnimation::segment(const char* marker) + * * @note Experimental API */ Result segment(float begin, float end) noexcept; @@ -1895,7 +1995,7 @@ class TVG_API Animation * It's useful when you need to save the composed scene or image from a paint object and recreate it later. * * The file format is decided by the extension name(i.e. "*.tvg") while the supported formats depend on the TVG packaging environment. - * If it doesn't support the file format, the save() method returns the @c Result::NonSuppport result. + * If it doesn't support the file format, the save() method returns the @c Result::NonSupport result. * * Once you export a paint to the file successfully, you can recreate it using the Picture class. * diff --git a/thirdparty/thorvg/patches/pr2740-renderer-crash-hotfix.patch b/thirdparty/thorvg/patches/pr2740-renderer-crash-hotfix.patch deleted file mode 100644 index 50b1e1e4a77..00000000000 --- a/thirdparty/thorvg/patches/pr2740-renderer-crash-hotfix.patch +++ /dev/null @@ -1,45 +0,0 @@ -From 8009c75465e5b35da2d5f53532bc65f6df202a3a Mon Sep 17 00:00:00 2001 -From: Hermet Park -Date: Tue, 17 Sep 2024 11:35:48 +0900 -Subject: [PATCH] renderer: hotfix a crash - -prevent a nullptr memory access -regression by f5337015e971d24379d2ee664895503ab8945e13 - -issue: https://github.com/godotengine/godot/issues/97078 ---- - src/renderer/tvgShape.h | 6 ++++-- - 2 files changed, 4 insertions(+), 4 deletions(-) - -diff --git a/src/renderer/tvgShape.h b/src/renderer/tvgShape.h -index 221931dee..e120a85c6 100644 ---- a/src/renderer/tvgShape.h -+++ b/src/renderer/tvgShape.h -@@ -51,8 +51,9 @@ struct Shape::Impl - - bool render(RenderMethod* renderer) - { -+ if (!rd) return false; -+ - Compositor* cmp = nullptr; -- bool ret; - - renderer->blend(shape->blend()); - -@@ -61,7 +62,7 @@ struct Shape::Impl - renderer->beginComposite(cmp, CompositeMethod::None, opacity); - } - -- ret = renderer->renderShape(rd); -+ auto ret = renderer->renderShape(rd); - if (cmp) renderer->endComposite(cmp); - return ret; - } -@@ -117,6 +118,7 @@ struct Shape::Impl - - RenderRegion bounds(RenderMethod* renderer) - { -+ if (!rd) return {0, 0, 0, 0}; - return renderer->region(rd); - } - diff --git a/thirdparty/thorvg/patches/revert-tvgLines-bezier-precision-change.patch b/thirdparty/thorvg/patches/revert-tvgLines-bezier-precision-change.patch index dd6c8ba5e70..c36836eadc1 100644 --- a/thirdparty/thorvg/patches/revert-tvgLines-bezier-precision-change.patch +++ b/thirdparty/thorvg/patches/revert-tvgLines-bezier-precision-change.patch @@ -1,10 +1,10 @@ -diff --git a/thirdparty/thorvg/src/common/tvgLines.cpp b/thirdparty/thorvg/src/common/tvgLines.cpp -index 49d992f127..9d704900a5 100644 ---- a/thirdparty/thorvg/src/common/tvgLines.cpp -+++ b/thirdparty/thorvg/src/common/tvgLines.cpp +diff --git a/thirdparty/thorvg/src/common/tvgMath.cpp b/thirdparty/thorvg/src/common/tvgMath.cpp +index cb7f24ff40..f27f69faeb 100644 +--- a/thirdparty/thorvg/src/common/tvgMath.cpp ++++ b/thirdparty/thorvg/src/common/tvgMath.cpp @@ -79,7 +79,7 @@ float _bezAt(const Bezier& bz, float at, float length, LengthFunc lineLengthFunc Bezier left; - bezSplitLeft(right, t, left); + right.split(t, left); length = _bezLength(left, lineLengthFunc); - if (fabsf(length - at) < BEZIER_EPSILON || fabsf(smallest - biggest) < 1e-3f) { + if (fabsf(length - at) < BEZIER_EPSILON || fabsf(smallest - biggest) < BEZIER_EPSILON) { diff --git a/thirdparty/thorvg/src/common/tvgCompressor.cpp b/thirdparty/thorvg/src/common/tvgCompressor.cpp index aebe9a4ef16..714f21e07c9 100644 --- a/thirdparty/thorvg/src/common/tvgCompressor.cpp +++ b/thirdparty/thorvg/src/common/tvgCompressor.cpp @@ -468,7 +468,7 @@ size_t b64Decode(const char* encoded, const size_t len, char** decoded) encoded += 4; } *decoded = output; - return reserved; + return idx; } diff --git a/thirdparty/thorvg/src/common/tvgLines.cpp b/thirdparty/thorvg/src/common/tvgLines.cpp deleted file mode 100644 index 9d704900a50..00000000000 --- a/thirdparty/thorvg/src/common/tvgLines.cpp +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "tvgMath.h" -#include "tvgLines.h" - -#define BEZIER_EPSILON 1e-2f - -/************************************************************************/ -/* Internal Class Implementation */ -/************************************************************************/ - -static float _lineLengthApprox(const Point& pt1, const Point& pt2) -{ - /* approximate sqrt(x*x + y*y) using alpha max plus beta min algorithm. - With alpha = 1, beta = 3/8, giving results with the largest error less - than 7% compared to the exact value. */ - Point diff = {pt2.x - pt1.x, pt2.y - pt1.y}; - if (diff.x < 0) diff.x = -diff.x; - if (diff.y < 0) diff.y = -diff.y; - return (diff.x > diff.y) ? (diff.x + diff.y * 0.375f) : (diff.y + diff.x * 0.375f); -} - - -static float _lineLength(const Point& pt1, const Point& pt2) -{ - Point diff = {pt2.x - pt1.x, pt2.y - pt1.y}; - return sqrtf(diff.x * diff.x + diff.y * diff.y); -} - - -template -float _bezLength(const Bezier& cur, LengthFunc lineLengthFunc) -{ - Bezier left, right; - auto len = lineLengthFunc(cur.start, cur.ctrl1) + lineLengthFunc(cur.ctrl1, cur.ctrl2) + lineLengthFunc(cur.ctrl2, cur.end); - auto chord = lineLengthFunc(cur.start, cur.end); - - if (fabsf(len - chord) > BEZIER_EPSILON) { - tvg::bezSplit(cur, left, right); - return _bezLength(left, lineLengthFunc) + _bezLength(right, lineLengthFunc); - } - return len; -} - - -template -float _bezAt(const Bezier& bz, float at, float length, LengthFunc lineLengthFunc) -{ - auto biggest = 1.0f; - auto smallest = 0.0f; - auto t = 0.5f; - - //just in case to prevent an infinite loop - if (at <= 0) return 0.0f; - if (at >= length) return 1.0f; - - while (true) { - auto right = bz; - Bezier left; - bezSplitLeft(right, t, left); - length = _bezLength(left, lineLengthFunc); - if (fabsf(length - at) < BEZIER_EPSILON || fabsf(smallest - biggest) < BEZIER_EPSILON) { - break; - } - if (length < at) { - smallest = t; - t = (t + biggest) * 0.5f; - } else { - biggest = t; - t = (smallest + t) * 0.5f; - } - } - return t; -} - - -/************************************************************************/ -/* External Class Implementation */ -/************************************************************************/ - -namespace tvg -{ - -float lineLength(const Point& pt1, const Point& pt2) -{ - return _lineLength(pt1, pt2); -} - - -void lineSplitAt(const Line& cur, float at, Line& left, Line& right) -{ - auto len = lineLength(cur.pt1, cur.pt2); - auto dx = ((cur.pt2.x - cur.pt1.x) / len) * at; - auto dy = ((cur.pt2.y - cur.pt1.y) / len) * at; - left.pt1 = cur.pt1; - left.pt2.x = left.pt1.x + dx; - left.pt2.y = left.pt1.y + dy; - right.pt1 = left.pt2; - right.pt2 = cur.pt2; -} - - -void bezSplit(const Bezier& cur, Bezier& left, Bezier& right) -{ - auto c = (cur.ctrl1.x + cur.ctrl2.x) * 0.5f; - left.ctrl1.x = (cur.start.x + cur.ctrl1.x) * 0.5f; - right.ctrl2.x = (cur.ctrl2.x + cur.end.x) * 0.5f; - left.start.x = cur.start.x; - right.end.x = cur.end.x; - left.ctrl2.x = (left.ctrl1.x + c) * 0.5f; - right.ctrl1.x = (right.ctrl2.x + c) * 0.5f; - left.end.x = right.start.x = (left.ctrl2.x + right.ctrl1.x) * 0.5f; - - c = (cur.ctrl1.y + cur.ctrl2.y) * 0.5f; - left.ctrl1.y = (cur.start.y + cur.ctrl1.y) * 0.5f; - right.ctrl2.y = (cur.ctrl2.y + cur.end.y) * 0.5f; - left.start.y = cur.start.y; - right.end.y = cur.end.y; - left.ctrl2.y = (left.ctrl1.y + c) * 0.5f; - right.ctrl1.y = (right.ctrl2.y + c) * 0.5f; - left.end.y = right.start.y = (left.ctrl2.y + right.ctrl1.y) * 0.5f; -} - - -float bezLength(const Bezier& cur) -{ - return _bezLength(cur, _lineLength); -} - - -float bezLengthApprox(const Bezier& cur) -{ - return _bezLength(cur, _lineLengthApprox); -} - - -void bezSplitLeft(Bezier& cur, float at, Bezier& left) -{ - left.start = cur.start; - - left.ctrl1.x = cur.start.x + at * (cur.ctrl1.x - cur.start.x); - left.ctrl1.y = cur.start.y + at * (cur.ctrl1.y - cur.start.y); - - left.ctrl2.x = cur.ctrl1.x + at * (cur.ctrl2.x - cur.ctrl1.x); //temporary holding spot - left.ctrl2.y = cur.ctrl1.y + at * (cur.ctrl2.y - cur.ctrl1.y); //temporary holding spot - - cur.ctrl2.x = cur.ctrl2.x + at * (cur.end.x - cur.ctrl2.x); - cur.ctrl2.y = cur.ctrl2.y + at * (cur.end.y - cur.ctrl2.y); - - cur.ctrl1.x = left.ctrl2.x + at * (cur.ctrl2.x - left.ctrl2.x); - cur.ctrl1.y = left.ctrl2.y + at * (cur.ctrl2.y - left.ctrl2.y); - - left.ctrl2.x = left.ctrl1.x + at * (left.ctrl2.x - left.ctrl1.x); - left.ctrl2.y = left.ctrl1.y + at * (left.ctrl2.y - left.ctrl1.y); - - left.end.x = cur.start.x = left.ctrl2.x + at * (cur.ctrl1.x - left.ctrl2.x); - left.end.y = cur.start.y = left.ctrl2.y + at * (cur.ctrl1.y - left.ctrl2.y); -} - - -float bezAt(const Bezier& bz, float at, float length) -{ - return _bezAt(bz, at, length, _lineLength); -} - - -float bezAtApprox(const Bezier& bz, float at, float length) -{ - return _bezAt(bz, at, length, _lineLengthApprox); -} - - -void bezSplitAt(const Bezier& cur, float at, Bezier& left, Bezier& right) -{ - right = cur; - auto t = bezAt(right, at, bezLength(right)); - bezSplitLeft(right, t, left); -} - - -Point bezPointAt(const Bezier& bz, float t) -{ - Point cur; - auto it = 1.0f - t; - - auto ax = bz.start.x * it + bz.ctrl1.x * t; - auto bx = bz.ctrl1.x * it + bz.ctrl2.x * t; - auto cx = bz.ctrl2.x * it + bz.end.x * t; - ax = ax * it + bx * t; - bx = bx * it + cx * t; - cur.x = ax * it + bx * t; - - float ay = bz.start.y * it + bz.ctrl1.y * t; - float by = bz.ctrl1.y * it + bz.ctrl2.y * t; - float cy = bz.ctrl2.y * it + bz.end.y * t; - ay = ay * it + by * t; - by = by * it + cy * t; - cur.y = ay * it + by * t; - - return cur; -} - - -float bezAngleAt(const Bezier& bz, float t) -{ - if (t < 0 || t > 1) return 0; - - //derivate - // p'(t) = 3 * (-(1-2t+t^2) * p0 + (1 - 4 * t + 3 * t^2) * p1 + (2 * t - 3 * - // t^2) * p2 + t^2 * p3) - float mt = 1.0f - t; - float d = t * t; - float a = -mt * mt; - float b = 1 - 4 * t + 3 * d; - float c = 2 * t - 3 * d; - - Point pt ={a * bz.start.x + b * bz.ctrl1.x + c * bz.ctrl2.x + d * bz.end.x, a * bz.start.y + b * bz.ctrl1.y + c * bz.ctrl2.y + d * bz.end.y}; - pt.x *= 3; - pt.y *= 3; - - return mathRad2Deg(mathAtan2(pt.y, pt.x)); -} - - -} diff --git a/thirdparty/thorvg/src/common/tvgLines.h b/thirdparty/thorvg/src/common/tvgLines.h deleted file mode 100644 index d900782b562..00000000000 --- a/thirdparty/thorvg/src/common/tvgLines.h +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef _TVG_LINES_H_ -#define _TVG_LINES_H_ - -#include "tvgCommon.h" - -namespace tvg -{ - -struct Line -{ - Point pt1; - Point pt2; -}; - -float lineLength(const Point& pt1, const Point& pt2); -void lineSplitAt(const Line& cur, float at, Line& left, Line& right); - - -struct Bezier -{ - Point start; - Point ctrl1; - Point ctrl2; - Point end; -}; - -void bezSplit(const Bezier&cur, Bezier& left, Bezier& right); -float bezLength(const Bezier& cur); -void bezSplitLeft(Bezier& cur, float at, Bezier& left); -float bezAt(const Bezier& bz, float at, float length); -void bezSplitAt(const Bezier& cur, float at, Bezier& left, Bezier& right); -Point bezPointAt(const Bezier& bz, float t); -float bezAngleAt(const Bezier& bz, float t); - -float bezLengthApprox(const Bezier& cur); -float bezAtApprox(const Bezier& bz, float at, float length); -} - -#endif //_TVG_LINES_H_ diff --git a/thirdparty/thorvg/src/common/tvgMath.cpp b/thirdparty/thorvg/src/common/tvgMath.cpp index c03b54e5f8b..f27f69faebb 100644 --- a/thirdparty/thorvg/src/common/tvgMath.cpp +++ b/thirdparty/thorvg/src/common/tvgMath.cpp @@ -22,11 +22,88 @@ #include "tvgMath.h" -//see: https://en.wikipedia.org/wiki/Remez_algorithm -float mathAtan2(float y, float x) +#define BEZIER_EPSILON 1e-2f + + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +static float _lineLengthApprox(const Point& pt1, const Point& pt2) { - if (y == 0.0f && x == 0.0f) return 0.0f; + /* approximate sqrt(x*x + y*y) using alpha max plus beta min algorithm. + With alpha = 1, beta = 3/8, giving results with the largest error less + than 7% compared to the exact value. */ + Point diff = {pt2.x - pt1.x, pt2.y - pt1.y}; + if (diff.x < 0) diff.x = -diff.x; + if (diff.y < 0) diff.y = -diff.y; + return (diff.x > diff.y) ? (diff.x + diff.y * 0.375f) : (diff.y + diff.x * 0.375f); +} + + +static float _lineLength(const Point& pt1, const Point& pt2) +{ + Point diff = {pt2.x - pt1.x, pt2.y - pt1.y}; + return sqrtf(diff.x * diff.x + diff.y * diff.y); +} + + +template +float _bezLength(const Bezier& cur, LengthFunc lineLengthFunc) +{ + Bezier left, right; + auto len = lineLengthFunc(cur.start, cur.ctrl1) + lineLengthFunc(cur.ctrl1, cur.ctrl2) + lineLengthFunc(cur.ctrl2, cur.end); + auto chord = lineLengthFunc(cur.start, cur.end); + + if (fabsf(len - chord) > BEZIER_EPSILON) { + cur.split(left, right); + return _bezLength(left, lineLengthFunc) + _bezLength(right, lineLengthFunc); + } + return len; +} + + +template +float _bezAt(const Bezier& bz, float at, float length, LengthFunc lineLengthFunc) +{ + auto biggest = 1.0f; + auto smallest = 0.0f; + auto t = 0.5f; + + //just in case to prevent an infinite loop + if (at <= 0) return 0.0f; + if (at >= length) return 1.0f; + + while (true) { + auto right = bz; + Bezier left; + right.split(t, left); + length = _bezLength(left, lineLengthFunc); + if (fabsf(length - at) < BEZIER_EPSILON || fabsf(smallest - biggest) < BEZIER_EPSILON) { + break; + } + if (length < at) { + smallest = t; + t = (t + biggest) * 0.5f; + } else { + biggest = t; + t = (smallest + t) * 0.5f; + } + } + return t; +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ +namespace tvg { + +//https://en.wikipedia.org/wiki/Remez_algorithm +float atan2(float y, float x) +{ + if (y == 0.0f && x == 0.0f) return 0.0f; auto a = std::min(fabsf(x), fabsf(y)) / std::max(fabsf(x), fabsf(y)); auto s = a * a; auto r = ((-0.0464964749f * s + 0.15931422f) * s - 0.327622764f) * s * a + a; @@ -37,7 +114,7 @@ float mathAtan2(float y, float x) } -bool mathInverse(const Matrix* m, Matrix* out) +bool inverse(const Matrix* m, Matrix* out) { auto det = m->e11 * (m->e22 * m->e33 - m->e32 * m->e23) - m->e12 * (m->e21 * m->e33 - m->e23 * m->e31) + @@ -60,7 +137,7 @@ bool mathInverse(const Matrix* m, Matrix* out) } -bool mathIdentity(const Matrix* m) +bool identity(const Matrix* m) { if (m->e11 != 1.0f || m->e12 != 0.0f || m->e13 != 0.0f || m->e21 != 0.0f || m->e22 != 1.0f || m->e23 != 0.0f || @@ -71,7 +148,7 @@ bool mathIdentity(const Matrix* m) } -void mathRotate(Matrix* m, float degree) +void rotate(Matrix* m, float degree) { if (degree == 0.0f) return; @@ -108,9 +185,9 @@ Matrix operator*(const Matrix& lhs, const Matrix& rhs) bool operator==(const Matrix& lhs, const Matrix& rhs) { - if (!mathEqual(lhs.e11, rhs.e11) || !mathEqual(lhs.e12, rhs.e12) || !mathEqual(lhs.e13, rhs.e13) || - !mathEqual(lhs.e21, rhs.e21) || !mathEqual(lhs.e22, rhs.e22) || !mathEqual(lhs.e23, rhs.e23) || - !mathEqual(lhs.e31, rhs.e31) || !mathEqual(lhs.e32, rhs.e32) || !mathEqual(lhs.e33, rhs.e33)) { + if (!tvg::equal(lhs.e11, rhs.e11) || !tvg::equal(lhs.e12, rhs.e12) || !tvg::equal(lhs.e13, rhs.e13) || + !tvg::equal(lhs.e21, rhs.e21) || !tvg::equal(lhs.e22, rhs.e22) || !tvg::equal(lhs.e23, rhs.e23) || + !tvg::equal(lhs.e31, rhs.e31) || !tvg::equal(lhs.e32, rhs.e32) || !tvg::equal(lhs.e33, rhs.e33)) { return false; } return true; @@ -133,9 +210,165 @@ Point operator*(const Point& pt, const Matrix& m) return {tx, ty}; } -uint8_t mathLerp(const uint8_t &start, const uint8_t &end, float t) + +Point normal(const Point& p1, const Point& p2) +{ + auto dir = p2 - p1; + auto len = length(dir); + if (tvg::zero(len)) return {}; + + auto unitDir = dir / len; + return {-unitDir.y, unitDir.x}; +} + + +float Line::length() const +{ + return _lineLength(pt1, pt2); +} + + +void Line::split(float at, Line& left, Line& right) const +{ + auto len = length(); + auto dx = ((pt2.x - pt1.x) / len) * at; + auto dy = ((pt2.y - pt1.y) / len) * at; + left.pt1 = pt1; + left.pt2.x = left.pt1.x + dx; + left.pt2.y = left.pt1.y + dy; + right.pt1 = left.pt2; + right.pt2 = pt2; +} + + +void Bezier::split(Bezier& left, Bezier& right) const +{ + auto c = (ctrl1.x + ctrl2.x) * 0.5f; + left.ctrl1.x = (start.x + ctrl1.x) * 0.5f; + right.ctrl2.x = (ctrl2.x + end.x) * 0.5f; + left.start.x = start.x; + right.end.x = end.x; + left.ctrl2.x = (left.ctrl1.x + c) * 0.5f; + right.ctrl1.x = (right.ctrl2.x + c) * 0.5f; + left.end.x = right.start.x = (left.ctrl2.x + right.ctrl1.x) * 0.5f; + + c = (ctrl1.y + ctrl2.y) * 0.5f; + left.ctrl1.y = (start.y + ctrl1.y) * 0.5f; + right.ctrl2.y = (ctrl2.y + end.y) * 0.5f; + left.start.y = start.y; + right.end.y = end.y; + left.ctrl2.y = (left.ctrl1.y + c) * 0.5f; + right.ctrl1.y = (right.ctrl2.y + c) * 0.5f; + left.end.y = right.start.y = (left.ctrl2.y + right.ctrl1.y) * 0.5f; +} + + +void Bezier::split(float at, Bezier& left, Bezier& right) const +{ + right = *this; + auto t = right.at(at, right.length()); + right.split(t, left); +} + + +float Bezier::length() const +{ + return _bezLength(*this, _lineLength); +} + + +float Bezier::lengthApprox() const +{ + return _bezLength(*this, _lineLengthApprox); +} + + +void Bezier::split(float t, Bezier& left) +{ + left.start = start; + + left.ctrl1.x = start.x + t * (ctrl1.x - start.x); + left.ctrl1.y = start.y + t * (ctrl1.y - start.y); + + left.ctrl2.x = ctrl1.x + t * (ctrl2.x - ctrl1.x); //temporary holding spot + left.ctrl2.y = ctrl1.y + t * (ctrl2.y - ctrl1.y); //temporary holding spot + + ctrl2.x = ctrl2.x + t * (end.x - ctrl2.x); + ctrl2.y = ctrl2.y + t * (end.y - ctrl2.y); + + ctrl1.x = left.ctrl2.x + t * (ctrl2.x - left.ctrl2.x); + ctrl1.y = left.ctrl2.y + t * (ctrl2.y - left.ctrl2.y); + + left.ctrl2.x = left.ctrl1.x + t * (left.ctrl2.x - left.ctrl1.x); + left.ctrl2.y = left.ctrl1.y + t * (left.ctrl2.y - left.ctrl1.y); + + left.end.x = start.x = left.ctrl2.x + t * (ctrl1.x - left.ctrl2.x); + left.end.y = start.y = left.ctrl2.y + t * (ctrl1.y - left.ctrl2.y); +} + + +float Bezier::at(float at, float length) const +{ + return _bezAt(*this, at, length, _lineLength); +} + + +float Bezier::atApprox(float at, float length) const +{ + return _bezAt(*this, at, length, _lineLengthApprox); +} + + +Point Bezier::at(float t) const +{ + Point cur; + auto it = 1.0f - t; + + auto ax = start.x * it + ctrl1.x * t; + auto bx = ctrl1.x * it + ctrl2.x * t; + auto cx = ctrl2.x * it + end.x * t; + ax = ax * it + bx * t; + bx = bx * it + cx * t; + cur.x = ax * it + bx * t; + + float ay = start.y * it + ctrl1.y * t; + float by = ctrl1.y * it + ctrl2.y * t; + float cy = ctrl2.y * it + end.y * t; + ay = ay * it + by * t; + by = by * it + cy * t; + cur.y = ay * it + by * t; + + return cur; +} + + +float Bezier::angle(float t) const +{ + if (t < 0 || t > 1) return 0; + + //derivate + // p'(t) = 3 * (-(1-2t+t^2) * p0 + (1 - 4 * t + 3 * t^2) * p1 + (2 * t - 3 * + // t^2) * p2 + t^2 * p3) + float mt = 1.0f - t; + float d = t * t; + float a = -mt * mt; + float b = 1 - 4 * t + 3 * d; + float c = 2 * t - 3 * d; + + Point pt ={a * start.x + b * ctrl1.x + c * ctrl2.x + d * end.x, a * start.y + b * ctrl1.y + c * ctrl2.y + d * end.y}; + pt.x *= 3; + pt.y *= 3; + + return rad2deg(tvg::atan2(pt.y, pt.x)); +} + + +uint8_t lerp(const uint8_t &start, const uint8_t &end, float t) { auto result = static_cast(start + (end - start) * t); - mathClamp(result, 0, 255); + tvg::clamp(result, 0, 255); return static_cast(result); } + +} + diff --git a/thirdparty/thorvg/src/common/tvgMath.h b/thirdparty/thorvg/src/common/tvgMath.h index 50786754a15..a917998256f 100644 --- a/thirdparty/thorvg/src/common/tvgMath.h +++ b/thirdparty/thorvg/src/common/tvgMath.h @@ -29,47 +29,47 @@ #include #include "tvgCommon.h" +namespace tvg +{ + #define MATH_PI 3.14159265358979323846f #define MATH_PI2 1.57079632679489661923f #define FLOAT_EPSILON 1.0e-06f //1.192092896e-07f #define PATH_KAPPA 0.552284f -#define mathMin(x, y) (((x) < (y)) ? (x) : (y)) -#define mathMax(x, y) (((x) > (y)) ? (x) : (y)) - - /************************************************************************/ /* General functions */ /************************************************************************/ -float mathAtan2(float y, float x); +float atan2(float y, float x); -static inline float mathDeg2Rad(float degree) + +static inline float deg2rad(float degree) { return degree * (MATH_PI / 180.0f); } -static inline float mathRad2Deg(float radian) +static inline float rad2deg(float radian) { return radian * (180.0f / MATH_PI); } -static inline bool mathZero(float a) +static inline bool zero(float a) { return (fabsf(a) <= FLOAT_EPSILON) ? true : false; } -static inline bool mathEqual(float a, float b) +static inline bool equal(float a, float b) { - return mathZero(a - b); + return tvg::zero(a - b); } template -static inline void mathClamp(T& v, const T& min, const T& max) +static inline void clamp(T& v, const T& min, const T& max) { if (v < min) v = min; else if (v > max) v = max; @@ -79,27 +79,27 @@ static inline void mathClamp(T& v, const T& min, const T& max) /* Matrix functions */ /************************************************************************/ -void mathRotate(Matrix* m, float degree); -bool mathInverse(const Matrix* m, Matrix* out); -bool mathIdentity(const Matrix* m); +void rotate(Matrix* m, float degree); +bool inverse(const Matrix* m, Matrix* out); +bool identity(const Matrix* m); Matrix operator*(const Matrix& lhs, const Matrix& rhs); bool operator==(const Matrix& lhs, const Matrix& rhs); -static inline bool mathRightAngle(const Matrix& m) +static inline bool rightAngle(const Matrix& m) { - auto radian = fabsf(mathAtan2(m.e21, m.e11)); - if (radian < FLOAT_EPSILON || mathEqual(radian, MATH_PI2) || mathEqual(radian, MATH_PI)) return true; + auto radian = fabsf(tvg::atan2(m.e21, m.e11)); + if (radian < FLOAT_EPSILON || tvg::equal(radian, MATH_PI2) || tvg::equal(radian, MATH_PI)) return true; return false; } -static inline bool mathSkewed(const Matrix& m) +static inline bool skewed(const Matrix& m) { - return !mathZero(m.e21 + m.e12); + return !tvg::zero(m.e21 + m.e12); } -static inline void mathIdentity(Matrix* m) +static inline void identity(Matrix* m) { m->e11 = 1.0f; m->e12 = 0.0f; @@ -113,14 +113,14 @@ static inline void mathIdentity(Matrix* m) } -static inline void mathScale(Matrix* m, float sx, float sy) +static inline void scale(Matrix* m, float sx, float sy) { m->e11 *= sx; m->e22 *= sy; } -static inline void mathScaleR(Matrix* m, float x, float y) +static inline void scaleR(Matrix* m, float x, float y) { if (x != 1.0f) { m->e11 *= x; @@ -133,14 +133,14 @@ static inline void mathScaleR(Matrix* m, float x, float y) } -static inline void mathTranslate(Matrix* m, float x, float y) +static inline void translate(Matrix* m, float x, float y) { m->e13 += x; m->e23 += y; } -static inline void mathTranslateR(Matrix* m, float x, float y) +static inline void translateR(Matrix* m, float x, float y) { if (x == 0.0f && y == 0.0f) return; m->e13 += (x * m->e11 + y * m->e12); @@ -160,7 +160,7 @@ static inline void operator*=(Matrix& lhs, const Matrix& rhs) } -static inline void mathLog(const Matrix& m) +static inline void log(const Matrix& m) { TVGLOG("COMMON", "Matrix: [%f %f %f] [%f %f %f] [%f %f %f]", m.e11, m.e12, m.e13, m.e21, m.e22, m.e23, m.e31, m.e32, m.e33); } @@ -172,15 +172,21 @@ static inline void mathLog(const Matrix& m) void operator*=(Point& pt, const Matrix& m); Point operator*(const Point& pt, const Matrix& m); +Point normal(const Point& p1, const Point& p2); +static inline float cross(const Point& lhs, const Point& rhs) +{ + return lhs.x * rhs.y - rhs.x * lhs.y; +} -static inline bool mathZero(const Point& p) + +static inline bool zero(const Point& p) { - return mathZero(p.x) && mathZero(p.y); + return tvg::zero(p.x) && tvg::zero(p.y); } -static inline float mathLength(const Point* a, const Point* b) +static inline float length(const Point* a, const Point* b) { auto x = b->x - a->x; auto y = b->y - a->y; @@ -192,7 +198,7 @@ static inline float mathLength(const Point* a, const Point* b) } -static inline float mathLength(const Point& a) +static inline float length(const Point& a) { return sqrtf(a.x * a.x + a.y * a.y); } @@ -200,7 +206,7 @@ static inline float mathLength(const Point& a) static inline bool operator==(const Point& lhs, const Point& rhs) { - return mathEqual(lhs.x, rhs.x) && mathEqual(lhs.y, rhs.y); + return tvg::equal(lhs.x, rhs.x) && tvg::equal(lhs.y, rhs.y); } @@ -240,32 +246,61 @@ static inline Point operator/(const Point& lhs, const float rhs) } -static inline Point mathNormal(const Point& p1, const Point& p2) +static inline void log(const Point& pt) { - auto dir = p2 - p1; - auto len = mathLength(dir); - if (mathZero(len)) return {}; - - auto unitDir = dir / len; - return {-unitDir.y, unitDir.x}; + TVGLOG("COMMON", "Point: [%f %f]", pt.x, pt.y); } -static inline void mathLog(const Point& pt) +/************************************************************************/ +/* Line functions */ +/************************************************************************/ + +struct Line { - TVGLOG("COMMON", "Point: [%f %f]", pt.x, pt.y); -} + Point pt1; + Point pt2; + + void split(float at, Line& left, Line& right) const; + float length() const; +}; + + +/************************************************************************/ +/* Bezier functions */ +/************************************************************************/ + +struct Bezier +{ + Point start; + Point ctrl1; + Point ctrl2; + Point end; + + void split(float t, Bezier& left); + void split(Bezier& left, Bezier& right) const; + void split(float at, Bezier& left, Bezier& right) const; + float length() const; + float lengthApprox() const; + float at(float at, float length) const; + float atApprox(float at, float length) const; + Point at(float t) const; + float angle(float t) const; +}; + /************************************************************************/ /* Interpolation functions */ /************************************************************************/ template -static inline T mathLerp(const T &start, const T &end, float t) +static inline T lerp(const T &start, const T &end, float t) { return static_cast(start + (end - start) * t); } -uint8_t mathLerp(const uint8_t &start, const uint8_t &end, float t); +uint8_t lerp(const uint8_t &start, const uint8_t &end, float t); + +} #endif //_TVG_MATH_H_ diff --git a/thirdparty/thorvg/src/common/tvgStr.cpp b/thirdparty/thorvg/src/common/tvgStr.cpp index 3f189668096..1ebdd41c5e9 100644 --- a/thirdparty/thorvg/src/common/tvgStr.cpp +++ b/thirdparty/thorvg/src/common/tvgStr.cpp @@ -207,16 +207,6 @@ float strToFloat(const char *nPtr, char **endPtr) return 0.0f; } - -int str2int(const char* str, size_t n) -{ - int ret = 0; - for(size_t i = 0; i < n; ++i) { - ret = ret * 10 + (str[i] - '0'); - } - return ret; -} - char* strDuplicate(const char *str, size_t n) { auto len = strlen(str); diff --git a/thirdparty/thorvg/src/common/tvgStr.h b/thirdparty/thorvg/src/common/tvgStr.h index 56fdf86fb2d..6b16b4c7610 100644 --- a/thirdparty/thorvg/src/common/tvgStr.h +++ b/thirdparty/thorvg/src/common/tvgStr.h @@ -29,7 +29,6 @@ namespace tvg { float strToFloat(const char *nPtr, char **endPtr); //convert to float -int str2int(const char* str, size_t n); //convert to integer char* strDuplicate(const char *str, size_t n); //copy the string char* strAppend(char* lhs, const char* rhs, size_t n); //append the rhs to the lhs char* strDirname(const char* path); //return the full directory name diff --git a/thirdparty/thorvg/src/loaders/external_png/tvgPngLoader.cpp b/thirdparty/thorvg/src/loaders/external_png/tvgPngLoader.cpp index 6c4c8410e82..49c9f6e8aa2 100644 --- a/thirdparty/thorvg/src/loaders/external_png/tvgPngLoader.cpp +++ b/thirdparty/thorvg/src/loaders/external_png/tvgPngLoader.cpp @@ -86,10 +86,10 @@ bool PngLoader::read() if (cs == ColorSpace::ARGB8888 || cs == ColorSpace::ARGB8888S) { image->format = PNG_FORMAT_BGRA; - surface.cs = ColorSpace::ARGB8888; + surface.cs = ColorSpace::ARGB8888S; } else { image->format = PNG_FORMAT_RGBA; - surface.cs = ColorSpace::ABGR8888; + surface.cs = ColorSpace::ABGR8888S; } auto buffer = static_cast(malloc(PNG_IMAGE_SIZE((*image)))); diff --git a/thirdparty/thorvg/src/loaders/external_webp/tvgWebpLoader.cpp b/thirdparty/thorvg/src/loaders/external_webp/tvgWebpLoader.cpp index fbaaea87431..0db7d2d233f 100644 --- a/thirdparty/thorvg/src/loaders/external_webp/tvgWebpLoader.cpp +++ b/thirdparty/thorvg/src/loaders/external_webp/tvgWebpLoader.cpp @@ -134,7 +134,7 @@ bool WebpLoader::read() } -Surface* WebpLoader::bitmap() +RenderSurface* WebpLoader::bitmap() { this->done(); diff --git a/thirdparty/thorvg/src/loaders/external_webp/tvgWebpLoader.h b/thirdparty/thorvg/src/loaders/external_webp/tvgWebpLoader.h index ad8fb5a6995..3d44edac3a1 100644 --- a/thirdparty/thorvg/src/loaders/external_webp/tvgWebpLoader.h +++ b/thirdparty/thorvg/src/loaders/external_webp/tvgWebpLoader.h @@ -36,7 +36,7 @@ class WebpLoader : public ImageLoader, public Task bool open(const char* data, uint32_t size, bool copy) override; bool read() override; - Surface* bitmap() override; + RenderSurface* bitmap() override; private: void run(unsigned tid) override; diff --git a/thirdparty/thorvg/src/loaders/jpg/tvgJpgLoader.cpp b/thirdparty/thorvg/src/loaders/jpg/tvgJpgLoader.cpp index 86a46245d98..cb1306b71a3 100644 --- a/thirdparty/thorvg/src/loaders/jpg/tvgJpgLoader.cpp +++ b/thirdparty/thorvg/src/loaders/jpg/tvgJpgLoader.cpp @@ -125,7 +125,7 @@ bool JpgLoader::close() } -Surface* JpgLoader::bitmap() +RenderSurface* JpgLoader::bitmap() { this->done(); return ImageLoader::bitmap(); diff --git a/thirdparty/thorvg/src/loaders/jpg/tvgJpgLoader.h b/thirdparty/thorvg/src/loaders/jpg/tvgJpgLoader.h index 05cbb54c852..85f6d25dc39 100644 --- a/thirdparty/thorvg/src/loaders/jpg/tvgJpgLoader.h +++ b/thirdparty/thorvg/src/loaders/jpg/tvgJpgLoader.h @@ -46,7 +46,7 @@ class JpgLoader : public ImageLoader, public Task bool read() override; bool close() override; - Surface* bitmap() override; + RenderSurface* bitmap() override; }; #endif //_TVG_JPG_LOADER_H_ diff --git a/thirdparty/thorvg/src/loaders/jpg/tvgJpgd.cpp b/thirdparty/thorvg/src/loaders/jpg/tvgJpgd.cpp index 6f2bd916d5e..92e23698af2 100644 --- a/thirdparty/thorvg/src/loaders/jpg/tvgJpgd.cpp +++ b/thirdparty/thorvg/src/loaders/jpg/tvgJpgd.cpp @@ -36,6 +36,8 @@ #include #include #include + +#include "tvgCommon.h" #include "tvgJpgd.h" #ifdef _MSC_VER @@ -70,7 +72,7 @@ enum jpgd_status JPGD_BAD_DHT_COUNTS = -256, JPGD_BAD_DHT_INDEX, JPGD_BAD_DHT_MARKER, JPGD_BAD_DQT_MARKER, JPGD_BAD_DQT_TABLE, JPGD_BAD_PRECISION, JPGD_BAD_HEIGHT, JPGD_BAD_WIDTH, JPGD_TOO_MANY_COMPONENTS, JPGD_BAD_SOF_LENGTH, JPGD_BAD_VARIABLE_MARKER, JPGD_BAD_DRI_LENGTH, JPGD_BAD_SOS_LENGTH, - JPGD_BAD_SOS_COMP_ID, JPGD_W_EXTRA_BYTES_BEFORE_MARKER, JPGD_NO_ARITHMITIC_SUPPORT, JPGD_UNEXPECTED_MARKER, + JPGD_BAD_SOS_COMP_ID, JPGD_W_EXTRA_BYTES_BEFORE_MARKER, JPGD_NO_ARITHMETIC_SUPPORT, JPGD_UNEXPECTED_MARKER, JPGD_NOT_JPEG, JPGD_UNSUPPORTED_MARKER, JPGD_BAD_DQT_LENGTH, JPGD_TOO_MANY_BLOCKS, JPGD_UNDEFINED_QUANT_TABLE, JPGD_UNDEFINED_HUFF_TABLE, JPGD_NOT_SINGLE_SCAN, JPGD_UNSUPPORTED_COLORSPACE, JPGD_UNSUPPORTED_SAMP_FACTORS, JPGD_DECODE_ERROR, JPGD_BAD_RESTART_MARKER, JPGD_ASSERTION_ERROR, @@ -1382,9 +1384,9 @@ int jpeg_decoder::process_markers() read_dht_marker(); break; } - // No arithmitic support - dumb patents! + // No arithmetic support - dumb patents! case M_DAC: { - stop_decoding(JPGD_NO_ARITHMITIC_SUPPORT); + stop_decoding(JPGD_NO_ARITHMETIC_SUPPORT); break; } case M_DQT: { @@ -1466,8 +1468,8 @@ void jpeg_decoder::locate_sof_marker() read_sof_marker(); break; } - case M_SOF9: { /* Arithmitic coding */ - stop_decoding(JPGD_NO_ARITHMITIC_SUPPORT); + case M_SOF9: { /* Arithmetic coding */ + stop_decoding(JPGD_NO_ARITHMETIC_SUPPORT); break; } default: { @@ -1738,7 +1740,8 @@ void jpeg_decoder::transform_mcu_expand(int mcu_row) DCT_Upsample::R_S<8, 8>::calc(R, S, pSrc_ptr); break; default: - JPGD_ASSERT(false); + TVGERR("JPG", "invalid transform_mcu_expand"); + return; } DCT_Upsample::Matrix44 a(P + Q); P -= Q; DCT_Upsample::Matrix44& b = P; @@ -1831,7 +1834,7 @@ void jpeg_decoder::process_restart() int i; int c = 0; - // Align to a byte boundry + // Align to a byte boundary // FIXME: Is this really necessary? get_bits_no_markers() never reads in markers! //get_bits_no_markers(m_bits_left & 7); diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp index 492cebe3a31..1ab3043c24e 100644 --- a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp +++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp @@ -177,6 +177,7 @@ static float _toFloat(const SvgParser* svgParse, const char* str, SvgParserLengt else if (strstr(str, "%")) { if (type == SvgParserLengthType::Vertical) parsedValue = (parsedValue / 100.0f) * svgParse->global.h; else if (type == SvgParserLengthType::Horizontal) parsedValue = (parsedValue / 100.0f) * svgParse->global.w; + else if (type == SvgParserLengthType::Diagonal) parsedValue = (sqrtf(powf(svgParse->global.w, 2) + powf(svgParse->global.h, 2)) / sqrtf(2.0f)) * (parsedValue / 100.0f); else //if other than it's radius { float max = svgParse->global.w; @@ -593,15 +594,15 @@ static bool _hslToRgb(float hue, float saturation, float brightness, uint8_t* re saturation = saturation > 0 ? std::min(saturation, 1.0f) : 0.0f; brightness = brightness > 0 ? std::min(brightness, 1.0f) : 0.0f; - if (mathZero(saturation)) _red = _green = _blue = brightness; + if (tvg::zero(saturation)) _red = _green = _blue = brightness; else { - if (mathEqual(hue, 360.0)) hue = 0.0f; + if (tvg::equal(hue, 360.0)) hue = 0.0f; hue /= 60.0f; v = (brightness <= 0.5f) ? (brightness * (1.0f + saturation)) : (brightness + saturation - (brightness * saturation)); p = brightness + brightness - v; - if (!mathZero(v)) sv = (v - p) / v; + if (!tvg::zero(v)) sv = (v - p) / v; else sv = 0; i = static_cast(hue); @@ -858,8 +859,8 @@ static Matrix* _parseTransformationMatrix(const char* value) //Transform to signed. points[0] = fmodf(points[0], 360.0f); if (points[0] < 0) points[0] += 360.0f; - auto c = cosf(mathDeg2Rad(points[0])); - auto s = sinf(mathDeg2Rad(points[0])); + auto c = cosf(deg2rad(points[0])); + auto s = sinf(deg2rad(points[0])); if (ptCount == 1) { Matrix tmp = { c, -s, 0, s, c, 0, 0, 0, 1 }; *matrix *= tmp; @@ -882,12 +883,12 @@ static Matrix* _parseTransformationMatrix(const char* value) *matrix *= tmp; } else if (state == MatrixState::SkewX) { if (ptCount != 1) goto error; - auto deg = tanf(mathDeg2Rad(points[0])); + auto deg = tanf(deg2rad(points[0])); Matrix tmp = { 1, deg, 0, 0, 1, 0, 0, 0, 1 }; *matrix *= tmp; } else if (state == MatrixState::SkewY) { if (ptCount != 1) goto error; - auto deg = tanf(mathDeg2Rad(points[0])); + auto deg = tanf(deg2rad(points[0])); Matrix tmp = { 1, 0, 0, deg, 1, 0, 0, 0, 1 }; *matrix *= tmp; } @@ -1066,7 +1067,7 @@ static void _handleStrokeDashOffsetAttr(SvgLoaderData* loader, SvgNode* node, co static void _handleStrokeWidthAttr(SvgLoaderData* loader, SvgNode* node, const char* value) { node->style->stroke.flags = (node->style->stroke.flags | SvgStrokeFlags::Width); - node->style->stroke.width = _toFloat(loader->svgParse, value, SvgParserLengthType::Horizontal); + node->style->stroke.width = _toFloat(loader->svgParse, value, SvgParserLengthType::Diagonal); } @@ -1614,7 +1615,7 @@ static constexpr struct } circleTags[] = { {"cx", SvgParserLengthType::Horizontal, sizeof("cx"), offsetof(SvgCircleNode, cx)}, {"cy", SvgParserLengthType::Vertical, sizeof("cy"), offsetof(SvgCircleNode, cy)}, - {"r", SvgParserLengthType::Other, sizeof("r"), offsetof(SvgCircleNode, r)} + {"r", SvgParserLengthType::Diagonal, sizeof("r"), offsetof(SvgCircleNode, r)} }; @@ -2554,6 +2555,18 @@ static SvgStyleGradient* _createRadialGradient(SvgLoaderData* loader, const char } +static SvgColor* _findLatestColor(const SvgLoaderData* loader) +{ + auto parent = loader->stack.count > 0 ? loader->stack.last() : loader->doc; + + while (parent != nullptr) { + if (parent->style->curColorSet) return &parent->style->color; + parent = parent->parent; + } + return nullptr; +} + + static bool _attrParseStopsStyle(void* data, const char* key, const char* value) { SvgLoaderData* loader = (SvgLoaderData*)data; @@ -2563,7 +2576,13 @@ static bool _attrParseStopsStyle(void* data, const char* key, const char* value) stop->a = _toOpacity(value); loader->svgParse->flags = (loader->svgParse->flags | SvgStopStyleFlags::StopOpacity); } else if (!strcmp(key, "stop-color")) { - if (_toColor(value, &stop->r, &stop->g, &stop->b, nullptr)) { + if (!strcmp(value, "currentColor")) { + if (auto latestColor = _findLatestColor(loader)) { + stop->r = latestColor->r; + stop->g = latestColor->g; + stop->b = latestColor->b; + } + } else if (_toColor(value, &stop->r, &stop->g, &stop->b, nullptr)) { loader->svgParse->flags = (loader->svgParse->flags | SvgStopStyleFlags::StopColor); } } else { @@ -2586,7 +2605,13 @@ static bool _attrParseStops(void* data, const char* key, const char* value) stop->a = _toOpacity(value); } } else if (!strcmp(key, "stop-color")) { - if (!(loader->svgParse->flags & SvgStopStyleFlags::StopColor)) { + if (!strcmp(value, "currentColor")) { + if (auto latestColor = _findLatestColor(loader)) { + stop->r = latestColor->r; + stop->g = latestColor->g; + stop->b = latestColor->b; + } + } else if (!(loader->svgParse->flags & SvgStopStyleFlags::StopColor)) { _toColor(value, &stop->r, &stop->g, &stop->b, nullptr); } } else if (!strcmp(key, "style")) { @@ -3341,7 +3366,7 @@ static void _svgLoaderParserXmlOpen(SvgLoaderData* loader, const char* content, else parent = loader->doc; if (!strcmp(tagName, "style")) { // TODO: For now only the first style node is saved. After the css id selector - // is introduced this if condition shouldin't be necessary any more + // is introduced this if condition shouldn't be necessary any more if (!loader->cssStyle) { node = method(loader, nullptr, attrs, attrsLength, simpleXmlParseAttributes); loader->cssStyle = node; diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoaderCommon.h b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoaderCommon.h index 5661c8ae82c..e64d7afb411 100644 --- a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoaderCommon.h +++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoaderCommon.h @@ -215,6 +215,7 @@ enum class SvgParserLengthType { Vertical, Horizontal, + Diagonal, //In case of, for example, radius of radial gradient Other }; diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp index 115e81aee12..57442139cdf 100644 --- a/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp +++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp @@ -126,7 +126,7 @@ void _pathAppendArcTo(Array* cmds, Array* pts, Point* cur, P rx = fabsf(rx); ry = fabsf(ry); - angle = mathDeg2Rad(angle); + angle = deg2rad(angle); cosPhi = cosf(angle); sinPhi = sinf(angle); dx2 = (sx - x) / 2.0f; @@ -190,14 +190,14 @@ void _pathAppendArcTo(Array* cmds, Array* pts, Point* cur, P cx += (sx + x) / 2.0f; cy += (sy + y) / 2.0f; - //Sstep 4 (F6.5.4) + //Step 4 (F6.5.4) //We dont' use arccos (as per w3c doc), see //http://www.euclideanspace.com/maths/algebra/vectors/angleBetween/index.htm //Note: atan2 (0.0, 1.0) == 0.0 - at = mathAtan2(((y1p - cyp) / ry), ((x1p - cxp) / rx)); + at = tvg::atan2(((y1p - cyp) / ry), ((x1p - cxp) / rx)); theta1 = (at < 0.0f) ? 2.0f * MATH_PI + at : at; - nat = mathAtan2(((-y1p - cyp) / ry), ((-x1p - cxp) / rx)); + nat = tvg::atan2(((-y1p - cyp) / ry), ((-x1p - cxp) / rx)); deltaTheta = (nat < at) ? 2.0f * MATH_PI - at + nat : nat - at; if (sweep) { @@ -469,12 +469,12 @@ static bool _processCommand(Array* cmds, Array* pts, char cm } case 'a': case 'A': { - if (mathZero(arr[0]) || mathZero(arr[1])) { + if (tvg::zero(arr[0]) || tvg::zero(arr[1])) { Point p = {arr[5], arr[6]}; cmds->push(PathCommand::LineTo); pts->push(p); *cur = {arr[5], arr[6]}; - } else if (!mathEqual(cur->x, arr[5]) || !mathEqual(cur->y, arr[6])) { + } else if (!tvg::equal(cur->x, arr[5]) || !tvg::equal(cur->y, arr[6])) { _pathAppendArcTo(cmds, pts, cur, curCtl, arr[5], arr[6], fabsf(arr[0]), fabsf(arr[1]), arr[2], arr[3], arr[4]); *cur = *curCtl = {arr[5], arr[6]}; *isQuadratic = false; diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp index b048695a234..00c7408a7e8 100644 --- a/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp +++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp @@ -153,7 +153,7 @@ static unique_ptr _applyRadialGradientProperty(SvgStyleGradient* if (isTransform) finalTransform = *g->transform; if (g->userSpace) { - //The radius scalling is done according to the Units section: + //The radius scaling is done according to the Units section: //https://www.w3.org/TR/2015/WD-SVG2-20150915/coords.html g->radial->cx = g->radial->cx * vBox.w; g->radial->cy = g->radial->cy * vBox.h; @@ -215,7 +215,7 @@ static bool _appendClipUseNode(SvgLoaderData& loaderData, SvgNode* node, Shape* } if (child->transform) finalTransform = *child->transform * finalTransform; - return _appendClipShape(loaderData, child, shape, vBox, svgPath, mathIdentity((const Matrix*)(&finalTransform)) ? nullptr : &finalTransform); + return _appendClipShape(loaderData, child, shape, vBox, svgPath, identity((const Matrix*)(&finalTransform)) ? nullptr : &finalTransform); } @@ -272,8 +272,7 @@ static void _applyComposition(SvgLoaderData& loaderData, Paint* paint, const Svg if (valid) { Matrix finalTransform = _compositionTransform(paint, node, compNode, SvgNodeType::ClipPath); comp->transform(finalTransform); - - paint->composite(std::move(comp), CompositeMethod::ClipPath); + paint->clip(std::move(comp)); } node->style->clipPath.applying = false; @@ -714,7 +713,6 @@ static Matrix _calculateAspectRatioMatrix(AspectRatioAlign align, AspectRatioMee static unique_ptr _useBuildHelper(SvgLoaderData& loaderData, const SvgNode* node, const Box& vBox, const string& svgPath, int depth, bool* isMaskWhite) { - unique_ptr finalScene; auto scene = _sceneBuildHelper(loaderData, node, vBox, svgPath, false, depth + 1, isMaskWhite); // mUseTransform = mUseTransform * mTranslate @@ -736,10 +734,10 @@ static unique_ptr _useBuildHelper(SvgLoaderData& loaderData, const SvgNod auto vh = (symbol.hasViewBox ? symbol.vh : height); Matrix mViewBox = {1, 0, 0, 0, 1, 0, 0, 0, 1}; - if ((!mathEqual(width, vw) || !mathEqual(height, vh)) && vw > 0 && vh > 0) { + if ((!tvg::equal(width, vw) || !tvg::equal(height, vh)) && vw > 0 && vh > 0) { Box box = {symbol.vx, symbol.vy, vw, vh}; mViewBox = _calculateAspectRatioMatrix(symbol.align, symbol.meetOrSlice, width, height, box); - } else if (!mathZero(symbol.vx) || !mathZero(symbol.vy)) { + } else if (!tvg::zero(symbol.vx) || !tvg::zero(symbol.vy)) { mViewBox = {1, 0, -symbol.vx, 0, 1, -symbol.vy, 0, 0, 1}; } @@ -751,9 +749,7 @@ static unique_ptr _useBuildHelper(SvgLoaderData& loaderData, const SvgNod mSceneTransform = mUseTransform * mSceneTransform; scene->transform(mSceneTransform); - if (node->node.use.symbol->node.symbol.overflowVisible) { - finalScene = std::move(scene); - } else { + if (!node->node.use.symbol->node.symbol.overflowVisible) { auto viewBoxClip = Shape::gen(); viewBoxClip->appendRect(0, 0, width, height, 0, 0); @@ -764,21 +760,13 @@ static unique_ptr _useBuildHelper(SvgLoaderData& loaderData, const SvgNod } viewBoxClip->transform(mClipTransform); - auto compositeLayer = Scene::gen(); - compositeLayer->composite(std::move(viewBoxClip), CompositeMethod::ClipPath); - compositeLayer->push(std::move(scene)); - - auto root = Scene::gen(); - root->push(std::move(compositeLayer)); - - finalScene = std::move(root); + scene->clip(std::move(viewBoxClip)); } } else { scene->transform(mUseTransform); - finalScene = std::move(scene); } - return finalScene; + return scene; } @@ -821,7 +809,7 @@ static unique_ptr _textBuildHelper(SvgLoaderData& loaderData, const SvgNod Matrix textTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1}; if (node->transform) textTransform = *node->transform; - mathTranslateR(&textTransform, node->node.text.x, node->node.text.y - textNode->fontSize); + translateR(&textTransform, node->node.text.x, node->node.text.y - textNode->fontSize); text->transform(textTransform); //TODO: handle def values of font and size as used in a system? @@ -926,10 +914,10 @@ Scene* svgSceneBuild(SvgLoaderData& loaderData, Box vBox, float w, float h, Aspe if (!(viewFlag & SvgViewFlag::Viewbox)) _updateInvalidViewSize(docNode.get(), vBox, w, h, viewFlag); - if (!mathEqual(w, vBox.w) || !mathEqual(h, vBox.h)) { + if (!tvg::equal(w, vBox.w) || !tvg::equal(h, vBox.h)) { Matrix m = _calculateAspectRatioMatrix(align, meetOrSlice, w, h, vBox); docNode->transform(m); - } else if (!mathZero(vBox.x) || !mathZero(vBox.y)) { + } else if (!tvg::zero(vBox.x) || !tvg::zero(vBox.y)) { docNode->translate(-vBox.x, -vBox.y); } @@ -937,7 +925,7 @@ Scene* svgSceneBuild(SvgLoaderData& loaderData, Box vBox, float w, float h, Aspe viewBoxClip->appendRect(0, 0, w, h); auto compositeLayer = Scene::gen(); - compositeLayer->composite(std::move(viewBoxClip), CompositeMethod::ClipPath); + compositeLayer->clip(std::move(viewBoxClip)); compositeLayer->push(std::move(docNode)); auto root = Scene::gen(); diff --git a/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp b/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp index 09fc8aaac13..da1cdae9e0e 100644 --- a/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp +++ b/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp @@ -492,13 +492,13 @@ bool simpleXmlParseW3CAttribute(const char* buf, unsigned bufLength, simpleXMLAt key[0] = '\0'; val[0] = '\0'; - if (next == nullptr && sep != nullptr) { + if (sep != nullptr && next == nullptr) { memcpy(key, buf, sep - buf); key[sep - buf] = '\0'; memcpy(val, sep + 1, end - sep - 1); val[end - sep - 1] = '\0'; - } else if (sep < next && sep != nullptr) { + } else if (sep != nullptr && sep < next) { memcpy(key, buf, sep - buf); key[sep - buf] = '\0'; @@ -522,8 +522,9 @@ bool simpleXmlParseW3CAttribute(const char* buf, unsigned bufLength, simpleXMLAt } } + if (!next) break; buf = next + 1; - } while (next != nullptr); + } while (true); return true; } diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h index ab1fc18b58a..9371ae6c5ad 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h @@ -113,7 +113,7 @@ struct SwSpan uint8_t coverage; }; -struct SwRleData +struct SwRle { SwSpan *spans; uint32_t alloc; @@ -211,8 +211,8 @@ struct SwShape SwOutline* outline = nullptr; SwStroke* stroke = nullptr; SwFill* fill = nullptr; - SwRleData* rle = nullptr; - SwRleData* strokeRle = nullptr; + SwRle* rle = nullptr; + SwRle* strokeRle = nullptr; SwBBox bbox; //Keep it boundary without stroke region. Using for optimal filling. bool fastTrack = false; //Fast Track: axis-aligned rectangle without any clips? @@ -221,7 +221,7 @@ struct SwShape struct SwImage { SwOutline* outline = nullptr; - SwRleData* rle = nullptr; + SwRle* rle = nullptr; union { pixel_t* data; //system based data pointer uint32_t* buf32; //for explicit 32bits channels @@ -244,13 +244,13 @@ typedef uint8_t(*SwAlpha)(uint8_t*); //bl struct SwCompositor; -struct SwSurface : Surface +struct SwSurface : RenderSurface { SwJoin join; SwAlpha alphas[4]; //Alpha:2, InvAlpha:3, Luma:4, InvLuma:5 SwBlender blender = nullptr; //blender (optional) SwCompositor* compositor = nullptr; //compositor (optional) - BlendMethod blendMethod; //blending method (uint8_t) + BlendMethod blendMethod = BlendMethod::Normal; SwAlpha alpha(CompositeMethod method) { @@ -262,7 +262,7 @@ struct SwSurface : Surface { } - SwSurface(const SwSurface* rhs) : Surface(rhs) + SwSurface(const SwSurface* rhs) : RenderSurface(rhs) { join = rhs->join; memcpy(alphas, rhs->alphas, sizeof(alphas)); @@ -272,7 +272,7 @@ struct SwSurface : Surface } }; -struct SwCompositor : Compositor +struct SwCompositor : RenderCompositor { SwSurface* recoverSfc; //Recover surface when composition is started SwCompositor* recoverCmp; //Recover compositor when composition is done @@ -488,13 +488,14 @@ SwFixed mathAtan(const SwPoint& pt); SwFixed mathCos(SwFixed angle); SwFixed mathSin(SwFixed angle); void mathSplitCubic(SwPoint* base); +void mathSplitLine(SwPoint* base); SwFixed mathDiff(SwFixed angle1, SwFixed angle2); SwFixed mathLength(const SwPoint& pt); -bool mathSmallCubic(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut); +int mathCubicAngle(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut); SwFixed mathMean(SwFixed angle1, SwFixed angle2); SwPoint mathTransform(const Point* to, const Matrix& transform); bool mathUpdateOutlineBBox(const SwOutline* outline, const SwBBox& clipRegion, SwBBox& renderRegion, bool fastTrack); -bool mathClipBBox(const SwBBox& clipper, SwBBox& clipee); +bool mathClipBBox(const SwBBox& clipper, SwBBox& clippee); void shapeReset(SwShape* shape); bool shapePrepare(SwShape* shape, const RenderShape* rshape, const Matrix& transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid, bool hasComposite); @@ -541,13 +542,13 @@ void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint3 void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, SwBlender op2, uint8_t a); //blending + BlendingMethod(op2) ver. void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, uint8_t* cmp, SwAlpha alpha, uint8_t csize, uint8_t opacity); //matting ver. -SwRleData* rleRender(SwRleData* rle, const SwOutline* outline, const SwBBox& renderRegion, bool antiAlias); -SwRleData* rleRender(const SwBBox* bbox); -void rleFree(SwRleData* rle); -void rleReset(SwRleData* rle); -void rleMerge(SwRleData* rle, SwRleData* clip1, SwRleData* clip2); -void rleClipPath(SwRleData* rle, const SwRleData* clip); -void rleClipRect(SwRleData* rle, const SwBBox* clip); +SwRle* rleRender(SwRle* rle, const SwOutline* outline, const SwBBox& renderRegion, bool antiAlias); +SwRle* rleRender(const SwBBox* bbox); +void rleFree(SwRle* rle); +void rleReset(SwRle* rle); +void rleMerge(SwRle* rle, SwRle* clip1, SwRle* clip2); +void rleClip(SwRle* rle, const SwRle* clip); +void rleClip(SwRle* rle, const SwBBox* clip); SwMpool* mpoolInit(uint32_t threads); bool mpoolTerm(SwMpool* mpool); @@ -567,9 +568,17 @@ bool rasterStroke(SwSurface* surface, SwShape* shape, uint8_t r, uint8_t g, uint bool rasterGradientStroke(SwSurface* surface, SwShape* shape, const Fill* fdata, uint8_t opacity); bool rasterClear(SwSurface* surface, uint32_t x, uint32_t y, uint32_t w, uint32_t h, pixel_t val = 0); void rasterPixel32(uint32_t *dst, uint32_t val, uint32_t offset, int32_t len); +void rasterTranslucentPixel32(uint32_t* dst, uint32_t* src, uint32_t len, uint8_t opacity); +void rasterPixel32(uint32_t* dst, uint32_t* src, uint32_t len, uint8_t opacity); void rasterGrayscale8(uint8_t *dst, uint8_t val, uint32_t offset, int32_t len); -void rasterUnpremultiply(Surface* surface); -void rasterPremultiply(Surface* surface); -bool rasterConvertCS(Surface* surface, ColorSpace to); +void rasterXYFlip(uint32_t* src, uint32_t* dst, int32_t stride, int32_t w, int32_t h, const SwBBox& bbox, bool flipped); +void rasterUnpremultiply(RenderSurface* surface); +void rasterPremultiply(RenderSurface* surface); +bool rasterConvertCS(RenderSurface* surface, ColorSpace to); + +bool effectGaussianBlur(SwCompositor* cmp, SwSurface* surface, const RenderEffectGaussianBlur* params); +bool effectGaussianBlurPrepare(RenderEffectGaussianBlur* effect); +bool effectDropShadow(SwCompositor* cmp, SwSurface* surfaces[2], const RenderEffectDropShadow* params, uint8_t opacity, bool direct); +bool effectDropShadowPrepare(RenderEffectDropShadow* effect); #endif /* _TVG_SW_COMMON_H_ */ diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwFill.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwFill.cpp index 631294ad40b..f70ba7a13d6 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwFill.cpp +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwFill.cpp @@ -66,15 +66,15 @@ static void _calculateCoefficients(const SwFill* fill, uint32_t x, uint32_t y, f static uint32_t _estimateAAMargin(const Fill* fdata) { constexpr float marginScalingFactor = 800.0f; - if (fdata->identifier() == TVG_CLASS_ID_RADIAL) { + if (fdata->type() == Type::RadialGradient) { auto radius = P(static_cast(fdata))->r; - return mathZero(radius) ? 0 : static_cast(marginScalingFactor / radius); + return tvg::zero(radius) ? 0 : static_cast(marginScalingFactor / radius); } auto grad = P(static_cast(fdata)); Point p1 {grad->x1, grad->y1}; Point p2 {grad->x2, grad->y2}; - auto length = mathLength(&p1, &p2); - return mathZero(length) ? 0 : static_cast(marginScalingFactor / length); + auto len = length(&p1, &p2); + return tvg::zero(len) ? 0 : static_cast(marginScalingFactor / len); } @@ -217,7 +217,7 @@ bool _prepareLinear(SwFill* fill, const LinearGradient* linear, const Matrix& tr auto len = fill->linear.dx * fill->linear.dx + fill->linear.dy * fill->linear.dy; if (len < FLOAT_EPSILON) { - if (mathZero(fill->linear.dx) && mathZero(fill->linear.dy)) { + if (tvg::zero(fill->linear.dx) && tvg::zero(fill->linear.dy)) { fill->solid = true; } return true; @@ -228,7 +228,7 @@ bool _prepareLinear(SwFill* fill, const LinearGradient* linear, const Matrix& tr fill->linear.offset = -fill->linear.dx * x1 - fill->linear.dy * y1; auto gradTransform = linear->transform(); - bool isTransformation = !mathIdentity((const Matrix*)(&gradTransform)); + bool isTransformation = !identity((const Matrix*)(&gradTransform)); if (isTransformation) { gradTransform = transform * gradTransform; @@ -239,7 +239,7 @@ bool _prepareLinear(SwFill* fill, const LinearGradient* linear, const Matrix& tr if (isTransformation) { Matrix invTransform; - if (!mathInverse(&gradTransform, &invTransform)) return false; + if (!inverse(&gradTransform, &invTransform)) return false; fill->linear.offset += fill->linear.dx * invTransform.e13 + fill->linear.dy * invTransform.e23; @@ -261,7 +261,7 @@ bool _prepareRadial(SwFill* fill, const RadialGradient* radial, const Matrix& tr auto fy = P(radial)->fy; auto fr = P(radial)->fr; - if (mathZero(r)) { + if (tvg::zero(r)) { fill->solid = true; return true; } @@ -295,7 +295,7 @@ bool _prepareRadial(SwFill* fill, const RadialGradient* radial, const Matrix& tr if (fill->radial.a > 0) fill->radial.invA = 1.0f / fill->radial.a; auto gradTransform = radial->transform(); - bool isTransformation = !mathIdentity((const Matrix*)(&gradTransform)); + bool isTransformation = !identity((const Matrix*)(&gradTransform)); if (isTransformation) gradTransform = transform * gradTransform; else { @@ -305,7 +305,7 @@ bool _prepareRadial(SwFill* fill, const RadialGradient* radial, const Matrix& tr if (isTransformation) { Matrix invTransform; - if (!mathInverse(&gradTransform, &invTransform)) return false; + if (!inverse(&gradTransform, &invTransform)) return false; fill->radial.a11 = invTransform.e11; fill->radial.a12 = invTransform.e12; fill->radial.a13 = invTransform.e13; @@ -553,7 +553,7 @@ void fillLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint3 float inc = (fill->linear.dx) * (GRADIENT_STOP_SIZE - 1); if (opacity == 255) { - if (mathZero(inc)) { + if (tvg::zero(inc)) { auto color = _fixedPixel(fill, static_cast(t * FIXPT_SIZE)); for (uint32_t i = 0; i < len; ++i, ++dst, cmp += csize) { *dst = opBlendNormal(color, *dst, alpha(cmp)); @@ -584,7 +584,7 @@ void fillLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint3 } } } else { - if (mathZero(inc)) { + if (tvg::zero(inc)) { auto color = _fixedPixel(fill, static_cast(t * FIXPT_SIZE)); for (uint32_t i = 0; i < len; ++i, ++dst, cmp += csize) { *dst = opBlendNormal(color, *dst, MULTIPLY(alpha(cmp), opacity)); @@ -626,7 +626,7 @@ void fillLinear(const SwFill* fill, uint8_t* dst, uint32_t y, uint32_t x, uint32 float t = (fill->linear.dx * rx + fill->linear.dy * ry + fill->linear.offset) * (GRADIENT_STOP_SIZE - 1); float inc = (fill->linear.dx) * (GRADIENT_STOP_SIZE - 1); - if (mathZero(inc)) { + if (tvg::zero(inc)) { auto src = MULTIPLY(a, A(_fixedPixel(fill, static_cast(t * FIXPT_SIZE)))); for (uint32_t i = 0; i < len; ++i, ++dst) { *dst = maskOp(src, *dst, ~src); @@ -643,7 +643,7 @@ void fillLinear(const SwFill* fill, uint8_t* dst, uint32_t y, uint32_t x, uint32 auto t2 = static_cast(t * FIXPT_SIZE); auto inc2 = static_cast(inc * FIXPT_SIZE); for (uint32_t j = 0; j < len; ++j, ++dst) { - auto src = MULTIPLY(_fixedPixel(fill, t2), a); + auto src = MULTIPLY(A(_fixedPixel(fill, t2)), a); *dst = maskOp(src, *dst, ~src); t2 += inc2; } @@ -651,7 +651,7 @@ void fillLinear(const SwFill* fill, uint8_t* dst, uint32_t y, uint32_t x, uint32 } else { uint32_t counter = 0; while (counter++ < len) { - auto src = MULTIPLY(_pixel(fill, t / GRADIENT_STOP_SIZE), a); + auto src = MULTIPLY(A(_pixel(fill, t / GRADIENT_STOP_SIZE)), a); *dst = maskOp(src, *dst, ~src); ++dst; t += inc; @@ -668,7 +668,7 @@ void fillLinear(const SwFill* fill, uint8_t* dst, uint32_t y, uint32_t x, uint32 float t = (fill->linear.dx * rx + fill->linear.dy * ry + fill->linear.offset) * (GRADIENT_STOP_SIZE - 1); float inc = (fill->linear.dx) * (GRADIENT_STOP_SIZE - 1); - if (mathZero(inc)) { + if (tvg::zero(inc)) { auto src = A(_fixedPixel(fill, static_cast(t * FIXPT_SIZE))); src = MULTIPLY(src, a); for (uint32_t i = 0; i < len; ++i, ++dst, ++cmp) { @@ -715,7 +715,7 @@ void fillLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint3 float t = (fill->linear.dx * rx + fill->linear.dy * ry + fill->linear.offset) * (GRADIENT_STOP_SIZE - 1); float inc = (fill->linear.dx) * (GRADIENT_STOP_SIZE - 1); - if (mathZero(inc)) { + if (tvg::zero(inc)) { auto color = _fixedPixel(fill, static_cast(t * FIXPT_SIZE)); for (uint32_t i = 0; i < len; ++i, ++dst) { *dst = op(color, *dst, a); @@ -755,7 +755,7 @@ void fillLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint3 float t = (fill->linear.dx * rx + fill->linear.dy * ry + fill->linear.offset) * (GRADIENT_STOP_SIZE - 1); float inc = (fill->linear.dx) * (GRADIENT_STOP_SIZE - 1); - if (mathZero(inc)) { + if (tvg::zero(inc)) { auto color = _fixedPixel(fill, static_cast(t * FIXPT_SIZE)); if (a == 255) { for (uint32_t i = 0; i < len; ++i, ++dst) { @@ -828,9 +828,9 @@ bool fillGenColorTable(SwFill* fill, const Fill* fdata, const Matrix& transform, fill->spread = fdata->spread(); - if (fdata->identifier() == TVG_CLASS_ID_LINEAR) { + if (fdata->type() == Type::LinearGradient) { if (!_prepareLinear(fill, static_cast(fdata), transform)) return false; - } else if (fdata->identifier() == TVG_CLASS_ID_RADIAL) { + } else if (fdata->type() == Type::RadialGradient) { if (!_prepareRadial(fill, static_cast(fdata), transform)) return false; } diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwImage.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwImage.cpp index 3fc64ce036a..d2c02bb932d 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwImage.cpp +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwImage.cpp @@ -29,7 +29,7 @@ static inline bool _onlyShifted(const Matrix& m) { - if (mathEqual(m.e11, 1.0f) && mathEqual(m.e22, 1.0f) && mathZero(m.e12) && mathZero(m.e21)) return true; + if (tvg::equal(m.e11, 1.0f) && tvg::equal(m.e22, 1.0f) && tvg::zero(m.e12) && tvg::zero(m.e21)) return true; return false; } @@ -86,7 +86,7 @@ bool imagePrepare(SwImage* image, const Matrix& transform, const SwBBox& clipReg auto scaleY = sqrtf((transform.e22 * transform.e22) + (transform.e12 * transform.e12)); image->scale = (fabsf(scaleX - scaleY) > 0.01f) ? 1.0f : scaleX; - if (mathZero(transform.e12) && mathZero(transform.e21)) image->scaled = true; + if (tvg::zero(transform.e12) && tvg::zero(transform.e21)) image->scaled = true; else image->scaled = false; } diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwMath.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwMath.cpp index fb809c4f7e9..1ff99f6aece 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwMath.cpp +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwMath.cpp @@ -44,7 +44,7 @@ SwFixed mathMean(SwFixed angle1, SwFixed angle2) } -bool mathSmallCubic(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut) +int mathCubicAngle(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut) { auto d1 = base[2] - base[3]; auto d2 = base[1] - base[2]; @@ -54,7 +54,7 @@ bool mathSmallCubic(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, Sw if (d2.small()) { if (d3.small()) { angleIn = angleMid = angleOut = 0; - return true; + return -1; //ignoreable } else { angleIn = angleMid = angleOut = mathAtan(d3); } @@ -90,8 +90,8 @@ bool mathSmallCubic(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, Sw auto theta1 = abs(mathDiff(angleIn, angleMid)); auto theta2 = abs(mathDiff(angleMid, angleOut)); - if ((theta1 < (SW_ANGLE_PI / 8)) && (theta2 < (SW_ANGLE_PI / 8))) return true; - return false; + if ((theta1 < (SW_ANGLE_PI / 8)) && (theta2 < (SW_ANGLE_PI / 8))) return 0; //small size + return 1; } @@ -179,7 +179,7 @@ SwFixed mathTan(SwFixed angle) SwFixed mathAtan(const SwPoint& pt) { if (pt.zero()) return 0; - return SwFixed(mathAtan2(TO_FLOAT(pt.y), TO_FLOAT(pt.x)) * (180.0f / MATH_PI) * 65536.0f); + return SwFixed(tvg::atan2(TO_FLOAT(pt.y), TO_FLOAT(pt.x)) * (180.0f / MATH_PI) * 65536.0f); } @@ -242,6 +242,15 @@ void mathSplitCubic(SwPoint* base) } +void mathSplitLine(SwPoint* base) +{ + base[2] = base[1]; + + base[1].x = (base[0].x + base[1].x) >> 1; + base[1].y = (base[0].y + base[1].y) >> 1; +} + + SwFixed mathDiff(SwFixed angle1, SwFixed angle2) { auto delta = angle2 - angle1; @@ -263,19 +272,19 @@ SwPoint mathTransform(const Point* to, const Matrix& transform) } -bool mathClipBBox(const SwBBox& clipper, SwBBox& clipee) +bool mathClipBBox(const SwBBox& clipper, SwBBox& clippee) { - clipee.max.x = (clipee.max.x < clipper.max.x) ? clipee.max.x : clipper.max.x; - clipee.max.y = (clipee.max.y < clipper.max.y) ? clipee.max.y : clipper.max.y; - clipee.min.x = (clipee.min.x > clipper.min.x) ? clipee.min.x : clipper.min.x; - clipee.min.y = (clipee.min.y > clipper.min.y) ? clipee.min.y : clipper.min.y; + clippee.max.x = (clippee.max.x < clipper.max.x) ? clippee.max.x : clipper.max.x; + clippee.max.y = (clippee.max.y < clipper.max.y) ? clippee.max.y : clipper.max.y; + clippee.min.x = (clippee.min.x > clipper.min.x) ? clippee.min.x : clipper.min.x; + clippee.min.y = (clippee.min.y > clipper.min.y) ? clippee.min.y : clipper.min.y; //Check valid region - if (clipee.max.x - clipee.min.x < 1 && clipee.max.y - clipee.min.y < 1) return false; + if (clippee.max.x - clippee.min.x < 1 && clippee.max.y - clippee.min.y < 1) return false; //Check boundary - if (clipee.min.x >= clipper.max.x || clipee.min.y >= clipper.max.y || - clipee.max.x <= clipper.min.x || clipee.max.y <= clipper.min.y) return false; + if (clippee.min.x >= clipper.max.x || clippee.min.y >= clipper.max.y || + clippee.max.x <= clipper.min.x || clippee.max.y <= clipper.min.y) return false; return true; } @@ -303,9 +312,7 @@ bool mathUpdateOutlineBBox(const SwOutline* outline, const SwBBox& clipRegion, S if (yMin > pt->y) yMin = pt->y; if (yMax < pt->y) yMax = pt->y; } - //Since no antialiasing is applied in the Fast Track case, - //the rasterization region has to be rearranged. - //https://github.com/Samsung/thorvg/issues/916 + if (fastTrack) { renderRegion.min.x = static_cast(nearbyint(xMin / 64.0f)); renderRegion.max.x = static_cast(nearbyint(xMax / 64.0f)); diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwPostEffect.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwPostEffect.cpp new file mode 100644 index 00000000000..fd8e532e12a --- /dev/null +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwPostEffect.cpp @@ -0,0 +1,410 @@ +/* + * Copyright (c) 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "tvgMath.h" +#include "tvgSwCommon.h" + +/************************************************************************/ +/* Gaussian Blur Implementation */ +/************************************************************************/ + +struct SwGaussianBlur +{ + static constexpr int MAX_LEVEL = 3; + int level; + int kernel[MAX_LEVEL]; +}; + + +static void _gaussianExtendRegion(RenderRegion& region, int extra, int8_t direction) +{ + //bbox region expansion for feathering + if (direction != 2) { + region.x = -extra; + region.w = extra * 2; + } + if (direction != 1) { + region.y = -extra; + region.h = extra * 2; + } +} + + +static int _gaussianEdgeWrap(int end, int idx) +{ + auto r = idx % end; + return (r < 0) ? end + r : r; +} + + +static int _gaussianEdgeExtend(int end, int idx) +{ + if (idx < 0) return 0; + else if (idx >= end) return end - 1; + return idx; +} + + +static int _gaussianRemap(int end, int idx, int border) +{ + if (border == 1) return _gaussianEdgeWrap(end, idx); + return _gaussianEdgeExtend(end, idx); +} + + +//TODO: SIMD OPTIMIZATION? +static void _gaussianFilter(uint8_t* dst, uint8_t* src, int32_t stride, int32_t w, int32_t h, const SwBBox& bbox, int32_t dimension, int border, bool flipped) +{ + if (flipped) { + src += (bbox.min.x * stride + bbox.min.y) << 2; + dst += (bbox.min.x * stride + bbox.min.y) << 2; + } else { + src += (bbox.min.y * stride + bbox.min.x) << 2; + dst += (bbox.min.y * stride + bbox.min.x) << 2; + } + + auto iarr = 1.0f / (dimension + dimension + 1); + + #pragma omp parallel for + for (int y = 0; y < h; ++y) { + auto p = y * stride; + auto i = p * 4; //current index + auto l = -(dimension + 1); //left index + auto r = dimension; //right index + int acc[4] = {0, 0, 0, 0}; //sliding accumulator + + //initial accumulation + for (int x = l; x < r; ++x) { + auto id = (_gaussianRemap(w, x, border) + p) * 4; + acc[0] += src[id++]; + acc[1] += src[id++]; + acc[2] += src[id++]; + acc[3] += src[id]; + } + //perform filtering + for (int x = 0; x < w; ++x, ++r, ++l) { + auto rid = (_gaussianRemap(w, r, border) + p) * 4; + auto lid = (_gaussianRemap(w, l, border) + p) * 4; + acc[0] += src[rid++] - src[lid++]; + acc[1] += src[rid++] - src[lid++]; + acc[2] += src[rid++] - src[lid++]; + acc[3] += src[rid] - src[lid]; + dst[i++] = static_cast(acc[0] * iarr + 0.5f); + dst[i++] = static_cast(acc[1] * iarr + 0.5f); + dst[i++] = static_cast(acc[2] * iarr + 0.5f); + dst[i++] = static_cast(acc[3] * iarr + 0.5f); + } + } +} + + +static int _gaussianInit(SwGaussianBlur* data, float sigma, int quality) +{ + const auto MAX_LEVEL = SwGaussianBlur::MAX_LEVEL; + + if (tvg::zero(sigma)) return 0; + + data->level = int(SwGaussianBlur::MAX_LEVEL * ((quality - 1) * 0.01f)) + 1; + + //compute box kernel sizes + auto wl = (int) sqrt((12 * sigma / MAX_LEVEL) + 1); + if (wl % 2 == 0) --wl; + auto wu = wl + 2; + auto mi = (12 * sigma - MAX_LEVEL * wl * wl - 4 * MAX_LEVEL * wl - 3 * MAX_LEVEL) / (-4 * wl - 4); + auto m = int(mi + 0.5f); + auto extends = 0; + + for (int i = 0; i < data->level; i++) { + data->kernel[i] = ((i < m ? wl : wu) - 1) / 2; + extends += data->kernel[i]; + } + + return extends; +} + + +bool effectGaussianBlurPrepare(RenderEffectGaussianBlur* params) +{ + auto rd = (SwGaussianBlur*)malloc(sizeof(SwGaussianBlur)); + + auto extends = _gaussianInit(rd, params->sigma * params->sigma, params->quality); + + //invalid + if (extends == 0) { + params->invalid = true; + free(rd); + return false; + } + + _gaussianExtendRegion(params->extend, extends, params->direction); + + params->rd = rd; + + return true; +} + + +bool effectGaussianBlur(SwCompositor* cmp, SwSurface* surface, const RenderEffectGaussianBlur* params) +{ + if (cmp->image.channelSize != sizeof(uint32_t)) { + TVGERR("SW_ENGINE", "Not supported grayscale Gaussian Blur!"); + return false; + } + + auto& buffer = surface->compositor->image; + auto data = static_cast(params->rd); + auto& bbox = cmp->bbox; + auto w = (bbox.max.x - bbox.min.x); + auto h = (bbox.max.y - bbox.min.y); + auto stride = cmp->image.stride; + auto front = cmp->image.buf32; + auto back = buffer.buf32; + auto swapped = false; + + TVGLOG("SW_ENGINE", "GaussianFilter region(%ld, %ld, %ld, %ld) params(%f %d %d), level(%d)", bbox.min.x, bbox.min.y, bbox.max.x, bbox.max.y, params->sigma, params->direction, params->border, data->level); + + /* It is best to take advantage of the Gaussian blur’s separable property + by dividing the process into two passes. horizontal and vertical. + We can expect fewer calculations. */ + + //horizontal + if (params->direction != 2) { + for (int i = 0; i < data->level; ++i) { + _gaussianFilter(reinterpret_cast(back), reinterpret_cast(front), stride, w, h, bbox, data->kernel[i], params->border, false); + std::swap(front, back); + swapped = !swapped; + } + } + + //vertical. x/y flipping and horionztal access is pretty compatible with the memory architecture. + if (params->direction != 1) { + rasterXYFlip(front, back, stride, w, h, bbox, false); + std::swap(front, back); + + for (int i = 0; i < data->level; ++i) { + _gaussianFilter(reinterpret_cast(back), reinterpret_cast(front), stride, h, w, bbox, data->kernel[i], params->border, true); + std::swap(front, back); + swapped = !swapped; + } + + rasterXYFlip(front, back, stride, h, w, bbox, true); + std::swap(front, back); + } + + if (swapped) std::swap(cmp->image.buf8, buffer.buf8); + + return true; +} + +/************************************************************************/ +/* Drop Shadow Implementation */ +/************************************************************************/ + +struct SwDropShadow : SwGaussianBlur +{ + SwPoint offset; +}; + + +//TODO: SIMD OPTIMIZATION? +static void _dropShadowFilter(uint32_t* dst, uint32_t* src, int stride, int w, int h, const SwBBox& bbox, int32_t dimension, uint32_t color, bool flipped) +{ + if (flipped) { + src += (bbox.min.x * stride + bbox.min.y); + dst += (bbox.min.x * stride + bbox.min.y); + } else { + src += (bbox.min.y * stride + bbox.min.x); + dst += (bbox.min.y * stride + bbox.min.x); + } + auto iarr = 1.0f / (dimension + dimension + 1); + + #pragma omp parallel for + for (int y = 0; y < h; ++y) { + auto p = y * stride; + auto i = p; //current index + auto l = -(dimension + 1); //left index + auto r = dimension; //right index + int acc = 0; //sliding accumulator + + //initial accumulation + for (int x = l; x < r; ++x) { + auto id = _gaussianEdgeExtend(w, x) + p; + acc += A(src[id]); + } + //perform filtering + for (int x = 0; x < w; ++x, ++r, ++l) { + auto rid = _gaussianEdgeExtend(w, r) + p; + auto lid = _gaussianEdgeExtend(w, l) + p; + acc += A(src[rid]) - A(src[lid]); + dst[i++] = ALPHA_BLEND(color, static_cast(acc * iarr + 0.5f)); + } + } +} + + +static void _dropShadowShift(uint32_t* dst, uint32_t* src, int stride, SwBBox& region, SwPoint& offset, uint8_t opacity, bool direct) +{ + src += (region.min.y * stride + region.min.x); + dst += (region.min.y * stride + region.min.x); + + auto w = region.max.x - region.min.x; + auto h = region.max.y - region.min.y; + auto translucent = (direct || opacity < 255); + + //shift offset + if (region.min.x + offset.x < 0) src -= offset.x; + else dst += offset.x; + + if (region.min.y + offset.y < 0) src -= (offset.y * stride); + else dst += (offset.y * stride); + + for (auto y = 0; y < h; ++y) { + if (translucent) rasterTranslucentPixel32(dst, src, w, opacity); + else rasterPixel32(dst, src, w, opacity); + src += stride; + dst += stride; + } +} + + +static void _dropShadowExtendRegion(RenderRegion& region, int extra, SwPoint& offset) +{ + //bbox region expansion for feathering + region.x = -extra; + region.w = extra * 2; + region.y = -extra; + region.h = extra * 2; + + region.x = std::min(region.x + (int32_t)offset.x, region.x); + region.y = std::min(region.y + (int32_t)offset.y, region.y); + region.w += abs(offset.x); + region.h += abs(offset.y); +} + + +bool effectDropShadowPrepare(RenderEffectDropShadow* params) +{ + auto rd = (SwDropShadow*)malloc(sizeof(SwDropShadow)); + + //compute box kernel sizes + auto extends = _gaussianInit(rd, params->sigma * params->sigma, params->quality); + + //invalid + if (extends == 0 || params->color[3] == 0) { + params->invalid = true; + free(rd); + return false; + } + + //offset + if (params->distance > 0.0f) { + auto radian = tvg::deg2rad(90.0f - params->angle); + rd->offset = {(SwCoord)(params->distance * cosf(radian)), (SwCoord)(-1.0f * params->distance * sinf(radian))}; + } else { + rd->offset = {0, 0}; + } + + //bbox region expansion for feathering + _dropShadowExtendRegion(params->extend, extends, rd->offset); + + params->rd = rd; + + return true; +} + + +//A quite same integration with effectGaussianBlur(). See it for detailed comments. +//surface[0]: the original image, to overlay it into the filtered image. +//surface[1]: temporary buffer for generating the filtered image. +bool effectDropShadow(SwCompositor* cmp, SwSurface* surface[2], const RenderEffectDropShadow* params, uint8_t opacity, bool direct) +{ + if (cmp->image.channelSize != sizeof(uint32_t)) { + TVGERR("SW_ENGINE", "Not supported grayscale Drop Shadow!"); + return false; + } + + //FIXME: if the body is partially visible due to clipping, the shadow also becomes partially visible. + + auto data = static_cast(params->rd); + auto& bbox = cmp->bbox; + auto w = (bbox.max.x - bbox.min.x); + auto h = (bbox.max.y - bbox.min.y); + + //outside the screen + if (abs(data->offset.x) >= w || abs(data->offset.y) >= h) return true; + + SwImage* buffer[] = {&surface[0]->compositor->image, &surface[1]->compositor->image}; + auto color = cmp->recoverSfc->join(params->color[0], params->color[1], params->color[2], 255); + auto stride = cmp->image.stride; + auto front = cmp->image.buf32; + auto back = buffer[1]->buf32; + opacity = MULTIPLY(params->color[3], opacity); + + TVGLOG("SW_ENGINE", "DropShadow region(%ld, %ld, %ld, %ld) params(%f %f %f), level(%d)", bbox.min.x, bbox.min.y, bbox.max.x, bbox.max.y, params->angle, params->distance, params->sigma, data->level); + + //saving the original image in order to overlay it into the filtered image. + _dropShadowFilter(back, front, stride, w, h, bbox, data->kernel[0], color, false); + std::swap(front, buffer[0]->buf32); + std::swap(front, back); + + //horizontal + for (int i = 1; i < data->level; ++i) { + _dropShadowFilter(back, front, stride, w, h, bbox, data->kernel[i], color, false); + std::swap(front, back); + } + + //vertical + rasterXYFlip(front, back, stride, w, h, bbox, false); + std::swap(front, back); + + for (int i = 0; i < data->level; ++i) { + _dropShadowFilter(back, front, stride, h, w, bbox, data->kernel[i], color, true); + std::swap(front, back); + } + + rasterXYFlip(front, back, stride, h, w, bbox, true); + std::swap(cmp->image.buf32, back); + + //draw to the main surface directly + if (direct) { + _dropShadowShift(cmp->recoverSfc->buf32, cmp->image.buf32, stride, bbox, data->offset, opacity, direct); + std::swap(cmp->image.buf32, buffer[0]->buf32); + return true; + } + + //draw to the intermediate surface + rasterClear(surface[1], bbox.min.x, bbox.min.y, w, h); + _dropShadowShift(buffer[1]->buf32, cmp->image.buf32, stride, bbox, data->offset, opacity, direct); + std::swap(cmp->image.buf32, buffer[1]->buf32); + + //compositing shadow and body + auto s = buffer[0]->buf32 + (bbox.min.y * buffer[0]->stride + bbox.min.x); + auto d = cmp->image.buf32 + (bbox.min.y * cmp->image.stride + bbox.min.x); + + for (auto y = 0; y < h; ++y) { + rasterTranslucentPixel32(d, s, w, 255); + s += buffer[0]->stride; + d += cmp->image.stride; + } + + return true; +} diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp index b9e95f88cf7..18ffc18e1e0 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp @@ -490,7 +490,7 @@ static bool _rasterRect(SwSurface* surface, const SwBBox& region, uint8_t r, uin /* Rle */ /************************************************************************/ -static bool _rasterCompositeMaskedRle(SwSurface* surface, SwRleData* rle, SwMask maskOp, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +static bool _rasterCompositeMaskedRle(SwSurface* surface, SwRle* rle, SwMask maskOp, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { auto span = rle->spans; auto cbuffer = surface->compositor->image.buf8; @@ -510,7 +510,7 @@ static bool _rasterCompositeMaskedRle(SwSurface* surface, SwRleData* rle, SwMask } -static bool _rasterDirectMaskedRle(SwSurface* surface, SwRleData* rle, SwMask maskOp, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +static bool _rasterDirectMaskedRle(SwSurface* surface, SwRle* rle, SwMask maskOp, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { auto span = rle->spans; auto cbuffer = surface->compositor->image.buf8; @@ -531,7 +531,7 @@ static bool _rasterDirectMaskedRle(SwSurface* surface, SwRleData* rle, SwMask ma } -static bool _rasterMaskedRle(SwSurface* surface, SwRleData* rle, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +static bool _rasterMaskedRle(SwSurface* surface, SwRle* rle, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { TVGLOG("SW_ENGINE", "Masked(%d) Rle", (int)surface->compositor->method); @@ -545,7 +545,7 @@ static bool _rasterMaskedRle(SwSurface* surface, SwRleData* rle, uint8_t r, uint } -static bool _rasterMattedRle(SwSurface* surface, SwRleData* rle, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +static bool _rasterMattedRle(SwSurface* surface, SwRle* rle, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { TVGLOG("SW_ENGINE", "Matted(%d) Rle", (int)surface->compositor->method); @@ -568,10 +568,8 @@ static bool _rasterMattedRle(SwSurface* surface, SwRleData* rle, uint8_t r, uint *dst = tmp + ALPHA_BLEND(*dst, IA(tmp)); } } - return true; - } //8bit grayscale - if (surface->channelSize == sizeof(uint8_t)) { + } else if (surface->channelSize == sizeof(uint8_t)) { uint8_t src; for (uint32_t i = 0; i < rle->size; ++i, ++span) { auto dst = &surface->buf8[span->y * surface->stride + span->x]; @@ -582,13 +580,12 @@ static bool _rasterMattedRle(SwSurface* surface, SwRleData* rle, uint8_t r, uint *dst = INTERPOLATE8(src, *dst, alpha(cmp)); } } - return true; } - return false; + return true; } -static bool _rasterBlendingRle(SwSurface* surface, const SwRleData* rle, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +static bool _rasterBlendingRle(SwSurface* surface, const SwRle* rle, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { if (surface->channelSize != sizeof(uint32_t)) return false; @@ -612,7 +609,7 @@ static bool _rasterBlendingRle(SwSurface* surface, const SwRleData* rle, uint8_t } -static bool _rasterTranslucentRle(SwSurface* surface, const SwRleData* rle, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +static bool _rasterTranslucentRle(SwSurface* surface, const SwRle* rle, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { #if defined(THORVG_AVX_VECTOR_SUPPORT) return avxRasterTranslucentRle(surface, rle, r, g, b, a); @@ -624,7 +621,7 @@ static bool _rasterTranslucentRle(SwSurface* surface, const SwRleData* rle, uint } -static bool _rasterSolidRle(SwSurface* surface, const SwRleData* rle, uint8_t r, uint8_t g, uint8_t b) +static bool _rasterSolidRle(SwSurface* surface, const SwRle* rle, uint8_t r, uint8_t g, uint8_t b) { auto span = rle->spans; @@ -661,7 +658,7 @@ static bool _rasterSolidRle(SwSurface* surface, const SwRleData* rle, uint8_t r, } -static bool _rasterRle(SwSurface* surface, SwRleData* rle, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +static bool _rasterRle(SwSurface* surface, SwRle* rle, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { if (!rle) return false; @@ -697,66 +694,9 @@ static bool _rasterRle(SwSurface* surface, SwRleData* rle, uint8_t r, uint8_t g, auto sx = (x) * itransform->e11 + itransform->e13 - 0.49f; \ if (sx <= -0.5f || (uint32_t)(sx + 0.5f) >= image->w) continue; \ - -#if 0 //Enable it when GRAYSCALE image is supported -static bool _rasterCompositeScaledMaskedRleImage(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, SwMask maskOp, uint8_t opacity) -{ - auto scaleMethod = image->scale < DOWN_SCALE_TOLERANCE ? _interpDownScaler : _interpUpScaler; - auto sampleSize = _sampleSize(image->scale); - auto span = image->rle->spans; - int32_t miny = 0, maxy = 0; - - for (uint32_t i = 0; i < image->rle->size; ++i, ++span) { - SCALED_IMAGE_RANGE_Y(span->y) - auto cmp = &surface->compositor->image.buf8[span->y * surface->compositor->image.stride + span->x]; - auto a = MULTIPLY(span->coverage, opacity); - for (uint32_t x = static_cast(span->x); x < static_cast(span->x) + span->len; ++x, ++cmp) { - SCALED_IMAGE_RANGE_X - auto src = scaleMethod(image->buf8, image->stride, image->w, image->h, sx, sy, miny, maxy, sampleSize); - if (a < 255) src = MULTIPLY(src, a); - *cmp = maskOp(src, *cmp, ~src); - } - } - return true; -} - - -static bool _rasterDirectScaledMaskedRleImage(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, SwMask maskOp, uint8_t opacity) -{ - auto scaleMethod = image->scale < DOWN_SCALE_TOLERANCE ? _interpDownScaler : _interpUpScaler; - auto sampleSize = _sampleSize(image->scale); - auto span = image->rle->spans; - int32_t miny = 0, maxy = 0; - - for (uint32_t i = 0; i < image->rle->size; ++i, ++span) { - SCALED_IMAGE_RANGE_Y(span->y) - auto cmp = &surface->compositor->image.buf8[span->y * surface->compositor->image.stride + span->x]; - auto dst = &surface->buf8[span->y * surface->stride + span->x]; - auto a = MULTIPLY(span->coverage, opacity); - for (uint32_t x = static_cast(span->x); x < static_cast(span->x) + span->len; ++x, ++cmp, ++dst) { - SCALED_IMAGE_RANGE_X - auto src = scaleMethod(image->buf8, image->stride, image->w, image->h, sx, sy, miny, maxy, sampleSize); - if (a < 255) src = MULTIPLY(src, a); - src = maskOp(src, *cmp, 0); //not use alpha - *dst = src + MULTIPLY(*dst, ~src); - } - } - return _compositeMaskImage(surface, &surface->compositor->image, surface->compositor->bbox); -} -#endif - static bool _rasterScaledMaskedRleImage(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, uint8_t opacity) { -#if 0 //Enable it when GRAYSCALE image is supported - TVGLOG("SW_ENGINE", "Scaled Masked(%d) Rle Image", (int)surface->compositor->method); - - //8bit masking channels composition - if (surface->channelSize != sizeof(uint8_t)) return false; - - auto maskOp = _getMaskOp(surface->compositor->method); - if (_direct(surface->compositor->method)) return _rasterDirectScaledMaskedRleImage(surface, image, itransform, region, maskOp, opacity); - else return _rasterCompositeScaledMaskedRleImage(surface, image, itransform, region, maskOp, opacity); -#endif + TVGERR("SW_ENGINE", "Not Supported Scaled Masked(%d) Rle Image", (int)surface->compositor->method); return false; } @@ -784,7 +724,6 @@ static bool _rasterScaledMattedRleImage(SwSurface* surface, const SwImage* image *dst = src + ALPHA_BLEND(*dst, IA(src)); } } - return true; } @@ -851,7 +790,7 @@ static bool _scaledRleImage(SwSurface* surface, const SwImage* image, const Matr Matrix itransform; - if (!mathInverse(&transform, &itransform)) return true; + if (!inverse(&transform, &itransform)) return true; if (_compositing(surface)) { if (_matting(surface)) return _rasterScaledMattedRleImage(surface, image, &itransform, region, opacity); @@ -869,75 +808,6 @@ static bool _scaledRleImage(SwSurface* surface, const SwImage* image, const Matr /* RLE Direct Image */ /************************************************************************/ -#if 0 //Enable it when GRAYSCALE image is supported -static bool _rasterCompositeDirectMaskedRleImage(SwSurface* surface, const SwImage* image, SwMask maskOp, uint8_t opacity) -{ - auto span = image->rle->spans; - auto cbuffer = surface->compositor->image.buf8; - auto ctride = surface->compositor->image.stride; - - for (uint32_t i = 0; i < image->rle->size; ++i, ++span) { - auto src = image->buf8 + (span->y + image->oy) * image->stride + (span->x + image->ox); - auto cmp = &cbuffer[span->y * ctride + span->x]; - auto alpha = MULTIPLY(span->coverage, opacity); - if (alpha == 255) { - for (uint32_t x = 0; x < span->len; ++x, ++src, ++cmp) { - *cmp = maskOp(*src, *cmp, ~*src); - } - } else { - for (uint32_t x = 0; x < span->len; ++x, ++src, ++cmp) { - auto tmp = MULTIPLY(*src, alpha); - *cmp = maskOp(*src, *cmp, ~tmp); - } - } - } - return _compositeMaskImage(surface, &surface->compositor->image, surface->compositor->bbox); -} - - -static bool _rasterDirectDirectMaskedRleImage(SwSurface* surface, const SwImage* image, SwMask maskOp, uint8_t opacity) -{ - auto span = image->rle->spans; - auto cbuffer = surface->compositor->image.buf8; - auto ctride = surface->compositor->image.stride; - - for (uint32_t i = 0; i < image->rle->size; ++i, ++span) { - auto src = image->buf8 + (span->y + image->oy) * image->stride + (span->x + image->ox); - auto cmp = &cbuffer[span->y * ctride + span->x]; - auto dst = &surface->buf8[span->y * surface->stride + span->x]; - auto alpha = MULTIPLY(span->coverage, opacity); - if (alpha == 255) { - for (uint32_t x = 0; x < span->len; ++x, ++src, ++cmp, ++dst) { - auto tmp = maskOp(*src, *cmp, 0); //not use alpha - *dst = INTERPOLATE8(tmp, *dst, (255 - tmp)); - } - } else { - for (uint32_t x = 0; x < span->len; ++x, ++src, ++cmp, ++dst) { - auto tmp = maskOp(MULTIPLY(*src, alpha), *cmp, 0); //not use alpha - *dst = INTERPOLATE8(tmp, *dst, (255 - tmp)); - } - } - } - return true; -} -#endif - -static bool _rasterDirectMaskedRleImage(SwSurface* surface, const SwImage* image, uint8_t opacity) -{ -#if 0 //Enable it when GRAYSCALE image is supported - TVGLOG("SW_ENGINE", "Direct Masked(%d) Rle Image", (int)surface->compositor->method); - - //8bit masking channels composition - if (surface->channelSize != sizeof(uint8_t)) return false; - - auto maskOp = _getMaskOp(surface->compositor->method); - if (_direct(surface->compositor->method)) _rasterDirectDirectMaskedRleImage(surface, image, maskOp, opacity); - else return _rasterCompositeDirectMaskedRleImage(surface, image, maskOp, opacity); -#endif - return false; -} - - static bool _rasterDirectMattedRleImage(SwSurface* surface, const SwImage* image, uint8_t opacity) { TVGLOG("SW_ENGINE", "Direct Matted(%d) Rle Image", (int)surface->compositor->method); @@ -999,21 +869,19 @@ static bool _rasterDirectRleImage(SwSurface* surface, const SwImage* image, uint auto dst = &surface->buf32[span->y * surface->stride + span->x]; auto img = image->buf32 + (span->y + image->oy) * image->stride + (span->x + image->ox); auto alpha = MULTIPLY(span->coverage, opacity); - if (alpha == 255) { - for (uint32_t x = 0; x < span->len; ++x, ++dst, ++img) { - *dst = *img + ALPHA_BLEND(*dst, IA(*img)); - } - } else { - for (uint32_t x = 0; x < span->len; ++x, ++dst, ++img) { - auto src = ALPHA_BLEND(*img, alpha); - *dst = src + ALPHA_BLEND(*dst, IA(src)); - } - } + rasterTranslucentPixel32(dst, img, span->len, alpha); } return true; } +static bool _rasterDirectMaskedRleImage(SwSurface* surface, const SwImage* image, uint8_t opacity) +{ + TVGERR("SW_ENGINE", "Not Supported Direct Masked(%d) Rle Image", (int)surface->compositor->method); + return false; +} + + static bool _directRleImage(SwSurface* surface, const SwImage* image, uint8_t opacity) { if (surface->channelSize == sizeof(uint8_t)) { @@ -1037,67 +905,9 @@ static bool _directRleImage(SwSurface* surface, const SwImage* image, uint8_t op /*Scaled Image */ /************************************************************************/ -#if 0 //Enable it when GRAYSCALE image is supported -static bool _rasterCompositeScaledMaskedImage(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, SwMask maskOp, uint8_t opacity) -{ - auto scaleMethod = image->scale < DOWN_SCALE_TOLERANCE ? _interpDownScaler : _interpUpScaler; - auto sampleSize = _sampleSize(image->scale); - auto cstride = surface->compositor->image.stride; - auto cbuffer = surface->compositor->image.buf8 + (region.min.y * cstride + region.min.x); - int32_t miny = 0, maxy = 0; - - for (auto y = region.min.y; y < region.max.y; ++y) { - SCALED_IMAGE_RANGE_Y(y) - auto cmp = cbuffer; - for (auto x = region.min.x; x < region.max.x; ++x, ++cmp) { - SCALED_IMAGE_RANGE_X - auto src = scaleMethod(image->buf8, image->stride, image->w, image->h, sx, sy, miny, maxy, sampleSize); - if (opacity < 255) src = MULTIPLY(src, opacity); - *cmp = maskOp(src, *cmp, ~src); - } - cbuffer += cstride; - } - return _compositeMaskImage(surface, &surface->compositor->image, surface->compositor->bbox); -} - - -static bool _rasterDirectScaledMaskedImage(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, SwMask maskOp, uint8_t opacity) -{ - auto scaleMethod = image->scale < DOWN_SCALE_TOLERANCE ? _interpDownScaler : _interpUpScaler; - auto sampleSize = _sampleSize(image->scale); - auto cstride = surface->compositor->image.stride; - auto cbuffer = surface->compositor->image.buf8 + (region.min.y * cstride + region.min.x); - auto dbuffer = surface->buf8 + (region.min.y * surface->stride + region.min.x); - int32_t miny = 0, maxy = 0; - - for (auto y = region.min.y; y < region.max.y; ++y) { - SCALED_IMAGE_RANGE_Y(y) - auto cmp = cbuffer; - auto dst = dbuffer; - for (auto x = region.min.x; x < region.max.x; ++x, ++cmp, ++dst) { - SCALED_IMAGE_RANGE_X - auto src = scaleMethod(image->buf8, image->stride, image->w, image->h, sx, sy, miny, maxy, sampleSize); - if (opacity < 255) src = MULTIPLY(src, opacity); - src = maskOp(src, *cmp, 0); //not use alpha - *dst = src + MULTIPLY(*dst, ~src); - } - cbuffer += cstride; - dbuffer += surface->stride; - } - return true; -} -#endif - static bool _rasterScaledMaskedImage(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, uint8_t opacity) { - TVGERR("SW_ENGINE", "Not supported ScaledMaskedImage!"); -#if 0 //Enable it when GRAYSCALE image is supported - TVGLOG("SW_ENGINE", "Scaled Masked(%d) Image [Region: %lu %lu %lu %lu]", (int)surface->compositor->method, region.min.x, region.min.y, region.max.x - region.min.x, region.max.y - region.min.y); - - auto maskOp = _getMaskOp(surface->compositor->method); - if (_direct(surface->compositor->method)) return _rasterDirectScaledMaskedImage(surface, image, itransform, region, maskOp, opacity); - else return _rasterCompositeScaledMaskedImage(surface, image, itransform, region, maskOp, opacity); -#endif + TVGERR("SW_ENGINE", "Not Supported Scaled Masked Image!"); return false; } @@ -1202,7 +1012,7 @@ static bool _scaledImage(SwSurface* surface, const SwImage* image, const Matrix& { Matrix itransform; - if (!mathInverse(&transform, &itransform)) return true; + if (!inverse(&transform, &itransform)) return true; if (_compositing(surface)) { if (_matting(surface)) return _rasterScaledMattedImage(surface, image, &itransform, region, opacity); @@ -1220,78 +1030,9 @@ static bool _scaledImage(SwSurface* surface, const SwImage* image, const Matrix& /* Direct Image */ /************************************************************************/ -#if 0 //Enable it when GRAYSCALE image is supported -static bool _rasterCompositeDirectMaskedImage(SwSurface* surface, const SwImage* image, const SwBBox& region, SwMask maskOp, uint8_t opacity) -{ - auto h = static_cast(region.max.y - region.min.y); - auto w = static_cast(region.max.x - region.min.x); - auto cstride = surface->compositor->image.stride; - - auto cbuffer = surface->compositor->image.buf8 + (region.min.y * cstride + region.min.x); //compositor buffer - auto sbuffer = image->buf8 + (region.min.y + image->oy) * image->stride + (region.min.x + image->ox); - - for (uint32_t y = 0; y < h; ++y) { - auto cmp = cbuffer; - auto src = sbuffer; - if (opacity == 255) { - for (uint32_t x = 0; x < w; ++x, ++src, ++cmp) { - *cmp = maskOp(*src, *cmp, ~*src); - } - } else { - for (uint32_t x = 0; x < w; ++x, ++src, ++cmp) { - auto tmp = MULTIPLY(*src, opacity); - *cmp = maskOp(tmp, *cmp, ~tmp); - } - } - cbuffer += cstride; - sbuffer += image->stride; - } - return _compositeMaskImage(surface, &surface->compositor->image, surface->compositor->bbox); -} - - -static bool _rasterDirectDirectMaskedImage(SwSurface* surface, const SwImage* image, const SwBBox& region, SwMask maskOp, uint8_t opacity) -{ - auto h = static_cast(region.max.y - region.min.y); - auto w = static_cast(region.max.x - region.min.x); - auto cstride = surface->compositor->image.stride; - - auto cbuffer = surface->compositor->image.buf32 + (region.min.y * cstride + region.min.x); //compositor buffer - auto dbuffer = surface->buf8 + (region.min.y * surface->stride + region.min.x); //destination buffer - auto sbuffer = image->buf8 + (region.min.y + image->oy) * image->stride + (region.min.x + image->ox); - - for (uint32_t y = 0; y < h; ++y) { - auto cmp = cbuffer; - auto dst = dbuffer; - auto src = sbuffer; - if (opacity == 255) { - for (uint32_t x = 0; x < w; ++x, ++src, ++cmp, ++dst) { - auto tmp = maskOp(*src, *cmp, 0); //not use alpha - *dst = tmp + MULTIPLY(*dst, ~tmp); - } - } else { - for (uint32_t x = 0; x < w; ++x, ++src, ++cmp, ++dst) { - auto tmp = maskOp(MULTIPLY(*src, opacity), *cmp, 0); //not use alpha - *dst = tmp + MULTIPLY(*dst, ~tmp); - } - } - cbuffer += cstride; - dbuffer += surface->stride; - sbuffer += image->stride; - } - return true; -} -#endif - static bool _rasterDirectMaskedImage(SwSurface* surface, const SwImage* image, const SwBBox& region, uint8_t opacity) { - TVGERR("SW_ENGINE", "Not Supported: Direct Masked(%d) Image [Region: %lu %lu %lu %lu]", (int)surface->compositor->method, region.min.x, region.min.y, region.max.x - region.min.x, region.max.y - region.min.y); - -#if 0 //Enable it when GRAYSCALE image is supported - auto maskOp = _getMaskOp(surface->compositor->method); - if (_direct(surface->compositor->method)) return _rasterDirectDirectMaskedImage(surface, image, region, maskOp, opacity); - else return _rasterCompositeDirectMaskedImage(surface, image, region, maskOp, opacity); -#endif + TVGERR("SW_ENGINE", "Not Supported: Direct Masked Image"); return false; } @@ -1394,37 +1135,24 @@ static bool _rasterDirectImage(SwSurface* surface, const SwImage* image, const S //32bits channels if (surface->channelSize == sizeof(uint32_t)) { auto dbuffer = &surface->buf32[region.min.y * surface->stride + region.min.x]; - for (auto y = region.min.y; y < region.max.y; ++y) { - auto dst = dbuffer; - auto src = sbuffer; - if (opacity == 255) { - for (auto x = region.min.x; x < region.max.x; x++, dst++, src++) { - *dst = *src + ALPHA_BLEND(*dst, IA(*src)); - } - } else { - for (auto x = region.min.x; x < region.max.x; ++x, ++dst, ++src) { - auto tmp = ALPHA_BLEND(*src, opacity); - *dst = tmp + ALPHA_BLEND(*dst, IA(tmp)); - } - } + rasterTranslucentPixel32(dbuffer, sbuffer, region.max.x - region.min.x, opacity); dbuffer += surface->stride; sbuffer += image->stride; } //8bits grayscale } else if (surface->channelSize == sizeof(uint8_t)) { auto dbuffer = &surface->buf8[region.min.y * surface->stride + region.min.x]; - for (auto y = region.min.y; y < region.max.y; ++y, dbuffer += surface->stride, sbuffer += image->stride) { auto dst = dbuffer; auto src = sbuffer; if (opacity == 255) { for (auto x = region.min.x; x < region.max.x; ++x, ++dst, ++src) { - *dst = *src + MULTIPLY(*dst, ~*src); + *dst = *src + MULTIPLY(*dst, IA(*src)); } } else { for (auto x = region.min.x; x < region.max.x; ++x, ++dst, ++src) { - *dst = INTERPOLATE8(*src, *dst, opacity); + *dst = INTERPOLATE8(A(*src), *dst, opacity); } } } @@ -1602,13 +1330,23 @@ static bool _rasterBlendingGradientRect(SwSurface* surface, const SwBBox& region template static bool _rasterTranslucentGradientRect(SwSurface* surface, const SwBBox& region, const SwFill* fill) { - auto buffer = surface->buf32 + (region.min.y * surface->stride) + region.min.x; auto h = static_cast(region.max.y - region.min.y); auto w = static_cast(region.max.x - region.min.x); - for (uint32_t y = 0; y < h; ++y) { - fillMethod()(fill, buffer, region.min.y + y, region.min.x, w, opBlendPreNormal, 255); - buffer += surface->stride; + //32 bits + if (surface->channelSize == sizeof(uint32_t)) { + auto buffer = surface->buf32 + (region.min.y * surface->stride) + region.min.x; + for (uint32_t y = 0; y < h; ++y) { + fillMethod()(fill, buffer, region.min.y + y, region.min.x, w, opBlendPreNormal, 255); + buffer += surface->stride; + } + //8 bits + } else if (surface->channelSize == sizeof(uint8_t)) { + auto buffer = surface->buf8 + (region.min.y * surface->stride) + region.min.x; + for (uint32_t y = 0; y < h; ++y) { + fillMethod()(fill, buffer, region.min.y + y, region.min.x, w, _opMaskAdd, 255); + buffer += surface->stride; + } } return true; } @@ -1617,12 +1355,23 @@ static bool _rasterTranslucentGradientRect(SwSurface* surface, const SwBBox& reg template static bool _rasterSolidGradientRect(SwSurface* surface, const SwBBox& region, const SwFill* fill) { - auto buffer = surface->buf32 + (region.min.y * surface->stride) + region.min.x; auto w = static_cast(region.max.x - region.min.x); auto h = static_cast(region.max.y - region.min.y); - for (uint32_t y = 0; y < h; ++y) { - fillMethod()(fill, buffer + y * surface->stride, region.min.y + y, region.min.x, w, opBlendSrcOver, 255); + //32 bits + if (surface->channelSize == sizeof(uint32_t)) { + auto buffer = surface->buf32 + (region.min.y * surface->stride) + region.min.x; + for (uint32_t y = 0; y < h; ++y) { + fillMethod()(fill, buffer, region.min.y + y, region.min.x, w, opBlendSrcOver, 255); + buffer += surface->stride; + } + //8 bits + } else if (surface->channelSize == sizeof(uint8_t)) { + auto buffer = surface->buf8 + (region.min.y * surface->stride) + region.min.x; + for (uint32_t y = 0; y < h; ++y) { + fillMethod()(fill, buffer, region.min.y + y, region.min.x, w, _opMaskNone, 255); + buffer += surface->stride; + } } return true; } @@ -1663,7 +1412,7 @@ static bool _rasterRadialGradientRect(SwSurface* surface, const SwBBox& region, /************************************************************************/ template -static bool _rasterCompositeGradientMaskedRle(SwSurface* surface, const SwRleData* rle, const SwFill* fill, SwMask maskOp) +static bool _rasterCompositeGradientMaskedRle(SwSurface* surface, const SwRle* rle, const SwFill* fill, SwMask maskOp) { auto span = rle->spans; auto cstride = surface->compositor->image.stride; @@ -1678,7 +1427,7 @@ static bool _rasterCompositeGradientMaskedRle(SwSurface* surface, const SwRleDat template -static bool _rasterDirectGradientMaskedRle(SwSurface* surface, const SwRleData* rle, const SwFill* fill, SwMask maskOp) +static bool _rasterDirectGradientMaskedRle(SwSurface* surface, const SwRle* rle, const SwFill* fill, SwMask maskOp) { auto span = rle->spans; auto cstride = surface->compositor->image.stride; @@ -1695,7 +1444,7 @@ static bool _rasterDirectGradientMaskedRle(SwSurface* surface, const SwRleData* template -static bool _rasterGradientMaskedRle(SwSurface* surface, const SwRleData* rle, const SwFill* fill) +static bool _rasterGradientMaskedRle(SwSurface* surface, const SwRle* rle, const SwFill* fill) { auto method = surface->compositor->method; @@ -1710,7 +1459,7 @@ static bool _rasterGradientMaskedRle(SwSurface* surface, const SwRleData* rle, c template -static bool _rasterGradientMattedRle(SwSurface* surface, const SwRleData* rle, const SwFill* fill) +static bool _rasterGradientMattedRle(SwSurface* surface, const SwRle* rle, const SwFill* fill) { TVGLOG("SW_ENGINE", "Matted(%d) Rle Linear Gradient", (int)surface->compositor->method); @@ -1729,7 +1478,7 @@ static bool _rasterGradientMattedRle(SwSurface* surface, const SwRleData* rle, c template -static bool _rasterBlendingGradientRle(SwSurface* surface, const SwRleData* rle, const SwFill* fill) +static bool _rasterBlendingGradientRle(SwSurface* surface, const SwRle* rle, const SwFill* fill) { auto span = rle->spans; @@ -1742,7 +1491,7 @@ static bool _rasterBlendingGradientRle(SwSurface* surface, const SwRleData* rle, template -static bool _rasterTranslucentGradientRle(SwSurface* surface, const SwRleData* rle, const SwFill* fill) +static bool _rasterTranslucentGradientRle(SwSurface* surface, const SwRle* rle, const SwFill* fill) { auto span = rle->spans; @@ -1757,7 +1506,7 @@ static bool _rasterTranslucentGradientRle(SwSurface* surface, const SwRleData* r } else if (surface->channelSize == sizeof(uint8_t)) { for (uint32_t i = 0; i < rle->size; ++i, ++span) { auto dst = &surface->buf8[span->y * surface->stride + span->x]; - fillMethod()(fill, dst, span->y, span->x, span->len, _opMaskAdd, 255); + fillMethod()(fill, dst, span->y, span->x, span->len, _opMaskAdd, span->coverage); } } return true; @@ -1765,7 +1514,7 @@ static bool _rasterTranslucentGradientRle(SwSurface* surface, const SwRleData* r template -static bool _rasterSolidGradientRle(SwSurface* surface, const SwRleData* rle, const SwFill* fill) +static bool _rasterSolidGradientRle(SwSurface* surface, const SwRle* rle, const SwFill* fill) { auto span = rle->spans; @@ -1789,7 +1538,7 @@ static bool _rasterSolidGradientRle(SwSurface* surface, const SwRleData* rle, co } -static bool _rasterLinearGradientRle(SwSurface* surface, const SwRleData* rle, const SwFill* fill) +static bool _rasterLinearGradientRle(SwSurface* surface, const SwRle* rle, const SwFill* fill) { if (!rle) return false; @@ -1806,7 +1555,7 @@ static bool _rasterLinearGradientRle(SwSurface* surface, const SwRleData* rle, c } -static bool _rasterRadialGradientRle(SwSurface* surface, const SwRleData* rle, const SwFill* fill) +static bool _rasterRadialGradientRle(SwSurface* surface, const SwRle* rle, const SwFill* fill) { if (!rle) return false; @@ -1814,9 +1563,9 @@ static bool _rasterRadialGradientRle(SwSurface* surface, const SwRleData* rle, c if (_matting(surface)) return _rasterGradientMattedRle(surface, rle, fill); else return _rasterGradientMaskedRle(surface, rle, fill); } else if (_blending(surface)) { - _rasterBlendingGradientRle(surface, rle, fill); + return _rasterBlendingGradientRle(surface, rle, fill); } else { - if (fill->translucent) _rasterTranslucentGradientRle(surface, rle, fill); + if (fill->translucent) return _rasterTranslucentGradientRle(surface, rle, fill); else return _rasterSolidGradientRle(surface, rle, fill); } return false; @@ -1827,6 +1576,19 @@ static bool _rasterRadialGradientRle(SwSurface* surface, const SwRleData* rle, c /* External Class Implementation */ /************************************************************************/ +void rasterTranslucentPixel32(uint32_t* dst, uint32_t* src, uint32_t len, uint8_t opacity) +{ + //TODO: Support SIMD accelerations + cRasterTranslucentPixels(dst, src, len, opacity); +} + + +void rasterPixel32(uint32_t* dst, uint32_t* src, uint32_t len, uint8_t opacity) +{ + //TODO: Support SIMD accelerations + cRasterPixels(dst, src, len, opacity); +} + void rasterGrayscale8(uint8_t *dst, uint8_t val, uint32_t offset, int32_t len) { @@ -1905,7 +1667,7 @@ bool rasterClear(SwSurface* surface, uint32_t x, uint32_t y, uint32_t w, uint32_ } -void rasterUnpremultiply(Surface* surface) +void rasterUnpremultiply(RenderSurface* surface) { if (surface->channelSize != sizeof(uint32_t)) return; @@ -1935,7 +1697,7 @@ void rasterUnpremultiply(Surface* surface) } -void rasterPremultiply(Surface* surface) +void rasterPremultiply(RenderSurface* surface) { ScopedLock lock(surface->key); if (surface->premultiplied || (surface->channelSize != sizeof(uint32_t))) return; @@ -1965,13 +1727,13 @@ bool rasterGradientShape(SwSurface* surface, SwShape* shape, const Fill* fdata, return a > 0 ? rasterShape(surface, shape, color->r, color->g, color->b, a) : true; } - auto id = fdata->identifier(); + auto type = fdata->type(); if (shape->fastTrack) { - if (id == TVG_CLASS_ID_LINEAR) return _rasterLinearGradientRect(surface, shape->bbox, shape->fill); - else if (id == TVG_CLASS_ID_RADIAL)return _rasterRadialGradientRect(surface, shape->bbox, shape->fill); + if (type == Type::LinearGradient) return _rasterLinearGradientRect(surface, shape->bbox, shape->fill); + else if (type == Type::RadialGradient)return _rasterRadialGradientRect(surface, shape->bbox, shape->fill); } else { - if (id == TVG_CLASS_ID_LINEAR) return _rasterLinearGradientRle(surface, shape->rle, shape->fill); - else if (id == TVG_CLASS_ID_RADIAL) return _rasterRadialGradientRle(surface, shape->rle, shape->fill); + if (type == Type::LinearGradient) return _rasterLinearGradientRle(surface, shape->rle, shape->fill); + else if (type == Type::RadialGradient) return _rasterRadialGradientRle(surface, shape->rle, shape->fill); } return false; } @@ -1986,9 +1748,9 @@ bool rasterGradientStroke(SwSurface* surface, SwShape* shape, const Fill* fdata, return a > 0 ? rasterStroke(surface, shape, color->r, color->g, color->b, a) : true; } - auto id = fdata->identifier(); - if (id == TVG_CLASS_ID_LINEAR) return _rasterLinearGradientRle(surface, shape->strokeRle, shape->stroke->fill); - else if (id == TVG_CLASS_ID_RADIAL) return _rasterRadialGradientRle(surface, shape->strokeRle, shape->stroke->fill); + auto type = fdata->type(); + if (type == Type::LinearGradient) return _rasterLinearGradientRle(surface, shape->strokeRle, shape->stroke->fill); + else if (type == Type::RadialGradient) return _rasterRadialGradientRle(surface, shape->strokeRle, shape->stroke->fill); return false; } @@ -2027,12 +1789,12 @@ bool rasterImage(SwSurface* surface, SwImage* image, const Matrix& transform, co } -bool rasterConvertCS(Surface* surface, ColorSpace to) +bool rasterConvertCS(RenderSurface* surface, ColorSpace to) { ScopedLock lock(surface->key); if (surface->cs == to) return true; - //TOOD: Support SIMD accelerations + //TODO: Support SIMD accelerations auto from = surface->cs; if (((from == ColorSpace::ABGR8888) || (from == ColorSpace::ABGR8888S)) && ((to == ColorSpace::ARGB8888) || (to == ColorSpace::ARGB8888S))) { @@ -2045,3 +1807,39 @@ bool rasterConvertCS(Surface* surface, ColorSpace to) } return false; } + + +//TODO: SIMD OPTIMIZATION? +void rasterXYFlip(uint32_t* src, uint32_t* dst, int32_t stride, int32_t w, int32_t h, const SwBBox& bbox, bool flipped) +{ + constexpr int BLOCK = 8; //experimental decision + + if (flipped) { + src += ((bbox.min.x * stride) + bbox.min.y); + dst += ((bbox.min.y * stride) + bbox.min.x); + } else { + src += ((bbox.min.y * stride) + bbox.min.x); + dst += ((bbox.min.x * stride) + bbox.min.y); + } + + #pragma omp parallel for + for (int x = 0; x < w; x += BLOCK) { + auto bx = std::min(w, x + BLOCK) - x; + auto in = &src[x]; + auto out = &dst[x * stride]; + for (int y = 0; y < h; y += BLOCK) { + auto p = &in[y * stride]; + auto q = &out[y]; + auto by = std::min(h, y + BLOCK) - y; + for (int xx = 0; xx < bx; ++xx) { + for (int yy = 0; yy < by; ++yy) { + *q = *p; + p += stride; + ++q; + } + p += 1 - by * stride; + q += stride - by; + } + } + } +} diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterAvx.h b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterAvx.h index a072a88819a..79cab043f24 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterAvx.h +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterAvx.h @@ -158,7 +158,7 @@ static bool avxRasterTranslucentRect(SwSurface* surface, const SwBBox& region, u } -static bool avxRasterTranslucentRle(SwSurface* surface, const SwRleData* rle, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +static bool avxRasterTranslucentRle(SwSurface* surface, const SwRle* rle, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { auto span = rle->spans; @@ -185,7 +185,7 @@ static bool avxRasterTranslucentRle(SwSurface* surface, const SwRleData* rle, ui } //2. fill the aligned memory using avx - N_32BITS_IN_128REG pixels processed at once - //In order to avoid unneccessary avx variables declarations a check is made whether there are any iterations at all + //In order to avoid unnecessary avx variables declarations a check is made whether there are any iterations at all uint32_t iterations = (span->len - notAligned) / N_32BITS_IN_128REG; uint32_t avxFilled = 0; if (iterations > 0) { diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterC.h b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterC.h index 6d0bd9383da..d79da0e4d8c 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterC.h +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterC.h @@ -20,6 +20,38 @@ * SOFTWARE. */ + +template +static void inline cRasterTranslucentPixels(PIXEL_T* dst, PIXEL_T* src, uint32_t len, uint32_t opacity) +{ + //TODO: 64bits faster? + if (opacity == 255) { + for (uint32_t x = 0; x < len; ++x, ++dst, ++src) { + *dst = *src + ALPHA_BLEND(*dst, IA(*src)); + } + } else { + for (uint32_t x = 0; x < len; ++x, ++dst, ++src) { + auto tmp = ALPHA_BLEND(*src, opacity); + *dst = tmp + ALPHA_BLEND(*dst, IA(tmp)); + } + } +} + + +template +static void inline cRasterPixels(PIXEL_T* dst, PIXEL_T* src, uint32_t len, uint32_t opacity) +{ + //TODO: 64bits faster? + if (opacity == 255) { + for (uint32_t x = 0; x < len; ++x, ++dst, ++src) { + *dst = *src; + } + } else { + cRasterTranslucentPixels(dst, src, len, opacity); + } +} + + template static void inline cRasterPixels(PIXEL_T* dst, PIXEL_T val, uint32_t offset, int32_t len) { @@ -60,7 +92,7 @@ static void inline cRasterPixels(PIXEL_T* dst, PIXEL_T val, uint32_t offset, int } -static bool inline cRasterTranslucentRle(SwSurface* surface, const SwRleData* rle, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +static bool inline cRasterTranslucentRle(SwSurface* surface, const SwRle* rle, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { auto span = rle->spans; @@ -125,7 +157,7 @@ static bool inline cRasterTranslucentRect(SwSurface* surface, const SwBBox& regi } -static bool inline cRasterABGRtoARGB(Surface* surface) +static bool inline cRasterABGRtoARGB(RenderSurface* surface) { TVGLOG("SW_ENGINE", "Convert ColorSpace ABGR - ARGB [Size: %d x %d]", surface->w, surface->h); @@ -156,7 +188,7 @@ static bool inline cRasterABGRtoARGB(Surface* surface) } -static bool inline cRasterARGBtoABGR(Surface* surface) +static bool inline cRasterARGBtoABGR(RenderSurface* surface) { //exactly same with ABGRtoARGB return cRasterABGRtoARGB(surface); diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterNeon.h b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterNeon.h index 91cf7743c19..fe693b7f33a 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterNeon.h +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterNeon.h @@ -89,7 +89,7 @@ static void neonRasterPixel32(uint32_t *dst, uint32_t val, uint32_t offset, int3 } -static bool neonRasterTranslucentRle(SwSurface* surface, const SwRleData* rle, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +static bool neonRasterTranslucentRle(SwSurface* surface, const SwRle* rle, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { auto span = rle->spans; diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterTexmap.h b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterTexmap.h index 88ef2118f27..1162edc8381 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterTexmap.h +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterTexmap.h @@ -20,6 +20,17 @@ * SOFTWARE. */ +struct Vertex +{ + Point pt; + Point uv; +}; + +struct Polygon +{ + Vertex vertex[3]; +}; + struct AALine { int32_t x[2]; @@ -53,10 +64,12 @@ static bool _arrange(const SwImage* image, const SwBBox* region, int& yStart, in regionBottom = image->rle->spans[image->rle->size - 1].y; } + if (yStart >= regionBottom) return false; + if (yStart < regionTop) yStart = regionTop; if (yEnd > regionBottom) yEnd = regionBottom; - return yEnd > yStart; + return true; } @@ -673,7 +686,7 @@ static void _rasterPolygonImage(SwSurface* surface, const SwImage* image, const auto denom = ((x[2] - x[0]) * (y[1] - y[0]) - (x[1] - x[0]) * (y[2] - y[0])); //Skip poly if it's an infinitely thin line - if (mathZero(denom)) return; + if (tvg::zero(denom)) return; denom = 1 / denom; //Reciprocal for speeding up dudx = ((u[2] - u[0]) * (y[1] - y[0]) - (u[1] - u[0]) * (y[2] - y[0])) * denom; @@ -689,8 +702,8 @@ static void _rasterPolygonImage(SwSurface* surface, const SwImage* image, const //Determine which side of the polygon the longer edge is on auto side = (dxdy[1] > dxdy[0]) ? true : false; - if (mathEqual(y[0], y[1])) side = x[0] > x[1]; - if (mathEqual(y[1], y[2])) side = x[2] > x[1]; + if (tvg::equal(y[0], y[1])) side = x[0] > x[1]; + if (tvg::equal(y[1], y[2])) side = x[2] > x[1]; auto regionTop = region ? region->min.y : image->rle->spans->y; //Normal Image or Rle Image? auto compositing = _compositing(surface); //Composition required diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp index 116c4e81352..180f3cc37a2 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp @@ -20,6 +20,9 @@ * SOFTWARE. */ +#ifdef THORVG_SW_OPENMP_SUPPORT + #include +#endif #include #include "tvgMath.h" #include "tvgSwCommon.h" @@ -38,7 +41,7 @@ struct SwTask : Task { SwSurface* surface = nullptr; SwMpool* mpool = nullptr; - SwBBox bbox = {{0, 0}, {0, 0}}; //Whole Rendering Region + SwBBox bbox; //Rendering Region Matrix transform; Array clips; RenderUpdateFlag flags = RenderUpdateFlag::None; @@ -65,9 +68,7 @@ struct SwTask : Task } virtual void dispose() = 0; - virtual bool clip(SwRleData* target) = 0; - virtual SwRleData* rle() = 0; - + virtual bool clip(SwRle* target) = 0; virtual ~SwTask() {} }; @@ -92,38 +93,34 @@ struct SwShapeTask : SwTask if (!rshape->stroke) return 0.0f; auto width = rshape->stroke->width; - if (mathZero(width)) return 0.0f; + if (tvg::zero(width)) return 0.0f; if (!rshape->stroke->fill && (MULTIPLY(rshape->stroke->color[3], opacity) == 0)) return 0.0f; - if (mathZero(rshape->stroke->trim.begin - rshape->stroke->trim.end)) return 0.0f; + if (tvg::zero(rshape->stroke->trim.begin - rshape->stroke->trim.end)) return 0.0f; return (width * sqrt(transform.e11 * transform.e11 + transform.e12 * transform.e12)); } - bool clip(SwRleData* target) override + bool clip(SwRle* target) override { - if (shape.fastTrack) rleClipRect(target, &bbox); - else if (shape.rle) rleClipPath(target, shape.rle); + if (shape.fastTrack) rleClip(target, &bbox); + else if (shape.rle) rleClip(target, shape.rle); else return false; return true; } - SwRleData* rle() override - { - if (!shape.rle && shape.fastTrack) { - shape.rle = rleRender(&shape.bbox); - } - return shape.rle; - } - void run(unsigned tid) override { - if (opacity == 0 && !clipper) return; //Invisible + //Invisible + if (opacity == 0 && !clipper) { + bbox.reset(); + return; + } auto strokeWidth = validStrokeWidth(); - bool visibleFill = false; - auto clipRegion = bbox; + SwBBox renderRegion{}; + auto visibleFill = false; //This checks also for the case, if the invisible shape turned to visible by alpha. auto prepareShape = false; @@ -135,10 +132,11 @@ struct SwShapeTask : SwTask rshape->fillColor(nullptr, nullptr, nullptr, &alpha); alpha = MULTIPLY(alpha, opacity); visibleFill = (alpha > 0 || rshape->fill); + shapeReset(&shape); if (visibleFill || clipper) { - shapeReset(&shape); - if (!shapePrepare(&shape, rshape, transform, clipRegion, bbox, mpool, tid, clips.count > 0 ? true : false)) { + if (!shapePrepare(&shape, rshape, transform, bbox, renderRegion, mpool, tid, clips.count > 0 ? true : false)) { visibleFill = false; + renderRegion.reset(); } } } @@ -159,8 +157,8 @@ struct SwShapeTask : SwTask if (flags & (RenderUpdateFlag::Path | RenderUpdateFlag::Stroke | RenderUpdateFlag::Transform)) { if (strokeWidth > 0.0f) { shapeResetStroke(&shape, rshape, transform); - if (!shapeGenStrokeRle(&shape, rshape, transform, clipRegion, bbox, mpool, tid)) goto err; + if (!shapeGenStrokeRle(&shape, rshape, transform, bbox, renderRegion, mpool, tid)) goto err; if (auto fill = rshape->strokeFill()) { auto ctable = (flags & RenderUpdateFlag::GradientStroke) ? true : false; if (ctable) shapeResetStrokeFill(&shape); @@ -184,9 +182,13 @@ struct SwShapeTask : SwTask //Clip stroke rle if (shape.strokeRle && !clipper->clip(shape.strokeRle)) goto err; } + + bbox = renderRegion; //sync + return; err: + bbox.reset(); shapeReset(&shape); shapeDelOutline(&shape, mpool, tid); } @@ -201,20 +203,14 @@ struct SwShapeTask : SwTask struct SwImageTask : SwTask { SwImage image; - Surface* source; //Image source + RenderSurface* source; //Image source - bool clip(SwRleData* target) override + bool clip(SwRle* target) override { TVGERR("SW_ENGINE", "Image is used as ClipPath?"); return true; } - SwRleData* rle() override - { - TVGERR("SW_ENGINE", "Image is used as Scene ClipPath?"); - return nullptr; - } - void run(unsigned tid) override { auto clipRegion = bbox; @@ -452,27 +448,18 @@ bool SwRenderer::blend(BlendMethod method) surface->blendMethod = method; switch (method) { - case BlendMethod::Add: - surface->blender = opBlendAdd; - break; - case BlendMethod::Screen: - surface->blender = opBlendScreen; + case BlendMethod::Normal: + surface->blender = nullptr; break; case BlendMethod::Multiply: surface->blender = opBlendMultiply; break; + case BlendMethod::Screen: + surface->blender = opBlendScreen; + break; case BlendMethod::Overlay: surface->blender = opBlendOverlay; break; - case BlendMethod::Difference: - surface->blender = opBlendDifference; - break; - case BlendMethod::Exclusion: - surface->blender = opBlendExclusion; - break; - case BlendMethod::SrcOver: - surface->blender = opBlendSrcOver; - break; case BlendMethod::Darken: surface->blender = opBlendDarken; break; @@ -491,7 +478,17 @@ bool SwRenderer::blend(BlendMethod method) case BlendMethod::SoftLight: surface->blender = opBlendSoftLight; break; + case BlendMethod::Difference: + surface->blender = opBlendDifference; + break; + case BlendMethod::Exclusion: + surface->blender = opBlendExclusion; + break; + case BlendMethod::Add: + surface->blender = opBlendAdd; + break; default: + TVGLOG("SW_ENGINE", "Non supported blending option = %d", (int) method); surface->blender = nullptr; break; } @@ -505,7 +502,7 @@ RenderRegion SwRenderer::region(RenderData data) } -bool SwRenderer::beginComposite(Compositor* cmp, CompositeMethod method, uint8_t opacity) +bool SwRenderer::beginComposite(RenderCompositor* cmp, CompositeMethod method, uint8_t opacity) { if (!cmp) return false; auto p = static_cast(cmp); @@ -543,31 +540,19 @@ bool SwRenderer::mempool(bool shared) } -const Surface* SwRenderer::mainSurface() +const RenderSurface* SwRenderer::mainSurface() { return surface; } -Compositor* SwRenderer::target(const RenderRegion& region, ColorSpace cs) +SwSurface* SwRenderer::request(int channelSize) { - auto x = region.x; - auto y = region.y; - auto w = region.w; - auto h = region.h; - auto sw = static_cast(surface->w); - auto sh = static_cast(surface->h); - - //Out of boundary - if (x >= sw || y >= sh || x + w < 0 || y + h < 0) return nullptr; - SwSurface* cmp = nullptr; - auto reqChannelSize = CHANNEL_SIZE(cs); - //Use cached data for (auto p = compositors.begin(); p < compositors.end(); ++p) { - if ((*p)->compositor->valid && (*p)->compositor->image.channelSize == reqChannelSize) { + if ((*p)->compositor->valid && (*p)->compositor->image.channelSize == channelSize) { cmp = *p; break; } @@ -578,18 +563,48 @@ Compositor* SwRenderer::target(const RenderRegion& region, ColorSpace cs) //Inherits attributes from main surface cmp = new SwSurface(surface); cmp->compositor = new SwCompositor; - - //TODO: We can optimize compositor surface size from (surface->stride x surface->h) to Parameter(w x h) - cmp->compositor->image.data = (pixel_t*)malloc(reqChannelSize * surface->stride * surface->h); - cmp->channelSize = cmp->compositor->image.channelSize = reqChannelSize; + cmp->compositor->image.data = (pixel_t*)malloc(channelSize * surface->stride * surface->h); + cmp->compositor->image.w = surface->w; + cmp->compositor->image.h = surface->h; + cmp->compositor->image.stride = surface->stride; + cmp->compositor->image.direct = true; + cmp->compositor->valid = true; + cmp->channelSize = cmp->compositor->image.channelSize = channelSize; + cmp->w = cmp->compositor->image.w; + cmp->h = cmp->compositor->image.h; compositors.push(cmp); } + //Sync. This may have been modified by post-processing. + cmp->data = cmp->compositor->image.data; + + return cmp; +} + + +RenderCompositor* SwRenderer::target(const RenderRegion& region, ColorSpace cs) +{ + auto x = region.x; + auto y = region.y; + auto w = region.w; + auto h = region.h; + auto sw = static_cast(surface->w); + auto sh = static_cast(surface->h); + + //Out of boundary + if (x >= sw || y >= sh || x + w < 0 || y + h < 0) return nullptr; + + auto cmp = request(CHANNEL_SIZE(cs)); + //Boundary Check + if (x < 0) x = 0; + if (y < 0) y = 0; if (x + w > sw) w = (sw - x); if (y + h > sh) h = (sh - y); + if (w == 0 || h == 0) return nullptr; + cmp->compositor->recoverSfc = surface; cmp->compositor->recoverCmp = surface->compositor; cmp->compositor->valid = false; @@ -597,14 +612,6 @@ Compositor* SwRenderer::target(const RenderRegion& region, ColorSpace cs) cmp->compositor->bbox.min.y = y; cmp->compositor->bbox.max.x = x + w; cmp->compositor->bbox.max.y = y + h; - cmp->compositor->image.stride = surface->stride; - cmp->compositor->image.w = surface->w; - cmp->compositor->image.h = surface->h; - cmp->compositor->image.direct = true; - - cmp->data = cmp->compositor->image.data; - cmp->w = cmp->compositor->image.w; - cmp->h = cmp->compositor->image.h; /* TODO: Currently, only blending might work. Blending and composition must be handled together. */ @@ -618,7 +625,7 @@ Compositor* SwRenderer::target(const RenderRegion& region, ColorSpace cs) } -bool SwRenderer::endComposite(Compositor* cmp) +bool SwRenderer::endComposite(RenderCompositor* cmp) { if (!cmp) return false; @@ -639,6 +646,40 @@ bool SwRenderer::endComposite(Compositor* cmp) } +bool SwRenderer::prepare(RenderEffect* effect) +{ + switch (effect->type) { + case SceneEffect::GaussianBlur: return effectGaussianBlurPrepare(static_cast(effect)); + case SceneEffect::DropShadow: return effectDropShadowPrepare(static_cast(effect)); + default: return false; + } +} + + +bool SwRenderer::effect(RenderCompositor* cmp, const RenderEffect* effect, uint8_t opacity, bool direct) +{ + if (effect->invalid) return false; + + auto p = static_cast(cmp); + + switch (effect->type) { + case SceneEffect::GaussianBlur: { + return effectGaussianBlur(p, request(surface->channelSize), static_cast(effect)); + } + case SceneEffect::DropShadow: { + auto cmp1 = request(surface->channelSize); + cmp1->compositor->valid = false; + auto cmp2 = request(surface->channelSize); + SwSurface* surfaces[] = {cmp1, cmp2}; + auto ret = effectDropShadow(p, surfaces, static_cast(effect), opacity, direct); + cmp1->compositor->valid = true; + return ret; + } + default: return false; + } +} + + ColorSpace SwRenderer::colorSpace() { if (surface) return surface->cs; @@ -681,10 +722,10 @@ void* SwRenderer::prepareCommon(SwTask* task, const Matrix& transform, const Arr task->surface = surface; task->mpool = mpool; task->flags = flags; - task->bbox.min.x = mathMax(static_cast(0), static_cast(vport.x)); - task->bbox.min.y = mathMax(static_cast(0), static_cast(vport.y)); - task->bbox.max.x = mathMin(static_cast(surface->w), static_cast(vport.x + vport.w)); - task->bbox.max.y = mathMin(static_cast(surface->h), static_cast(vport.y + vport.h)); + task->bbox.min.x = std::max(static_cast(0), static_cast(vport.x)); + task->bbox.min.y = std::max(static_cast(0), static_cast(vport.y)); + task->bbox.max.x = std::min(static_cast(surface->w), static_cast(vport.x + vport.w)); + task->bbox.max.y = std::min(static_cast(surface->h), static_cast(vport.y + vport.h)); if (!task->pushed) { task->pushed = true; @@ -697,7 +738,7 @@ void* SwRenderer::prepareCommon(SwTask* task, const Matrix& transform, const Arr } -RenderData SwRenderer::prepare(Surface* surface, RenderData data, const Matrix& transform, Array& clips, uint8_t opacity, RenderUpdateFlag flags) +RenderData SwRenderer::prepare(RenderSurface* surface, RenderData data, const Matrix& transform, Array& clips, uint8_t opacity, RenderUpdateFlag flags) { //prepare task auto task = static_cast(data); @@ -748,6 +789,10 @@ bool SwRenderer::init(uint32_t threads) int32_t SwRenderer::init() { +#ifdef THORVG_SW_OPENMP_SUPPORT + omp_set_num_threads(TaskScheduler::threads()); +#endif + return initEngineCnt; } diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.h b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.h index fcd8ad46205..bd6beb8d85c 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.h +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.h @@ -37,7 +37,7 @@ class SwRenderer : public RenderMethod { public: RenderData prepare(const RenderShape& rshape, RenderData data, const Matrix& transform, Array& clips, uint8_t opacity, RenderUpdateFlag flags, bool clipper) override; - RenderData prepare(Surface* surface, RenderData data, const Matrix& transform, Array& clips, uint8_t opacity, RenderUpdateFlag flags) override; + RenderData prepare(RenderSurface* surface, RenderData data, const Matrix& transform, Array& clips, uint8_t opacity, RenderUpdateFlag flags) override; bool preRender() override; bool renderShape(RenderData data) override; bool renderImage(RenderData data) override; @@ -48,18 +48,21 @@ class SwRenderer : public RenderMethod bool viewport(const RenderRegion& vp) override; bool blend(BlendMethod method) override; ColorSpace colorSpace() override; - const Surface* mainSurface() override; + const RenderSurface* mainSurface() override; bool clear() override; bool sync() override; bool target(pixel_t* data, uint32_t stride, uint32_t w, uint32_t h, ColorSpace cs); bool mempool(bool shared); - Compositor* target(const RenderRegion& region, ColorSpace cs) override; - bool beginComposite(Compositor* cmp, CompositeMethod method, uint8_t opacity) override; - bool endComposite(Compositor* cmp) override; + RenderCompositor* target(const RenderRegion& region, ColorSpace cs) override; + bool beginComposite(RenderCompositor* cmp, CompositeMethod method, uint8_t opacity) override; + bool endComposite(RenderCompositor* cmp) override; void clearCompositors(); + bool prepare(RenderEffect* effect) override; + bool effect(RenderCompositor* cmp, const RenderEffect* effect, uint8_t opacity, bool direct) override; + static SwRenderer* gen(); static bool init(uint32_t threads); static int32_t init(); @@ -76,6 +79,7 @@ class SwRenderer : public RenderMethod SwRenderer(); ~SwRenderer(); + SwSurface* request(int channelSize); RenderData prepareCommon(SwTask* task, const Matrix& transform, const Array& clips, uint8_t opacity, RenderUpdateFlag flags); }; diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRle.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRle.cpp index 42b08de6a58..3e4ad679a8a 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRle.cpp +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRle.cpp @@ -217,7 +217,7 @@ struct Cell struct RleWorker { - SwRleData* rle; + SwRle* rle; SwPoint cellPos; SwPoint cellMin; @@ -235,6 +235,7 @@ struct RleWorker SwPoint pos; SwPoint bezStack[32 * 3 + 1]; + SwPoint lineStack[32 + 1]; int levStack[32]; SwOutline* outline; @@ -297,7 +298,7 @@ static inline SwCoord HYPOT(SwPoint pt) } -static void _horizLine(RleWorker& rw, SwCoord x, SwCoord y, SwCoord area, SwCoord acount) +static void _horizLine(RleWorker& rw, SwCoord x, SwCoord y, SwCoord area, SwCoord aCount) { x += rw.cellMin.x; y += rw.cellMin.y; @@ -341,11 +342,11 @@ static void _horizLine(RleWorker& rw, SwCoord x, SwCoord y, SwCoord area, SwCoor if ((span->coverage == coverage) && (span->y == y) && (span->x + span->len == x)) { //Clip x range SwCoord xOver = 0; - if (x + acount >= rw.cellMax.x) xOver -= (x + acount - rw.cellMax.x); + if (x + aCount >= rw.cellMax.x) xOver -= (x + aCount - rw.cellMax.x); if (x < rw.cellMin.x) xOver -= (rw.cellMin.x - x); - //span->len += (acount + xOver) - 1; - span->len += (acount + xOver); + //span->len += (aCount + xOver) - 1; + span->len += (aCount + xOver); return; } } @@ -361,20 +362,20 @@ static void _horizLine(RleWorker& rw, SwCoord x, SwCoord y, SwCoord area, SwCoor //Clip x range SwCoord xOver = 0; - if (x + acount >= rw.cellMax.x) xOver -= (x + acount - rw.cellMax.x); + if (x + aCount >= rw.cellMax.x) xOver -= (x + aCount - rw.cellMax.x); if (x < rw.cellMin.x) { xOver -= (rw.cellMin.x - x); x = rw.cellMin.x; } //Nothing to draw - if (acount + xOver <= 0) return; + if (aCount + xOver <= 0) return; //add a span to the current list auto span = rle->spans + rle->size; span->x = x; span->y = y; - span->len = (acount + xOver); + span->len = (aCount + xOver); span->coverage = coverage; rle->size++; } @@ -513,98 +514,116 @@ static void _lineTo(RleWorker& rw, const SwPoint& to) return; } - auto diff = to - rw.pos; - auto f1 = rw.pos - SUBPIXELS(e1); - SwPoint f2; - - //inside one cell - if (e1 == e2) { - ; - //any horizontal line - } else if (diff.y == 0) { - e1.x = e2.x; - _setCell(rw, e1); - } else if (diff.x == 0) { - //vertical line up - if (diff.y > 0) { - do { - f2.y = ONE_PIXEL; - rw.cover += (f2.y - f1.y); - rw.area += (f2.y - f1.y) * f1.x * 2; - f1.y = 0; - ++e1.y; - _setCell(rw, e1); - } while(e1.y != e2.y); - //vertical line down + auto line = rw.lineStack; + line[0] = to; + line[1] = rw.pos; + + while (true) { + auto diff = line[0] - line[1]; + auto L = HYPOT(diff); + + if (L > SHRT_MAX) { + mathSplitLine(line); + ++line; + continue; + } + e1 = TRUNC(line[1]); + e2 = TRUNC(line[0]); + + auto f1 = line[1] - SUBPIXELS(e1); + SwPoint f2; + + //inside one cell + if (e1 == e2) { + ; + //any horizontal line + } else if (diff.y == 0) { + e1.x = e2.x; + _setCell(rw, e1); + } else if (diff.x == 0) { + //vertical line up + if (diff.y > 0) { + do { + f2.y = ONE_PIXEL; + rw.cover += (f2.y - f1.y); + rw.area += (f2.y - f1.y) * f1.x * 2; + f1.y = 0; + ++e1.y; + _setCell(rw, e1); + } while(e1.y != e2.y); + //vertical line down + } else { + do { + f2.y = 0; + rw.cover += (f2.y - f1.y); + rw.area += (f2.y - f1.y) * f1.x * 2; + f1.y = ONE_PIXEL; + --e1.y; + _setCell(rw, e1); + } while(e1.y != e2.y); + } + //any other line } else { + Area prod = diff.x * f1.y - diff.y * f1.x; + + /* These macros speed up repetitive divisions by replacing them + with multiplications and right shifts. */ + auto dx_r = static_cast(ULONG_MAX >> PIXEL_BITS) / (diff.x); + auto dy_r = static_cast(ULONG_MAX >> PIXEL_BITS) / (diff.y); + + /* The fundamental value `prod' determines which side and the */ + /* exact coordinate where the line exits current cell. It is */ + /* also easily updated when moving from one cell to the next. */ do { - f2.y = 0; - rw.cover += (f2.y - f1.y); - rw.area += (f2.y - f1.y) * f1.x * 2; - f1.y = ONE_PIXEL; - --e1.y; + auto px = diff.x * ONE_PIXEL; + auto py = diff.y * ONE_PIXEL; + + //left + if (prod <= 0 && prod - px > 0) { + f2 = {0, SW_UDIV(-prod, -dx_r)}; + prod -= py; + rw.cover += (f2.y - f1.y); + rw.area += (f2.y - f1.y) * (f1.x + f2.x); + f1 = {ONE_PIXEL, f2.y}; + --e1.x; + //up + } else if (prod - px <= 0 && prod - px + py > 0) { + prod -= px; + f2 = {SW_UDIV(-prod, dy_r), ONE_PIXEL}; + rw.cover += (f2.y - f1.y); + rw.area += (f2.y - f1.y) * (f1.x + f2.x); + f1 = {f2.x, 0}; + ++e1.y; + //right + } else if (prod - px + py <= 0 && prod + py >= 0) { + prod += py; + f2 = {ONE_PIXEL, SW_UDIV(prod, dx_r)}; + rw.cover += (f2.y - f1.y); + rw.area += (f2.y - f1.y) * (f1.x + f2.x); + f1 = {0, f2.y}; + ++e1.x; + //down + } else { + f2 = {SW_UDIV(prod, -dy_r), 0}; + prod += px; + rw.cover += (f2.y - f1.y); + rw.area += (f2.y - f1.y) * (f1.x + f2.x); + f1 = {f2.x, ONE_PIXEL}; + --e1.y; + } + _setCell(rw, e1); - } while(e1.y != e2.y); + + } while(e1 != e2); } - //any other line - } else { - Area prod = diff.x * f1.y - diff.y * f1.x; - - /* These macros speed up repetitive divisions by replacing them - with multiplications and right shifts. */ - auto dx_r = static_cast(ULONG_MAX >> PIXEL_BITS) / (diff.x); - auto dy_r = static_cast(ULONG_MAX >> PIXEL_BITS) / (diff.y); - - /* The fundamental value `prod' determines which side and the */ - /* exact coordinate where the line exits current cell. It is */ - /* also easily updated when moving from one cell to the next. */ - do { - auto px = diff.x * ONE_PIXEL; - auto py = diff.y * ONE_PIXEL; - - //left - if (prod <= 0 && prod - px > 0) { - f2 = {0, SW_UDIV(-prod, -dx_r)}; - prod -= py; - rw.cover += (f2.y - f1.y); - rw.area += (f2.y - f1.y) * (f1.x + f2.x); - f1 = {ONE_PIXEL, f2.y}; - --e1.x; - //up - } else if (prod - px <= 0 && prod - px + py > 0) { - prod -= px; - f2 = {SW_UDIV(-prod, dy_r), ONE_PIXEL}; - rw.cover += (f2.y - f1.y); - rw.area += (f2.y - f1.y) * (f1.x + f2.x); - f1 = {f2.x, 0}; - ++e1.y; - //right - } else if (prod - px + py <= 0 && prod + py >= 0) { - prod += py; - f2 = {ONE_PIXEL, SW_UDIV(prod, dx_r)}; - rw.cover += (f2.y - f1.y); - rw.area += (f2.y - f1.y) * (f1.x + f2.x); - f1 = {0, f2.y}; - ++e1.x; - //down - } else { - f2 = {SW_UDIV(prod, -dy_r), 0}; - prod += px; - rw.cover += (f2.y - f1.y); - rw.area += (f2.y - f1.y) * (f1.x + f2.x); - f1 = {f2.x, ONE_PIXEL}; - --e1.y; - } - _setCell(rw, e1); + f2 = {line[0].x - SUBPIXELS(e2.x), line[0].y - SUBPIXELS(e2.y)}; + rw.cover += (f2.y - f1.y); + rw.area += (f2.y - f1.y) * (f1.x + f2.x); + rw.pos = line[0]; - } while(e1 != e2); + if (line-- == rw.lineStack) return; } - - f2 = {to.x - SUBPIXELS(e2.x), to.y - SUBPIXELS(e2.y)}; - rw.cover += (f2.y - f1.y); - rw.area += (f2.y - f1.y) * (f1.x + f2.x); - rw.pos = to; } @@ -690,31 +709,27 @@ static void _decomposeOutline(RleWorker& rw) auto start = UPSCALE(outline->pts[first]); auto pt = outline->pts.data + first; auto types = outline->types.data + first; + ++types; _moveTo(rw, UPSCALE(outline->pts[first])); while (pt < limit) { - ++pt; - ++types; - //emit a single line_to if (types[0] == SW_CURVE_TYPE_POINT) { + ++pt; + ++types; _lineTo(rw, UPSCALE(*pt)); //types cubic } else { - pt += 2; - types += 2; - - if (pt <= limit) { - _cubicTo(rw, UPSCALE(pt[-2]), UPSCALE(pt[-1]), UPSCALE(pt[0])); - continue; - } - _cubicTo(rw, UPSCALE(pt[-2]), UPSCALE(pt[-1]), start); - goto close; + pt += 3; + types += 3; + if (pt <= limit) _cubicTo(rw, UPSCALE(pt[-2]), UPSCALE(pt[-1]), UPSCALE(pt[0])); + else if (pt - 1 == limit) _cubicTo(rw, UPSCALE(pt[-2]), UPSCALE(pt[-1]), start); + else goto close; } } - _lineTo(rw, start); close: + _lineTo(rw, start); first = last + 1; } } @@ -731,7 +746,7 @@ static int _genRle(RleWorker& rw) } -static SwSpan* _intersectSpansRegion(const SwRleData *clip, const SwRleData *target, SwSpan *outSpans, uint32_t outSpansCnt) +static SwSpan* _intersectSpansRegion(const SwRle *clip, const SwRle *target, SwSpan *outSpans, uint32_t outSpansCnt) { auto out = outSpans; auto spans = target->spans; @@ -740,7 +755,7 @@ static SwSpan* _intersectSpansRegion(const SwRleData *clip, const SwRleData *tar auto clipEnd = clip->spans + clip->size; while (spans < end && clipSpans < clipEnd) { - //align y cooridnates. + //align y-coordinates. if (clipSpans->y > spans->y) { ++spans; continue; @@ -750,7 +765,7 @@ static SwSpan* _intersectSpansRegion(const SwRleData *clip, const SwRleData *tar continue; } - //Try clipping with all clip spans which have a same y coordinate. + //Try clipping with all clip spans which have a same y-coordinate. auto temp = clipSpans; while(temp < clipEnd && outSpansCnt > 0 && temp->y == clipSpans->y) { auto sx1 = spans->x; @@ -783,7 +798,7 @@ static SwSpan* _intersectSpansRegion(const SwRleData *clip, const SwRleData *tar } -static SwSpan* _intersectSpansRect(const SwBBox *bbox, const SwRleData *targetRle, SwSpan *outSpans, uint32_t outSpansCnt) +static SwSpan* _intersectSpansRect(const SwBBox *bbox, const SwRle *targetRle, SwSpan *outSpans, uint32_t outSpansCnt) { auto out = outSpans; auto spans = targetRle->spans; @@ -822,7 +837,7 @@ static SwSpan* _intersectSpansRect(const SwBBox *bbox, const SwRleData *targetRl } -void _replaceClipSpan(SwRleData *rle, SwSpan* clippedSpans, uint32_t size) +void _replaceClipSpan(SwRle *rle, SwSpan* clippedSpans, uint32_t size) { free(rle->spans); rle->spans = clippedSpans; @@ -834,7 +849,7 @@ void _replaceClipSpan(SwRleData *rle, SwSpan* clippedSpans, uint32_t size) /* External Class Implementation */ /************************************************************************/ -SwRleData* rleRender(SwRleData* rle, const SwOutline* outline, const SwBBox& renderRegion, bool antiAlias) +SwRle* rleRender(SwRle* rle, const SwOutline* outline, const SwBBox& renderRegion, bool antiAlias) { constexpr auto RENDER_POOL_SIZE = 16384L; constexpr auto BAND_SIZE = 40; @@ -862,7 +877,7 @@ SwRleData* rleRender(SwRleData* rle, const SwOutline* outline, const SwBBox& ren rw.bandShoot = 0; rw.antiAlias = antiAlias; - if (!rle) rw.rle = reinterpret_cast(calloc(1, sizeof(SwRleData))); + if (!rle) rw.rle = reinterpret_cast(calloc(1, sizeof(SwRle))); else rw.rle = rle; //Generate RLE @@ -953,12 +968,12 @@ SwRleData* rleRender(SwRleData* rle, const SwOutline* outline, const SwBBox& ren } -SwRleData* rleRender(const SwBBox* bbox) +SwRle* rleRender(const SwBBox* bbox) { auto width = static_cast(bbox->max.x - bbox->min.x); auto height = static_cast(bbox->max.y - bbox->min.y); - auto rle = static_cast(malloc(sizeof(SwRleData))); + auto rle = static_cast(malloc(sizeof(SwRle))); rle->spans = static_cast(malloc(sizeof(SwSpan) * height)); rle->size = height; rle->alloc = height; @@ -975,14 +990,14 @@ SwRleData* rleRender(const SwBBox* bbox) } -void rleReset(SwRleData* rle) +void rleReset(SwRle* rle) { if (!rle) return; rle->size = 0; } -void rleFree(SwRleData* rle) +void rleFree(SwRle* rle) { if (!rle) return; if (rle->spans) free(rle->spans); @@ -990,7 +1005,7 @@ void rleFree(SwRleData* rle) } -void rleClipPath(SwRleData *rle, const SwRleData *clip) +void rleClip(SwRle *rle, const SwRle *clip) { if (rle->size == 0 || clip->size == 0) return; auto spanCnt = rle->size > clip->size ? rle->size : clip->size; @@ -999,11 +1014,11 @@ void rleClipPath(SwRleData *rle, const SwRleData *clip) _replaceClipSpan(rle, spans, spansEnd - spans); - TVGLOG("SW_ENGINE", "Using ClipPath!"); + TVGLOG("SW_ENGINE", "Using Path Clipping!"); } -void rleClipRect(SwRleData *rle, const SwBBox* clip) +void rleClip(SwRle *rle, const SwBBox* clip) { if (rle->size == 0) return; auto spans = static_cast(malloc(sizeof(SwSpan) * (rle->size))); @@ -1011,5 +1026,5 @@ void rleClipRect(SwRleData *rle, const SwBBox* clip) _replaceClipSpan(rle, spans, spansEnd - spans); - TVGLOG("SW_ENGINE", "Using ClipRect!"); + TVGLOG("SW_ENGINE", "Using Box Clipping!"); } diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwShape.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwShape.cpp index 24c4a9e3725..4408db0b86f 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwShape.cpp +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwShape.cpp @@ -22,7 +22,6 @@ #include "tvgSwCommon.h" #include "tvgMath.h" -#include "tvgLines.h" /************************************************************************/ /* Internal Class Implementation */ @@ -102,9 +101,9 @@ static bool _outlineClose(SwOutline& outline) static void _dashLineTo(SwDashStroke& dash, const Point* to, const Matrix& transform) { Line cur = {dash.ptCur, *to}; - auto len = lineLength(cur.pt1, cur.pt2); + auto len = cur.length(); - if (mathZero(len)) { + if (tvg::zero(len)) { _outlineMoveTo(*dash.outline, &dash.ptCur, transform); //draw the current line fully } else if (len <= dash.curLen) { @@ -122,7 +121,7 @@ static void _dashLineTo(SwDashStroke& dash, const Point* to, const Matrix& trans Line left, right; if (dash.curLen > 0) { len -= dash.curLen; - lineSplitAt(cur, dash.curLen, left, right); + cur.split(dash.curLen, left, right); if (!dash.curOpGap) { if (dash.move || dash.pattern[dash.curIdx] - dash.curLen < FLOAT_EPSILON) { _outlineMoveTo(*dash.outline, &left.pt1, transform); @@ -163,10 +162,10 @@ static void _dashLineTo(SwDashStroke& dash, const Point* to, const Matrix& trans static void _dashCubicTo(SwDashStroke& dash, const Point* ctrl1, const Point* ctrl2, const Point* to, const Matrix& transform) { Bezier cur = {dash.ptCur, *ctrl1, *ctrl2, *to}; - auto len = bezLength(cur); + auto len = cur.length(); //draw the current line fully - if (mathZero(len)) { + if (tvg::zero(len)) { _outlineMoveTo(*dash.outline, &dash.ptCur, transform); } else if (len <= dash.curLen) { dash.curLen -= len; @@ -183,7 +182,7 @@ static void _dashCubicTo(SwDashStroke& dash, const Point* ctrl1, const Point* ct Bezier left, right; if (dash.curLen > 0) { len -= dash.curLen; - bezSplitAt(cur, dash.curLen, left, right); + cur.split(dash.curLen, left, right); if (!dash.curOpGap) { if (dash.move || dash.pattern[dash.curIdx] - dash.curLen < FLOAT_EPSILON) { _outlineMoveTo(*dash.outline, &left.start, transform); @@ -284,7 +283,7 @@ static float _outlineLength(const RenderShape* rshape, uint32_t shiftPts, uint32 if (cmdCnt <= 0 || ptsCnt <= 0) return 0.0f; const Point* close = nullptr; - auto length = 0.0f; + auto len = 0.0f; //must begin with moveTo if (cmds[0] == PathCommand::MoveTo) { @@ -297,30 +296,30 @@ static float _outlineLength(const RenderShape* rshape, uint32_t shiftPts, uint32 while (cmdCnt-- > 0) { switch (*cmds) { case PathCommand::Close: { - length += mathLength(pts - 1, close); - if (subpath) return length; + len += length(pts - 1, close); + if (subpath) return len; break; } case PathCommand::MoveTo: { - if (subpath) return length; + if (subpath) return len; close = pts; ++pts; break; } case PathCommand::LineTo: { - length += mathLength(pts - 1, pts); + len += length(pts - 1, pts); ++pts; break; } case PathCommand::CubicTo: { - length += bezLength({*(pts - 1), *pts, *(pts + 1), *(pts + 2)}); + len += Bezier{*(pts - 1), *pts, *(pts + 1), *(pts + 2)}.length(); pts += 3; break; } } ++cmds; } - return length; + return len; } @@ -355,7 +354,7 @@ static SwOutline* _genDashOutline(const RenderShape* rshape, const Matrix& trans //offset auto patternLength = 0.0f; uint32_t offIdx = 0; - if (!mathZero(offset)) { + if (!tvg::zero(offset)) { for (size_t i = 0; i < dash.cnt; ++i) patternLength += dash.pattern[i]; bool isOdd = dash.cnt % 2; if (isOdd) patternLength *= 2; @@ -499,7 +498,6 @@ bool shapePrepare(SwShape* shape, const RenderShape* rshape, const Matrix& trans if (!_genOutline(shape, rshape, transform, mpool, tid, hasComposite)) return false; if (!mathUpdateOutlineBBox(shape->outline, clipRegion, renderRegion, shape->fastTrack)) return false; - //Keep it for Rasterization Region shape->bbox = renderRegion; //Check valid region diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwStroke.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwStroke.cpp index 75ac96be04d..e195f72adf2 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwStroke.cpp +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwStroke.cpp @@ -441,13 +441,23 @@ static void _cubicTo(SwStroke& stroke, const SwPoint& ctrl1, const SwPoint& ctrl //initialize with current direction angleIn = angleOut = angleMid = stroke.angleIn; - if (arc < limit && !mathSmallCubic(arc, angleIn, angleMid, angleOut)) { + auto valid = mathCubicAngle(arc, angleIn, angleMid, angleOut); + + //valid size + if (valid > 0 && arc < limit) { if (stroke.firstPt) stroke.angleIn = angleIn; mathSplitCubic(arc); arc += 3; continue; } + //ignoreable size + if (valid < 0 && arc == bezStack) { + stroke.center = to; + return; + } + + //small size if (firstArc) { firstArc = false; //process corner if necessary @@ -662,7 +672,7 @@ static void _beginSubPath(SwStroke& stroke, const SwPoint& to, bool closed) /* Determine if we need to check whether the border radius is greater than the radius of curvature of a curve, to handle this case specially. This is only required if bevel joins or butt caps may be created because - round & miter joins and round & square caps cover the nagative sector + round & miter joins and round & square caps cover the negative sector created with wide strokes. */ if ((stroke.join != StrokeJoin::Round) || (!stroke.closedSubPath && stroke.cap == StrokeCap::Butt)) stroke.handleWideStrokes = true; @@ -715,7 +725,7 @@ static void _endSubPath(SwStroke& stroke) _addCap(stroke, stroke.subPathAngle + SW_ANGLE_PI, 0); /* now end the right subpath accordingly. The left one is rewind - and deosn't need further processing */ + and doesn't need further processing */ _borderClose(right, false); } } @@ -845,31 +855,25 @@ bool strokeParseOutline(SwStroke* stroke, const SwOutline& outline) //A contour cannot start with a cubic control point if (type == SW_CURVE_TYPE_CUBIC) return false; + ++types; auto closed = outline.closed.data ? outline.closed.data[i]: false; _beginSubPath(*stroke, start, closed); while (pt < limit) { - ++pt; - ++types; - - //emit a signel line_to + //emit a single line_to if (types[0] == SW_CURVE_TYPE_POINT) { + ++pt; + ++types; _lineTo(*stroke, *pt); //types cubic } else { - if (pt + 1 > limit || types[1] != SW_CURVE_TYPE_CUBIC) return false; - - pt += 2; - types += 2; - - if (pt <= limit) { - _cubicTo(*stroke, pt[-2], pt[-1], pt[0]); - continue; - } - _cubicTo(*stroke, pt[-2], pt[-1], start); - goto close; + pt += 3; + types += 3; + if (pt <= limit) _cubicTo(*stroke, pt[-2], pt[-1], pt[0]); + else if (pt - 1 == limit) _cubicTo(*stroke, pt[-2], pt[-1], start); + else goto close; } } close: diff --git a/thirdparty/thorvg/src/renderer/tvgBinaryDesc.h b/thirdparty/thorvg/src/renderer/tvgBinaryDesc.h index 29f84eb82a6..e40859b6db9 100644 --- a/thirdparty/thorvg/src/renderer/tvgBinaryDesc.h +++ b/thirdparty/thorvg/src/renderer/tvgBinaryDesc.h @@ -36,7 +36,7 @@ using TvgBinFlag = TvgBinByte; #define TVG_HEADER_SIZE 33 //TVG_HEADER_SIGNATURE_LENGTH + TVG_HEADER_VERSION_LENGTH + 2*SIZE(float) + TVG_HEADER_RESERVED_LENGTH + TVG_HEADER_COMPRESS_SIZE #define TVG_HEADER_SIGNATURE "ThorVG" #define TVG_HEADER_SIGNATURE_LENGTH 6 -#define TVG_HEADER_VERSION "001200" //Major 00, Minor 12, Micro 00 +#define TVG_HEADER_VERSION "001500" //Major 00, Minor 15, Micro 00 #define TVG_HEADER_VERSION_LENGTH 6 #define TVG_HEADER_RESERVED_LENGTH 1 //Storing flags for extensions #define TVG_HEADER_COMPRESS_SIZE 12 //TVG_HEADER_UNCOMPRESSED_SIZE + TVG_HEADER_COMPRESSED_SIZE + TVG_HEADER_COMPRESSED_SIZE_BITS diff --git a/thirdparty/thorvg/src/renderer/tvgCanvas.h b/thirdparty/thorvg/src/renderer/tvgCanvas.h index 199e823034f..c5d2127f9c6 100644 --- a/thirdparty/thorvg/src/renderer/tvgCanvas.h +++ b/thirdparty/thorvg/src/renderer/tvgCanvas.h @@ -26,7 +26,7 @@ #include "tvgPaint.h" -enum Status : uint8_t {Synced = 0, Updating, Drawing, Damanged}; +enum Status : uint8_t {Synced = 0, Updating, Drawing, Damaged}; struct Canvas::Impl { @@ -42,7 +42,7 @@ struct Canvas::Impl ~Impl() { - //make it sure any deffered jobs + //make it sure any deferred jobs renderer->sync(); renderer->clear(); @@ -61,7 +61,7 @@ struct Canvas::Impl Result push(unique_ptr paint) { - //You can not push paints during rendering. + //You cannot push paints during rendering. if (status == Status::Drawing) return Result::InsufficientCondition; auto p = paint.release(); @@ -91,7 +91,7 @@ struct Canvas::Impl Array clips; auto flag = RenderUpdateFlag::None; - if (status == Status::Damanged || force) flag = RenderUpdateFlag::All; + if (status == Status::Damaged || force) flag = RenderUpdateFlag::All; auto m = Matrix{1, 0, 0, 0, 1, 0, 0, 0, 1}; @@ -108,7 +108,7 @@ struct Canvas::Impl Result draw() { - if (status == Status::Damanged) update(nullptr, false); + if (status == Status::Damaged) update(nullptr, false); if (status == Status::Drawing || paints.empty() || !renderer->preRender()) return Result::InsufficientCondition; bool rendered = false; @@ -124,7 +124,7 @@ struct Canvas::Impl Result sync() { - if (status == Status::Synced || status == Status::Damanged) return Result::InsufficientCondition; + if (status == Status::Synced || status == Status::Damaged) return Result::InsufficientCondition; if (renderer->sync()) { status = Status::Synced; @@ -136,7 +136,7 @@ struct Canvas::Impl Result viewport(int32_t x, int32_t y, int32_t w, int32_t h) { - if (status != Status::Damanged && status != Status::Synced) return Result::InsufficientCondition; + if (status != Status::Damaged && status != Status::Synced) return Result::InsufficientCondition; RenderRegion val = {x, y, w, h}; //intersect if the target buffer is already set. @@ -147,7 +147,7 @@ struct Canvas::Impl if (vport == val) return Result::Success; renderer->viewport(val); vport = val; - status = Status::Damanged; + status = Status::Damaged; return Result::Success; } }; diff --git a/thirdparty/thorvg/src/renderer/tvgCommon.h b/thirdparty/thorvg/src/renderer/tvgCommon.h index 15a2cc4ef04..527221625b6 100644 --- a/thirdparty/thorvg/src/renderer/tvgCommon.h +++ b/thirdparty/thorvg/src/renderer/tvgCommon.h @@ -54,15 +54,6 @@ using namespace tvg; #define strdup _strdup #endif -//TVG class identifier values -#define TVG_CLASS_ID_UNDEFINED 0 -#define TVG_CLASS_ID_SHAPE 1 -#define TVG_CLASS_ID_SCENE 2 -#define TVG_CLASS_ID_PICTURE 3 -#define TVG_CLASS_ID_LINEAR 4 -#define TVG_CLASS_ID_RADIAL 5 -#define TVG_CLASS_ID_TEXT 6 - enum class FileType { Png = 0, Jpg, Webp, Tvg, Svg, Lottie, Ttf, Raw, Gif, Unknown }; using Size = Point; diff --git a/thirdparty/thorvg/src/renderer/tvgFill.cpp b/thirdparty/thorvg/src/renderer/tvgFill.cpp index ea1010051ee..19edff5a2cd 100644 --- a/thirdparty/thorvg/src/renderer/tvgFill.cpp +++ b/thirdparty/thorvg/src/renderer/tvgFill.cpp @@ -155,15 +155,14 @@ Fill* Fill::duplicate() const noexcept } -uint32_t Fill::identifier() const noexcept +TVG_DEPRECATED uint32_t Fill::identifier() const noexcept { - return pImpl->id; + return (uint32_t) type(); } RadialGradient::RadialGradient():pImpl(new Impl()) { - Fill::pImpl->id = TVG_CLASS_ID_RADIAL; Fill::pImpl->method(new FillDup(pImpl)); } @@ -196,15 +195,20 @@ unique_ptr RadialGradient::gen() noexcept } -uint32_t RadialGradient::identifier() noexcept +TVG_DEPRECATED uint32_t RadialGradient::identifier() noexcept { - return TVG_CLASS_ID_RADIAL; + return (uint32_t) Type::RadialGradient; +} + + +Type RadialGradient::type() const noexcept +{ + return Type::RadialGradient; } LinearGradient::LinearGradient():pImpl(new Impl()) { - Fill::pImpl->id = TVG_CLASS_ID_LINEAR; Fill::pImpl->method(new FillDup(pImpl)); } @@ -243,8 +247,13 @@ unique_ptr LinearGradient::gen() noexcept } -uint32_t LinearGradient::identifier() noexcept +TVG_DEPRECATED uint32_t LinearGradient::identifier() noexcept { - return TVG_CLASS_ID_LINEAR; + return (uint32_t) Type::LinearGradient; } + +Type LinearGradient::type() const noexcept +{ + return Type::LinearGradient; +} diff --git a/thirdparty/thorvg/src/renderer/tvgFill.h b/thirdparty/thorvg/src/renderer/tvgFill.h index 47f0c051c02..f249356aa2a 100644 --- a/thirdparty/thorvg/src/renderer/tvgFill.h +++ b/thirdparty/thorvg/src/renderer/tvgFill.h @@ -55,7 +55,6 @@ struct Fill::Impl uint32_t cnt = 0; FillSpread spread; DuplicateMethod* dup = nullptr; - uint8_t id; ~Impl() { diff --git a/thirdparty/thorvg/src/renderer/tvgGlCanvas.cpp b/thirdparty/thorvg/src/renderer/tvgGlCanvas.cpp index 82666b7ae31..24e2fb8b1be 100644 --- a/thirdparty/thorvg/src/renderer/tvgGlCanvas.cpp +++ b/thirdparty/thorvg/src/renderer/tvgGlCanvas.cpp @@ -62,7 +62,7 @@ GlCanvas::~GlCanvas() Result GlCanvas::target(int32_t id, uint32_t w, uint32_t h) noexcept { #ifdef THORVG_GL_RASTER_SUPPORT - if (Canvas::pImpl->status != Status::Damanged && Canvas::pImpl->status != Status::Synced) { + if (Canvas::pImpl->status != Status::Damaged && Canvas::pImpl->status != Status::Synced) { return Result::InsufficientCondition; } @@ -75,7 +75,7 @@ Result GlCanvas::target(int32_t id, uint32_t w, uint32_t h) noexcept renderer->viewport(Canvas::pImpl->vport); //Paints must be updated again with this new target. - Canvas::pImpl->status = Status::Damanged; + Canvas::pImpl->status = Status::Damaged; return Result::Success; #endif diff --git a/thirdparty/thorvg/src/renderer/tvgLoadModule.h b/thirdparty/thorvg/src/renderer/tvgLoadModule.h index 1b81d81a4f7..a9c1a685442 100644 --- a/thirdparty/thorvg/src/renderer/tvgLoadModule.h +++ b/thirdparty/thorvg/src/renderer/tvgLoadModule.h @@ -80,14 +80,14 @@ struct ImageLoader : LoadModule static ColorSpace cs; //desired value float w = 0, h = 0; //default image size - Surface surface; + RenderSurface surface; ImageLoader(FileType type) : LoadModule(type) {} virtual bool animatable() { return false; } //true if this loader supports animation. virtual Paint* paint() { return nullptr; } - virtual Surface* bitmap() + virtual RenderSurface* bitmap() { if (surface.data) return &surface; return nullptr; diff --git a/thirdparty/thorvg/src/renderer/tvgLoader.cpp b/thirdparty/thorvg/src/renderer/tvgLoader.cpp index 6a81ddcdbb3..db51fc215a0 100644 --- a/thirdparty/thorvg/src/renderer/tvgLoader.cpp +++ b/thirdparty/thorvg/src/renderer/tvgLoader.cpp @@ -294,10 +294,10 @@ LoadModule* LoaderMgr::loader(const string& path, bool* invalid) { *invalid = false; - //TODO: lottie is not sharable. + //TODO: svg & lottie is not sharable. auto allowCache = true; auto ext = path.substr(path.find_last_of(".") + 1); - if (!ext.compare("json")) allowCache = false; + if (!ext.compare("svg") || !ext.compare("json")) allowCache = false; if (allowCache) { if (auto loader = _findFromCache(path)) return loader; @@ -317,7 +317,7 @@ LoadModule* LoaderMgr::loader(const string& path, bool* invalid) } delete(loader); } - //Unkown MimeType. Try with the candidates in the order + //Unknown MimeType. Try with the candidates in the order for (int i = 0; i < static_cast(FileType::Raw); i++) { if (auto loader = _find(static_cast(i))) { if (loader->open(path)) { @@ -392,7 +392,7 @@ LoadModule* LoaderMgr::loader(const char* data, uint32_t size, const string& mim } } } - //Unkown MimeType. Try with the candidates in the order + //Unknown MimeType. Try with the candidates in the order for (int i = 0; i < static_cast(FileType::Raw); i++) { auto loader = _find(static_cast(i)); if (loader) { diff --git a/thirdparty/thorvg/src/renderer/tvgPaint.cpp b/thirdparty/thorvg/src/renderer/tvgPaint.cpp index 37813b19ef7..536e1878521 100644 --- a/thirdparty/thorvg/src/renderer/tvgPaint.cpp +++ b/thirdparty/thorvg/src/renderer/tvgPaint.cpp @@ -32,11 +32,11 @@ /************************************************************************/ #define PAINT_METHOD(ret, METHOD) \ - switch (id) { \ - case TVG_CLASS_ID_SHAPE: ret = P((Shape*)paint)->METHOD; break; \ - case TVG_CLASS_ID_SCENE: ret = P((Scene*)paint)->METHOD; break; \ - case TVG_CLASS_ID_PICTURE: ret = P((Picture*)paint)->METHOD; break; \ - case TVG_CLASS_ID_TEXT: ret = P((Text*)paint)->METHOD; break; \ + switch (paint->type()) { \ + case Type::Shape: ret = P((Shape*)paint)->METHOD; break; \ + case Type::Scene: ret = P((Scene*)paint)->METHOD; break; \ + case Type::Picture: ret = P((Picture*)paint)->METHOD; break; \ + case Type::Text: ret = P((Text*)paint)->METHOD; break; \ default: ret = {}; \ } @@ -91,8 +91,8 @@ static Result _compFastTrack(RenderMethod* renderer, Paint* cmpTarget, const Mat //No rotation and no skewing, still can try out clipping the rect region. auto tryClip = false; - if ((!mathRightAngle(pm) || mathSkewed(pm))) tryClip = true; - if ((!mathRightAngle(rm) || mathSkewed(rm))) tryClip = true; + if ((!rightAngle(pm) || skewed(pm))) tryClip = true; + if ((!rightAngle(rm) || skewed(rm))) tryClip = true; if (tryClip) return _clipRect(renderer, pts, pm, rm, before); @@ -102,8 +102,8 @@ static Result _compFastTrack(RenderMethod* renderer, Paint* cmpTarget, const Mat auto pt3 = pts + 2; auto pt4 = pts + 3; - if ((mathEqual(pt1->x, pt2->x) && mathEqual(pt2->y, pt3->y) && mathEqual(pt3->x, pt4->x) && mathEqual(pt1->y, pt4->y)) || - (mathEqual(pt2->x, pt3->x) && mathEqual(pt1->y, pt2->y) && mathEqual(pt1->x, pt4->x) && mathEqual(pt3->y, pt4->y))) { + if ((tvg::equal(pt1->x, pt2->x) && tvg::equal(pt2->y, pt3->y) && tvg::equal(pt3->x, pt4->x) && tvg::equal(pt1->y, pt4->y)) || + (tvg::equal(pt2->x, pt3->x) && tvg::equal(pt1->y, pt2->y) && tvg::equal(pt1->x, pt4->x) && tvg::equal(pt3->y, pt4->y))) { RenderRegion after; @@ -164,6 +164,7 @@ Paint* Paint::Impl::duplicate(Paint* ret) ret->pImpl->opacity = opacity; if (compData) ret->pImpl->composite(ret, compData->target->duplicate(), compData->method); + if (clipper) ret->pImpl->clip(clipper->duplicate()); return ret; } @@ -172,7 +173,7 @@ Paint* Paint::Impl::duplicate(Paint* ret) bool Paint::Impl::rotate(float degree) { if (tr.overriding) return false; - if (mathEqual(degree, tr.degree)) return true; + if (tvg::equal(degree, tr.degree)) return true; tr.degree = degree; renderFlag |= RenderUpdateFlag::Transform; @@ -183,7 +184,7 @@ bool Paint::Impl::rotate(float degree) bool Paint::Impl::scale(float factor) { if (tr.overriding) return false; - if (mathEqual(factor, tr.scale)) return true; + if (tvg::equal(factor, tr.scale)) return true; tr.scale = factor; renderFlag |= RenderUpdateFlag::Transform; @@ -194,7 +195,7 @@ bool Paint::Impl::scale(float factor) bool Paint::Impl::translate(float x, float y) { if (tr.overriding) return false; - if (mathEqual(x, tr.m.e13) && mathEqual(y, tr.m.e23)) return true; + if (tvg::equal(x, tr.m.e13) && tvg::equal(y, tr.m.e23)) return true; tr.m.e13 = x; tr.m.e23 = y; renderFlag |= RenderUpdateFlag::Transform; @@ -207,11 +208,9 @@ bool Paint::Impl::render(RenderMethod* renderer) { if (opacity == 0) return true; - Compositor* cmp = nullptr; + RenderCompositor* cmp = nullptr; - /* Note: only ClipPath is processed in update() step. - Create a composition image. */ - if (compData && compData->method != CompositeMethod::ClipPath && !(compData->target->pImpl->ctxFlag & ContextFlag::FastTrack)) { + if (compData && !(compData->target->pImpl->ctxFlag & ContextFlag::FastTrack)) { RenderRegion region; PAINT_METHOD(region, bounds(renderer)); @@ -248,43 +247,49 @@ RenderData Paint::Impl::update(RenderMethod* renderer, const Matrix& pm, Arraytarget; auto method = compData->method; P(target)->ctxFlag &= ~ContextFlag::FastTrack; //reset - /* If the transformation has no rotational factors and the ClipPath/Alpha(InvAlpha)Masking involves a simple rectangle, - we can optimize by using the viewport instead of the regular ClipPath/AlphaMasking sequence for improved performance. */ - auto tryFastTrack = false; - if (target->identifier() == TVG_CLASS_ID_SHAPE) { - if (method == CompositeMethod::ClipPath) tryFastTrack = true; - else { - auto shape = static_cast(target); - uint8_t a; - shape->fillColor(nullptr, nullptr, nullptr, &a); - //no gradient fill & no compositions of the composition target. - if (!shape->fill() && !(PP(shape)->compData)) { - if (method == CompositeMethod::AlphaMask && a == 255 && PP(shape)->opacity == 255) tryFastTrack = true; - else if (method == CompositeMethod::InvAlphaMask && (a == 0 || PP(shape)->opacity == 0)) tryFastTrack = true; - } - } - if (tryFastTrack) { - viewport = renderer->viewport(); - if ((compFastTrack = _compFastTrack(renderer, target, pm, viewport)) == Result::Success) { - P(target)->ctxFlag |= ContextFlag::FastTrack; + /* If the transformation has no rotational factors and the Alpha(InvAlpha)Masking involves a simple rectangle, + we can optimize by using the viewport instead of the regular AlphaMasking sequence for improved performance. */ + if (target->type() == Type::Shape) { + auto shape = static_cast(target); + uint8_t a; + shape->fillColor(nullptr, nullptr, nullptr, &a); + //no gradient fill & no compositions of the composition target. + if (!shape->fill() && !(PP(shape)->compData)) { + if ((method == CompositeMethod::AlphaMask && a == 255 && PP(shape)->opacity == 255) || (method == CompositeMethod::InvAlphaMask && (a == 0 || PP(shape)->opacity == 0))) { + viewport = renderer->viewport(); + if ((compFastTrack = _compFastTrack(renderer, target, pm, viewport)) == Result::Success) { + P(target)->ctxFlag |= ContextFlag::FastTrack; + } } } } if (compFastTrack == Result::InsufficientCondition) { - childClipper = compData->method == CompositeMethod::ClipPath ? true : false; - trd = P(target)->update(renderer, pm, clips, 255, pFlag, childClipper); - if (childClipper) clips.push(trd); + trd = P(target)->update(renderer, pm, clips, 255, pFlag, false); + } + } + + /* 2. Clipping */ + if (this->clipper) { + P(this->clipper)->ctxFlag &= ~ContextFlag::FastTrack; //reset + viewport = renderer->viewport(); + /* TODO: Intersect the clipper's clipper, if both are FastTrack. + Update the subsequent clipper first and check its ctxFlag. */ + if (!P(this->clipper)->clipper && (compFastTrack = _compFastTrack(renderer, this->clipper, pm, viewport)) == Result::Success) { + P(this->clipper)->ctxFlag |= ContextFlag::FastTrack; + } + if (compFastTrack == Result::InsufficientCondition) { + trd = P(this->clipper)->update(renderer, pm, clips, 255, pFlag, true); + clips.push(trd); } } - /* 2. Main Update */ + /* 3. Main Update */ auto newFlag = static_cast(pFlag | renderFlag); renderFlag = RenderUpdateFlag::None; opacity = MULTIPLY(opacity, this->opacity); @@ -294,9 +299,9 @@ RenderData Paint::Impl::update(RenderMethod* renderer, const Matrix& pm, Arrayviewport(viewport); - else if (childClipper) clips.pop(); + else if (this->clipper) clips.pop(); return rd; } @@ -308,7 +313,7 @@ bool Paint::Impl::bounds(float* x, float* y, float* w, float* h, bool transforme const auto& m = this->transform(origin); //Case: No transformed, quick return! - if (!transformed || mathIdentity(&m)) { + if (!transformed || identity(&m)) { PAINT_METHOD(ret, bounds(x, y, w, h, stroking)); return ret; } @@ -351,12 +356,18 @@ bool Paint::Impl::bounds(float* x, float* y, float* w, float* h, bool transforme void Paint::Impl::reset() { + if (clipper) { + delete(clipper); + clipper = nullptr; + } + if (compData) { if (P(compData->target)->unref() == 0) delete(compData->target); free(compData); compData = nullptr; } - mathIdentity(&tr.m); + + tvg::identity(&tr.m); tr.degree = 0.0f; tr.scale = 1.0f; tr.overriding = false; @@ -437,15 +448,27 @@ Paint* Paint::duplicate() const noexcept } -Result Paint::composite(std::unique_ptr target, CompositeMethod method) noexcept +Result Paint::clip(std::unique_ptr clipper) noexcept { - if (method == CompositeMethod::ClipPath && target && target->identifier() != TVG_CLASS_ID_SHAPE) { - TVGERR("RENDERER", "ClipPath only allows the Shape!"); + auto p = clipper.release(); + + if (p && p->type() != Type::Shape) { + TVGERR("RENDERER", "Clipping only supports the Shape!"); return Result::NonSupport; } + pImpl->clip(p); + return Result::Success; +} + + +Result Paint::composite(std::unique_ptr target, CompositeMethod method) noexcept +{ + //TODO: remove. Keep this for the backward compatibility + if (target && method == CompositeMethod::ClipPath) return clip(std::move(target)); auto p = target.release(); if (pImpl->composite(this, p, method)) return Result::Success; + delete(p); return Result::InvalidArguments; } @@ -457,6 +480,11 @@ CompositeMethod Paint::composite(const Paint** target) const noexcept if (target) *target = pImpl->compData->target; return pImpl->compData->method; } else { + //TODO: remove. Keep this for the backward compatibility + if (pImpl->clipper) { + if (target) *target = pImpl->clipper; + return CompositeMethod::ClipPath; + } if (target) *target = nullptr; return CompositeMethod::None; } @@ -480,14 +508,17 @@ uint8_t Paint::opacity() const noexcept } -uint32_t Paint::identifier() const noexcept +TVG_DEPRECATED uint32_t Paint::identifier() const noexcept { - return pImpl->id; + return (uint32_t) type(); } Result Paint::blend(BlendMethod method) noexcept { + //TODO: Remove later + if (method == BlendMethod::Hue || method == BlendMethod::Saturation || method == BlendMethod::Color || method == BlendMethod::Luminosity || method == BlendMethod::HardMix) return Result::NonSupport; + if (pImpl->blendMethod != method) { pImpl->blendMethod = method; pImpl->renderFlag |= RenderUpdateFlag::Blend; @@ -495,9 +526,3 @@ Result Paint::blend(BlendMethod method) noexcept return Result::Success; } - - -BlendMethod Paint::blend() const noexcept -{ - return pImpl->blendMethod; -} diff --git a/thirdparty/thorvg/src/renderer/tvgPaint.h b/thirdparty/thorvg/src/renderer/tvgPaint.h index e43ca239bb7..d78e9bb3d1c 100644 --- a/thirdparty/thorvg/src/renderer/tvgPaint.h +++ b/thirdparty/thorvg/src/renderer/tvgPaint.h @@ -49,6 +49,7 @@ namespace tvg { Paint* paint = nullptr; Composite* compData = nullptr; + Paint* clipper = nullptr; RenderMethod* renderer = nullptr; struct { Matrix m; //input matrix @@ -67,8 +68,8 @@ namespace tvg m.e31 = 0.0f; m.e32 = 0.0f; m.e33 = 1.0f; - mathScale(&m, scale, scale); - mathRotate(&m, degree); + tvg::scale(&m, scale, scale); + tvg::rotate(&m, degree); } } tr; BlendMethod blendMethod; @@ -76,7 +77,6 @@ namespace tvg uint8_t ctxFlag; uint8_t opacity; uint8_t refCnt = 0; //reference count - uint8_t id; //TODO: deprecated, remove it Impl(Paint* pnt) : paint(pnt) { @@ -89,6 +89,7 @@ namespace tvg if (P(compData->target)->unref() == 0) delete(compData->target); free(compData); } + if (clipper && P(clipper)->unref() == 0) delete(clipper); if (renderer && (renderer->unref() == 0)) delete(renderer); } @@ -106,7 +107,7 @@ namespace tvg bool transform(const Matrix& m) { - tr.m = m; + if (&tr.m != &m) tr.m = m; tr.overriding = true; renderFlag |= RenderUpdateFlag::Transform; @@ -121,6 +122,20 @@ namespace tvg return tr.m; } + void clip(Paint* clp) + { + if (this->clipper) { + P(this->clipper)->unref(); + if (this->clipper != clp && P(this->clipper)->refCnt == 0) { + delete(this->clipper); + } + } + this->clipper = clp; + if (!clp) return; + + P(clipper)->ref(); + } + bool composite(Paint* source, Paint* target, CompositeMethod method) { //Invalid case diff --git a/thirdparty/thorvg/src/renderer/tvgPicture.cpp b/thirdparty/thorvg/src/renderer/tvgPicture.cpp index 223cc5b026f..d3e31d198a3 100644 --- a/thirdparty/thorvg/src/renderer/tvgPicture.cpp +++ b/thirdparty/thorvg/src/renderer/tvgPicture.cpp @@ -20,6 +20,7 @@ * SOFTWARE. */ +#include "tvgPaint.h" #include "tvgPicture.h" /************************************************************************/ @@ -73,11 +74,11 @@ bool Picture::Impl::needComposition(uint8_t opacity) bool Picture::Impl::render(RenderMethod* renderer) { bool ret = false; - renderer->blend(picture->blend()); + renderer->blend(PP(picture)->blendMethod); if (surface) return renderer->renderImage(rd); else if (paint) { - Compositor* cmp = nullptr; + RenderCompositor* cmp = nullptr; if (needComp) { cmp = renderer->target(bounds(renderer), renderer->colorSpace()); renderer->beginComposite(cmp, CompositeMethod::None, 255); @@ -134,7 +135,6 @@ Result Picture::Impl::load(ImageLoader* loader) Picture::Picture() : pImpl(new Impl(this)) { - Paint::pImpl->id = TVG_CLASS_ID_PICTURE; } @@ -150,9 +150,15 @@ unique_ptr Picture::gen() noexcept } -uint32_t Picture::identifier() noexcept +TVG_DEPRECATED uint32_t Picture::identifier() noexcept { - return TVG_CLASS_ID_PICTURE; + return (uint32_t) Type::Picture; +} + + +Type Picture::type() const noexcept +{ + return Type::Picture; } diff --git a/thirdparty/thorvg/src/renderer/tvgPicture.h b/thirdparty/thorvg/src/renderer/tvgPicture.h index 3a4880cabaa..bbbc4391059 100644 --- a/thirdparty/thorvg/src/renderer/tvgPicture.h +++ b/thirdparty/thorvg/src/renderer/tvgPicture.h @@ -60,7 +60,7 @@ struct Picture::Impl ImageLoader* loader = nullptr; Paint* paint = nullptr; //vector picture uses - Surface* surface = nullptr; //bitmap picture uses + RenderSurface* surface = nullptr; //bitmap picture uses RenderData rd = nullptr; //engine data float w = 0, h = 0; Picture* picture = nullptr; diff --git a/thirdparty/thorvg/src/renderer/tvgRender.h b/thirdparty/thorvg/src/renderer/tvgRender.h index b0ee42db8d0..eae44a2e8ad 100644 --- a/thirdparty/thorvg/src/renderer/tvgRender.h +++ b/thirdparty/thorvg/src/renderer/tvgRender.h @@ -24,6 +24,7 @@ #define _TVG_RENDER_H_ #include +#include #include "tvgCommon.h" #include "tvgArray.h" #include "tvgLock.h" @@ -36,9 +37,8 @@ using pixel_t = uint32_t; enum RenderUpdateFlag : uint8_t {None = 0, Path = 1, Color = 2, Gradient = 4, Stroke = 8, Transform = 16, Image = 32, GradientStroke = 64, Blend = 128, All = 255}; -struct Surface; - -enum ColorSpace +//TODO: Move this in public header unifying with SwCanvas::Colorspace +enum ColorSpace : uint8_t { ABGR8888 = 0, //The channels are joined in the order: alpha, blue, green, red. Colors are alpha-premultiplied. ARGB8888, //The channels are joined in the order: alpha, red, green, blue. Colors are alpha-premultiplied. @@ -48,7 +48,7 @@ enum ColorSpace Unsupported //TODO: Change to the default, At the moment, we put it in the last to align with SwCanvas::Colorspace. }; -struct Surface +struct RenderSurface { union { pixel_t* data = nullptr; //system based data pointer @@ -62,11 +62,11 @@ struct Surface uint8_t channelSize = 0; bool premultiplied = false; //Alpha-premultiplied - Surface() + RenderSurface() { } - Surface(const Surface* rhs) + RenderSurface(const RenderSurface* rhs) { data = rhs->data; stride = rhs->stride; @@ -80,21 +80,10 @@ struct Surface }; -struct Compositor +struct RenderCompositor { CompositeMethod method; - uint8_t opacity; -}; - -struct Vertex -{ - Point pt; - Point uv; -}; - -struct Polygon -{ - Vertex vertex[3]; + uint8_t opacity; }; struct RenderRegion @@ -270,11 +259,66 @@ struct RenderShape float strokeMiterlimit() const { if (!stroke) return 4.0f; - return stroke->miterlimit;; } }; +struct RenderEffect +{ + RenderData rd = nullptr; + RenderRegion extend = {0, 0, 0, 0}; + SceneEffect type; + bool invalid = false; + + virtual ~RenderEffect() + { + free(rd); + } +}; + +struct RenderEffectGaussianBlur : RenderEffect +{ + float sigma; + uint8_t direction; //0: both, 1: horizontal, 2: vertical + uint8_t border; //0: duplicate, 1: wrap + uint8_t quality; //0 ~ 100 (optional) + + static RenderEffectGaussianBlur* gen(va_list& args) + { + auto inst = new RenderEffectGaussianBlur; + inst->sigma = std::max((float) va_arg(args, double), 0.0f); + inst->direction = std::min(va_arg(args, int), 2); + inst->border = std::min(va_arg(args, int), 1); + inst->quality = std::min(va_arg(args, int), 100); + inst->type = SceneEffect::GaussianBlur; + return inst; + } +}; + +struct RenderEffectDropShadow : RenderEffect +{ + uint8_t color[4]; //rgba + float angle; + float distance; + float sigma; + uint8_t quality; //0 ~ 100 (optional) + + static RenderEffectDropShadow* gen(va_list& args) + { + auto inst = new RenderEffectDropShadow; + inst->color[0] = va_arg(args, int); + inst->color[1] = va_arg(args, int); + inst->color[2] = va_arg(args, int); + inst->color[3] = std::min(va_arg(args, int), 255); + inst->angle = (float) va_arg(args, double); + inst->distance = (float) va_arg(args, double); + inst->sigma = std::max((float) va_arg(args, double), 0.0f); + inst->quality = std::min(va_arg(args, int), 100); + inst->type = SceneEffect::DropShadow; + return inst; + } +}; + class RenderMethod { private: @@ -287,7 +331,7 @@ class RenderMethod virtual ~RenderMethod() {} virtual RenderData prepare(const RenderShape& rshape, RenderData data, const Matrix& transform, Array& clips, uint8_t opacity, RenderUpdateFlag flags, bool clipper) = 0; - virtual RenderData prepare(Surface* surface, RenderData data, const Matrix& transform, Array& clips, uint8_t opacity, RenderUpdateFlag flags) = 0; + virtual RenderData prepare(RenderSurface* surface, RenderData data, const Matrix& transform, Array& clips, uint8_t opacity, RenderUpdateFlag flags) = 0; virtual bool preRender() = 0; virtual bool renderShape(RenderData data) = 0; virtual bool renderImage(RenderData data) = 0; @@ -298,14 +342,17 @@ class RenderMethod virtual bool viewport(const RenderRegion& vp) = 0; virtual bool blend(BlendMethod method) = 0; virtual ColorSpace colorSpace() = 0; - virtual const Surface* mainSurface() = 0; + virtual const RenderSurface* mainSurface() = 0; virtual bool clear() = 0; virtual bool sync() = 0; - virtual Compositor* target(const RenderRegion& region, ColorSpace cs) = 0; - virtual bool beginComposite(Compositor* cmp, CompositeMethod method, uint8_t opacity) = 0; - virtual bool endComposite(Compositor* cmp) = 0; + virtual RenderCompositor* target(const RenderRegion& region, ColorSpace cs) = 0; + virtual bool beginComposite(RenderCompositor* cmp, CompositeMethod method, uint8_t opacity) = 0; + virtual bool endComposite(RenderCompositor* cmp) = 0; + + virtual bool prepare(RenderEffect* effect) = 0; + virtual bool effect(RenderCompositor* cmp, const RenderEffect* effect, uint8_t opacity, bool direct) = 0; }; static inline bool MASK_REGION_MERGING(CompositeMethod method) @@ -374,7 +421,6 @@ static inline uint8_t MULTIPLY(uint8_t c, uint8_t a) return (((c) * (a) + 0xff) >> 8); } - } #endif //_TVG_RENDER_H_ diff --git a/thirdparty/thorvg/src/renderer/tvgSaver.cpp b/thirdparty/thorvg/src/renderer/tvgSaver.cpp index 79302f69fa5..993fe6d80f8 100644 --- a/thirdparty/thorvg/src/renderer/tvgSaver.cpp +++ b/thirdparty/thorvg/src/renderer/tvgSaver.cpp @@ -20,6 +20,7 @@ * SOFTWARE. */ +#include #include "tvgCommon.h" #include "tvgSaveModule.h" #include "tvgPaint.h" @@ -122,7 +123,7 @@ Result Saver::save(std::unique_ptr paint, const string& path, bool compre auto p = paint.release(); if (!p) return Result::MemoryCorruption; - //Already on saving an other resource. + //Already on saving another resource. if (pImpl->saveModule) { if (P(p)->refCnt == 0) delete(p); return Result::InsufficientCondition; @@ -160,12 +161,12 @@ Result Saver::save(unique_ptr animation, const string& path, uint32_t //animation holds the picture, it must be 1 at the bottom. auto remove = PP(a->picture())->refCnt <= 1 ? true : false; - if (mathZero(a->totalFrame())) { + if (tvg::zero(a->totalFrame())) { if (remove) delete(a); return Result::InsufficientCondition; } - //Already on saving an other resource. + //Already on saving another resource. if (pImpl->saveModule) { if (remove) delete(a); return Result::InsufficientCondition; diff --git a/thirdparty/thorvg/src/renderer/tvgScene.cpp b/thirdparty/thorvg/src/renderer/tvgScene.cpp index f5809cf93b5..ce169d33ba6 100644 --- a/thirdparty/thorvg/src/renderer/tvgScene.cpp +++ b/thirdparty/thorvg/src/renderer/tvgScene.cpp @@ -20,15 +20,32 @@ * SOFTWARE. */ +#include #include "tvgScene.h" +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +Result Scene::Impl::resetEffects() +{ + if (effects) { + for (auto e = effects->begin(); e < effects->end(); ++e) { + delete(*e); + } + delete(effects); + effects = nullptr; + } + return Result::Success; +} + + /************************************************************************/ /* External Class Implementation */ /************************************************************************/ Scene::Scene() : pImpl(new Impl(this)) { - Paint::pImpl->id = TVG_CLASS_ID_SCENE; } @@ -44,9 +61,15 @@ unique_ptr Scene::gen() noexcept } -uint32_t Scene::identifier() noexcept +TVG_DEPRECATED uint32_t Scene::identifier() noexcept +{ + return (uint32_t) Type::Scene; +} + + +Type Scene::type() const noexcept { - return TVG_CLASS_ID_SCENE; + return Type::Scene; } @@ -54,7 +77,11 @@ Result Scene::push(unique_ptr paint) noexcept { auto p = paint.release(); if (!p) return Result::MemoryCorruption; - PP(p)->ref(); + P(p)->ref(); + + //Relocated the paint to the current scene space + P(p)->renderFlag |= RenderUpdateFlag::Transform; + pImpl->paints.push_back(p); return Result::Success; @@ -79,3 +106,34 @@ list& Scene::paints() noexcept { return pImpl->paints; } + + +Result Scene::push(SceneEffect effect, ...) noexcept +{ + if (effect == SceneEffect::ClearAll) return pImpl->resetEffects(); + + if (!pImpl->effects) pImpl->effects = new Array; + + va_list args; + va_start(args, effect); + + RenderEffect* re = nullptr; + + switch (effect) { + case SceneEffect::GaussianBlur: { + re = RenderEffectGaussianBlur::gen(args); + break; + } + case SceneEffect::DropShadow: { + re = RenderEffectDropShadow::gen(args); + break; + } + default: break; + } + + if (!re) return Result::InvalidArguments; + + pImpl->effects->push(re); + + return Result::Success; +} diff --git a/thirdparty/thorvg/src/renderer/tvgScene.h b/thirdparty/thorvg/src/renderer/tvgScene.h index 190ecd31b91..7972ae33fb1 100644 --- a/thirdparty/thorvg/src/renderer/tvgScene.h +++ b/thirdparty/thorvg/src/renderer/tvgScene.h @@ -23,10 +23,9 @@ #ifndef _TVG_SCENE_H_ #define _TVG_SCENE_H_ -#include +#include "tvgMath.h" #include "tvgPaint.h" - struct SceneIterator : Iterator { list* paints; @@ -61,8 +60,10 @@ struct Scene::Impl list paints; RenderData rd = nullptr; Scene* scene = nullptr; - uint8_t opacity; //for composition - bool needComp = false; //composite or not + RenderRegion vport = {0, 0, INT32_MAX, INT32_MAX}; + Array* effects = nullptr; + uint8_t opacity; //for composition + bool needComp = false; //composite or not Impl(Scene* s) : scene(s) { @@ -70,6 +71,8 @@ struct Scene::Impl ~Impl() { + resetEffects(); + for (auto paint : paints) { if (P(paint)->unref() == 0) delete(paint); } @@ -83,12 +86,15 @@ struct Scene::Impl { if (opacity == 0 || paints.empty()) return false; + //post effects requires composition + if (effects) return true; + //Masking may require composition (even if opacity == 255) auto compMethod = scene->composite(nullptr); if (compMethod != CompositeMethod::None && compMethod != CompositeMethod::ClipPath) return true; //Blending may require composition (even if opacity == 255) - if (scene->blend() != BlendMethod::Normal) return true; + if (PP(scene)->blendMethod != BlendMethod::Normal) return true; //Half translucent requires intermediate composition. if (opacity == 255) return false; @@ -96,31 +102,34 @@ struct Scene::Impl //If scene has several children or only scene, it may require composition. //OPTIMIZE: the bitmap type of the picture would not need the composition. //OPTIMIZE: a single paint of a scene would not need the composition. - if (paints.size() == 1 && paints.front()->identifier() == TVG_CLASS_ID_SHAPE) return false; + if (paints.size() == 1 && paints.front()->type() == Type::Shape) return false; return true; } RenderData update(RenderMethod* renderer, const Matrix& transform, Array& clips, uint8_t opacity, RenderUpdateFlag flag, TVG_UNUSED bool clipper) { + this->vport = renderer->viewport(); + if ((needComp = needComposition(opacity))) { /* Overriding opacity value. If this scene is half-translucent, - It must do intermeidate composition with that opacity value. */ + It must do intermediate composition with that opacity value. */ this->opacity = opacity; opacity = 255; } for (auto paint : paints) { paint->pImpl->update(renderer, transform, clips, opacity, flag, false); } + return nullptr; } bool render(RenderMethod* renderer) { - Compositor* cmp = nullptr; + RenderCompositor* cmp = nullptr; auto ret = true; - renderer->blend(scene->blend()); + renderer->blend(PP(scene)->blendMethod); if (needComp) { cmp = renderer->target(bounds(renderer), renderer->colorSpace()); @@ -131,7 +140,16 @@ struct Scene::Impl ret &= paint->pImpl->render(renderer); } - if (cmp) renderer->endComposite(cmp); + if (cmp) { + //Apply post effects if any. + if (effects) { + auto direct = effects->count == 1 ? true : false; + for (auto e = effects->begin(); e < effects->end(); ++e) { + renderer->effect(cmp, *e, opacity, direct); + } + } + renderer->endComposite(cmp); + } return ret; } @@ -155,7 +173,23 @@ struct Scene::Impl if (y2 < region.y + region.h) y2 = (region.y + region.h); } - return {x1, y1, (x2 - x1), (y2 - y1)}; + //Extends the render region if post effects require + int32_t ex = 0, ey = 0, ew = 0, eh = 0; + if (effects) { + for (auto e = effects->begin(); e < effects->end(); ++e) { + auto effect = *e; + if (effect->rd || renderer->prepare(effect)) { + ex = std::min(ex, effect->extend.x); + ey = std::min(ey, effect->extend.y); + ew = std::max(ew, effect->extend.w); + eh = std::max(eh, effect->extend.h); + } + } + } + + auto ret = RenderRegion{x1 + ex, y1 + ey, (x2 - x1) + ew, (y2 - y1) + eh}; + ret.intersect(this->vport); + return ret; } bool bounds(float* px, float* py, float* pw, float* ph, bool stroking) @@ -203,6 +237,8 @@ struct Scene::Impl dup->paints.push_back(cdup); } + if (effects) TVGERR("RENDERER", "TODO: Duplicate Effects?"); + return scene; } @@ -218,6 +254,8 @@ struct Scene::Impl { return new SceneIterator(&paints); } + + Result resetEffects(); }; #endif //_TVG_SCENE_H_ diff --git a/thirdparty/thorvg/src/renderer/tvgShape.cpp b/thirdparty/thorvg/src/renderer/tvgShape.cpp index 3b9293a00e0..269d951f05a 100644 --- a/thirdparty/thorvg/src/renderer/tvgShape.cpp +++ b/thirdparty/thorvg/src/renderer/tvgShape.cpp @@ -34,7 +34,6 @@ Shape :: Shape() : pImpl(new Impl(this)) { - Paint::pImpl->id = TVG_CLASS_ID_SHAPE; } @@ -52,7 +51,13 @@ unique_ptr Shape::gen() noexcept uint32_t Shape::identifier() noexcept { - return TVG_CLASS_ID_SHAPE; + return (uint32_t) Type::Shape; +} + + +Type Shape::type() const noexcept +{ + return Type::Shape; } @@ -151,14 +156,14 @@ Result Shape::appendCircle(float cx, float cy, float rx, float ry) noexcept } -Result Shape::appendArc(float cx, float cy, float radius, float startAngle, float sweep, bool pie) noexcept +TVG_DEPRECATED Result Shape::appendArc(float cx, float cy, float radius, float startAngle, float sweep, bool pie) noexcept { //just circle if (sweep >= 360.0f || sweep <= -360.0f) return appendCircle(cx, cy, radius, radius); const float arcPrecision = 1e-5f; - startAngle = mathDeg2Rad(startAngle); - sweep = mathDeg2Rad(sweep); + startAngle = deg2rad(startAngle); + sweep = deg2rad(sweep); auto nCurves = static_cast(fabsf(sweep / MATH_PI2)); if (fabsf(sweep / MATH_PI2) - nCurves > arcPrecision) ++nCurves; @@ -409,12 +414,6 @@ Result Shape::strokeTrim(float begin, float end, bool simultaneous) noexcept } -bool Shape::strokeTrim(float* begin, float* end) const noexcept -{ - return pImpl->strokeTrim(begin, end); -} - - Result Shape::fill(FillRule r) noexcept { pImpl->rs.rule = r; diff --git a/thirdparty/thorvg/src/renderer/tvgShape.h b/thirdparty/thorvg/src/renderer/tvgShape.h index 440fb312b6c..42f81520606 100644 --- a/thirdparty/thorvg/src/renderer/tvgShape.h +++ b/thirdparty/thorvg/src/renderer/tvgShape.h @@ -53,9 +53,9 @@ struct Shape::Impl { if (!rd) return false; - Compositor* cmp = nullptr; + RenderCompositor* cmp = nullptr; - renderer->blend(shape->blend()); + renderer->blend(PP(shape)->blendMethod); if (needComp) { cmp = renderer->target(bounds(renderer), renderer->colorSpace()); @@ -83,7 +83,7 @@ struct Shape::Impl auto method = shape->composite(&target); if (!target || method == CompositeMethod::ClipPath) return false; if (target->pImpl->opacity == 255 || target->pImpl->opacity == 0) { - if (target->identifier() == TVG_CLASS_ID_SHAPE) { + if (target->type() == Type::Shape) { auto shape = static_cast(target); if (!shape->fill()) { uint8_t r, g, b, a; @@ -106,7 +106,7 @@ struct Shape::Impl if ((needComp = needComposition(opacity))) { /* Overriding opacity value. If this scene is half-translucent, - It must do intermeidate composition with that opacity value. */ + It must do intermediate composition with that opacity value. */ this->opacity = opacity; opacity = 255; } @@ -219,7 +219,7 @@ struct Shape::Impl rs.stroke = new RenderStroke(); } - if (mathEqual(rs.stroke->trim.begin, begin) && mathEqual(rs.stroke->trim.end, end) && + if (tvg::equal(rs.stroke->trim.begin, begin) && tvg::equal(rs.stroke->trim.end, end) && rs.stroke->trim.simultaneous == simultaneous) return; rs.stroke->trim.begin = begin; diff --git a/thirdparty/thorvg/src/renderer/tvgSwCanvas.cpp b/thirdparty/thorvg/src/renderer/tvgSwCanvas.cpp index d762492f22d..6c4b6da1de4 100644 --- a/thirdparty/thorvg/src/renderer/tvgSwCanvas.cpp +++ b/thirdparty/thorvg/src/renderer/tvgSwCanvas.cpp @@ -82,7 +82,7 @@ Result SwCanvas::mempool(MempoolPolicy policy) noexcept Result SwCanvas::target(uint32_t* buffer, uint32_t stride, uint32_t w, uint32_t h, Colorspace cs) noexcept { #ifdef THORVG_SW_RASTER_SUPPORT - if (Canvas::pImpl->status != Status::Damanged && Canvas::pImpl->status != Status::Synced) { + if (Canvas::pImpl->status != Status::Damaged && Canvas::pImpl->status != Status::Synced) { return Result::InsufficientCondition; } @@ -98,7 +98,7 @@ Result SwCanvas::target(uint32_t* buffer, uint32_t stride, uint32_t w, uint32_t ImageLoader::cs = static_cast(cs); //Paints must be updated again with this new target. - Canvas::pImpl->status = Status::Damanged; + Canvas::pImpl->status = Status::Damaged; return Result::Success; #endif diff --git a/thirdparty/thorvg/src/renderer/tvgText.cpp b/thirdparty/thorvg/src/renderer/tvgText.cpp index d54c78783c5..b324b95049e 100644 --- a/thirdparty/thorvg/src/renderer/tvgText.cpp +++ b/thirdparty/thorvg/src/renderer/tvgText.cpp @@ -37,7 +37,6 @@ Text::Text() : pImpl(new Impl(this)) { - Paint::pImpl->id = TVG_CLASS_ID_TEXT; } @@ -111,7 +110,7 @@ unique_ptr Text::gen() noexcept } -uint32_t Text::identifier() noexcept +Type Text::type() const noexcept { - return TVG_CLASS_ID_TEXT; + return Type::Text; } diff --git a/thirdparty/thorvg/src/renderer/tvgText.h b/thirdparty/thorvg/src/renderer/tvgText.h index cb9a76c0521..11e01b58ce6 100644 --- a/thirdparty/thorvg/src/renderer/tvgText.h +++ b/thirdparty/thorvg/src/renderer/tvgText.h @@ -90,7 +90,7 @@ struct Text::Impl bool render(RenderMethod* renderer) { if (!loader) return true; - renderer->blend(paint->blend()); + renderer->blend(PP(paint)->blendMethod); return PP(shape)->render(renderer); } @@ -115,7 +115,7 @@ struct Text::Impl auto fill = P(shape)->rs.fill; if (fill && P(shape)->flag & RenderUpdateFlag::Gradient) { auto scale = 1.0f / loader->scale; - if (fill->identifier() == TVG_CLASS_ID_LINEAR) { + if (fill->type() == Type::LinearGradient) { P(static_cast(fill))->x1 *= scale; P(static_cast(fill))->y1 *= scale; P(static_cast(fill))->x2 *= scale; diff --git a/thirdparty/thorvg/src/renderer/tvgWgCanvas.cpp b/thirdparty/thorvg/src/renderer/tvgWgCanvas.cpp index 067e35b1f0d..991f73fc542 100644 --- a/thirdparty/thorvg/src/renderer/tvgWgCanvas.cpp +++ b/thirdparty/thorvg/src/renderer/tvgWgCanvas.cpp @@ -40,7 +40,7 @@ struct WgCanvas::Impl /************************************************************************/ #ifdef THORVG_WG_RASTER_SUPPORT -WgCanvas::WgCanvas() : Canvas(WgRenderer::gen()), pImpl(new Impl) +WgCanvas::WgCanvas() : Canvas(WgRenderer::gen()), pImpl(nullptr) #else WgCanvas::WgCanvas() : Canvas(nullptr), pImpl(nullptr) #endif @@ -50,14 +50,17 @@ WgCanvas::WgCanvas() : Canvas(nullptr), pImpl(nullptr) WgCanvas::~WgCanvas() { - delete pImpl; +#ifdef THORVG_WG_RASTER_SUPPORT + auto renderer = static_cast(Canvas::pImpl->renderer); + renderer->target(nullptr, 0, 0); +#endif } -Result WgCanvas::target(void* instance, void* surface, uint32_t w, uint32_t h) noexcept +Result WgCanvas::target(void* instance, void* surface, uint32_t w, uint32_t h, void* device) noexcept { #ifdef THORVG_WG_RASTER_SUPPORT - if (Canvas::pImpl->status != Status::Damanged && Canvas::pImpl->status != Status::Synced) { + if (Canvas::pImpl->status != Status::Damaged && Canvas::pImpl->status != Status::Synced) { return Result::InsufficientCondition; } @@ -67,12 +70,12 @@ Result WgCanvas::target(void* instance, void* surface, uint32_t w, uint32_t h) n auto renderer = static_cast(Canvas::pImpl->renderer); if (!renderer) return Result::MemoryCorruption; - if (!renderer->target((WGPUInstance)instance, (WGPUSurface)surface, w, h)) return Result::Unknown; + if (!renderer->target((WGPUInstance)instance, (WGPUSurface)surface, w, h, (WGPUDevice)device)) return Result::Unknown; Canvas::pImpl->vport = {0, 0, (int32_t)w, (int32_t)h}; renderer->viewport(Canvas::pImpl->vport); //Paints must be updated again with this new target. - Canvas::pImpl->status = Status::Damanged; + Canvas::pImpl->status = Status::Damaged; return Result::Success; #endif diff --git a/thirdparty/thorvg/update-thorvg.sh b/thirdparty/thorvg/update-thorvg.sh index 2c5d84d266c..f9953f2fc9e 100755 --- a/thirdparty/thorvg/update-thorvg.sh +++ b/thirdparty/thorvg/update-thorvg.sh @@ -1,6 +1,6 @@ #!/bin/bash -e -VERSION=0.14.10 +VERSION=0.15.5 # Uncomment and set a git hash to use specific commit instead of tag. #GIT_COMMIT= diff --git a/thirdparty/ufbx/ufbx.c b/thirdparty/ufbx/ufbx.c index 2d13c78bd8c..77bd3dea623 100644 --- a/thirdparty/ufbx/ufbx.c +++ b/thirdparty/ufbx/ufbx.c @@ -20,6 +20,21 @@ // UFBX_TRACE Log calls of `ufbxi_check()` for tracing execution // UFBX_LITTLE_ENDIAN=0/1 Explicitly define little/big endian architecture // UFBX_PATH_SEPARATOR='' Specify default platform path separator +// UFBX_NO_SSE Do not try to include SSE + +// Dependencies: +// UFBX_NO_MALLOC Disable default malloc/realloc/free +// UFBX_NO_STDIO Disable stdio FILE API +// UFBX_EXTERNAL_MALLOC Link to external ufbx_malloc() interface +// UFBX_EXTERNAL_STDIO Link to external ufbx_stdio_() interface +// UFBX_EXTERNAL_MATH Link to external interface +// UFBX_EXTERNAL_STRING Link to external interface + +// Freestanding: +// UFBX_MATH_PREFIX='ufbx_' Prefix for external functions used +// UFBX_STRING_PREFIX='ufbx_' Prefix for external functions used +// UFBX_NO_LIBC Do not include libc (implies UFBX_EXTERNAL_MATH/STRING/MALLOC/STDIO by default) +// UFBX_NO_LIBC_TYPES Do not include any libc headers, you must define all types in // Mostly internal for debugging: // UFBX_STATIC_ANALYSIS Enable static analysis augmentation @@ -175,28 +190,71 @@ // -- Headers -#include -#include -#include -#include -#include -#include - -#if !defined(UFBX_NO_MATH_H) - #include - #define UFBX_INFINITY INFINITY - #define UFBX_NAN NAN +// Legacy mapping +#if !defined(UFBX_EXTERNAL_MATH) && defined(UFBX_NO_MATH_H) + #define UFBX_EXTERNAL_MATH #endif -#if !defined(UFBX_MATH_PREFIX) - #define UFBX_MATH_PREFIX +#if !defined(UFBX_NO_LIBC_TYPES) + #include #endif -#define ufbxi_math_cat2(a, b) a##b -#define ufbxi_math_cat(a, b) ufbxi_math_cat2(a, b) -#define ufbxi_math_fn(name) ufbxi_math_cat(UFBX_MATH_PREFIX, name) +#if !defined(UFBX_NO_LIBC) + #if !defined(UFBX_NO_FLOAT_H) + #include + #endif + #if !defined(UFBX_EXTERNAL_MATH) + #include + #endif + #if !defined(UFBX_EXTERNAL_STRING) + #include + #endif + #if !defined(UFBX_NO_STDIO) && !defined(UFBX_EXTERNAL_STDIO) + #include + #endif + #if !defined(UFBX_NO_MALLOC) && !defined(UFBX_EXTERNAL_MALLOC) + #include + #endif +#else + #if !defined(UFBX_EXTERNAL_MATH) && !defined(UFBX_NO_EXTERNAL_MATH) + #define UFBX_EXTERNAL_MATH + #endif + #if !defined(UFBX_EXTERNAL_STRING) && !defined(UFBX_NO_EXTERNAL_STRING) + #define UFBX_EXTERNAL_STRING + #endif + #if !defined(UFBX_EXTERNAL_MALLOC) && !defined(UFBX_NO_EXTERNAL_MALLOC) && !defined(UFBX_NO_MALLOC) + #define UFBX_EXTERNAL_MALLOC + #endif + #if !defined(UFBX_EXTERNAL_STDIO) && !defined(UFBX_NO_EXTERNAL_STDIO) && !defined(UFBX_NO_STDIO) + #define UFBX_EXTERNAL_STDIO + #endif +#endif + +#if defined(UFBX_EXTERNAL_STRING) && !defined(UFBX_STRING_PREFIX) + #define UFBX_STRING_PREFIX ufbx_ +#endif -#if !defined(UFBX_NO_MATH_DEFINES) +#if !defined(UFBX_EXTERNAL_MATH) + #if !defined(UFBX_MATH_PREFIX) + #define UFBX_MATH_PREFIX + #endif +#endif + +#define ufbxi_pre_cat2(a, b) a##b +#define ufbxi_pre_cat(a, b) ufbxi_pre_cat2(a, b) + +// -- External functions + +#ifndef ufbx_extern_abi + #if defined(UFBX_STATIC) + #define ufbx_extern_abi static + #else + #define ufbx_extern_abi + #endif +#endif + +#if defined(UFBX_MATH_PREFIX) + #define ufbxi_math_fn(name) ufbxi_pre_cat(UFBX_MATH_PREFIX, name) #define ufbx_sqrt ufbxi_math_fn(sqrt) #define ufbx_fabs ufbxi_math_fn(fabs) #define ufbx_pow ufbxi_math_fn(pow) @@ -216,31 +274,123 @@ #define ufbx_isnan ufbxi_math_fn(isnan) #endif -#if defined(UFBX_NO_MATH_H) && !defined(UFBX_NO_MATH_DECLARATIONS) - double ufbx_sqrt(double x); - double ufbx_sin(double x); - double ufbx_cos(double x); - double ufbx_tan(double x); - double ufbx_asin(double x); - double ufbx_acos(double x); - double ufbx_atan(double x); - double ufbx_atan2(double y, double x); - double ufbx_pow(double x, double y); - double ufbx_fmin(double a, double b); - double ufbx_fmax(double a, double b); - double ufbx_fabs(double x); - double ufbx_copysign(double x, double y); - double ufbx_nextafter(double x, double y); - double ufbx_rint(double x); - double ufbx_ceil(double x); - int ufbx_isnan(double x); +#if !defined(UFBX_NO_EXTERNAL_DEFINES) + +#if defined(__cplusplus) +extern "C" { +#endif + +#if defined(UFBX_EXTERNAL_MATH) + ufbx_extern_abi double ufbx_sqrt(double x); + ufbx_extern_abi double ufbx_sin(double x); + ufbx_extern_abi double ufbx_cos(double x); + ufbx_extern_abi double ufbx_tan(double x); + ufbx_extern_abi double ufbx_asin(double x); + ufbx_extern_abi double ufbx_acos(double x); + ufbx_extern_abi double ufbx_atan(double x); + ufbx_extern_abi double ufbx_atan2(double y, double x); + ufbx_extern_abi double ufbx_pow(double x, double y); + ufbx_extern_abi double ufbx_fmin(double a, double b); + ufbx_extern_abi double ufbx_fmax(double a, double b); + ufbx_extern_abi double ufbx_fabs(double x); + ufbx_extern_abi double ufbx_copysign(double x, double y); + ufbx_extern_abi double ufbx_nextafter(double x, double y); + ufbx_extern_abi double ufbx_rint(double x); + ufbx_extern_abi double ufbx_ceil(double x); + ufbx_extern_abi int ufbx_isnan(double x); +#endif + +#if defined(UFBX_EXTERNAL_STRING) + ufbx_extern_abi size_t ufbx_strlen(const char *str); + ufbx_extern_abi void *ufbx_memcpy(void *dst, const void *src, size_t count); + ufbx_extern_abi void *ufbx_memmove(void *dst, const void *src, size_t count); + ufbx_extern_abi void *ufbx_memset(void *dst, int ch, size_t count); + ufbx_extern_abi const void *ufbx_memchr(const void *ptr, int value, size_t count); + ufbx_extern_abi int ufbx_memcmp(const void *a, const void *b, size_t count); + ufbx_extern_abi int ufbx_strcmp(const char *a, const char *b); + ufbx_extern_abi int ufbx_strncmp(const char *a, const char *b, size_t count); +#endif + +#if defined(UFBX_EXTERNAL_MALLOC) + ufbx_extern_abi void *ufbx_malloc(size_t size); + ufbx_extern_abi void *ufbx_realloc(void *ptr, size_t old_size, size_t new_size); + ufbx_extern_abi void ufbx_free(void *ptr, size_t old_size); +#endif + +#if defined(UFBX_EXTERNAL_STDIO) + ufbx_extern_abi void *ufbx_stdio_open(const char *path, size_t path_len); + ufbx_extern_abi size_t ufbx_stdio_read(void *file, void *data, size_t size); + ufbx_extern_abi bool ufbx_stdio_skip(void *file, size_t size); + ufbx_extern_abi uint64_t ufbx_stdio_size(void *file); + ufbx_extern_abi void ufbx_stdio_close(void *file); +#endif + +#if defined(__cplusplus) +} +#endif + #endif #if !defined(UFBX_INFINITY) - #define UFBX_INFINITY (1e+300 * 1e+300) + #if defined(INFINITY) + #define UFBX_INFINITY INFINITY + #else + #define UFBX_INFINITY (1e+300 * 1e+300) + #endif #endif #if !defined(UFBX_NAN) - #define UFBX_NAN (UFBX_INFINITY * 0.0f) + #if defined(NAN) + #define UFBX_NAN NAN + #else + #define UFBX_NAN (UFBX_INFINITY * 0.0f) + #endif +#endif +#if !defined(UFBX_FLT_EPSILON) + #if defined(FLT_EPSILON) + #define UFBX_FLT_EPSILON FLT_EPSILON + #else + #define UFBX_FLT_EPSILON 1.192092896e-07f + #endif +#endif +#if !defined(UFBX_FLT_EVAL_METHOD) + #if defined(FLT_EVAL_METHOD) + #define UFBX_FLT_EVAL_METHOD FLT_EVAL_METHOD + #elif defined(__FLT_EVAL_METHOD__) + #define UFBX_FLT_EVAL_METHOD __FLT_EVAL_METHOD__ + #elif defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64)) + #define UFBX_FLT_EVAL_METHOD 0 + #else + #define UFBX_FLT_EVAL_METHOD -1 + #endif +#endif + +#if defined(ufbx_malloc) || defined(ufbx_realloc) || defined(ufbx_free) + // User provided allocators + #if !defined(ufbx_malloc) || !defined(ufbx_realloc) || !defined(ufbx_free) + #error Inconsistent custom global allocator + #endif +#elif defined(UFBX_NO_MALLOC) + #define ufbx_malloc(size) ((void)(size), (void*)NULL) + #define ufbx_realloc(ptr, old_size, new_size) ((void)(ptr), (void)(old_size), (void)(new_size), (void*)NULL) + #define ufbx_free(ptr, old_size) ((void)(ptr), (void*)(old_size)) +#elif defined(UFBX_EXTERNAL_MALLOC) + // Nop +#else + #define ufbx_malloc(size) malloc((size)) + #define ufbx_realloc(ptr, old_size, new_size) realloc((ptr), (new_size)) + #define ufbx_free(ptr, old_size) free((ptr)) +#endif + +#if !defined(ufbx_panic_handler) + static void ufbxi_panic_handler(const char *message) + { + (void)message; + #if !defined(UFBX_NO_STDIO) && !defined(UFBX_EXTERNAL_STDIO) + fprintf(stderr, "ufbx panic: %s\n", message); + #endif + ufbx_assert(false && "ufbx panic: See stderr for more information"); + } + #define ufbx_panic_handler ufbxi_panic_handler #endif // -- Platform @@ -414,8 +564,16 @@ #if defined(UFBX_STATIC_ANALYSIS) bool ufbxi_analysis_opaque; #define ufbxi_maybe_null(ptr) (ufbxi_analysis_opaque ? (ptr) : NULL) + #define ufbxi_analysis_assert(cond) ufbx_assert(cond) #else #define ufbxi_maybe_null(ptr) (ptr) + #define ufbxi_analysis_assert(cond) (void)0 +#endif + +#if defined(UFBX_STATIC_ANALYSIS) || defined(UFBX_UBSAN) + #define ufbxi_maybe_uninit(cond, value, def) ((cond) ? (value) : (def)) +#else + #define ufbxi_maybe_uninit(cond, value, def) (value) #endif #if !defined(ufbxi_trace) @@ -442,7 +600,7 @@ #endif #endif -#if !defined(UFBX_STANDARD_C) && (defined(_MSC_VER) && defined(_M_X64)) || ((defined(__GNUC__) || defined(__clang__)) && defined(__x86_64__)) || defined(UFBX_USE_SSE) +#if !defined(UFBX_STANDARD_C) && !defined(UFBX_NO_SSE) && (defined(_MSC_VER) && defined(_M_X64) && !defined(_M_ARM64EC)) || ((defined(__GNUC__) || defined(__clang__)) && defined(__x86_64__)) || defined(UFBX_USE_SSE) #define UFBXI_HAS_SSE 1 #include #include @@ -450,6 +608,108 @@ #define UFBXI_HAS_SSE 0 #endif +// -- Atomic counter + +#define UFBXI_THREAD_SAFE 1 + +#if defined(__cplusplus) + #define ufbxi_extern_c extern "C" +#else + #define ufbxi_extern_c +#endif + +#if !defined(UFBX_STANDARD_C) && (defined(__GNUC__) || defined(__clang__) || defined(__INTEL_COMPILER)) + typedef size_t ufbxi_atomic_counter; + #define ufbxi_atomic_counter_init(ptr) (*(ptr) = 0) + #define ufbxi_atomic_counter_free(ptr) (*(ptr) = 0) + #define ufbxi_atomic_counter_inc(ptr) __sync_fetch_and_add((ptr), 1) + #define ufbxi_atomic_counter_dec(ptr) __sync_fetch_and_sub((ptr), 1) + #define ufbxi_atomic_counter_load(ptr) __sync_fetch_and_add((ptr), 0) // TODO: Proper atomic load +#elif !defined(UFBX_STANDARD_C) && defined(_MSC_VER) + #if defined(_M_X64) || defined(_M_ARM64) + ufbxi_extern_c __int64 _InterlockedIncrement64(__int64 volatile * lpAddend); + ufbxi_extern_c __int64 _InterlockedDecrement64(__int64 volatile * lpAddend); + ufbxi_extern_c __int64 _InterlockedExchangeAdd64(__int64 volatile * lpAddend, __int64 Value); + typedef volatile __int64 ufbxi_atomic_counter; + #define ufbxi_atomic_counter_init(ptr) (*(ptr) = 0) + #define ufbxi_atomic_counter_free(ptr) (*(ptr) = 0) + #define ufbxi_atomic_counter_inc(ptr) ((size_t)_InterlockedIncrement64(ptr) - 1) + #define ufbxi_atomic_counter_dec(ptr) ((size_t)_InterlockedDecrement64(ptr) + 1) + #define ufbxi_atomic_counter_load(ptr) ((size_t)_InterlockedExchangeAdd64((ptr), 0)) + #else + ufbxi_extern_c long __cdecl _InterlockedIncrement(long volatile * lpAddend); + ufbxi_extern_c long __cdecl _InterlockedDecrement(long volatile * lpAddend); + ufbxi_extern_c long __cdecl _InterlockedExchangeAdd(long volatile * lpAddend, long Value); + typedef volatile long ufbxi_atomic_counter; + #define ufbxi_atomic_counter_init(ptr) (*(ptr) = 0) + #define ufbxi_atomic_counter_free(ptr) (*(ptr) = 0) + #define ufbxi_atomic_counter_inc(ptr) ((size_t)_InterlockedIncrement(ptr) - 1) + #define ufbxi_atomic_counter_dec(ptr) ((size_t)_InterlockedDecrement(ptr) + 1) + #define ufbxi_atomic_counter_load(ptr) ((size_t)_InterlockedExchangeAdd((ptr), 0)) + #endif +#elif !defined(UFBX_STANDARD_C) && defined(__TINYC__) + #if defined(__x86_64__) || defined(_AMD64_) + static size_t ufbxi_tcc_atomic_add(volatile size_t *dst, size_t value) { + __asm__ __volatile__("lock; xaddq %0, %1;" : "+r" (value), "=m" (*dst) : "m" (dst)); + return value; + } + #elif defined(__i386__) || defined(_X86_) + static size_t ufbxi_tcc_atomic_add(volatile size_t *dst, size_t value) { + __asm__ __volatile__("lock; xaddl %0, %1;" : "+r" (value), "=m" (*dst) : "m" (dst)); + return value; + } + #else + #error Unexpected TCC architecture + #endif + typedef volatile size_t ufbxi_atomic_counter; + #define ufbxi_atomic_counter_init(ptr) (*(ptr) = 0) + #define ufbxi_atomic_counter_free(ptr) (*(ptr) = 0) + #define ufbxi_atomic_counter_inc(ptr) ufbxi_tcc_atomic_add((ptr), 1) + #define ufbxi_atomic_counter_dec(ptr) ufbxi_tcc_atomic_add((ptr), SIZE_MAX) + #define ufbxi_atomic_counter_load(ptr) ufbxi_tcc_atomic_add((ptr), 0) +#elif defined(__cplusplus) && (__cplusplus >= 201103L) + #include + #include + typedef struct { alignas(std::atomic_size_t) char data[sizeof(std::atomic_size_t)]; } ufbxi_atomic_counter; + #define ufbxi_atomic_counter_init(ptr) (new (&(ptr)->data) std::atomic_size_t(0)) + #define ufbxi_atomic_counter_free(ptr) (((std::atomic_size_t*)(ptr)->data)->~atomic()) + #define ufbxi_atomic_counter_inc(ptr) ((std::atomic_size_t*)(ptr)->data)->fetch_add(1) + #define ufbxi_atomic_counter_dec(ptr) ((std::atomic_size_t*)(ptr)->data)->fetch_sub(1) + #define ufbxi_atomic_counter_load(ptr) ((std::atomic_size_t*)(ptr)->data)->load(std::memory_order_acquire) +#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && !defined(__STDC_NO_ATOMICS__) + #include + typedef volatile atomic_size_t ufbxi_atomic_counter; + #define ufbxi_atomic_counter_init(ptr) atomic_init(ptr, 0) + #define ufbxi_atomic_counter_free(ptr) (void)(ptr) + #define ufbxi_atomic_counter_inc(ptr) atomic_fetch_add((ptr), 1) + #define ufbxi_atomic_counter_dec(ptr) atomic_fetch_sub((ptr), 1) + #define ufbxi_atomic_counter_load(ptr) atomic_load_explicit((ptr), memory_order_acquire) +#else + typedef volatile size_t ufbxi_atomic_counter; + #define ufbxi_atomic_counter_init(ptr) (*(ptr) = 0) + #define ufbxi_atomic_counter_free(ptr) (*(ptr) = 0) + #define ufbxi_atomic_counter_inc(ptr) ((*(ptr))++) + #define ufbxi_atomic_counter_dec(ptr) ((*(ptr))--) + #define ufbxi_atomic_counter_load(ptr) (*(ptr)) + #undef UFBXI_THREAD_SAFE + #define UFBXI_THREAD_SAFE 0 +#endif + +// ^^ No references to before this point ^^ +// vv No more includes past this point vv + +#if defined(UFBX_STRING_PREFIX) + #define ufbxi_string_fn(name) ufbxi_pre_cat(UFBX_STRING_PREFIX, name) + #define strlen ufbxi_string_fn(strlen) + #define memcpy ufbxi_string_fn(memcpy) + #define memmove ufbxi_string_fn(memmove) + #define memset ufbxi_string_fn(memset) + #define memchr ufbxi_string_fn(memchr) + #define memcmp ufbxi_string_fn(memcmp) + #define strcmp ufbxi_string_fn(strcmp) + #define strncmp ufbxi_string_fn(strncmp) +#endif + #if !defined(UFBX_LITTLE_ENDIAN) #if !defined(UFBX_STANDARD_C) && (defined(_M_IX86) || defined(__i386__) || defined(_M_X64) || defined(__x86_64__) || defined(_M_ARM64) || defined(__aarch64__) || defined(__wasm__) || defined(__EMSCRIPTEN__)) #define UFBX_LITTLE_ENDIAN 1 @@ -570,7 +830,7 @@ ufbx_static_assert(sizeof_f64, sizeof(double) == 8); // -- Version -#define UFBX_SOURCE_VERSION ufbx_pack_version(0, 14, 3) +#define UFBX_SOURCE_VERSION ufbx_pack_version(0, 15, 0) ufbx_abi_data_def const uint32_t ufbx_source_version = UFBX_SOURCE_VERSION; ufbx_static_assert(source_header_version, UFBX_SOURCE_VERSION/1000u == UFBX_HEADER_VERSION/1000u); @@ -607,98 +867,16 @@ ufbx_static_assert(source_header_version, UFBX_SOURCE_VERSION/1000u == UFBX_HEAD #define ufbxi_wrap_shr64(a, b) ((a) >> ((b) & 63)) #endif -// -- Atomic counter - -#define UFBXI_THREAD_SAFE 1 - -#if defined(__cplusplus) - #define ufbxi_extern_c extern "C" -#else - #define ufbxi_extern_c -#endif - -#if !defined(UFBX_STANDARD_C) && (defined(__GNUC__) || defined(__clang__) || defined(__INTEL_COMPILER)) - typedef size_t ufbxi_atomic_counter; - #define ufbxi_atomic_counter_init(ptr) (*(ptr) = 0) - #define ufbxi_atomic_counter_free(ptr) (*(ptr) = 0) - #define ufbxi_atomic_counter_inc(ptr) __sync_fetch_and_add((ptr), 1) - #define ufbxi_atomic_counter_dec(ptr) __sync_fetch_and_sub((ptr), 1) - #define ufbxi_atomic_counter_load(ptr) __sync_fetch_and_add((ptr), 0) // TODO: Proper atomic load -#elif !defined(UFBX_STANDARD_C) && defined(_MSC_VER) - #if defined(_M_X64) || defined(_M_ARM64) - ufbxi_extern_c __int64 _InterlockedIncrement64(__int64 volatile * lpAddend); - ufbxi_extern_c __int64 _InterlockedDecrement64(__int64 volatile * lpAddend); - ufbxi_extern_c __int64 _InterlockedExchangeAdd64(__int64 volatile * lpAddend, __int64 Value); - typedef volatile __int64 ufbxi_atomic_counter; - #define ufbxi_atomic_counter_init(ptr) (*(ptr) = 0) - #define ufbxi_atomic_counter_free(ptr) (*(ptr) = 0) - #define ufbxi_atomic_counter_inc(ptr) ((size_t)_InterlockedIncrement64(ptr) - 1) - #define ufbxi_atomic_counter_dec(ptr) ((size_t)_InterlockedDecrement64(ptr) + 1) - #define ufbxi_atomic_counter_load(ptr) ((size_t)_InterlockedExchangeAdd64((ptr), 0)) - #else - ufbxi_extern_c long __cdecl _InterlockedIncrement(long volatile * lpAddend); - ufbxi_extern_c long __cdecl _InterlockedDecrement(long volatile * lpAddend); - ufbxi_extern_c long __cdecl _InterlockedExchangeAdd(long volatile * lpAddend, long Value); - typedef volatile long ufbxi_atomic_counter; - #define ufbxi_atomic_counter_init(ptr) (*(ptr) = 0) - #define ufbxi_atomic_counter_free(ptr) (*(ptr) = 0) - #define ufbxi_atomic_counter_inc(ptr) ((size_t)_InterlockedIncrement(ptr) - 1) - #define ufbxi_atomic_counter_dec(ptr) ((size_t)_InterlockedDecrement(ptr) + 1) - #define ufbxi_atomic_counter_load(ptr) ((size_t)_InterlockedExchangeAdd((ptr), 0)) - #endif -#elif !defined(UFBX_STANDARD_C) && defined(__TINYC__) - #if defined(__x86_64__) || defined(_AMD64_) - static size_t ufbxi_tcc_atomic_add(volatile size_t *dst, size_t value) { - __asm__ __volatile__("lock; xaddq %0, %1;" : "+r" (value), "=m" (*dst) : "m" (dst)); - return value; - } - #elif defined(__i386__) || defined(_X86_) - static size_t ufbxi_tcc_atomic_add(volatile size_t *dst, size_t value) { - __asm__ __volatile__("lock; xaddl %0, %1;" : "+r" (value), "=m" (*dst) : "m" (dst)); - return value; - } - #else - #error Unexpected TCC architecture - #endif - typedef volatile size_t ufbxi_atomic_counter; - #define ufbxi_atomic_counter_init(ptr) (*(ptr) = 0) - #define ufbxi_atomic_counter_free(ptr) (*(ptr) = 0) - #define ufbxi_atomic_counter_inc(ptr) ufbxi_tcc_atomic_add((ptr), 1) - #define ufbxi_atomic_counter_dec(ptr) ufbxi_tcc_atomic_add((ptr), SIZE_MAX) - #define ufbxi_atomic_counter_load(ptr) ufbxi_tcc_atomic_add((ptr), 0) -#elif defined(__cplusplus) && (__cplusplus >= 201103L) - #include - #include - typedef struct { alignas(std::atomic_size_t) char data[sizeof(std::atomic_size_t)]; } ufbxi_atomic_counter; - #define ufbxi_atomic_counter_init(ptr) (new (&(ptr)->data) std::atomic_size_t(0)) - #define ufbxi_atomic_counter_free(ptr) (((std::atomic_size_t*)(ptr)->data)->~atomic()) - #define ufbxi_atomic_counter_inc(ptr) ((std::atomic_size_t*)(ptr)->data)->fetch_add(1) - #define ufbxi_atomic_counter_dec(ptr) ((std::atomic_size_t*)(ptr)->data)->fetch_sub(1) - #define ufbxi_atomic_counter_load(ptr) ((std::atomic_size_t*)(ptr)->data)->load(std::memory_order_acquire) -#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && !defined(__STDC_NO_ATOMICS__) - #include - typedef volatile atomic_size_t ufbxi_atomic_counter; - #define ufbxi_atomic_counter_init(ptr) atomic_init(ptr, 0) - #define ufbxi_atomic_counter_free(ptr) (void)(ptr) - #define ufbxi_atomic_counter_inc(ptr) atomic_fetch_add((ptr), 1) - #define ufbxi_atomic_counter_dec(ptr) atomic_fetch_sub((ptr), 1) - #define ufbxi_atomic_counter_load(ptr) atomic_load_explicit((ptr), memory_order_acquire) -#else - typedef volatile size_t ufbxi_atomic_counter; - #define ufbxi_atomic_counter_init(ptr) (*(ptr) = 0) - #define ufbxi_atomic_counter_free(ptr) (*(ptr) = 0) - #define ufbxi_atomic_counter_inc(ptr) ((*(ptr))++) - #define ufbxi_atomic_counter_dec(ptr) ((*(ptr))--) - #define ufbxi_atomic_counter_load(ptr) (*(ptr)) - #undef UFBXI_THREAD_SAFE - #define UFBXI_THREAD_SAFE 0 -#endif - // -- Bit manipulation #if !defined(UFBX_STANDARD_C) && defined(_MSC_VER) && (defined(_M_X64) || defined(_M_IX86)) ufbxi_extern_c unsigned char _BitScanReverse(unsigned long * _Index, unsigned long _Mask); ufbxi_extern_c unsigned char _BitScanReverse64(unsigned long * _Index, unsigned __int64 _Mask); + static ufbxi_forceinline ufbxi_unused uint32_t ufbxi_lzcnt32(uint32_t v) { + unsigned long index; + _BitScanReverse(&index, (unsigned long)v); + return 31 - (uint32_t)index; + } static ufbxi_forceinline ufbxi_unused uint32_t ufbxi_lzcnt64(uint64_t v) { unsigned long index; #if defined(_M_X64) @@ -713,14 +891,26 @@ ufbx_static_assert(source_header_version, UFBX_SOURCE_VERSION/1000u == UFBX_HEAD return 63 - (uint32_t)index; } #elif !defined(UFBX_STANDARD_C) && (defined(__GNUC__) || defined(__clang__)) + #define ufbxi_lzcnt32(v) ((uint32_t)__builtin_clz((unsigned)(v))) #define ufbxi_lzcnt64(v) ((uint32_t)__builtin_clzll((unsigned long long)(v))) #else // DeBrujin table lookup - static const uint8_t ufbxi_lzcnt_table[] = { + static const uint8_t ufbxi_lzcnt32_table[] = { + 31, 22, 30, 21, 18, 10, 29, 2, 20, 17, 15, 13, 9, 6, 28, 1, 23, 19, 11, 3, 16, 14, 7, 24, 12, 4, 8, 25, 5, 26, 27, 0, + }; + static const uint8_t ufbxi_lzcnt64_table[] = { 63, 16, 62, 7, 15, 36, 61, 3, 6, 14, 22, 26, 35, 47, 60, 2, 9, 5, 28, 11, 13, 21, 42, 19, 25, 31, 34, 40, 46, 52, 59, 1, 17, 8, 37, 4, 23, 27, 48, 10, 29, 12, 43, 20, 32, 41, 53, 18, 38, 24, 49, 30, 44, 33, 54, 39, 50, 45, 55, 51, 56, 57, 58, 0, }; + static ufbxi_noinline ufbxi_unused uint32_t ufbxi_lzcnt32(uint32_t v) { + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + return ufbxi_lzcnt32_table[(v * 0x07c4acddu) >> 27]; + } static ufbxi_noinline ufbxi_unused uint32_t ufbxi_lzcnt64(uint64_t v) { v |= v >> 1; v |= v >> 2; @@ -728,10 +918,20 @@ ufbx_static_assert(source_header_version, UFBX_SOURCE_VERSION/1000u == UFBX_HEAD v |= v >> 8; v |= v >> 16; v |= v >> 32; - return ufbxi_lzcnt_table[(v * UINT64_C(0x03f79d71b4cb0a89)) >> 58]; + return ufbxi_lzcnt64_table[(v * UINT64_C(0x03f79d71b4cb0a89)) >> 58]; } #endif +// -- Bit conversion + +#if defined(__cplusplus) + #define ufbxi_bit_cast(m_dst_type, m_dst, m_src_type, m_src) memcpy(&(m_dst), &(m_src), sizeof(m_dst_type)) +#else + #define ufbxi_bit_cast(m_dst_type, m_dst, m_src_type, m_src) do { \ + union { m_dst_type mi_dst; m_src_type mi_src; } mi_union; \ + mi_union.mi_src = (m_src); (m_dst) = mi_union.mi_dst; } while (0) +#endif + // -- Debug #if defined(UFBX_DEBUG_BINARY_SEARCH) || defined(UFBX_REGRESSION) @@ -1081,321 +1281,389 @@ static ufbxi_noinline void ufbxi_unstable_sort(void *in_data, size_t size, size_ } // -- Float parsing -// -// Custom float parsing that handles floats up to (-)ddddddddddddddddddd.ddddddddddddddddddd -// If larger or scientific notation is used then it defers to `strtod()`. -// For the algorithm we need 128-bit division that is either provided by hardware on x64 or -// a custom implementation below. - -#if !defined(UFBX_STANDARD_C) && UFBXI_MSC_VER >= 1920 && defined(_M_X64) && !defined(__clang__) - ufbxi_extern_c extern unsigned __int64 __cdecl _udiv128(unsigned __int64 highdividend, - unsigned __int64 lowdividend, unsigned __int64 divisor, unsigned __int64 *remainder); - #define ufbxi_div128(a_hi, a_lo, b, p_rem) (_udiv128((a_hi), (a_lo), (b), (p_rem))) -#elif !defined(UFBX_STANDARD_C) && (defined(__GNUC__) || defined(__clang__)) && (defined(__x86_64__) || defined(_M_X64)) - static ufbxi_forceinline uint64_t ufbxi_div128(uint64_t a_hi, uint64_t a_lo, uint64_t b, uint64_t *p_rem) { - uint64_t quot, rem; - __asm__("divq %[v]" : "=a"(quot), "=d"(rem) : [v] "r"(b), "a"(a_lo), "d"(a_hi)); - *p_rem = rem; - return quot; - } -#else - static ufbxi_forceinline uint64_t ufbxi_div128(uint64_t a_hi, uint64_t a_lo, uint64_t b, uint64_t *p_rem) { - // Divide `(a_hi << 64 | a_lo)` by `b`, returns quotinent and stores reminder in `p_rem`. - // Based on TAOCP 2.4 multi-word division single algorithm digit step. - // - // Notation: - // b is the base (2^32) in this case - // aN is the Nth digit (base b) of a from the least significant digit - // { x y z } is a multi-digit number b^2*x + b*y + z - // ie. for a 64-bit number a = { a1 a0 } = b*a1 + a0 - // - // We do the division in two steps by dividing three digits in each iteration: - // - // q1, r = { a3 a2 a1 } / { b1 b0 } - // q0, r = { r1 r0 a0 } / { b1 b0 } - // - // In each step we want to compute the expression: - // - // q, r = { u2 u1 u0 } / { v1 v0 } - // - // However we cannot rely on being able to do `u96 / u64` division we estimate - // the result by considering only the leading digits: - // - // q^ = { u2 u1 } / v1 [A] - // r^ = { u2 u1 } % v1 = { u2 u1 } - v1 * q^ [B] - // - // As long as `v1 >= b/2` the estimate `q^` is at most two larger than the actual `q` - // (proof in TAOCP 2.4) so we can compute the correction amount `c`: - // - // q <= q^ <= q + 2 - // q = q^ - c [C] - // - // We can compute the final remainder (that must be non-negative) as follows: - // - // r = { u2 u1 u0 } - v*q - // r = { u2 u1 u0 } - v*(q^ - c) - // r = { u2 u1 u0 } - v*q^ + v*c - // r = { u2 u1 u0 } - { v1 v0 } * q^ + v*c - // r = b^2*u2 + b*u1 + u0 - b*v1*q^ - v0*q^ + v*c - // r = b*(b*u2 + u1 - v1*q^) + u0 - v0*q^ + v*c - // r = b*({ u2 u1 } - v1*q^) + u0 - v0*q^ + v*c - // r = b*r^ + u0 - v0*q^ + v*c - // r = { r^ u0 } - v0*q^ + v*c [D] - // - // As we know `0 <= c <= 2` we can first check if `r < 0` requiring `c >= 1`: - // - // { r^ u0 } - v0*q^ < 0 - // { r^ u0 } < v0*q^ [E] - // - // If we know that `r < 0` we can check if `r < -v` requiring `c = 2`: - // - // { r^ u0 } - v0*q^ < -v - // v0*q^ - { r^ u0 } > v [F] - // - - // First we need to make sure `v1 >= b/2`, we can do this by multiplying the whole - // expression by `2^shift` so that the high bit of `v` is set. - uint32_t shift = ufbxi_lzcnt64(b); - a_hi = (a_hi << shift) | (shift ? a_lo >> (64 - shift) : 0); - a_lo <<= shift; - b <<= shift; - - uint64_t v = b; - uint32_t v1 = (uint32_t)(v >> 32); - uint32_t v0 = (uint32_t)(v); - uint64_t q1, q0, r; - - // q1, r = { a3 a2 a1 } / { b1 b0 } - { - uint64_t u2_u1 = a_hi; - uint32_t u0 = (uint32_t)(a_lo >> 32u); - uint64_t qh = u2_u1 / v1; // q^ = { u2 u1 } / v1 [A] - uint64_t rh = u2_u1 % v1; // r^ = { u2 u1 } % v1 [B] - uint64_t rh_u0 = rh << 32u | u0; // { r^ u0 } - uint64_t v0qh = v0 * qh; // v0*q^ - uint32_t c = rh_u0 < v0qh ? 1 : 0; // { r^ u0 } < v0*q^ [E] - c += c & (v0qh - rh_u0 > v ? 1 : 0); // v0*q^ - { r^ u0 } > v [F] - q1 = qh - c; // q1 = q^ - c [C] - r = rh_u0 - v0qh + v*c; // r = { r^ u0 } - v0*q^ + v*c [D] - } +#define UFBXI_BIGINT_LIMB_BITS 32 +#define UFBXI_BIGINT_ACCUM_BITS (UFBXI_BIGINT_LIMB_BITS * 2) +#define UFBXI_BIGINT_LIMB_MAX (ufbxi_bigint_limb)(((ufbxi_bigint_accum)1 << UFBXI_BIGINT_LIMB_BITS) - 1) +typedef uint32_t ufbxi_bigint_limb; +typedef uint64_t ufbxi_bigint_accum; - // q0, r = { r1 r0 a0 } / { b1 b0 } - { - uint64_t u2_u1 = r; - uint32_t u0 = (uint32_t)a_lo; - - uint64_t qh = u2_u1 / v1; // q^ = { u2 u1 } / v1 [A] - uint64_t rh = u2_u1 % v1; // r^ = { u2 u1 } % v1 [B] - uint64_t rh_u0 = rh << 32u | u0; // { r^ u0 } - uint64_t v0qh = v0 * qh; // v0*q^ - uint32_t c = rh_u0 < v0qh ? 1 : 0; // { r^ u0 } < v0*q^ [E] - c += c & (v0qh - rh_u0 > v ? 1 : 0); // v0*q^ - { r^ u0 } > v [F] - q0 = qh - c; // q0 = q^ - c [C] - r = rh_u0 - v0qh + v*c; // r = { r^ u0 } - v0*q^ + v*c [D] - } +typedef struct { + ufbxi_bigint_limb *limbs; + uint32_t capacity; + uint32_t length; +} ufbxi_bigint; - // Un-normalize the remainder and return the quotinent - *p_rem = r >> shift; - return q1 << 32u | q0; - } -#endif +static ufbxi_bigint ufbxi_bigint_make(ufbxi_bigint_limb *limbs, size_t capacity) +{ + ufbxi_bigint bi = { limbs, (uint32_t)capacity }; + return bi; +} -typedef enum { - UFBXI_PARSE_DOUBLE_ALLOW_FAST_PATH = 0x1, - UFBXI_PARSE_DOUBLE_VERIFY_LENGTH = 0x2, -} ufbxi_parse_double_flag; +#define ufbxi_bigint_array(arr) ufbxi_bigint_make((arr), sizeof(arr) / sizeof(*(arr))) static const uint64_t ufbxi_pow5_tab[] = { - UINT64_C(0x8000000000000000), // 5^0 * 2^63 - UINT64_C(0xa000000000000000), // 5^1 * 2^61 - UINT64_C(0xc800000000000000), // 5^2 * 2^59 - UINT64_C(0xfa00000000000000), // 5^3 * 2^57 - UINT64_C(0x9c40000000000000), // 5^4 * 2^54 - UINT64_C(0xc350000000000000), // 5^5 * 2^52 - UINT64_C(0xf424000000000000), // 5^6 * 2^50 - UINT64_C(0x9896800000000000), // 5^7 * 2^47 - UINT64_C(0xbebc200000000000), // 5^8 * 2^45 - UINT64_C(0xee6b280000000000), // 5^9 * 2^43 - UINT64_C(0x9502f90000000000), // 5^10 * 2^40 - UINT64_C(0xba43b74000000000), // 5^11 * 2^38 - UINT64_C(0xe8d4a51000000000), // 5^12 * 2^36 - UINT64_C(0x9184e72a00000000), // 5^13 * 2^33 - UINT64_C(0xb5e620f480000000), // 5^14 * 2^31 - UINT64_C(0xe35fa931a0000000), // 5^15 * 2^29 - UINT64_C(0x8e1bc9bf04000000), // 5^16 * 2^26 - UINT64_C(0xb1a2bc2ec5000000), // 5^17 * 2^24 - UINT64_C(0xde0b6b3a76400000), // 5^18 * 2^22 - UINT64_C(0x8ac7230489e80000), // 5^19 * 2^19 - UINT64_C(0xad78ebc5ac620000), // 5^20 * 2^17 - UINT64_C(0xd8d726b7177a8000), // 5^21 * 2^15 - UINT64_C(0x878678326eac9000), // 5^22 * 2^12 - UINT64_C(0xa968163f0a57b400), // 5^23 * 2^10 - UINT64_C(0xd3c21bcecceda100), // 5^24 * 2^8 - UINT64_C(0x84595161401484a0), // 5^25 * 2^5 - UINT64_C(0xa56fa5b99019a5c8), // 5^26 * 2^3 - UINT64_C(0xcecb8f27f4200f3a), // 5^27 * 2^1 -}; -static const int8_t ufbxi_pow2_tab[] = { - 62, 59, 56, 53, 49, 46, 43, 39, 36, 33, 29, 26, 23, 19, 16, 13, 9, 6, 3, -1, -4, -7, -11, -14, -17, -21, -24, -27, + UINT64_C(0x1), UINT64_C(0x5), UINT64_C(0x19), UINT64_C(0x7d), UINT64_C(0x271), UINT64_C(0xc35), UINT64_C(0x3d09), UINT64_C(0x1312d), UINT64_C(0x5f5e1), + UINT64_C(0x1dcd65), UINT64_C(0x9502f9), UINT64_C(0x2e90edd), UINT64_C(0xe8d4a51), UINT64_C(0x48c27395), UINT64_C(0x16bcc41e9), UINT64_C(0x71afd498d), + UINT64_C(0x2386f26fc1), UINT64_C(0xb1a2bc2ec5), UINT64_C(0x3782dace9d9), UINT64_C(0x1158e460913d), UINT64_C(0x56bc75e2d631), UINT64_C(0x1b1ae4d6e2ef5), + UINT64_C(0x878678326eac9), UINT64_C(0x2a5a058fc295ed), UINT64_C(0xd3c21bcecceda1), UINT64_C(0x422ca8b0a00a425), UINT64_C(0x14adf4b7320334b9), UINT64_C(0x6765c793fa10079d), }; + static const double ufbxi_pow10_tab_f64[] = { 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22, }; -static ufbxi_noinline uint32_t ufbxi_parse_double_init_flags() +static ufbxi_noinline void ufbxi_bigint_mad(ufbxi_bigint *bigint, ufbxi_bigint_accum multiplicand, ufbxi_bigint_accum addend) +{ + ufbxi_dev_assert((multiplicand | addend) >> (UFBXI_BIGINT_ACCUM_BITS - 1) == 0); + ufbxi_bigint b = *bigint; + ufbxi_bigint_limb m_lo = (ufbxi_bigint_limb)multiplicand; + ufbxi_bigint_limb m_hi = (ufbxi_bigint_limb)(multiplicand >> UFBXI_BIGINT_LIMB_BITS); + ufbxi_bigint_accum carry = addend; + for (uint32_t i = 0; i < b.length; i++) { + ufbxi_bigint_accum limb = (ufbxi_bigint_accum)b.limbs[i]; + ufbxi_bigint_accum lo = limb * m_lo + (carry & UFBXI_BIGINT_LIMB_MAX); + ufbxi_bigint_accum hi = limb * m_hi; + b.limbs[i] = (ufbxi_bigint_limb)lo; + carry = (carry >> 32u) + (lo >> 32u) + hi; + } + while (carry) { + b.limbs[b.length++] = (ufbxi_bigint_limb)carry; + ufbxi_dev_assert(b.length < b.capacity); + carry >>= 32u; + } + bigint->length = b.length; +} + +static ufbxi_noinline bool ufbxi_bigint_div(ufbxi_bigint *q, ufbxi_bigint *u, ufbxi_bigint *v) +{ + int32_t n = (int32_t)v->length; + int32_t m = (int32_t)u->length - n; + ufbxi_bigint_limb v_hi = v->limbs[v->length - 1]; + ufbxi_bigint_limb *un = u->limbs, *vn = v->limbs; + ufbxi_dev_assert(n >= 2 && m >= 1 && v_hi >> (UFBXI_BIGINT_LIMB_BITS - 1) != 0 && un[n+m - 1] >> (UFBXI_BIGINT_LIMB_BITS - 1) == 0); + un[n + m] = 0; + q->length = 0; + for (int32_t j = m - 1; j >= 0; j--) { + ufbxi_bigint_accum u_hi = ((ufbxi_bigint_accum)un[n+j] << UFBXI_BIGINT_LIMB_BITS) | un[n+j-1]; + ufbxi_bigint_accum t, qhat = u_hi / v_hi, rhat = u_hi % v_hi; + while (qhat >> UFBXI_BIGINT_LIMB_BITS != 0 || qhat*vn[n-2] > ((rhat<> UFBXI_BIGINT_LIMB_BITS != 0) break; + } + ufbxi_bigint_limb carry = 0; + for (int32_t i = 0; i < n; i++) { + ufbxi_bigint_accum p = qhat * vn[i]; + t = (ufbxi_bigint_accum)un[i+j] - carry - (ufbxi_bigint_limb)p; + un[i+j] = (ufbxi_bigint_limb)t; + carry = (ufbxi_bigint_limb)((p >> UFBXI_BIGINT_LIMB_BITS) - (t >> UFBXI_BIGINT_LIMB_BITS)); + } + t = (ufbxi_bigint_accum)un[j+n] - carry; + un[j+n] = (ufbxi_bigint_limb)t; + if (t >> UFBXI_BIGINT_LIMB_BITS != 0) { + qhat -= 1; + carry = 0; + for (int32_t i = 0; i < n; i++) { + t = (ufbxi_bigint_accum)un[i+j] + vn[i] + carry; + un[i+j] = (ufbxi_bigint_limb)t; + carry = (ufbxi_bigint_limb)(t >> UFBXI_BIGINT_LIMB_BITS); + } + un[j+n] += carry; + } + q->limbs[j] = (ufbxi_bigint_limb)qhat; + if (qhat && !q->length) { + ufbxi_dev_assert(j + 1 < (int32_t)q->capacity); + q->length = (uint32_t)(j + 1); + } + } + for (int32_t i = 0; i < n; i++) { + if (un[i]) return true; + } + return false; +} + +static void ufbxi_bigint_mul_pow5(ufbxi_bigint *b, uint32_t power) { - // We require evaluation in double precision, either for doubles (0) or always (1) - // and rounding to nearest, which we can check for with `1 + eps == 1 - eps`. - #if defined(FLT_EVAL_METHOD) - #if FLT_EVAL_METHOD == 0 || FLT_EVAL_METHOD == 1 - static volatile double ufbxi_volatile_eps = 2.2250738585072014e-308; - if (1.0 + ufbxi_volatile_eps == 1.0 - ufbxi_volatile_eps) return UFBXI_PARSE_DOUBLE_ALLOW_FAST_PATH; - #endif - #endif + for (; power > 27; power -= 27) { + ufbxi_bigint_mad(b, ufbxi_pow5_tab[27], 0); + } + ufbxi_bigint_mad(b, ufbxi_pow5_tab[power], 0); +} - return 0; +static ufbxi_noinline void ufbxi_bigint_shift_left(ufbxi_bigint *bigint, uint32_t amount) +{ + uint32_t words = amount / UFBXI_BIGINT_LIMB_BITS, bits = amount % UFBXI_BIGINT_LIMB_BITS; + ufbxi_bigint b = *bigint; + ufbxi_dev_assert(b.length + words + 1 < b.capacity && b.capacity >= 4); + uint32_t bits_down = UFBXI_BIGINT_LIMB_BITS - bits - 1; + bigint->length += words + (b.limbs[b.length - 1] >> 1 >> bits_down != 0 ? 1 : 0); + b.limbs[b.length] = 0; + if (b.length <= 3 && words <= 3) { + ufbxi_bigint_limb l0 = ufbxi_maybe_uninit(b.length >= 0, b.limbs[0], ~0u); + ufbxi_bigint_limb l1 = ufbxi_maybe_uninit(b.length >= 1, b.limbs[1], ~0u); + ufbxi_bigint_limb l2 = ufbxi_maybe_uninit(b.length >= 2, b.limbs[2], ~0u); + b.limbs[0] = 0; + b.limbs[1] = 0; + b.limbs[2] = 0; + b.limbs[words + 0] = l0 << bits; + b.limbs[words + 1] = (l1 << bits) | (l0 >> 1 >> bits_down); + b.limbs[words + 2] = (l2 << bits) | (l1 >> 1 >> bits_down); + b.limbs[words + 3] = (l2 >> 1 >> bits_down); + } else { + for (uint32_t i = b.length + 1; i-- > 1; ) { + b.limbs[i + words] = (b.limbs[i] << bits) | (b.limbs[i - 1] >> 1 >> bits_down); + } + b.limbs[words] = b.limbs[0] << bits; + for (uint32_t i = 0; i < words; i++) { + b.limbs[i] = 0; + } + } +} + +static ufbxi_bigint_limb ufbxi_bigint_top_limb(const ufbxi_bigint b, uint32_t index) { + return index < b.length ? b.limbs[b.length - 1 - index] : 0; +} + +static ufbxi_noinline uint64_t ufbxi_bigint_extract_high(const ufbxi_bigint b, int32_t *p_exponent, bool *p_tail) +{ + ufbxi_dev_assert(b.length != 0); + uint64_t result = 0; + const uint32_t limb_count = 64 / UFBXI_BIGINT_LIMB_BITS; + for (uint32_t i = 0; i < limb_count; i++) { + result = (result << UFBXI_BIGINT_LIMB_BITS) | ufbxi_bigint_top_limb(b, i); + } + uint32_t shift = ufbxi_lzcnt64(result); + result <<= shift; + ufbxi_bigint_limb lo = ufbxi_bigint_top_limb(b, limb_count); + if (shift > 0) { + result |= lo >> (UFBXI_BIGINT_LIMB_BITS - shift); + } + *p_tail |= (ufbxi_bigint_limb)(lo << shift) != 0; + for (uint32_t i = limb_count + 1; i < b.length; i++) { + *p_tail |= ufbxi_bigint_top_limb(b, i) != 0; + } + *p_exponent += (int32_t)(b.length * UFBXI_BIGINT_LIMB_BITS - shift - 1); + return result; } -static ufbxi_noinline double ufbxi_parse_double_slow(const char *str, char **end) +static uint64_t ufbxi_shift_right_round(uint64_t value, uint32_t shift, bool tail) { - // TODO: Locales - return strtod(str, end); + if (shift == 0) return value; + if (shift > 64) return 0; + uint64_t result = value >> (shift - 1); + uint64_t tail_mask = (UINT64_C(1) << (shift - 1)) - 1; + + bool r_odd = (result & 0x2) != 0; + bool r_round = (result & 0x1) != 0; + bool r_tail = tail || (value & tail_mask) != 0; + uint64_t round_bit = (r_round && (r_odd || r_tail)) ? 1u : 0u; + + return (result >> 1u) + round_bit; } +typedef enum { + UFBXI_PARSE_DOUBLE_ALLOW_FAST_PATH = 0x1, + UFBXI_PARSE_DOUBLE_VERIFY_LENGTH = 0x2, + UFBXI_PARSE_DOUBLE_AS_BINARY32 = 0x4, +} ufbxi_parse_double_flag; + static ufbxi_noinline double ufbxi_parse_double(const char *str, size_t max_length, char **end, uint32_t flags) { - // TODO: Use this for optimizing digit parsing - (void)max_length; + const uint32_t max_limbs = 14; - uint64_t integer = 0; - uint32_t n_integer = 0; - int32_t n_decimals = 0; - uint32_t n_exp = 0; - bool negative = false; + ufbxi_bigint_limb mantissa_limbs[42], divisor_limbs[42], quotient_limbs[42]; + ufbxi_bigint big_mantissa = ufbxi_bigint_array(mantissa_limbs); + ufbxi_bigint big_quotient = ufbxi_bigint_array(quotient_limbs); + int32_t dec_exponent = 0, has_dot = 0; + bool negative = false, tail = false, digits_valid = true; + uint64_t digits = 0; + uint32_t num_digits = 0; - // Parse /[+-]?[0-9]*(\.[0-9]*)([eE][+-]?[0-9]*)?/ retaining all digits - // in `integer` and number of decimals in `n_decimals`, exponent simply - // modifies `n_decimals` accordingly. const char *p = str; - if (*p == '-') { - negative = true; - p++; - } else if (*p == '+') { - p++; - } - while (((uint32_t)*p - '0') < 10) { - integer = integer * 10 + (uint64_t)(*p++ - '0'); - n_integer++; + if (*p == '+' || *p == '-') { + negative = *p++ == '-'; } - if (*p == '.') { - p++; - while (((uint32_t)*p - '0') < 10) { - integer = integer * 10 + (uint64_t)(*p++ - '0'); - n_integer++; - n_decimals++; + for (;;) { + char c = *p++; + if (c >= '0' && c <= '9') { + if (big_mantissa.length < max_limbs) { + digits = digits * 10 + (uint64_t)(c - '0'); + num_digits++; + if (num_digits >= 18) { + ufbxi_bigint_mad(&big_mantissa, ufbxi_pow5_tab[num_digits] << num_digits, digits); + digits = 0; + num_digits = 0; + digits_valid = false; + } + dec_exponent -= has_dot; + } else { + dec_exponent += 1 - has_dot; + } + } else if (c == '.' && !has_dot) { + has_dot = true; + } else { + break; } } - if ((*p | 0x20) == 'e') { + p--; + if (*p == 'e' || *p == 'E') { p++; - int32_t exp = 0; - int32_t exp_sign = -1; - if (*p == '-') { - p++; - exp_sign = 1; - } else if (*p == '+') { + bool exp_negative = false; + if (*p == '+' || *p == '-') { + exp_negative = *p == '-'; p++; } - while (((uint32_t)*p - '0') < 10) { - exp = exp * 10 + (int32_t)(*p++ - '0'); - n_exp++; + int32_t exp = 0; + for (;;) { + char c = *p; + if (c >= '0' && c <= '9') { + p++; + exp = exp * 10 + (c - '0'); + if (exp >= 10000) break; + } else { + break; + } } - n_decimals += exp * exp_sign; + dec_exponent += exp_negative ? -exp : exp; } - *end = (char*)p; + *end = (char*)p; // Check that the number is not potentially truncated. if (ufbxi_to_size(p - str) >= max_length && (flags & UFBXI_PARSE_DOUBLE_VERIFY_LENGTH) != 0) { *end = NULL; return 0.0; } - // Overflowed either 64-bit `integer` or 31-bit `exp`. - if (n_integer > 19 || n_exp > 9 || (integer >> 63) != 0) { - return ufbxi_parse_double_slow(str, end); - } - // Both power of 10 and integer are exactly representable as doubles // Powers of 10 are factored as 2*5, and 2^N can be always exactly represented. - if ((flags & UFBXI_PARSE_DOUBLE_ALLOW_FAST_PATH) != 0 && n_decimals >= -22 && n_decimals <= 22 && (integer >> 53) == 0) { + if ((flags & UFBXI_PARSE_DOUBLE_ALLOW_FAST_PATH) != 0 && big_mantissa.length == 0 && dec_exponent >= -22 && dec_exponent <= 22 && (digits >> 53) == 0) { double value; - if (n_decimals > 0) { - value = (double)integer / ufbxi_pow10_tab_f64[n_decimals]; + if (dec_exponent < 0) { + value = (double)digits / ufbxi_pow10_tab_f64[-dec_exponent]; } else { - value = (double)integer * ufbxi_pow10_tab_f64[-n_decimals]; + value = (double)digits * ufbxi_pow10_tab_f64[dec_exponent]; } return negative ? -value : value; } - // Cannot handle positive exponents here, fortunately the fast case should - // take care of most of them, for negative exponents we can only handle - // up to e-27 as `5^28 > 2^64` and cannot be used as a divisor below. - if (n_decimals < 0) { - return ufbxi_parse_double_slow(str, end); - } else if (!n_decimals || !integer) { - double value = (double)integer; - return negative ? -value : value; - } else if (n_decimals > 27) { - return ufbxi_parse_double_slow(str, end); - } - - // We want to compute `integer / 10^N` precisely, we can do this - // using 128-bit division `2^64 * dividend / divisor`: - // dividend = integer * 2^S (S set such that highest bit is 62) - // divisor = 10^N * 2^T (T set such that highest bit is 63) - // We have to compensate for the shifts in the exponent: - // (2^64 * integer * 2^S) / (10^N * 2^T) * 2^(-1 - S + T) - // To get larger exponent range split 10^N to 5^N * 2^N and move 2^N to the exponent - // (2^64 * integer * 2^S) / (5^N * 2^T) * 2^(-1 - S + T - N) - uint32_t shift = ufbxi_lzcnt64(integer) - 1; - uint64_t dividend = integer << shift; - uint64_t divisor = ufbxi_pow5_tab[n_decimals]; - int32_t exponent = (int32_t)ufbxi_pow2_tab[n_decimals] - (int32_t)shift; // (-1 + T - N) - S - uint64_t rem_hi; - uint64_t b_hi = ufbxi_div128(dividend, 0, divisor, &rem_hi); - - // Align the mantissa so that high bit is set, due to the shifting of the - // divisor and dividend the smallest result is `2^62 + N`, so we need to - // shift at most by one bit. - uint64_t b_bit = 1 - (b_hi >> 63u); - uint64_t mantissa = b_hi << b_bit; - exponent -= (int32_t)b_bit; - - // Round to 53 bits, accounting for potential remainder. - bool nonzero_tail = rem_hi != 0; - bool r_odd = mantissa & (1 << 11u); - bool r_round = mantissa & (1 << 10u); - bool r_tail = (mantissa & ((1 << 10u) - 1)) != 0 || nonzero_tail; - uint64_t round = (r_round && (r_odd || r_tail)) ? 1u : 0u; - - // Assemble the IEEE 754 binary64 number. - uint64_t bits - = (uint64_t)negative << 63u - | (uint64_t)(exponent + 1023) << 52u - | ((mantissa >> 11u) & ~(UINT64_C(1) << 52u)); - bits += round; - - // Type punning via unions is safe in C but in C++ the only safe way - // (pre std::bit_cast) is to use `memcpy()` and hope it gets optimized out. -#if defined(__cplusplus) - double result; - memcpy(&result, &bits, 8); - return result; -#else - union { uint64_t u; double d; } u_to_d; - u_to_d.u = bits; - return u_to_d.d; -#endif + if (big_mantissa.length == 0) { + big_mantissa.limbs[0] = (ufbxi_bigint_limb)digits; + big_mantissa.limbs[1] = (ufbxi_bigint_limb)(digits >> 32u); + big_mantissa.length = (digits >> 32u) ? 2 : digits ? 1 : 0; + if (big_mantissa.length == 0) return negative ? -0.0 : 0.0; + } else { + ufbxi_bigint_mad(&big_mantissa, ufbxi_pow5_tab[num_digits] << num_digits, digits); + } + + uint32_t enc_sign_shift = 63; + uint32_t enc_mantissa_bits = 53; + int32_t enc_max_exponent = 1023; + if (flags & UFBXI_PARSE_DOUBLE_AS_BINARY32) { + enc_sign_shift = 31; + enc_mantissa_bits = 24; + enc_max_exponent = 127; + } + + int32_t exponent = 0; + if (dec_exponent < 0) { + if (dec_exponent + (int32_t)big_mantissa.length * 10 <= -325) return negative ? -0.0 : 0.0; + + ufbxi_bigint big_divisor = ufbxi_bigint_array(divisor_limbs); + uint32_t pow5 = (uint32_t)-dec_exponent; + uint32_t initial_pow5 = pow5 <= 27 ? pow5 : 27; + uint64_t pow5_value = ufbxi_pow5_tab[initial_pow5]; + pow5 -= initial_pow5; + exponent += dec_exponent; + + if (pow5 == 0 && digits_valid && digits >> 63 == 0) { + uint32_t divisor_zeros = ufbxi_lzcnt64(pow5_value); + uint64_t mantissa_zeros = ufbxi_lzcnt64(digits) - 1; + uint64_t divisor_bits = pow5_value << divisor_zeros; + uint64_t mantissa_bits = digits << mantissa_zeros; + big_divisor.limbs[0] = (ufbxi_bigint_limb)divisor_bits; + big_divisor.limbs[1] = (ufbxi_bigint_limb)(divisor_bits >> 32u); + big_divisor.length = 2; + big_mantissa.limbs[0] = 0; + big_mantissa.limbs[1] = 0; + big_mantissa.limbs[2] = (ufbxi_bigint_limb)mantissa_bits; + big_mantissa.limbs[3] = (ufbxi_bigint_limb)(mantissa_bits >> 32u); + big_mantissa.length = 4; + exponent += (int32_t)divisor_zeros - (int32_t)mantissa_zeros - 64; + } else { + big_divisor.limbs[0] = (ufbxi_bigint_limb)pow5_value; + big_divisor.limbs[1] = (ufbxi_bigint_limb)(pow5_value >> 32u); + big_divisor.length = (pow5_value >> 32u) != 0 ? 2 : 1; + if (pow5 > 0) { + ufbxi_bigint_mul_pow5(&big_divisor, pow5); + } + + uint32_t divisor_zeros = ufbxi_lzcnt32(big_divisor.limbs[big_divisor.length - 1]); + if (big_divisor.length == 1) divisor_zeros += UFBXI_BIGINT_LIMB_BITS; + ufbxi_bigint_shift_left(&big_divisor, divisor_zeros); + uint32_t divisor_bits = big_divisor.length * UFBXI_BIGINT_LIMB_BITS; + + uint32_t mantissa_zeros = ufbxi_lzcnt32(big_mantissa.limbs[big_mantissa.length - 1]); + uint32_t mantissa_bits = big_mantissa.length * UFBXI_BIGINT_LIMB_BITS - mantissa_zeros; + uint32_t mantissa_min_bits = divisor_bits + enc_mantissa_bits + 2; + uint32_t mantissa_shift = mantissa_bits < mantissa_min_bits ? mantissa_min_bits - mantissa_bits : 0; + // Align mantissa to never have a high bit, this means we can skip the first digit during division. + mantissa_shift += ((mantissa_shift - mantissa_zeros) & (UFBXI_BIGINT_LIMB_BITS - 1)) == 0 ? 1 : 0; + if (mantissa_shift > 0) { + ufbxi_bigint_shift_left(&big_mantissa, mantissa_shift); + } + exponent += (int32_t)divisor_zeros - (int32_t)mantissa_shift; + } + + tail = ufbxi_bigint_div(&big_quotient, &big_mantissa, &big_divisor); + big_mantissa = big_quotient; + } else if (dec_exponent > 0) { + if (dec_exponent + (int32_t)(big_mantissa.length - 1) * 9 >= 310) return negative ? -UFBX_INFINITY : UFBX_INFINITY; + + exponent += dec_exponent; + ufbxi_bigint_mul_pow5(&big_mantissa, (uint32_t)dec_exponent); + } + + uint64_t mantissa = ufbxi_bigint_extract_high(big_mantissa, &exponent, &tail); + uint64_t sign_bit = (uint64_t)(negative ? 1u : 0u) << enc_sign_shift; + + uint32_t mantissa_shift = 64 - enc_mantissa_bits; + if (exponent > enc_max_exponent) { + return negative ? -UFBX_INFINITY : UFBX_INFINITY; + } else if (exponent <= -enc_max_exponent) { + mantissa_shift += (uint32_t)(-enc_max_exponent + 1 - exponent); + exponent = -enc_max_exponent + 1; + } + + mantissa = ufbxi_shift_right_round(mantissa, mantissa_shift, tail); + if (mantissa == 0) return negative ? -0.0 : 0.0; + + uint64_t bits = mantissa; + bits += (uint64_t)(exponent + enc_max_exponent - 1) << (enc_mantissa_bits - 1); + bits |= sign_bit; + + if (flags & UFBXI_PARSE_DOUBLE_AS_BINARY32) { + uint32_t bits_lo = (uint32_t)bits; + float result; + ufbxi_bit_cast(float, result, uint32_t, bits_lo); + return result; + } else { + double result; + ufbxi_bit_cast(double, result, uint64_t, bits); + return result; + } +} + +static ufbxi_noinline uint32_t ufbxi_parse_double_init_flags() +{ + // We require evaluation in double precision, either for doubles (0) or always (1) + // and rounding to nearest, which we can check for with `1 + eps == 1 - eps`. + #if UFBX_FLT_EVAL_METHOD == 0 || UFBX_FLT_EVAL_METHOD == 1 + static volatile double ufbxi_volatile_eps = 2.2250738585072014e-308; + if (1.0 + ufbxi_volatile_eps == 1.0 - ufbxi_volatile_eps) return UFBXI_PARSE_DOUBLE_ALLOW_FAST_PATH; + #endif + + return 0; } static ufbxi_forceinline int64_t ufbxi_parse_int64(const char *str, char **end) @@ -1421,6 +1689,24 @@ static ufbxi_forceinline int64_t ufbxi_parse_int64(const char *str, char **end) return negative ? -(int64_t)abs_val : (int64_t)abs_val; } +static ufbxi_noinline uint32_t ufbxi_parse_uint32_radix(const char *str, uint32_t radix) +{ + uint32_t value = 0; + for (const char *p = str; ; p++) { + char c = *p; + if (c >= '0' && c <= '9') { + value = value * radix + (uint32_t)(c - '0'); + } else if (radix == 16 && (c >= 'a' && c <= 'f')) { + value = value * radix + (uint32_t)(c + (10 - 'a')); + } else if (radix == 16 && (c >= 'A' && c <= 'F')) { + value = value * radix + (uint32_t)(c + (10 - 'A')); + } else { + break; + } + } + return value; +} + // -- DEFLATE implementation #if !defined(ufbx_inflate) @@ -2838,35 +3124,111 @@ ufbxi_extern_c ptrdiff_t ufbx_inflate(void *dst, size_t dst_size, const ufbx_inf uint32_t ref = (uint32_t)bits; ref = (ref>>24) | ((ref>>8)&0xff00) | ((ref<<8)&0xff0000) | (ref<<24); - uint32_t checksum = ufbxi_adler32(dc.out_begin, ufbxi_to_size(dc.out_ptr - dc.out_begin)); - if (ref != checksum) { - return -9; + uint32_t checksum = ufbxi_adler32(dc.out_begin, ufbxi_to_size(dc.out_ptr - dc.out_begin)); + if (ref != checksum) { + return -9; + } + } + } + + return dc.out_ptr - dc.out_begin; +} + +#endif // !defined(ufbx_inflate) + +// -- Printf + +typedef struct { + char *dst; + size_t length; + size_t pos; +} ufbxi_print_buffer; + +#define UFBXI_PRINT_UNSIGNED 0x1 +#define UFBXI_PRINT_STRING 0x2 +#define UFBXI_PRINT_SIZE_T 0x10 + +static void ufbxi_print_append(ufbxi_print_buffer *buf, size_t min_width, size_t max_width, const char *str) +{ + size_t width = 0; + for (width = 0; width < max_width; width++) { + if (!str[width]) break; + } + size_t pad = min_width > width ? min_width - width : 0; + for (size_t i = 0; i < pad; i++) { + if (buf->pos < buf->length) buf->dst[buf->pos++] = ' '; + } + for (size_t i = 0; i < width; i++) { + if (buf->pos < buf->length) buf->dst[buf->pos++] = str[i]; + } +} + +static char *ufbxi_print_format_int(char *buffer, uint64_t value) +{ + *--buffer = '\0'; + do { + uint32_t digit = (uint32_t)(value % 10); + value = value / 10; + *--buffer = (char)('0' + digit); + } while (value > 0); + return buffer; +} + +static void ufbxi_vprint(ufbxi_print_buffer *buf, const char *fmt, va_list args) +{ + char buffer[96]; // ufbxi_uninit + for (const char *p = fmt; *p;) { + if (*p == '%' && *++p != '%') { + size_t min_width = 0, max_width = SIZE_MAX; + if (*p == '*') { + p++; + min_width = (size_t)va_arg(args, int); + } + if (*p == '.') { + ufbxi_dev_assert(p[1] == '*'); + p += 2; + max_width = (size_t)va_arg(args, int); + } + uint32_t flags = 0; + switch (*p) { + case 'z': p++; flags |= UFBXI_PRINT_SIZE_T; break; + default: break; + } + switch (*p++) { + case 'u': flags |= UFBXI_PRINT_UNSIGNED; break; + case 's': flags |= UFBXI_PRINT_STRING; break; + default: break; + } + if (flags & UFBXI_PRINT_STRING) { + const char *str = va_arg(args, const char*); + ufbxi_print_append(buf, min_width, max_width, str); + } else if (flags & UFBXI_PRINT_UNSIGNED) { + uint64_t value = (flags & UFBXI_PRINT_SIZE_T) != 0 ? (uint64_t)va_arg(args, size_t) : (uint64_t)va_arg(args, uint32_t); + char *str = ufbxi_print_format_int(buffer + sizeof(buffer), value); + ufbxi_print_append(buf, min_width, max_width, str); + } else { + ufbxi_unreachable("Bad printf format"); } + } else { + if (buf->pos < buf->length) buf->dst[buf->pos++] = *p; + p++; } } - - return dc.out_ptr - dc.out_begin; + if (buf->length && buf->dst) { + size_t end = buf->pos <= buf->length - 1 ? buf->pos : buf->length - 1; + buf->dst[end] = '\0'; + } } -#endif // !defined(ufbx_inflate) - // -- Errors static const char ufbxi_empty_char[1] = { '\0' }; static ufbxi_noinline int ufbxi_vsnprintf(char *buf, size_t buf_size, const char *fmt, va_list args) { - int result = vsnprintf(buf, buf_size, fmt, args); - - if (result < 0) result = 0; - if ((size_t)result >= buf_size - 1) result = (int)buf_size - 1; - - // HACK: On some MSYS/MinGW implementations `vsnprintf` is broken and does - // not write the null terminator on truncation, it's always safe to do so - // let's just do it unconditionally here... - buf[result] = '\0'; - - return result; + ufbxi_print_buffer buffer = { buf, buf_size }; + ufbxi_vprint(&buffer, fmt, args); + return (int)ufbxi_min_sz(buffer.pos, buf_size - 1); } static ufbxi_noinline int ufbxi_snprintf(char *buf, size_t buf_size, const char *fmt, ...) @@ -2883,21 +3245,19 @@ static ufbxi_noinline void ufbxi_panicf_imp(ufbx_panic *panic, const char *fmt, if (panic && panic->did_panic) return; va_list args; // ufbxi_uninit - va_start(args, fmt); if (panic) { + va_start(args, fmt); panic->did_panic = true; panic->message_length = (size_t)ufbxi_vsnprintf(panic->message, sizeof(panic->message), fmt, args); + va_end(args); } else { - fprintf(stderr, "ufbx panic: "); - vfprintf(stderr, fmt, args); - fprintf(stderr, "\n"); - } - - va_end(args); + va_start(args, fmt); + char message[UFBX_PANIC_MESSAGE_LENGTH]; + ufbxi_vsnprintf(message, sizeof(message), fmt, args); + va_end(args); - if (!panic) { - ufbx_assert(false && "ufbx panic: See stderr for more information"); + ufbx_panic_handler(message); } } @@ -3046,7 +3406,7 @@ static ufbxi_noinline void ufbxi_clear_error(ufbx_error *err) #define ufbxi_fail_err_msg(err, desc, msg) return ufbxi_fail_imp_err(err, ufbxi_error_msg(desc, msg), ufbxi_function, ufbxi_line) #define ufbxi_report_err_msg(err, desc, msg) (void)ufbxi_fail_imp_err(err, ufbxi_error_msg(desc, msg), ufbxi_function, ufbxi_line) -static ufbxi_noinline void ufbxi_fix_error_type(ufbx_error *error, const char *default_desc) +static ufbxi_noinline void ufbxi_fix_error_type(ufbx_error *error, const char *default_desc, ufbx_error *p_error) { const char *desc = error->description.data; if (!desc) desc = default_desc; @@ -3096,6 +3456,9 @@ static ufbxi_noinline void ufbxi_fix_error_type(ufbx_error *error, const char *d } error->description.data = desc; error->description.length = strlen(desc); + if (p_error) { + memcpy(p_error, error, sizeof(ufbx_error)); + } } // -- Allocator @@ -3168,7 +3531,7 @@ static ufbxi_noinline void *ufbxi_alloc_size(ufbxi_allocator *ator, size_t size, } else if (ator->ator.allocator.realloc_fn) { ptr = ator->ator.allocator.realloc_fn(ator->ator.allocator.user, NULL, 0, total); } else { - ptr = malloc(total); + ptr = ufbx_malloc(total); } if (!ptr) { @@ -3215,7 +3578,7 @@ static ufbxi_noinline void *ufbxi_realloc_size(ufbxi_allocator *ator, size_t siz ator->ator.allocator.free_fn(ator->ator.allocator.user, old_ptr, old_total); } } else { - ptr = realloc(old_ptr, total); + ptr = ufbx_realloc(old_ptr, old_total, total); } ufbxi_check_return_err_msg(ator->error, ptr, NULL, "Out of memory"); @@ -3249,7 +3612,7 @@ static ufbxi_noinline void ufbxi_free_size(ufbxi_allocator *ator, size_t size, v ator->ator.allocator.realloc_fn(ator->ator.allocator.user, ptr, total, 0); } } else { - free(ptr); + ufbx_free(ptr, total); } } @@ -3888,7 +4251,7 @@ static ufbxi_noinline void ufbxi_map_init(ufbxi_map *map, ufbxi_allocator *ator, // allocation counts. We can work around this using a local allocator that doesn't // count the allocations. { - ufbxi_allocator *regression_ator = (ufbxi_allocator*)malloc(sizeof(ufbxi_allocator)); + ufbxi_allocator *regression_ator = (ufbxi_allocator*)ufbx_malloc(sizeof(ufbxi_allocator)); ufbx_assert(regression_ator); memset(regression_ator, 0, sizeof(ufbxi_allocator)); regression_ator->name = "regression"; @@ -3922,7 +4285,7 @@ static ufbxi_noinline void ufbxi_map_free(ufbxi_map *map) #if defined(UFBX_REGRESSION) if (regression_ator) { ufbxi_free_ator(regression_ator); - free(regression_ator); + ufbx_free(regression_ator, sizeof(ufbxi_allocator)); } #endif } @@ -5923,10 +6286,8 @@ typedef struct { // IO uint64_t data_offset; - ufbx_read_fn *read_fn; ufbx_skip_fn *skip_fn; - ufbx_close_fn *close_fn; void *read_user; char *read_buffer; @@ -6026,6 +6387,10 @@ typedef struct { // Temporary per-element flags uint8_t *tmp_element_flag; + // IO (cold) + ufbx_close_fn *close_fn; + ufbx_size_fn *size_fn; + ufbxi_ascii ascii; bool has_geometry_transform_nodes; @@ -6065,6 +6430,10 @@ typedef struct { ufbxi_warnings warnings; bool deferred_failure; + bool deferred_load; + + const char *load_filename; + size_t load_filename_len; bool parse_threaded; ufbxi_thread_pool thread_pool; @@ -6348,8 +6717,6 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_read_to(ufbxi_context *uc, void return 1; } -// -- File IO - static ufbxi_noinline void ufbxi_init_ator(ufbx_error *error, ufbxi_allocator *ator, const ufbx_allocator_opts *opts, const char *name) { ufbx_allocator_opts zero_opts; @@ -6369,19 +6736,56 @@ static ufbxi_noinline void ufbxi_init_ator(ufbx_error *error, ufbxi_allocator *a ator->name = name; } -static ufbxi_noinline FILE *ufbxi_fopen(const char *path, size_t path_len, ufbxi_allocator *tmp_ator) +typedef struct { + ufbx_error error; + + ufbxi_allocator *parent_ator; + ufbxi_allocator ator; +} ufbxi_file_context; + +static ufbxi_noinline void ufbxi_begin_file_context(ufbxi_file_context *fc, ufbx_open_file_context ctx, const ufbx_allocator_opts *ator_opts) { -#if !defined(UFBX_STANDARD_C) && defined(_WIN32) - wchar_t wpath_buf[256]; - wchar_t *wpath = NULL; + memset(fc, 0, sizeof(ufbxi_file_context)); + if (ctx) { + fc->parent_ator = (ufbxi_allocator*)ctx; + fc->ator = *fc->parent_ator; + fc->ator.error = &fc->error; + } else { + ufbxi_init_ator(&fc->error, &fc->ator, ator_opts, "file"); + } +} - if (path_len == SIZE_MAX) { - path_len = strlen(path); +static ufbxi_noinline void ufbxi_end_file_context(ufbxi_file_context *fc, ufbx_error *error, bool ok) +{ + if (fc->parent_ator) { + fc->ator.error = fc->parent_ator->error; + *fc->parent_ator = fc->ator; + } else { + ufbxi_free_ator(&fc->ator); + } + if (error) { + if (!ok) { + ufbxi_fix_error_type(&fc->error, "Failed to open file", error); + } else { + ufbxi_clear_error(error); + } } +} + +// -- File IO + +#if !defined(UFBX_NO_STDIO) && !defined(UFBX_EXTERNAL_STDIO) + +static ufbxi_noinline FILE *ufbxi_fopen(ufbxi_file_context *fc, const char *path, size_t path_len, bool null_terminated) +{ + FILE *file = NULL; +#if !defined(UFBX_STANDARD_C) && defined(_WIN32) + (void)null_terminated; + wchar_t wpath_buf[256], *wpath = NULL; // ufbxi_uninit if (path_len < ufbxi_arraycount(wpath_buf) - 1) { wpath = wpath_buf; } else { - wpath = ufbxi_alloc(tmp_ator, wchar_t, path_len + 1); + wpath = ufbxi_alloc(&fc->ator, wchar_t, path_len + 1); if (!wpath) return NULL; } @@ -6416,45 +6820,38 @@ static ufbxi_noinline FILE *ufbxi_fopen(const char *path, size_t path_len, ufbxi } wpath[wlen] = 0; - FILE *file = NULL; -#if UFBXI_MSC_VER >= 1400 - if (_wfopen_s(&file, wpath, L"rb") != 0) { - file = NULL; - } -#else - file = _wfopen(wpath, L"rb"); -#endif - + #if UFBXI_MSC_VER >= 1400 + if (_wfopen_s(&file, wpath, L"rb") != 0) file = NULL; + #else + file = _wfopen(wpath, L"rb"); + #endif if (wpath != wpath_buf) { - ufbxi_free(tmp_ator, wchar_t, wpath, path_len + 1); + ufbxi_free(&fc->ator, wchar_t, wpath, path_len + 1); } - - return file; #else - if (path_len == SIZE_MAX) { - return fopen(path, "rb"); - } - - char copy_buf[256]; // ufbxi_uninit - char *copy = NULL; - - if (path_len < ufbxi_arraycount(copy_buf) - 1) { - copy = copy_buf; + char copy_buf[256], *copy = NULL; // ufbxi_uninit + if (null_terminated) { + copy = (char*)path; } else { - copy = ufbxi_alloc(tmp_ator, char, path_len + 1); - if (!copy) return NULL; + if (path_len < ufbxi_arraycount(copy_buf) - 1) { + copy = copy_buf; + } else { + copy = ufbxi_alloc(&fc->ator, char, path_len + 1); + if (!copy) return NULL; + } + memcpy(copy, path, path_len); + copy[path_len] = '\0'; } - memcpy(copy, path, path_len); - copy[path_len] = '\0'; - - FILE *file = fopen(copy, "rb"); - - if (copy != copy_buf) { - ufbxi_free(tmp_ator, char, copy, path_len + 1); + file = fopen(copy, "rb"); + if (!null_terminated && copy != copy_buf) { + ufbxi_free(&fc->ator, char, copy, path_len + 1); } - - return file; #endif + if (!file) { + ufbxi_set_err_info(&fc->error, path, path_len); + ufbxi_report_err_msg(&fc->error, "file", "File not found"); + } + return file; } static uint64_t ufbxi_ftell(FILE *file) @@ -6466,20 +6863,20 @@ static uint64_t ufbxi_ftell(FILE *file) int64_t result = _ftelli64(file); if (result >= 0) return (uint64_t)result; #else - long result = ftell(file); + int64_t result = ftell(file); if (result >= 0) return (uint64_t)result; #endif return UINT64_MAX; } -static size_t ufbxi_file_read(void *user, void *data, size_t max_size) +static size_t ufbxi_stdio_read(void *user, void *data, size_t max_size) { FILE *file = (FILE*)user; if (ferror(file)) return SIZE_MAX; return fread(data, 1, max_size, file); } -static bool ufbxi_file_skip(void *user, size_t size) +static bool ufbxi_stdio_skip(void *user, size_t size) { FILE *file = (FILE*)user; ufbx_assert(size <= UFBXI_MAX_SKIP_SIZE); @@ -6488,12 +6885,94 @@ static bool ufbxi_file_skip(void *user, size_t size) return true; } -static void ufbxi_file_close(void *user) +static uint64_t ufbxi_stdio_size(void *user) +{ + FILE *file = (FILE*)user; + uint64_t result = 0; + uint64_t begin = ufbxi_ftell(file); + if (begin < UINT64_MAX) { + fpos_t pos; // ufbxi_uninit + if (fgetpos(file, &pos) == 0) { + if (fseek(file, 0, SEEK_END) == 0) { + uint64_t end = ufbxi_ftell(file); + if (end != UINT64_MAX && begin < end) { + result = end - begin; + } + // Both `rewind()` and `fsetpos()` to reset error and EOF + rewind(file); + fsetpos(file, &pos); + } + } + } + return result; +} + +static void ufbxi_stdio_close(void *user) { FILE *file = (FILE*)user; fclose(file); } +static ufbxi_noinline void ufbxi_stdio_init(ufbx_stream *stream, void *file, bool close) +{ + stream->read_fn = &ufbxi_stdio_read; + stream->skip_fn = &ufbxi_stdio_skip; + stream->size_fn = &ufbxi_stdio_size; + stream->close_fn = close ? &ufbxi_stdio_close : NULL; + stream->user = file; +} + +static ufbxi_noinline bool ufbxi_stdio_open(ufbxi_file_context *fc, ufbx_stream *stream, const char *path, size_t path_len, bool null_terminated) +{ + FILE *file = ufbxi_fopen(fc, path, path_len, null_terminated); + if (!file) return false; + ufbxi_stdio_init(stream, file, true); + return true; +} + +#elif defined(UFBX_EXTERNAL_STDIO) + +static ufbxi_noinline void ufbxi_stdio_init(ufbx_stream *stream, void *file, bool close) +{ + stream->read_fn = &ufbx_stdio_read; + stream->skip_fn = &ufbx_stdio_skip; + stream->size_fn = &ufbx_stdio_size; + stream->close_fn = close ? &ufbx_stdio_close : NULL; + stream->user = file; +} + +static ufbxi_noinline bool ufbxi_stdio_open(ufbxi_file_context *fc, ufbx_stream *stream, const char *path, size_t path_len, bool null_terminated) +{ + char copy_buf[256], *copy = NULL; // ufbxi_uninit + if (null_terminated) { + copy = (char*)path; + } else { + if (path_len < ufbxi_arraycount(copy_buf) - 1) { + copy = copy_buf; + } else { + copy = ufbxi_alloc(&fc->ator, char, path_len + 1); + if (!copy) return false; + } + memcpy(copy, path, path_len); + copy[path_len] = '\0'; + } + void *file = ufbx_stdio_open(copy, path_len); + if (!null_terminated && copy != copy_buf) { + ufbxi_free(&fc->ator, char, copy, path_len + 1); + } + if (!file) { + ufbxi_set_err_info(&fc->error, path, path_len); + ufbxi_report_err_msg(&fc->error, "file", "File not found"); + return false; + } + ufbxi_stdio_init(stream, file, true); + return true; +} + +#endif + +// -- Memory IO + typedef struct { const void *data; size_t size; @@ -6502,7 +6981,8 @@ typedef struct { // Own allocation information size_t self_size; - ufbxi_allocator ator; + ufbxi_allocator *parent_ator; + ufbxi_allocator local_ator; ufbx_error error; char data_copy[]; } ufbxi_memory_stream; @@ -6524,6 +7004,12 @@ static bool ufbxi_memory_skip(void *user, size_t size) return true; } +static uint64_t ufbxi_memory_size(void *user) +{ + ufbxi_memory_stream *stream = (ufbxi_memory_stream*)user; + return stream->size; +} + static void ufbxi_memory_close(void *user) { ufbxi_memory_stream *stream = (ufbxi_memory_stream*)user; @@ -6531,9 +7017,13 @@ static void ufbxi_memory_close(void *user) stream->close_cb.fn(stream->close_cb.user, (void*)stream->data, stream->size); } - ufbxi_allocator ator = stream->ator; - ufbxi_free(&ator, char, stream, stream->self_size); - ufbxi_free_ator(&ator); + if (stream->parent_ator) { + ufbxi_free(stream->parent_ator, char, stream, stream->self_size); + } else { + ufbxi_allocator ator = stream->local_ator; + ufbxi_free(&ator, char, stream, stream->self_size); + ufbxi_free_ator(&ator); + } } // -- XML @@ -6703,9 +7193,9 @@ static ufbxi_noinline int ufbxi_xml_read_until(ufbxi_xml_context *xc, ufbx_strin if (entity[0] == '#') { unsigned long code = 0; if (entity[1] == 'x') { - code = strtoul(entity + 2, NULL, 16); + code = ufbxi_parse_uint32_radix(entity + 2, 16); } else { - code = strtoul(entity + 1, NULL, 10); + code = ufbxi_parse_uint32_radix(entity + 1, 10); } char bytes[5] = { 0 }; @@ -9098,11 +9588,9 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_ascii_next_token(ufbxi_context * token->value.i64 = ufbxi_parse_int64(token->str_data, &end); ufbxi_check(end == token->str_data + token->str_len - 1); } else if (token->type == UFBXI_ASCII_FLOAT) { - if (ua->parse_as_f32) { - token->value.f64 = strtof(token->str_data, &end); - } else { - token->value.f64 = ufbxi_parse_double(token->str_data, token->str_len, &end, uc->double_parse_flags); - } + uint32_t flags = uc->double_parse_flags; + if (ua->parse_as_f32) flags = UFBXI_PARSE_DOUBLE_AS_BINARY32; + token->value.f64 = ufbxi_parse_double(token->str_data, token->str_len, &end, flags); ufbxi_check(end == token->str_data + token->str_len - 1); } } @@ -9499,8 +9987,6 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_ascii_read_float_array(ufbxi_con *v = (float)val; } - // TODO: Collect ASCII numbers to deferred parse integer/string segments - // Try to parse the next value, we don't commit this until we find a comma after it above. char *num_end = NULL; size_t left = ufbxi_to_size(end - src_scan); @@ -15725,44 +16211,12 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_resolve_relative_filename(ufbxi_ // Open file utility -static void *ufbxi_ator_alloc(void *user, size_t size) -{ - ufbxi_allocator *ator = (ufbxi_allocator*)user; - return ufbxi_alloc(ator, char, size); -} - -static void *ufbxi_ator_realloc(void *user, void *old_ptr, size_t old_size, size_t new_size) -{ - ufbxi_allocator *ator = (ufbxi_allocator*)user; - return ufbxi_realloc(ator, char, old_ptr, old_size, new_size); -} - -static void ufbxi_ator_free(void *user, void *ptr, size_t size) -{ - ufbxi_allocator *ator = (ufbxi_allocator*)user; - ufbxi_free(ator, char, ptr, size); -} - -static ufbxi_noinline void ufbxi_setup_ator_allocator(ufbx_allocator *allocator, ufbxi_allocator *ator) -{ - allocator->alloc_fn = &ufbxi_ator_alloc; - allocator->realloc_fn = &ufbxi_ator_realloc; - allocator->free_fn = &ufbxi_ator_free; - allocator->free_allocator_fn = NULL; - allocator->user = ator; -} - static ufbxi_noinline bool ufbxi_open_file(const ufbx_open_file_cb *cb, ufbx_stream *stream, const char *path, size_t path_len, const ufbx_blob *original_filename, ufbxi_allocator *ator, ufbx_open_file_type type) { if (!cb || !cb->fn) return false; ufbx_open_file_info info; // ufbxi_uninit - if (ator) { - ufbxi_setup_ator_allocator(&info.temp_allocator, ator); - } else { - memset(&info.temp_allocator, 0, sizeof(info.temp_allocator)); - } - + info.context = (uintptr_t)ator; if (original_filename) { info.original_filename = *original_filename; } else { @@ -17084,6 +17538,7 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_load_mtl(ufbxi_context *uc) if (!has_stream && uc->opts.load_external_files && uc->opts.obj_search_mtl_by_filename && path.length > 4) { ufbx_string ext = { path.data + path.length - 4, 4 }; if (ufbxi_match(&ext, "\\c.obj")) { + ufbxi_analysis_assert(path.length < SIZE_MAX - 1); char *copy = ufbxi_push_copy(&uc->tmp, char, path.length + 1, path.data); ufbxi_check(copy); copy[path.length - 3] = copy[path.length - 3] == 'O' ? 'M' : 'm'; @@ -23237,9 +23692,9 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_load_xml_imp(ufbxi_cache_c if (tag_fps) { ufbxi_xml_attrib *fps = ufbxi_xml_find_attrib(tag_fps, "TimePerFrame"); if (fps) { - int value = atoi(fps->value.data); + uint32_t value = ufbxi_parse_uint32_radix(fps->value.data, 10); if (value > 0) { - cc->xml_ticks_per_frame = (uint32_t)value; + cc->xml_ticks_per_frame = value; } } } @@ -23264,9 +23719,9 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_load_xml_imp(ufbxi_cache_c ufbxi_xml_attrib *start_time = ufbxi_xml_find_attrib(tag, "StartTime"); ufbxi_xml_attrib *end_time = ufbxi_xml_find_attrib(tag, "EndTime"); if (sampling_rate && start_time && end_time) { - channel->sample_rate = (uint32_t)atoi(sampling_rate->value.data); - channel->start_time = (uint32_t)atoi(start_time->value.data); - channel->end_time = (uint32_t)atoi(end_time->value.data); + channel->sample_rate = ufbxi_parse_uint32_radix(sampling_rate->value.data, 10); + channel->start_time = ufbxi_parse_uint32_radix(start_time->value.data, 10); + channel->end_time = ufbxi_parse_uint32_radix(end_time->value.data, 10); channel->current_time = channel->start_time; channel->try_load = true; } @@ -23591,7 +24046,7 @@ ufbxi_noinline static ufbx_geometry_cache *ufbxi_cache_load(ufbxi_cache_context if (ok) { return &cc->imp->cache; } else { - ufbxi_fix_error_type(&cc->error, "Failed to load geometry cache"); + ufbxi_fix_error_type(&cc->error, "Failed to load geometry cache", NULL); if (!cc->owned_by_scene) { ufbxi_buf_free(&cc->string_pool.buf); ufbxi_free_ator(&cc->ator_result); @@ -24090,6 +24545,50 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_load_imp(ufbxi_context *uc) { // Check for deferred failure if (uc->deferred_failure) return 0; + if (uc->deferred_load) { + ufbx_stream stream = { 0 }; + ufbx_open_file_opts opts = { 0 }; + const char *filename = uc->load_filename; + size_t filename_len = uc->load_filename_len; + bool ok = false; + if (filename_len == SIZE_MAX) { + opts.filename_null_terminated = true; + filename_len = strlen(filename); + } + if (uc->opts.filename.length == 0 || uc->opts.filename.data == NULL) { + uc->opts.filename.data = filename; + uc->opts.filename.length = filename_len; + } + ufbx_error error; + error.type = UFBX_ERROR_NONE; + if (uc->opts.open_main_file_with_default || uc->opts.open_file_cb.fn == &ufbx_default_open_file) { + ufbx_open_file_context ctx = (ufbx_open_file_context)&uc->ator_tmp; + ok = ufbx_open_file_ctx(&stream, ctx, filename, filename_len, &opts, &error); + } else { + ok = ufbxi_open_file(&uc->opts.open_file_cb, &stream, uc->load_filename, filename_len, NULL, &uc->ator_tmp, UFBX_OPEN_FILE_MAIN_MODEL); + } + if (!ok) { + if (error.type != UFBX_ERROR_NONE) { + // cppcheck-suppress uninitStructMember + uc->error = error; + } else { + ufbxi_set_err_info(&uc->error, filename, filename_len); + } + ufbxi_fail_msg("open_file_fn()", "File not found"); + } + uc->read_fn = stream.read_fn; + uc->skip_fn = stream.skip_fn; + uc->size_fn = stream.size_fn; + uc->close_fn = stream.close_fn; + uc->read_user = stream.user; + } + + if (uc->opts.progress_cb.fn && uc->progress_bytes_total == 0 && uc->size_fn) { + uint64_t total = uc->size_fn(uc->read_user); + ufbxi_check(total != UINT64_MAX); + uc->progress_bytes_total = total; + } + ufbxi_check(uc->opts.path_separator >= 0x20 && uc->opts.path_separator <= 0x7e); ufbxi_check(ufbxi_fixup_opts_string(uc, &uc->opts.filename, false)); @@ -24431,34 +24930,24 @@ static ufbxi_noinline ufbx_scene *ufbxi_load(ufbxi_context *uc, const ufbx_load_ int ok = ufbxi_load_imp(uc); - ufbxi_free_temp(uc); - if (uc->close_fn) { uc->close_fn(uc->read_user); } + ufbxi_free_temp(uc); + if (ok) { if (p_error) { ufbxi_clear_error(p_error); } return &uc->scene_imp->scene; } else { - ufbxi_fix_error_type(&uc->error, "Failed to load"); - if (p_error) *p_error = uc->error; + ufbxi_fix_error_type(&uc->error, "Failed to load", p_error); ufbxi_free_result(uc); return NULL; } } -static ufbxi_noinline ufbx_scene *ufbxi_load_not_found(const char *filename, size_t filename_len, ufbx_error *p_error) -{ - ufbxi_context uc = { UFBX_ERROR_NONE }; - ufbxi_set_err_info(&uc.error, filename, filename_len); - ufbxi_report_err_msg(&uc.error, "File not found", "File not found"); - uc.deferred_failure = true; - return ufbxi_load(&uc, NULL, p_error); -} - // -- Animation evaluation static ufbxi_forceinline bool ufbxi_override_less_than_prop(const ufbx_prop_override *over, uint32_t element_id, const ufbx_prop *prop) @@ -25240,8 +25729,7 @@ ufbxi_nodiscard static ufbxi_noinline ufbx_scene *ufbxi_evaluate_scene(ufbxi_eva } return &ec->scene_imp->scene; } else { - ufbxi_fix_error_type(&ec->error, "Failed to evaluate"); - if (p_error) *p_error = ec->error; + ufbxi_fix_error_type(&ec->error, "Failed to evaluate", p_error); ufbxi_buf_free(&ec->tmp); ufbxi_buf_free(&ec->result); ufbxi_free_ator(&ec->ator_tmp); @@ -25310,11 +25798,11 @@ static bool ufbxi_prop_override_less(void *user, const void *va, const void *vb) return strcmp(a->prop_name.data, b->prop_name.data) < 0; } -static int ufbxi_cmp_transform_override(const void *va, const void *vb) +static bool ufbxi_transform_override_less(void *user, const void *va, const void *vb) { + (void)user; const ufbx_transform_override *a = (const ufbx_transform_override*)va, *b = (const ufbx_transform_override*)vb; - if (a->node_id != b->node_id) return a->node_id < b->node_id ? -1 : 1; - return 0; + return a->node_id < b->node_id; } ufbxi_nodiscard static ufbxi_noinline int ufbxi_create_anim_imp(ufbxi_create_anim_context *ac) @@ -25419,8 +25907,7 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_create_anim_imp(ufbxi_create_ani anim->transform_overrides.count = ac->opts.transform_overrides.count; anim->transform_overrides.data = ufbxi_push_copy(&ac->result, ufbx_transform_override, anim->transform_overrides.count, ac->opts.transform_overrides.data); ufbxi_check_err(&ac->error, anim->transform_overrides.data); - - qsort(anim->transform_overrides.data, anim->transform_overrides.count, sizeof(ufbx_transform_override), &ufbxi_cmp_transform_override); + ufbxi_unstable_sort(anim->transform_overrides.data, anim->transform_overrides.count, sizeof(ufbx_transform_override), &ufbxi_transform_override_less, NULL); } ac->imp = ufbxi_push(&ac->result, ufbxi_anim_imp, 1); @@ -25473,6 +25960,9 @@ typedef struct { ufbx_baked_node **baked_nodes; bool *nodes_to_bake; + char *tmp_arr; + size_t tmp_arr_size; + const ufbx_scene *scene; const ufbx_anim *anim; ufbx_bake_opts opts; @@ -25495,14 +25985,15 @@ typedef struct { ufbx_anim_value *anim_value; } ufbxi_bake_prop; -static int ufbxi_cmp_bake_prop(const void *va, const void *vb) +static bool ufbxi_bake_prop_less(void *user, const void *va, const void *vb) { + (void)user; const ufbxi_bake_prop *a = (const ufbxi_bake_prop*)va; const ufbxi_bake_prop *b = (const ufbxi_bake_prop*)vb; - if (a->sort_id != b->sort_id) return a->sort_id < b->sort_id ? -1 : 1; - if (a->element_id != b->element_id) return a->element_id < b->element_id ? -1 : 1; - if (a->prop_name != b->prop_name) return strcmp(a->prop_name, b->prop_name); - return a->anim_value < b->anim_value; + if (a->sort_id != b->sort_id) return a->sort_id < b->sort_id; + if (a->element_id != b->element_id) return a->element_id < b->element_id; + if (a->prop_name != b->prop_name) return strcmp(a->prop_name, b->prop_name) < 0; + return false; } ufbx_static_assert(bake_step_left, UFBX_BAKED_KEY_STEP_LEFT == 0x1); @@ -25518,13 +26009,6 @@ static ufbxi_forceinline int ufbxi_cmp_bake_time(ufbxi_bake_time a, ufbxi_bake_t return 0; } -static int ufbxi_cmp_bake_time_fn(const void *va, const void *vb) -{ - const ufbxi_bake_time a = *(const ufbxi_bake_time*)va; - const ufbxi_bake_time b = *(const ufbxi_bake_time*)vb; - return ufbxi_cmp_bake_time(a, b); -} - ufbxi_nodiscard static ufbxi_forceinline int ufbxi_bake_push_time(ufbxi_bake_context *bc, double time, uint32_t flags) { ufbxi_bake_time *p_key = ufbxi_push_fast(&bc->tmp_times, ufbxi_bake_time, 1); @@ -25609,6 +26093,13 @@ ufbxi_nodiscard static ufbxi_noinline bool ufbxi_in_list(const char *const *item return false; } +ufbxi_nodiscard static ufbxi_noinline int ufbxi_sort_bake_times(ufbxi_bake_context *bc, ufbxi_bake_time *times, size_t count) +{ + ufbxi_check_err(&bc->error, ufbxi_grow_array(&bc->ator_tmp, &bc->tmp_arr, &bc->tmp_arr_size, count * sizeof(ufbxi_bake_time))); + ufbxi_macro_stable_sort(ufbxi_bake_time, 32, times, bc->tmp_arr, count, ( ufbxi_cmp_bake_time(*a, *b) < 0 )); + return 1; +} + ufbxi_nodiscard static ufbxi_noinline int ufbxi_finalize_bake_times(ufbxi_bake_context *bc, ufbxi_bake_time_list *p_dst) { if (bc->layer_weight_times.count > 0) { @@ -25624,8 +26115,7 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_finalize_bake_times(ufbxi_bake_c ufbxi_bake_time *times = ufbxi_push_pop(&bc->tmp_prop, &bc->tmp_times, ufbxi_bake_time, num_times); ufbxi_check_err(&bc->error, times); - // TODO: Something better - qsort(times, num_times, sizeof(ufbxi_bake_time), &ufbxi_cmp_bake_time_fn); + ufbxi_check_err(&bc->error, ufbxi_sort_bake_times(bc, times, num_times)); // Deduplicate times if (num_times > 0) { @@ -25736,13 +26226,13 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_finalize_bake_times(ufbxi_bake_c #define ufbxi_add_epsilon(a, epsilon) ((a)>0 ? (a)*(epsilon) : (a)/(epsilon)) #define ufbxi_sub_epsilon(a, epsilon) ((a)>0 ? (a)/(epsilon) : (a)*(epsilon)) -static ufbxi_noinline bool ufbxi_postprocess_step(ufbxi_bake_context *bc, double prev_time, double next_time, double *p_time, uint32_t flags) +static ufbxi_noinline bool ufbxi_postprocess_step(ufbxi_bake_context *bc, double prev_time, double next_time, double *p_time, ufbx_baked_key_flags flags) { ufbxi_dev_assert((flags & (UFBX_BAKED_KEY_STEP_LEFT|UFBX_BAKED_KEY_STEP_RIGHT)) != 0); bool left = (flags & UFBX_BAKED_KEY_STEP_LEFT) != 0; double step = 0.001; - double epsilon = 1.0 + FLT_EPSILON * 4.0f; + double epsilon = 1.0 + UFBX_FLT_EPSILON * 4.0f; double time = *p_time; switch (bc->opts.step_handling) { @@ -25982,7 +26472,7 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_push_resampled_times(ufbxi_bake_ ufbxi_bake_time *times = ufbxi_push(&bc->tmp_times, ufbxi_bake_time, keys.count); ufbxi_check_err(&bc->error, times); for (size_t i = 0; i < keys.count; i++) { - uint32_t flags = keys.data[i].flags; + ufbx_baked_key_flags flags = keys.data[i].flags; double time = keys.data[i].time; if ((flags & UFBX_BAKED_KEY_STEP_LEFT) != 0 && i + 1 < keys.count && (keys.data[i + 1].flags & UFBX_BAKED_KEY_STEP_KEY) != 0) { time = keys.data[i + 1].time; @@ -26402,8 +26892,7 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_anim(ufbxi_bake_context *bc ufbxi_bake_prop *props = ufbxi_push_pop(&bc->tmp, &bc->tmp_bake_props, ufbxi_bake_prop, num_props); ufbxi_check_err(&bc->error, props); - // TODO: Macro unstable/non allocating sort - qsort(props, num_props, sizeof(ufbxi_bake_prop), &ufbxi_cmp_bake_prop); + ufbxi_unstable_sort(props, num_props, sizeof(ufbxi_bake_prop), &ufbxi_bake_prop_less, NULL); // Pre-bake layer weight times if (!bc->opts.ignore_layer_weight_animation) { @@ -27453,19 +27942,19 @@ ufbxi_noinline static uint32_t ufbxi_triangulate_ngon(ufbxi_ngon_context *nc, ui #endif -static int ufbxi_cmp_topo_index_prev_next(const void *va, const void *vb) +static bool ufbxi_topo_less_index_prev_next(void *user, const void *va, const void *vb) { + (void)user; const ufbx_topo_edge *a = (const ufbx_topo_edge*)va, *b = (const ufbx_topo_edge*)vb; - if ((int32_t)a->prev != (int32_t)b->prev) return (int32_t)a->prev < (int32_t)b->prev ? -1 : +1; - if ((int32_t)a->next != (int32_t)b->next) return (int32_t)a->next < (int32_t)b->next ? -1 : +1; - return 0; + if ((int32_t)a->prev != (int32_t)b->prev) return (int32_t)a->prev < (int32_t)b->prev; + return (int32_t)a->next < (int32_t)b->next; } -static int ufbxi_cmp_topo_index_index(const void *va, const void *vb) +static bool ufbxi_topo_less_index_index(void *user, const void *va, const void *vb) { + (void)user; const ufbx_topo_edge *a = (const ufbx_topo_edge*)va, *b = (const ufbx_topo_edge*)vb; - if ((int32_t)a->index != (int32_t)b->index) return (int32_t)a->index < (int32_t)b->index ? -1 : +1; - return 0; + return (int32_t)a->index < (int32_t)b->index; } ufbxi_noinline static void ufbxi_compute_topology(const ufbx_mesh *mesh, ufbx_topo_edge *topo) @@ -27494,8 +27983,7 @@ ufbxi_noinline static void ufbxi_compute_topology(const ufbx_mesh *mesh, ufbx_to } } - // TODO: Macro unstable/non allocating sort - qsort(topo, num_indices, sizeof(ufbx_topo_edge), &ufbxi_cmp_topo_index_prev_next); + ufbxi_unstable_sort(topo, num_indices, sizeof(ufbx_topo_edge), &ufbxi_topo_less_index_prev_next, NULL); if (mesh->edges.data) { for (uint32_t ei = 0; ei < mesh->num_edges; ei++) { @@ -27535,8 +28023,7 @@ ufbxi_noinline static void ufbxi_compute_topology(const ufbx_mesh *mesh, ufbx_to i0 = i1 + 1; } - // TODO: Macro unstable/non allocating sort - qsort(topo, num_indices, sizeof(ufbx_topo_edge), &ufbxi_cmp_topo_index_index); + ufbxi_unstable_sort(topo, num_indices, sizeof(ufbx_topo_edge), &ufbxi_topo_less_index_index, NULL); // Fix `prev` and `next` to the actual index values for (uint32_t fi = 0; fi < mesh->num_faces; fi++) { @@ -27702,12 +28189,13 @@ static int ufbxi_subdivide_sum_vec4(void *user, void *output, const ufbxi_subdiv return 1; } -static ufbxi_noinline int ufbxi_cmp_subdivision_weight(const void *va, const void *vb) +static ufbxi_noinline bool ufbxi_subdivision_weight_less(void *user, const void *va, const void *vb) { + (void)user; ufbx_subdivision_weight a = *(const ufbx_subdivision_weight*)va, b = *(const ufbx_subdivision_weight*)vb; ufbxi_dev_assert(a.index != b.index); - if (a.weight != b.weight) return a.weight > b.weight ? -1 : +1; - return a.index < b.index ? -1 : +1; + if (a.weight != b.weight) return a.weight > b.weight; + return a.index < b.index; } static int ufbxi_subdivide_sum_vertex_weights(void *user, void *output, const ufbxi_subdivide_input *inputs, size_t num_inputs) @@ -27743,7 +28231,7 @@ static int ufbxi_subdivide_sum_vertex_weights(void *user, void *output, const uf vertex_weights[vx] = 0.0f; } - qsort(tmp_weights, num_weights, sizeof(ufbx_subdivision_weight), ufbxi_cmp_subdivision_weight); + ufbxi_unstable_sort(tmp_weights, num_weights, sizeof(ufbx_subdivision_weight), ufbxi_subdivision_weight_less, NULL); if (sc->max_vertex_weights != SIZE_MAX) { num_weights = ufbxi_min_sz(sc->max_vertex_weights, num_weights); @@ -28823,8 +29311,7 @@ ufbxi_noinline static ufbx_mesh *ufbxi_subdivide_mesh(const ufbx_mesh *mesh, siz ufbxi_mesh_imp *imp = sc.imp; return &imp->mesh; } else { - ufbxi_fix_error_type(&sc.error, "Failed to subdivide"); - if (p_error) *p_error = sc.error; + ufbxi_fix_error_type(&sc.error, "Failed to subdivide", p_error); ufbxi_buf_free(&sc.result); ufbxi_free_ator(&sc.ator_tmp); ufbxi_free_ator(&sc.ator_result); @@ -28976,7 +29463,7 @@ static ufbxi_noinline size_t ufbxi_generate_indices(const ufbx_vertex_stream *us ufbxi_clear_error(error); } else { - ufbxi_fix_error_type(error, "Failed to generate indices"); + ufbxi_fix_error_type(error, "Failed to generate indices", NULL); } if (streams && streams != local_streams) { @@ -29169,29 +29656,43 @@ ufbx_abi_data_def const size_t ufbx_element_type_size[UFBX_ELEMENT_TYPE_COUNT] = sizeof(ufbx_metadata_object), }; -ufbx_abi bool ufbx_open_file(ufbx_stream *stream, const char *path, size_t path_len) +ufbx_abi bool ufbx_default_open_file(void *user, ufbx_stream *stream, const char *path, size_t path_len, const ufbx_open_file_info *info) { - ufbxi_allocator tmp_ator = { 0 }; - ufbx_error tmp_error = { UFBX_ERROR_NONE }; - ufbxi_init_ator(&tmp_error, &tmp_ator, NULL, "filename"); - FILE *f = ufbxi_fopen(path, path_len, &tmp_ator); - if (!f) return false; + (void)user; + return ufbx_open_file_ctx(stream, info->context, path, path_len, NULL, NULL); +} - stream->read_fn = &ufbxi_file_read; - stream->skip_fn = &ufbxi_file_skip; - stream->close_fn = &ufbxi_file_close; - stream->user = f; - return true; +ufbx_abi bool ufbx_open_file(ufbx_stream *stream, const char *path, size_t path_len, const ufbx_open_file_opts *opts, ufbx_error *error) +{ + return ufbx_open_file_ctx(stream, (ufbx_open_file_context)NULL, path, path_len, opts, error); } -ufbx_abi bool ufbx_default_open_file(void *user, ufbx_stream *stream, const char *path, size_t path_len, const ufbx_open_file_info *info) +ufbx_abi bool ufbx_open_file_ctx(ufbx_stream *stream, ufbx_open_file_context ctx, const char *path, size_t path_len, const ufbx_open_file_opts *opts, ufbx_error *error) { - (void)user; - (void)info; - return ufbx_open_file(stream, path, path_len); + bool ok = false; + ufbxi_file_context fc; // ufbxi_uninit + ufbxi_begin_file_context(&fc, ctx, NULL); + if (path_len == SIZE_MAX) path_len = strlen(path); +#if !defined(UFBX_NO_STDIO) + ok = ufbxi_stdio_open(&fc, stream, path, path_len, opts ? opts->filename_null_terminated : false); +#else + (void)stream; + (void)path; + (void)path_len; + (void)opts; + ufbxi_fmt_err_info(&fc.error, "UFBX_NO_STDIO"); + ufbxi_report_err_msg(&fc.error, "UFBX_NO_STDIO", "Feature disabled"); +#endif + ufbxi_end_file_context(&fc, error, ok); + return ok; } ufbx_abi bool ufbx_open_memory(ufbx_stream *stream, const void *data, size_t data_size, const ufbx_open_memory_opts *opts, ufbx_error *error) +{ + return ufbx_open_memory_ctx(stream, (ufbx_open_file_context)NULL, data, data_size, opts, error); +} + +ufbx_abi bool ufbx_open_memory_ctx(ufbx_stream *stream, ufbx_open_file_context ctx, const void *data, size_t data_size, const ufbx_open_memory_opts *opts, ufbx_error *error) { ufbx_open_memory_opts local_opts; // ufbxi_uninit if (!opts) { @@ -29200,22 +29701,17 @@ ufbx_abi bool ufbx_open_memory(ufbx_stream *stream, const void *data, size_t dat } ufbx_assert(opts->_begin_zero == 0 && opts->_end_zero == 0); - ufbx_error local_error = { UFBX_ERROR_NONE }; - if (!error) error = &local_error; - ufbxi_clear_error(error); - - ufbxi_allocator ator = { 0 }; - ufbxi_init_ator(error, &ator, &opts->allocator, "memory"); + ufbxi_file_context fc; // ufbxi_uninit + ufbxi_begin_file_context(&fc, ctx, &opts->allocator); size_t copy_size = opts->no_copy ? 0 : data_size; // Align the allocation size to 8 bytes to make sure the header is aligned. size_t self_size = ufbxi_align_to_mask(sizeof(ufbxi_memory_stream) + copy_size, 7); - void *memory = ufbxi_alloc(&ator, char, self_size); + void *memory = ufbxi_alloc(&fc.ator, char, self_size); if (!memory) { - ufbxi_free_ator(&ator); - ufbxi_fix_error_type(error, "Failed to open memory"); + ufbxi_end_file_context(&fc, error, false); return false; } @@ -29234,14 +29730,20 @@ ufbx_abi bool ufbx_open_memory(ufbx_stream *stream, const void *data, size_t dat } // Transplant the allocator in the result blob - mem->ator = ator; - mem->ator.error = &mem->error; + if (fc.parent_ator) { + mem->parent_ator = fc.parent_ator; + } else { + fc.parent_ator = &mem->local_ator; + } stream->read_fn = ufbxi_memory_read; stream->skip_fn = ufbxi_memory_skip; + stream->size_fn = ufbxi_memory_size; stream->close_fn = ufbxi_memory_close; stream->user = mem; + ufbxi_end_file_context(&fc, error, true); + return true; } @@ -29253,7 +29755,8 @@ ufbx_abi bool ufbx_is_thread_safe(void) ufbx_abi ufbx_scene *ufbx_load_memory(const void *data, size_t size, const ufbx_load_opts *opts, ufbx_error *error) { ufbxi_check_opts_ptr(ufbx_scene, opts, error); - ufbxi_context uc = { UFBX_ERROR_NONE }; + ufbxi_context uc; // ufbxi_uninit + memset(&uc, 0, sizeof(ufbxi_context)); uc.data_begin = uc.data = (const char *)data; uc.data_size = size; uc.progress_bytes_total = size; @@ -29268,42 +29771,12 @@ ufbx_abi ufbx_scene *ufbx_load_file(const char *filename, const ufbx_load_opts * ufbx_abi ufbx_scene *ufbx_load_file_len(const char *filename, size_t filename_len, const ufbx_load_opts *opts, ufbx_error *error) { ufbxi_check_opts_ptr(ufbx_scene, opts, error); - ufbx_load_opts opts_copy; - if (opts) { - opts_copy = *opts; - } else { - memset(&opts_copy, 0, sizeof(opts_copy)); - opts = &opts_copy; - } - if (opts_copy.filename.length == 0 || opts_copy.filename.data == NULL) { - opts_copy.filename.data = filename; - opts_copy.filename.length = filename_len; - } - - // Defer to `ufbx_load_stream()` if the user so prefers. - if (!opts->open_main_file_with_default && opts->open_file_cb.fn) { - ufbx_stream stream = { 0 }; - if (ufbxi_open_file(&opts->open_file_cb, &stream, filename, filename_len, NULL, NULL, UFBX_OPEN_FILE_MAIN_MODEL)) { - return ufbx_load_stream_prefix(&stream, NULL, 0, &opts_copy, error); - } else { - return ufbxi_load_not_found(filename, filename_len, error); - } - } - - ufbxi_allocator tmp_ator = { 0 }; - ufbx_error tmp_error = { UFBX_ERROR_NONE }; - ufbxi_init_ator(&tmp_error, &tmp_ator, opts ? &opts->temp_allocator : NULL, "filename"); - - FILE *file = ufbxi_fopen(filename, filename_len, &tmp_ator); - if (!file) { - return ufbxi_load_not_found(filename, filename_len, error); - } - - ufbx_scene *scene = ufbx_load_stdio(file, &opts_copy, error); - - fclose(file); - - return scene; + ufbxi_context uc; // ufbxi_uninit + memset(&uc, 0, sizeof(ufbxi_context)); + uc.deferred_load = true; + uc.load_filename = filename; + uc.load_filename_len = filename_len; + return ufbxi_load(&uc, opts, error); } ufbx_abi ufbx_scene *ufbx_load_stdio(void *file_void, const ufbx_load_opts *opts, ufbx_error *error) @@ -29313,37 +29786,24 @@ ufbx_abi ufbx_scene *ufbx_load_stdio(void *file_void, const ufbx_load_opts *opts ufbx_abi ufbx_scene *ufbx_load_stdio_prefix(void *file_void, const void *prefix, size_t prefix_size, const ufbx_load_opts *opts, ufbx_error *error) { - ufbxi_check_opts_ptr(ufbx_scene, opts, error); - FILE *file = (FILE*)file_void; - - ufbxi_context uc = { UFBX_ERROR_NONE }; - uc.data_begin = uc.data = (const char *)prefix; - uc.data_size = prefix_size; - uc.read_fn = &ufbxi_file_read; - uc.skip_fn = &ufbxi_file_skip; - uc.read_user = file; - - if (opts && opts->progress_cb.fn && opts->file_size_estimate == 0) { - uint64_t begin = ufbxi_ftell(file); - if (begin < UINT64_MAX) { - fpos_t pos; // ufbxi_uninit - if (fgetpos(file, &pos) == 0) { - if (fseek(file, 0, SEEK_END) == 0) { - uint64_t end = ufbxi_ftell(file); - if (end != UINT64_MAX && begin < end) { - uc.progress_bytes_total = end - begin; - } - - // Both `rewind()` and `fsetpos()` to reset error and EOF - rewind(file); - fsetpos(file, &pos); - } - } - } - } - - ufbx_scene *scene = ufbxi_load(&uc, opts, error); - return scene; +#if !defined(UFBX_NO_STDIO) + if (!file_void) return NULL; + ufbx_stream stream = { 0 }; + ufbxi_stdio_init(&stream, file_void, false); + return ufbx_load_stream_prefix(&stream, prefix, prefix_size, opts, error); +#else + (void)file_void; + (void)prefix; + (void)prefix_size; + (void)opts; + + ufbxi_context uc; // ufbxi_uninit + memset(&uc, 0, sizeof(ufbxi_context)); + ufbxi_fmt_err_info(&uc.error, "UFBX_NO_STDIO"); + ufbxi_report_err_msg(&uc.error, "UFBX_NO_STDIO", "Feature disabled"); + uc.deferred_failure = true; + return ufbxi_load(&uc, NULL, error); +#endif } ufbx_abi ufbx_scene *ufbx_load_stream(const ufbx_stream *stream, const ufbx_load_opts *opts, ufbx_error *error) @@ -29354,13 +29814,16 @@ ufbx_abi ufbx_scene *ufbx_load_stream(const ufbx_stream *stream, const ufbx_load ufbx_abi ufbx_scene *ufbx_load_stream_prefix(const ufbx_stream *stream, const void *prefix, size_t prefix_size, const ufbx_load_opts *opts, ufbx_error *error) { ufbxi_check_opts_ptr(ufbx_scene, opts, error); - ufbxi_context uc = { UFBX_ERROR_NONE }; + ufbxi_context uc; // ufbxi_uninit + memset(&uc, 0, sizeof(ufbxi_context)); uc.data_begin = uc.data = (const char *)prefix; uc.data_size = prefix_size; uc.read_fn = stream->read_fn; uc.skip_fn = stream->skip_fn; + uc.size_fn = stream->size_fn; uc.close_fn = stream->close_fn; uc.read_user = stream->user; + ufbx_scene *scene = ufbxi_load(&uc, opts, error); return scene; } @@ -29412,9 +29875,10 @@ ufbx_abi ufbxi_noinline size_t ufbx_format_error(char *dst, size_t dst_size, con } size_t stack_size = ufbxi_min_sz(error->stack_size, UFBX_ERROR_STACK_MAX_DEPTH); + int line_width = 6; for (size_t i = 0; i < stack_size; i++) { const ufbx_error_frame *frame = &error->stack[i]; - int num = ufbxi_snprintf(dst + offset, dst_size - offset, "%6u:%s: %s\n", frame->source_line, frame->function.data, frame->description.data); + int num = ufbxi_snprintf(dst + offset, dst_size - offset, "%*u:%s: %s\n", line_width, frame->source_line, frame->function.data, frame->description.data); if (num > 0) offset = ufbxi_min_sz(offset + (size_t)num, dst_size - 1); } @@ -29958,8 +30422,7 @@ ufbx_abi ufbx_anim *ufbx_create_anim(const ufbx_scene *scene, const ufbx_anim_op ufbxi_anim_imp *imp = ac.imp; return &imp->anim; } else { - ufbxi_fix_error_type(&ac.error, "Failed to create anim"); - if (error) *error = ac.error; + ufbxi_fix_error_type(&ac.error, "Failed to create anim", error); ufbxi_buf_free(&ac.result); ufbxi_free_ator(&ac.ator_result); return NULL; @@ -30014,6 +30477,7 @@ ufbx_abi ufbx_baked_anim *ufbx_bake_anim(const ufbx_scene *scene, const ufbx_ani ufbxi_buf_free(&bc.tmp_elements); ufbxi_buf_free(&bc.tmp_props); ufbxi_buf_free(&bc.tmp_bake_stack); + ufbxi_free(&bc.ator_tmp, char, bc.tmp_arr, bc.tmp_arr_size); ufbxi_free_ator(&bc.ator_tmp); if (ok) { @@ -30021,8 +30485,7 @@ ufbx_abi ufbx_baked_anim *ufbx_bake_anim(const ufbx_scene *scene, const ufbx_ani ufbxi_baked_anim_imp *imp = bc.imp; return &imp->bake; } else { - ufbxi_fix_error_type(&bc.error, "Failed to bake anim"); - if (error) *error = bc.error; + ufbxi_fix_error_type(&bc.error, "Failed to bake anim", error); ufbxi_buf_free(&bc.result); ufbxi_free_ator(&bc.ator_result); return NULL; @@ -31046,8 +31509,7 @@ ufbx_abi ufbx_line_curve *ufbx_tessellate_nurbs_curve(const ufbx_nurbs_curve *cu ufbxi_line_curve_imp *imp = tc.imp; return &imp->curve; } else { - ufbxi_fix_error_type(&tc.error, "Failed to tessellate"); - if (error) *error = tc.error; + ufbxi_fix_error_type(&tc.error, "Failed to tessellate", error); ufbxi_buf_free(&tc.result); ufbxi_free_ator(&tc.ator_result); return NULL; @@ -31087,8 +31549,7 @@ ufbx_abi ufbx_mesh *ufbx_tessellate_nurbs_surface(const ufbx_nurbs_surface *surf ufbxi_mesh_imp *imp = tc.imp; return &imp->mesh; } else { - ufbxi_fix_error_type(&tc.error, "Failed to tessellate"); - if (error) *error = tc.error; + ufbxi_fix_error_type(&tc.error, "Failed to tessellate", error); ufbxi_buf_free(&tc.result); ufbxi_free_ator(&tc.ator_result); return NULL; @@ -31230,7 +31691,7 @@ ufbx_abi void ufbx_catch_compute_topology(ufbx_panic *panic, const ufbx_mesh *me ufbx_abi uint32_t ufbx_catch_topo_next_vertex_edge(ufbx_panic *panic, const ufbx_topo_edge *topo, size_t num_topo, uint32_t index) { if (index == UFBX_NO_INDEX) return UFBX_NO_INDEX; - if (ufbxi_panicf(panic, (size_t)index < num_topo, "index (%d) out of bounds (%zu)", index, num_topo)) return UFBX_NO_INDEX; + if (ufbxi_panicf(panic, (size_t)index < num_topo, "index (%u) out of bounds (%zu)", index, num_topo)) return UFBX_NO_INDEX; uint32_t twin = topo[index].twin; if (twin == UFBX_NO_INDEX) return UFBX_NO_INDEX; if (ufbxi_panicf(panic, (size_t)twin < num_topo, "Corrupted topology structure")) return UFBX_NO_INDEX; @@ -31240,7 +31701,7 @@ ufbx_abi uint32_t ufbx_catch_topo_next_vertex_edge(ufbx_panic *panic, const ufbx ufbx_abi uint32_t ufbx_catch_topo_prev_vertex_edge(ufbx_panic *panic, const ufbx_topo_edge *topo, size_t num_topo, uint32_t index) { if (index == UFBX_NO_INDEX) return UFBX_NO_INDEX; - if (ufbxi_panicf(panic, (size_t)index < num_topo, "index (%d) out of bounds (%zu)", index, num_topo)) return UFBX_NO_INDEX; + if (ufbxi_panicf(panic, (size_t)index < num_topo, "index (%u) out of bounds (%zu)", index, num_topo)) return UFBX_NO_INDEX; return topo[topo[index].prev].twin; } @@ -31340,7 +31801,7 @@ ufbx_abi void ufbx_catch_compute_normals(ufbx_panic *panic, const ufbx_mesh *mes for (size_t ix = 0; ix < face.num_indices; ix++) { uint32_t index = normal_indices[face.index_begin + ix]; - if (ufbxi_panicf(panic, index < num_normals, "Normal index (%d) out of bounds (%zu) at %zu", index, num_normals, ix)) return; + if (ufbxi_panicf(panic, index < num_normals, "Normal index (%u) out of bounds (%zu) at %zu", index, num_normals, ix)) return; ufbx_vec3 *n = &normals[index]; *n = ufbxi_add3(*n, normal); @@ -31820,12 +32281,63 @@ ufbx_abi ufbx_audio_clip *ufbx_as_audio_clip(const ufbx_element *element) { retu ufbx_abi ufbx_pose *ufbx_as_pose(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_POSE ? (ufbx_pose*)element : NULL; } ufbx_abi ufbx_metadata_object *ufbx_as_metadata_object(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_METADATA_OBJECT ? (ufbx_metadata_object*)element : NULL; } +// -- String API + +ufbx_abi ufbx_prop *ufbx_find_prop(const ufbx_props *props, const char *name) { return ufbx_find_prop_len(props, name, strlen(name)); } +ufbx_abi ufbx_real ufbx_find_real(const ufbx_props *props, const char *name, ufbx_real def) { return ufbx_find_real_len(props, name, strlen(name), def); } +ufbx_abi ufbx_vec3 ufbx_find_vec3(const ufbx_props *props, const char *name, ufbx_vec3 def) { return ufbx_find_vec3_len(props, name, strlen(name), def); } +ufbx_abi int64_t ufbx_find_int(const ufbx_props *props, const char *name, int64_t def) { return ufbx_find_int_len(props, name, strlen(name), def); } +ufbx_abi bool ufbx_find_bool(const ufbx_props *props, const char *name, bool def) { return ufbx_find_bool_len(props, name, strlen(name), def); } +ufbx_abi ufbx_string ufbx_find_string(const ufbx_props *props, const char *name, ufbx_string def) { return ufbx_find_string_len(props, name, strlen(name), def); } +ufbx_abi ufbx_blob ufbx_find_blob(const ufbx_props *props, const char *name, ufbx_blob def) { return ufbx_find_blob_len(props, name, strlen(name), def); } +ufbx_abi ufbx_element *ufbx_find_prop_element(const ufbx_element *element, const char *name, ufbx_element_type type) { return ufbx_find_prop_element_len(element, name, strlen(name), type); } +ufbx_abi ufbx_element *ufbx_find_element(const ufbx_scene *scene, ufbx_element_type type, const char *name) { return ufbx_find_element_len(scene, type, name, strlen(name)); } +ufbx_abi ufbx_node *ufbx_find_node(const ufbx_scene *scene, const char *name) { return ufbx_find_node_len(scene, name, strlen(name)); } +ufbx_abi ufbx_anim_stack *ufbx_find_anim_stack(const ufbx_scene *scene, const char *name) { return ufbx_find_anim_stack_len(scene, name, strlen(name)); } +ufbx_abi ufbx_material *ufbx_find_material(const ufbx_scene *scene, const char *name) { return ufbx_find_material_len(scene, name, strlen(name)); } +ufbx_abi ufbx_anim_prop *ufbx_find_anim_prop(const ufbx_anim_layer *layer, const ufbx_element *element, const char *prop) { return ufbx_find_anim_prop_len(layer, element, prop, strlen(prop)); } +ufbx_abi ufbx_prop ufbx_evaluate_prop(const ufbx_anim *anim, const ufbx_element *element, const char *name, double time) { return ufbx_evaluate_prop_len(anim, element, name, strlen(name), time); } +ufbx_abi ufbx_texture *ufbx_find_prop_texture(const ufbx_material *material, const char *name) { return ufbx_find_prop_texture_len(material, name, strlen(name)); } +ufbx_abi ufbx_string ufbx_find_shader_prop(const ufbx_shader *shader, const char *name) { return ufbx_find_shader_prop_len(shader, name, strlen(name)); } +ufbx_abi ufbx_shader_prop_binding_list ufbx_find_shader_prop_bindings(const ufbx_shader *shader, const char *name) { return ufbx_find_shader_prop_bindings_len(shader, name, strlen(name)); } +ufbx_abi ufbx_shader_texture_input *ufbx_find_shader_texture_input(const ufbx_shader_texture *shader, const char *name) { return ufbx_find_shader_texture_input_len(shader, name, strlen(name)); } +ufbx_abi ufbx_dom_node *ufbx_dom_find(const ufbx_dom_node *parent, const char *name) { return ufbx_dom_find_len(parent, name, strlen(name)); } + +// -- Catch API + +ufbx_abi uint32_t ufbx_triangulate_face(uint32_t *indices, size_t num_indices, const ufbx_mesh *mesh, ufbx_face face) { + return ufbx_catch_triangulate_face(NULL, indices, num_indices, mesh, face); +} +ufbx_abi void ufbx_compute_topology(const ufbx_mesh *mesh, ufbx_topo_edge *topo, size_t num_topo) { + ufbx_catch_compute_topology(NULL, mesh, topo, num_topo); +} +ufbx_abi uint32_t ufbx_topo_next_vertex_edge(const ufbx_topo_edge *topo, size_t num_topo, uint32_t index) { + return ufbx_catch_topo_next_vertex_edge(NULL, topo, num_topo, index); +} +ufbx_abi uint32_t ufbx_topo_prev_vertex_edge(const ufbx_topo_edge *topo, size_t num_topo, uint32_t index) { + return ufbx_catch_topo_prev_vertex_edge(NULL, topo, num_topo, index); +} +ufbx_abi ufbx_vec3 ufbx_get_weighted_face_normal(const ufbx_vertex_vec3 *positions, ufbx_face face) { + return ufbx_catch_get_weighted_face_normal(NULL, positions, face); +} + #ifdef __cplusplus } #endif #endif +#if defined(UFBX_STRING_PREFIX) + #undef strlen + #undef memcpy + #undef memmove + #undef memset + #undef memchr + #undef memcmp + #undef strcmp + #undef strncmp +#endif + #if defined(_MSC_VER) #pragma warning(pop) #elif defined(__clang__) diff --git a/thirdparty/ufbx/ufbx.h b/thirdparty/ufbx/ufbx.h index 8d856edaad0..1fc9a103655 100644 --- a/thirdparty/ufbx/ufbx.h +++ b/thirdparty/ufbx/ufbx.h @@ -9,10 +9,11 @@ // -- Headers -#include -#include -#include -#include +#if !defined(UFBX_NO_LIBC_TYPES) + #include + #include + #include +#endif // -- Platform @@ -99,7 +100,7 @@ // make sure that it is also used within `ufbx.c`. // Defining `UFBX_NO_ASSERT` to any value disables assertions. #ifndef ufbx_assert - #if defined(UFBX_NO_ASSERT) + #if defined(UFBX_NO_ASSERT) || defined(UFBX_NO_LIBC) #define ufbx_assert(cond) (void)0 #else #include @@ -266,7 +267,7 @@ struct ufbx_converter { }; // `ufbx_source_version` contains the version of the corresponding source file. // HINT: The version can be compared numerically to the result of `ufbx_pack_version()`, // for example `#if UFBX_VERSION >= ufbx_pack_version(0, 12, 0)`. -#define UFBX_HEADER_VERSION ufbx_pack_version(0, 14, 3) +#define UFBX_HEADER_VERSION ufbx_pack_version(0, 15, 0) #define UFBX_VERSION UFBX_HEADER_VERSION // -- Basic types @@ -3984,12 +3985,17 @@ typedef size_t ufbx_read_fn(void *user, void *data, size_t size); // Skip `size` bytes in the file. typedef bool ufbx_skip_fn(void *user, size_t size); +// Get the size of the file. +// Return `0` if unknown, `UINT64_MAX` if error. +typedef uint64_t ufbx_size_fn(void *user); + // Close the file typedef void ufbx_close_fn(void *user); typedef struct ufbx_stream { ufbx_read_fn *read_fn; // < Required ufbx_skip_fn *skip_fn; // < Optional: Will use `read_fn()` if missing + ufbx_size_fn *size_fn; // < Optional ufbx_close_fn *close_fn; // < Optional // Context passed to other functions @@ -4006,13 +4012,17 @@ typedef enum ufbx_open_file_type UFBX_ENUM_REPR { UFBX_ENUM_TYPE(ufbx_open_file_type, UFBX_OPEN_FILE_TYPE, UFBX_OPEN_FILE_OBJ_MTL); +typedef uintptr_t ufbx_open_file_context; + typedef struct ufbx_open_file_info { + // Context that can be passed to the following functions to use a shared allocator: + // ufbx_open_file_ctx() + // ufbx_open_memory_ctx() + ufbx_open_file_context context; + // Kind of file to load. ufbx_open_file_type type; - // Temporary allocator to use. - ufbx_allocator temp_allocator; - // Original filename in the file, not resolved or UTF-8 encoded. // NOTE: Not necessarily NULL-terminated! ufbx_blob original_filename; @@ -4030,6 +4040,19 @@ typedef struct ufbx_open_file_cb { (stream, path, path_len, info)) } ufbx_open_file_cb; +// Options for `ufbx_open_file()`. +typedef struct ufbx_open_file_opts { + uint32_t _begin_zero; + + // Allocator to allocate the memory with. + ufbx_allocator_opts allocator; + + // The filename is guaranteed to be NULL-terminated. + ufbx_unsafe bool filename_null_terminated; + + uint32_t _end_zero; +} ufbx_open_file_opts; + // Memory stream options typedef void ufbx_close_memory_fn(void *user, void *data, size_t data_size); @@ -5092,6 +5115,7 @@ ufbx_abi_data const size_t ufbx_element_type_size[UFBX_ELEMENT_TYPE_COUNT]; // Version of the source file, comparable to `UFBX_HEADER_VERSION` ufbx_abi_data const uint32_t ufbx_source_version; + // Practically always `true` (see below), if not you need to be careful with threads. // // Guaranteed to be `true` in _any_ of the following conditions: @@ -5160,23 +5184,23 @@ ufbx_abi size_t ufbx_format_error(char *dst, size_t dst_size, const ufbx_error * // Find a property `name` from `props`, returns `NULL` if not found. // Searches through `ufbx_props.defaults` as well. ufbx_abi ufbx_prop *ufbx_find_prop_len(const ufbx_props *props, const char *name, size_t name_len); -ufbx_inline ufbx_prop *ufbx_find_prop(const ufbx_props *props, const char *name) { return ufbx_find_prop_len(props, name, strlen(name));} +ufbx_abi ufbx_prop *ufbx_find_prop(const ufbx_props *props, const char *name); // Utility functions for finding the value of a property, returns `def` if not found. // NOTE: For `ufbx_string` you need to ensure the lifetime of the default is // sufficient as no copy is made. ufbx_abi ufbx_real ufbx_find_real_len(const ufbx_props *props, const char *name, size_t name_len, ufbx_real def); -ufbx_inline ufbx_real ufbx_find_real(const ufbx_props *props, const char *name, ufbx_real def) { return ufbx_find_real_len(props, name, strlen(name), def); } +ufbx_abi ufbx_real ufbx_find_real(const ufbx_props *props, const char *name, ufbx_real def); ufbx_abi ufbx_vec3 ufbx_find_vec3_len(const ufbx_props *props, const char *name, size_t name_len, ufbx_vec3 def); -ufbx_inline ufbx_vec3 ufbx_find_vec3(const ufbx_props *props, const char *name, ufbx_vec3 def) { return ufbx_find_vec3_len(props, name, strlen(name), def); } +ufbx_abi ufbx_vec3 ufbx_find_vec3(const ufbx_props *props, const char *name, ufbx_vec3 def); ufbx_abi int64_t ufbx_find_int_len(const ufbx_props *props, const char *name, size_t name_len, int64_t def); -ufbx_inline int64_t ufbx_find_int(const ufbx_props *props, const char *name, int64_t def) { return ufbx_find_int_len(props, name, strlen(name), def); } +ufbx_abi int64_t ufbx_find_int(const ufbx_props *props, const char *name, int64_t def); ufbx_abi bool ufbx_find_bool_len(const ufbx_props *props, const char *name, size_t name_len, bool def); -ufbx_inline bool ufbx_find_bool(const ufbx_props *props, const char *name, bool def) { return ufbx_find_bool_len(props, name, strlen(name), def); } +ufbx_abi bool ufbx_find_bool(const ufbx_props *props, const char *name, bool def); ufbx_abi ufbx_string ufbx_find_string_len(const ufbx_props *props, const char *name, size_t name_len, ufbx_string def); -ufbx_inline ufbx_string ufbx_find_string(const ufbx_props *props, const char *name, ufbx_string def) { return ufbx_find_string_len(props, name, strlen(name), def); } +ufbx_abi ufbx_string ufbx_find_string(const ufbx_props *props, const char *name, ufbx_string def); ufbx_abi ufbx_blob ufbx_find_blob_len(const ufbx_props *props, const char *name, size_t name_len, ufbx_blob def); -ufbx_inline ufbx_blob ufbx_find_blob(const ufbx_props *props, const char *name, ufbx_blob def) { return ufbx_find_blob_len(props, name, strlen(name), def); } +ufbx_abi ufbx_blob ufbx_find_blob(const ufbx_props *props, const char *name, ufbx_blob def); // Find property in `props` with concatendated `parts[num_parts]`. ufbx_abi ufbx_prop *ufbx_find_prop_concat(const ufbx_props *props, const ufbx_string *parts, size_t num_parts); @@ -5186,30 +5210,30 @@ ufbx_abi ufbx_element *ufbx_get_prop_element(const ufbx_element *element, const // Find an element connected to a property by name. ufbx_abi ufbx_element *ufbx_find_prop_element_len(const ufbx_element *element, const char *name, size_t name_len, ufbx_element_type type); -ufbx_inline ufbx_element *ufbx_find_prop_element(const ufbx_element *element, const char *name, ufbx_element_type type) { return ufbx_find_prop_element_len(element, name, strlen(name), type); } +ufbx_abi ufbx_element *ufbx_find_prop_element(const ufbx_element *element, const char *name, ufbx_element_type type); // Find any element of type `type` in `scene` by `name`. // For example if you want to find `ufbx_material` named `Mat`: // (ufbx_material*)ufbx_find_element(scene, UFBX_ELEMENT_MATERIAL, "Mat"); ufbx_abi ufbx_element *ufbx_find_element_len(const ufbx_scene *scene, ufbx_element_type type, const char *name, size_t name_len); -ufbx_inline ufbx_element *ufbx_find_element(const ufbx_scene *scene, ufbx_element_type type, const char *name) { return ufbx_find_element_len(scene, type, name, strlen(name)); } +ufbx_abi ufbx_element *ufbx_find_element(const ufbx_scene *scene, ufbx_element_type type, const char *name); // Find node in `scene` by `name` (shorthand for `ufbx_find_element(UFBX_ELEMENT_NODE)`). ufbx_abi ufbx_node *ufbx_find_node_len(const ufbx_scene *scene, const char *name, size_t name_len); -ufbx_inline ufbx_node *ufbx_find_node(const ufbx_scene *scene, const char *name) { return ufbx_find_node_len(scene, name, strlen(name)); } +ufbx_abi ufbx_node *ufbx_find_node(const ufbx_scene *scene, const char *name); // Find an animation stack in `scene` by `name` (shorthand for `ufbx_find_element(UFBX_ELEMENT_ANIM_STACK)`) ufbx_abi ufbx_anim_stack *ufbx_find_anim_stack_len(const ufbx_scene *scene, const char *name, size_t name_len); -ufbx_inline ufbx_anim_stack *ufbx_find_anim_stack(const ufbx_scene *scene, const char *name) { return ufbx_find_anim_stack_len(scene, name, strlen(name)); } +ufbx_abi ufbx_anim_stack *ufbx_find_anim_stack(const ufbx_scene *scene, const char *name); // Find a material in `scene` by `name` (shorthand for `ufbx_find_element(UFBX_ELEMENT_MATERIAL)`). ufbx_abi ufbx_material *ufbx_find_material_len(const ufbx_scene *scene, const char *name, size_t name_len); -ufbx_inline ufbx_material *ufbx_find_material(const ufbx_scene *scene, const char *name) { return ufbx_find_material_len(scene, name, strlen(name)); } +ufbx_abi ufbx_material *ufbx_find_material(const ufbx_scene *scene, const char *name); // Find a single animated property `prop` of `element` in `layer`. // Returns `NULL` if not found. ufbx_abi ufbx_anim_prop *ufbx_find_anim_prop_len(const ufbx_anim_layer *layer, const ufbx_element *element, const char *prop, size_t prop_len); -ufbx_inline ufbx_anim_prop *ufbx_find_anim_prop(const ufbx_anim_layer *layer, const ufbx_element *element, const char *prop) { return ufbx_find_anim_prop_len(layer, element, prop, strlen(prop)); } +ufbx_abi ufbx_anim_prop *ufbx_find_anim_prop(const ufbx_anim_layer *layer, const ufbx_element *element, const char *prop); // Find all animated properties of `element` in `layer`. ufbx_abi ufbx_anim_prop_list ufbx_find_anim_props(const ufbx_anim_layer *layer, const ufbx_element *element); @@ -5228,16 +5252,18 @@ ufbx_abi ufbx_matrix ufbx_get_compatible_matrix_for_normals(const ufbx_node *nod // but the rest can be uninitialized. ufbx_abi ptrdiff_t ufbx_inflate(void *dst, size_t dst_size, const ufbx_inflate_input *input, ufbx_inflate_retain *retain); -// Open a `ufbx_stream` from a file. -// Use `path_len == SIZE_MAX` for NULL terminated string. -ufbx_abi bool ufbx_open_file(ufbx_stream *stream, const char *path, size_t path_len); - // Same as `ufbx_open_file()` but compatible with the callback in `ufbx_open_file_fn`. // The `user` parameter is actually not used here. ufbx_abi bool ufbx_default_open_file(void *user, ufbx_stream *stream, const char *path, size_t path_len, const ufbx_open_file_info *info); +// Open a `ufbx_stream` from a file. +// Use `path_len == SIZE_MAX` for NULL terminated string. +ufbx_abi bool ufbx_open_file(ufbx_stream *stream, const char *path, size_t path_len, const ufbx_open_file_opts *opts, ufbx_error *error); +ufbx_unsafe ufbx_abi bool ufbx_open_file_ctx(ufbx_stream *stream, ufbx_open_file_context ctx, const char *path, size_t path_len, const ufbx_open_file_opts *opts, ufbx_error *error); + // NOTE: Uses the default ufbx allocator! ufbx_abi bool ufbx_open_memory(ufbx_stream *stream, const void *data, size_t data_size, const ufbx_open_memory_opts *opts, ufbx_error *error); +ufbx_unsafe ufbx_abi bool ufbx_open_memory_ctx(ufbx_stream *stream, ufbx_open_file_context ctx, const void *data, size_t data_size, const ufbx_open_memory_opts *opts, ufbx_error *error); // Animation evaluation @@ -5252,9 +5278,7 @@ ufbx_abi ufbx_vec3 ufbx_evaluate_anim_value_vec3(const ufbx_anim_value *anim_val // Evaluate an animated property `name` from `element` at `time`. // NOTE: If the property is not found it will have the flag `UFBX_PROP_FLAG_NOT_FOUND`. ufbx_abi ufbx_prop ufbx_evaluate_prop_len(const ufbx_anim *anim, const ufbx_element *element, const char *name, size_t name_len, double time); -ufbx_inline ufbx_prop ufbx_evaluate_prop(const ufbx_anim *anim, const ufbx_element *element, const char *name, double time) { - return ufbx_evaluate_prop_len(anim, element, name, strlen(name), time); -} +ufbx_abi ufbx_prop ufbx_evaluate_prop(const ufbx_anim *anim, const ufbx_element *element, const char *name, double time); // Evaluate all _animated_ properties of `element`. // HINT: This function returns an `ufbx_props` structure with the original properties as @@ -5351,27 +5375,19 @@ ufbx_abi ufbx_bone_pose *ufbx_get_bone_pose(const ufbx_pose *pose, const ufbx_no // Find a texture for a given material FBX property. ufbx_abi ufbx_texture *ufbx_find_prop_texture_len(const ufbx_material *material, const char *name, size_t name_len); -ufbx_inline ufbx_texture *ufbx_find_prop_texture(const ufbx_material *material, const char *name) { - return ufbx_find_prop_texture_len(material, name, strlen(name)); -} +ufbx_abi ufbx_texture *ufbx_find_prop_texture(const ufbx_material *material, const char *name); // Find a texture for a given shader property. ufbx_abi ufbx_string ufbx_find_shader_prop_len(const ufbx_shader *shader, const char *name, size_t name_len); -ufbx_inline ufbx_string ufbx_find_shader_prop(const ufbx_shader *shader, const char *name) { - return ufbx_find_shader_prop_len(shader, name, strlen(name)); -} +ufbx_abi ufbx_string ufbx_find_shader_prop(const ufbx_shader *shader, const char *name); // Map from a shader property to material property. ufbx_abi ufbx_shader_prop_binding_list ufbx_find_shader_prop_bindings_len(const ufbx_shader *shader, const char *name, size_t name_len); -ufbx_inline ufbx_shader_prop_binding_list ufbx_find_shader_prop_bindings(const ufbx_shader *shader, const char *name) { - return ufbx_find_shader_prop_bindings_len(shader, name, strlen(name)); -} +ufbx_abi ufbx_shader_prop_binding_list ufbx_find_shader_prop_bindings(const ufbx_shader *shader, const char *name); // Find an input in a shader texture. ufbx_abi ufbx_shader_texture_input *ufbx_find_shader_texture_input_len(const ufbx_shader_texture *shader, const char *name, size_t name_len); -ufbx_inline ufbx_shader_texture_input *ufbx_find_shader_texture_input(const ufbx_shader_texture *shader, const char *name) { - return ufbx_find_shader_texture_input_len(shader, name, strlen(name)); -} +ufbx_abi ufbx_shader_texture_input *ufbx_find_shader_texture_input(const ufbx_shader_texture *shader, const char *name); // Math @@ -5471,37 +5487,27 @@ ufbx_abi uint32_t ufbx_find_face_index(ufbx_mesh *mesh, size_t index); // NOTE: You need to space for `(face.num_indices - 2) * 3 - 1` indices! // HINT: Using `ufbx_mesh.max_face_triangles * 3` is always safe. ufbx_abi uint32_t ufbx_catch_triangulate_face(ufbx_panic *panic, uint32_t *indices, size_t num_indices, const ufbx_mesh *mesh, ufbx_face face); -ufbx_inline uint32_t ufbx_triangulate_face(uint32_t *indices, size_t num_indices, const ufbx_mesh *mesh, ufbx_face face) { - return ufbx_catch_triangulate_face(NULL, indices, num_indices, mesh, face); -} +ufbx_abi uint32_t ufbx_triangulate_face(uint32_t *indices, size_t num_indices, const ufbx_mesh *mesh, ufbx_face face); // Generate the half-edge representation of `mesh` to `topo[mesh->num_indices]` ufbx_abi void ufbx_catch_compute_topology(ufbx_panic *panic, const ufbx_mesh *mesh, ufbx_topo_edge *topo, size_t num_topo); -ufbx_inline void ufbx_compute_topology(const ufbx_mesh *mesh, ufbx_topo_edge *topo, size_t num_topo) { - ufbx_catch_compute_topology(NULL, mesh, topo, num_topo); -} +ufbx_abi void ufbx_compute_topology(const ufbx_mesh *mesh, ufbx_topo_edge *topo, size_t num_topo); // Get the next/previous edge around a vertex // NOTE: Does not return the half-edge on the opposite side (ie. `topo[index].twin`) // Get the next half-edge in `topo`. ufbx_abi uint32_t ufbx_catch_topo_next_vertex_edge(ufbx_panic *panic, const ufbx_topo_edge *topo, size_t num_topo, uint32_t index); -ufbx_inline uint32_t ufbx_topo_next_vertex_edge(const ufbx_topo_edge *topo, size_t num_topo, uint32_t index) { - return ufbx_catch_topo_next_vertex_edge(NULL, topo, num_topo, index); -} +ufbx_abi uint32_t ufbx_topo_next_vertex_edge(const ufbx_topo_edge *topo, size_t num_topo, uint32_t index); // Get the previous half-edge in `topo`. ufbx_abi uint32_t ufbx_catch_topo_prev_vertex_edge(ufbx_panic *panic, const ufbx_topo_edge *topo, size_t num_topo, uint32_t index); -ufbx_inline uint32_t ufbx_topo_prev_vertex_edge(const ufbx_topo_edge *topo, size_t num_topo, uint32_t index) { - return ufbx_catch_topo_prev_vertex_edge(NULL, topo, num_topo, index); -} +ufbx_abi uint32_t ufbx_topo_prev_vertex_edge(const ufbx_topo_edge *topo, size_t num_topo, uint32_t index); // Calculate a normal for a given face. // The returned normal is weighted by face area. ufbx_abi ufbx_vec3 ufbx_catch_get_weighted_face_normal(ufbx_panic *panic, const ufbx_vertex_vec3 *positions, ufbx_face face); -ufbx_inline ufbx_vec3 ufbx_get_weighted_face_normal(const ufbx_vertex_vec3 *positions, ufbx_face face) { - return ufbx_catch_get_weighted_face_normal(NULL, positions, face); -} +ufbx_abi ufbx_vec3 ufbx_get_weighted_face_normal(const ufbx_vertex_vec3 *positions, ufbx_face face); // Generate indices for normals from the topology. // Respects smoothing groups. @@ -5558,7 +5564,7 @@ ufbx_abi size_t ufbx_sample_geometry_cache_vec3(const ufbx_cache_channel *channe // Find a DOM node given a name. ufbx_abi ufbx_dom_node *ufbx_dom_find_len(const ufbx_dom_node *parent, const char *name, size_t name_len); -ufbx_inline ufbx_dom_node *ufbx_dom_find(const ufbx_dom_node *parent, const char *name) { return ufbx_dom_find_len(parent, name, strlen(name)); } +ufbx_abi ufbx_dom_node *ufbx_dom_find(const ufbx_dom_node *parent, const char *name); // Utility