diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt index 5cc6f7971b55..cff3642cb63e 100644 --- a/COPYRIGHT.txt +++ b/COPYRIGHT.txt @@ -240,6 +240,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 aac048e93f7e..250f39b08a32 100644 --- a/core/config/engine.cpp +++ b/core/config/engine.cpp @@ -248,6 +248,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/os/os.cpp b/core/os/os.cpp index 4a833645f06e..ff713ab3027a 100644 --- a/core/os/os.cpp +++ b/core/os/os.cpp @@ -518,6 +518,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/string/ustring.cpp b/core/string/ustring.cpp index 0e553a5680b6..9238e74ab188 100644 --- a/core/string/ustring.cpp +++ b/core/string/ustring.cpp @@ -65,6 +65,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()); @@ -424,11 +433,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); @@ -629,12 +634,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; @@ -668,12 +668,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; @@ -1109,17 +1104,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_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]); + 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]); + + 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]); } @@ -1132,6 +1131,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); @@ -5286,7 +5289,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(); @@ -5298,8 +5301,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; } @@ -5308,10 +5310,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/doc/classes/PackedFloat32Array.xml b/doc/classes/PackedFloat32Array.xml index d421993b8fd9..237b659a5225 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 4622d632586c..56cc08d8c409 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 3a3596b2d02b..5bb2ea97ef95 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 b82d0de350e3..2ed854895881 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 000000000000..a431eabf4e85 --- /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 9265e6b3459c..258d68752ea6 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 b72a1932f830..f78233b9b33f 100644 --- a/drivers/d3d12/rendering_device_driver_d3d12.cpp +++ b/drivers/d3d12/rendering_device_driver_d3d12.cpp @@ -6144,6 +6144,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 9b81430d45c0..886918a2f76e 100644 --- a/drivers/gles3/storage/light_storage.cpp +++ b/drivers/gles3/storage/light_storage.cpp @@ -1405,7 +1405,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/editor/connections_dialog.cpp b/editor/connections_dialog.cpp index d76c324be05c..b96c715bda7d 100644 --- a/editor/connections_dialog.cpp +++ b/editor/connections_dialog.cpp @@ -907,7 +907,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/editor_inspector.cpp b/editor/editor_inspector.cpp index 1e9c661b2401..f936a06e20e1 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -69,6 +69,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")); diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h index 2e4633ccea98..e87d90dc8f3d 100644 --- a/editor/editor_inspector.h +++ b/editor/editor_inspector.h @@ -160,6 +160,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); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 222696a60846..0dd05d4e3307 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -1427,7 +1427,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 { @@ -1436,18 +1436,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...")); @@ -7173,6 +7170,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); diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index e6c944581cd4..c54c97437179 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -91,6 +91,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 { @@ -104,6 +108,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()); @@ -150,10 +155,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); } @@ -182,6 +191,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); } diff --git a/editor/editor_property_name_processor.cpp b/editor/editor_property_name_processor.cpp index ca8854f79702..3560174e6b51 100644 --- a/editor/editor_property_name_processor.cpp +++ b/editor/editor_property_name_processor.cpp @@ -235,6 +235,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/export/export_template_manager.cpp b/editor/export/export_template_manager.cpp index a90c16f66e69..1e1999ddb702 100644 --- a/editor/export/export_template_manager.cpp +++ b/editor/export/export_template_manager.cpp @@ -437,6 +437,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); @@ -512,7 +519,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 f68dc7173462..2fdf96cf71e7 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -245,6 +245,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 @@ -371,6 +373,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); @@ -702,19 +705,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)); } @@ -723,17 +728,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) { diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h index 73462df36f12..7b10d8b3ef7a 100644 --- a/editor/filesystem_dock.h +++ b/editor/filesystem_dock.h @@ -133,6 +133,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 6f257f492d40..8ece6f5dfef2 100644 --- a/editor/gui/editor_file_dialog.cpp +++ b/editor/gui/editor_file_dialog.cpp @@ -44,7 +44,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" @@ -1400,11 +1399,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 { @@ -1571,6 +1567,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(); @@ -1579,6 +1577,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(); @@ -1768,6 +1768,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() { @@ -2354,7 +2357,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"); @@ -2473,7 +2477,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 a151f2b38f74..8a07a2094372 100644 --- a/editor/gui/editor_file_dialog.h +++ b/editor/gui/editor_file_dialog.h @@ -44,6 +44,7 @@ class MenuButton; class OptionButton; class PopupMenu; class TextureRect; +class VSeparator; class EditorFileDialog : public ConfirmationDialog { GDCLASS(EditorFileDialog, ConfirmationDialog); @@ -89,6 +90,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/plugins/gdextension_export_plugin.h b/editor/plugins/gdextension_export_plugin.h index c8ed05c6b70e..9189441c2bae 100644 --- a/editor/plugins/gdextension_export_plugin.h +++ b/editor/plugins/gdextension_export_plugin.h @@ -73,6 +73,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 85b84262f7c2..d74c434410df 100644 --- a/editor/plugins/gizmos/reflection_probe_gizmo_plugin.cpp +++ b/editor/plugins/gizmos/reflection_probe_gizmo_plugin.cpp @@ -47,7 +47,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))); @@ -165,22 +165,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/project_manager/project_dialog.cpp b/editor/project_manager/project_dialog.cpp index 01dc9229090a..82e2bda0ac27 100644 --- a/editor/project_manager/project_dialog.cpp +++ b/editor/project_manager/project_dialog.cpp @@ -132,6 +132,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. } @@ -604,6 +611,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; @@ -636,7 +650,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/themes/editor_fonts.cpp b/editor/themes/editor_fonts.cpp index da35ed332ee8..8ae4889214b8 100644 --- a/editor/themes/editor_fonts.cpp +++ b/editor/themes/editor_fonts.cpp @@ -169,7 +169,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); @@ -192,7 +192,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/modules/camera/config.py b/modules/camera/config.py index 7b368d219368..fa229ef2f5a8 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 cc4595929f31..eed5a7673792 100644 --- a/modules/csg/csg_shape.cpp +++ b/modules/csg/csg_shape.cpp @@ -30,6 +30,9 @@ #include "csg_shape.h" +#ifdef DEV_ENABLED +#include "core/io/json.h" +#endif // DEV_ENABLED #include "core/math/geometry_2d.h" #include @@ -142,6 +145,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; @@ -154,6 +158,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) { @@ -232,13 +237,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]; @@ -246,7 +349,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); @@ -291,12 +393,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))); } } @@ -330,7 +442,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); @@ -346,7 +458,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); @@ -834,8 +946,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); @@ -864,7 +978,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_"); diff --git a/modules/csg/csg_shape.h b/modules/csg/csg_shape.h index 8f23ae2f9e2c..9aa612885aa8 100644 --- a/modules/csg/csg_shape.h +++ b/modules/csg/csg_shape.h @@ -155,8 +155,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 ac62d8dd83dc..8ea471e62d83 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/interactive_music/audio_stream_interactive.cpp b/modules/interactive_music/audio_stream_interactive.cpp index 8656be988d98..995fa15015d2 100644 --- a/modules/interactive_music/audio_stream_interactive.cpp +++ b/modules/interactive_music/audio_stream_interactive.cpp @@ -691,9 +691,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 12d3ce8aad86..9710a7fce11e 100644 --- a/modules/interactive_music/audio_stream_interactive.h +++ b/modules/interactive_music/audio_stream_interactive.h @@ -100,7 +100,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 e38a57ba753a..ad67e778eec2 100644 --- a/modules/interactive_music/audio_stream_synchronized.cpp +++ b/modules/interactive_music/audio_stream_synchronized.cpp @@ -99,6 +99,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 a2d8c5540443..83798a13bd5b 100644 --- a/modules/interactive_music/audio_stream_synchronized.h +++ b/modules/interactive_music/audio_stream_synchronized.h @@ -54,6 +54,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/openxr/SCsub b/modules/openxr/SCsub index 48c87bcd59cd..edfe0bbfad6f 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/raycast/raycast_occlusion_cull.cpp b/modules/raycast/raycast_occlusion_cull.cpp index b136d54520a1..fb2d0023b60f 100644 --- a/modules/raycast/raycast_occlusion_cull.cpp +++ b/modules/raycast/raycast_occlusion_cull.cpp @@ -366,7 +366,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; diff --git a/modules/svg/SCsub b/modules/svg/SCsub index af8f6c14f4ed..83321cf4493b 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/platform/linuxbsd/detect.py b/platform/linuxbsd/detect.py index c8202b147d25..6c9f33684901 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 a44c86202e2d..7b50950646da 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 69ba742f72b1..7cd77dd5937c 100644 --- a/platform/linuxbsd/export/export_plugin.cpp +++ b/platform/linuxbsd/export/export_plugin.cpp @@ -180,7 +180,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" @@ -282,6 +282,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 b309e8d8ebb7..9fba97d552a0 100644 --- a/platform/linuxbsd/os_linuxbsd.cpp +++ b/platform/linuxbsd/os_linuxbsd.cpp @@ -184,7 +184,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 46fa1947e87d..1298a416e0cc 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 f1aac4f0696c..8e3dc7558184 100644 --- a/platform/linuxbsd/wayland/wayland_thread.cpp +++ b/platform/linuxbsd/wayland/wayland_thread.cpp @@ -32,8 +32,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_methods.py b/platform_methods.py index 201df3c0b547..c8646a402267 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/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 155824db3f33..ea0b8cd80864 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -364,7 +364,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); @@ -501,7 +502,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)); @@ -625,8 +627,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++; } @@ -977,6 +979,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; } @@ -2548,6 +2551,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; @@ -4295,7 +4302,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; @@ -4377,7 +4383,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) { @@ -4650,44 +4656,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 00b3280a22f3..d9aac546ca07 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -153,6 +153,7 @@ class RichTextLabel : public Control { Color dc_ol_color; Vector2 offset; + float indent = 0.0; int char_offset = 0; int char_count = 0; @@ -205,6 +206,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/resources/SCsub b/scene/resources/SCsub index 46f6251b9131..ae2a9b8c3a79 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/audio_stream_wav.cpp b/scene/resources/audio_stream_wav.cpp index cea9af729eb0..15e3f0f806b5 100644 --- a/scene/resources/audio_stream_wav.cpp +++ b/scene/resources/audio_stream_wav.cpp @@ -1142,13 +1142,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; @@ -1170,9 +1170,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(); @@ -1182,16 +1182,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 { @@ -1202,26 +1210,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 269ab1e05f12..e36d33cfa933 100644 --- a/scene/resources/audio_stream_wav.h +++ b/scene/resources/audio_stream_wav.h @@ -31,9 +31,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" @@ -273,6 +270,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/servers/audio/effects/audio_effect_record.cpp b/servers/audio/effects/audio_effect_record.cpp index f82a6fa3afb2..b6bc5d00c4fe 100644 --- a/servers/audio/effects/audio_effect_record.cpp +++ b/servers/audio/effects/audio_effect_record.cpp @@ -250,6 +250,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 18ee8630838d..3e0caeea6594 100644 --- a/servers/register_server_types.cpp +++ b/servers/register_server_types.cpp @@ -70,6 +70,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" @@ -210,6 +211,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_mobile/render_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp index e93b7f0339d5..e2d6796566b6 100644 --- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp +++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp @@ -1005,13 +1005,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 4525b50b6e5e..6274dd764407 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 @@ -238,6 +238,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 @@ -328,7 +329,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 e2549d1f0062..a40c5446c74a 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 @@ -67,6 +67,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; @@ -75,7 +78,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; }; @@ -97,9 +100,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; }; }; @@ -111,6 +123,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_scene_render_rd.cpp b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp index 7ef42aa79147..23685473fea0 100644 --- a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp @@ -35,9 +35,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) { @@ -1452,6 +1456,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 6234cddee393..fb89c11a1b40 100644 --- a/servers/rendering/renderer_rd/shader_rd.cpp +++ b/servers/rendering/renderer_rd/shader_rd.cpp @@ -37,6 +37,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 @@ -46,7 +47,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; @@ -78,6 +80,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 e102b839b59d..f1b6710383dc 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 da582ec1b46b..f5a806fbca39 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 1e1b6d893710..52b3d5d1e38c 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 c13a99f4a679..27fe67e1f6da 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 highp 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 2cc86482f6f0..5e4719c49881 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 1e8fc7eab482..f58304aa8d33 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 9de37050f0a7..1d309a80063d 100644 --- a/servers/rendering/renderer_rd/storage_rd/light_storage.cpp +++ b/servers/rendering/renderer_rd/storage_rd/light_storage.cpp @@ -2328,7 +2328,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 1a92c5470dcb..b5f1523123de 100644 --- a/servers/rendering/renderer_rd/storage_rd/light_storage.h +++ b/servers/rendering/renderer_rd/storage_rd/light_storage.h @@ -799,6 +799,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/rendering_device.cpp b/servers/rendering/rendering_device.cpp index ace1c34fca09..72984e5abf8b 100644 --- a/servers/rendering/rendering_device.cpp +++ b/servers/rendering/rendering_device.cpp @@ -32,6 +32,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" @@ -189,6 +190,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; } @@ -211,7 +216,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 e41a56b0a32e..9ccf1e41083d 100644 --- a/servers/rendering/rendering_device_binds.cpp +++ b/servers/rendering/rendering_device_binds.cpp @@ -30,6 +30,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(), @@ -144,11 +146,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_include_db.cpp b/servers/rendering/shader_include_db.cpp new file mode 100644 index 000000000000..bc62c1fe0295 --- /dev/null +++ b/servers/rendering/shader_include_db.cpp @@ -0,0 +1,115 @@ +/**************************************************************************/ +/* shader_include_db.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* 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 000000000000..d1967a3296dd --- /dev/null +++ b/servers/rendering/shader_include_db.h @@ -0,0 +1,53 @@ +/**************************************************************************/ +/* shader_include_db.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* 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 69833f44119f..9bf1c2ecad5c 100644 --- a/tests/core/io/test_tcp_server.h +++ b/tests/core/io/test_tcp_server.h @@ -85,8 +85,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; } @@ -183,8 +183,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(); @@ -210,7 +210,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/scene/test_audio_stream_wav.h b/tests/scene/test_audio_stream_wav.h index 7276dd0878c9..30e3f5b9ba47 100644 --- a/tests/scene/test_audio_stream_wav.h +++ b/tests/scene/test_audio_stream_wav.h @@ -37,11 +37,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. @@ -148,25 +143,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()); @@ -177,7 +155,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/servers/test_text_server.h b/tests/servers/test_text_server.h index 4190d3c23a4b..4e20d43efc29 100644 --- a/tests/servers/test_text_server.h +++ b/tests/servers/test_text_server.h @@ -124,7 +124,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); @@ -180,7 +180,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; @@ -503,7 +503,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(); @@ -584,7 +584,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 1e11c6c9725b..144ca870a4e5 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: @@ -928,7 +928,7 @@ number and run the script. ## 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 000000000000..be66b3824e97 --- /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 e9a834ac6907..000000000000 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 b0c0aaf7666d..000000000000 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 000000000000..b9b85b8549c4 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 000000000000..1f8d6600a7b4 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 a5461943825c..598175403cda 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 000000000000..f104d9df8547 --- /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 17df744ee965..2b3e528c6bbe 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 8f98fe29895c..a05fce3aa6b6 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 f2a2f692afed..5c24dc5a2050 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 9582b2c12638..59cc0293d6da 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 221cefac1bb7..a434939494c6 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 32f79e5b601f..911133776ab3 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 6008b5f8bcdc..000000000000 --- 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 000000000000..7f7d366dfa1c --- /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 23612bb0bfb4..f0f44214d813 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 e00e91a6967b..a15f3262a82b 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 fc2faca29f95..6df6f52d0476 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 4303092a5ea7..1ee898ca6ff2 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/src/common/tvgCompressor.cpp b/thirdparty/thorvg/src/common/tvgCompressor.cpp index aebe9a4ef164..714f21e07c9d 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 9d704900a50e..000000000000 --- 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 d900782b5627..000000000000 --- 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 c03b54e5f8b9..cb7f24ff4030 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) < 1e-3f) { + 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 50786754a159..a917998256fd 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 3f1896680960..1ebdd41c5e92 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 56fdf86fb2d5..6b16b4c7610c 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 6c4c8410e82f..49c9f6e8aa2e 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 fbaaea874319..0db7d2d233f8 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 ad8fb5a69954..3d44edac3a18 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 86a46245d988..cb1306b71a3e 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 05cbb54c852f..85f6d25dc392 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 6f2bd916d5ea..92e23698af2c 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 492cebe3a319..1ab3043c24e8 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 5661c8ae82ce..e64d7afb4113 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 115e81aee122..57442139cdff 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 b048695a234c..00c7408a7e88 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 09fc8aaac137..da1cdae9e0e7 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 ab1fc18b58a5..9371ae6c5ad3 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 631294ad40be..f70ba7a13d61 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 3fc64ce036a9..d2c02bb932df 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 fb809c4f7e91..1ff99f6aece9 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 000000000000..fd8e532e12a3 --- /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 b9e95f88cf79..18ffc18e1e0e 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 a072a88819a9..79cab043f243 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 6d0bd9383dad..d79da0e4d8ce 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 91cf7743c190..fe693b7f33ac 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 88ef2118f278..1162edc8381c 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 116c4e813527..180f3cc37a2b 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 fcd8ad46205f..bd6beb8d85ce 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 42b08de6a581..3e4ad679a8af 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 24c4a9e37255..4408db0b86f2 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 75ac96be04d4..e195f72adf2a 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 29f84eb82a6d..e40859b6db91 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 199e823034f7..c5d2127f9c63 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 15a2cc4ef045..527221625b66 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 ea1010051ee5..19edff5a2cd6 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 47f0c051c025..f249356aa2a7 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 82666b7ae31f..24e2fb8b1beb 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 1b81d81a4f70..a9c1a6854428 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 6a81ddcdbb31..db51fc215a0b 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 37813b19ef7e..536e18785211 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 e43ca239bb7a..d78e9bb3d1c2 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 223cc5b026f2..d3e31d198a3c 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 3a4880cabaa3..bbbc43910595 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 b0ee42db8d0d..eae44a2e8ad9 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 79302f69fa56..993fe6d80f8d 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 f5809cf93b56..ce169d33ba67 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 190ecd31b91d..7972ae33fb10 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 3b9293a00e05..269d951f05a3 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 440fb312b6c0..42f815206062 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 d762492f22dc..6c4b6da1de44 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 d54c78783c5d..b324b95049e9 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 cb9a76c05218..11e01b58ce6d 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 067e35b1f0d8..991f73fc542c 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 2c5d84d266cb..f9953f2fc9e5 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 2d13c78bd8c3..77bd3dea623f 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 8d856edaad09..1fc9a103655b 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