diff options
33 files changed, 615 insertions, 442 deletions
diff --git a/SConstruct b/SConstruct index 7b0c644aea..92dc4d9da2 100644 --- a/SConstruct +++ b/SConstruct @@ -422,7 +422,7 @@ if selected_platform in platform_list: if (can_build): config.configure(env) env.module_list.append(x) - + # Get doc classes paths (if present) try: doc_classes = config.get_doc_classes() @@ -522,13 +522,23 @@ if selected_platform in platform_list: env.AppendUnique(CPPDEFINES=[header[1]]) elif selected_platform != "": + if selected_platform == "list": + print("The following platforms are available:\n") + else: + print('Invalid target platform "' + selected_platform + '".') + print("The following platforms were detected:\n") - print("Invalid target platform: " + selected_platform) - print("The following platforms were detected:") for x in platform_list: print("\t" + x) + print("\nPlease run SCons again and select a valid platform: platform=<string>") + if selected_platform == "list": + # Exit early to suppress the rest of the built-in SCons messages + sys.exit(0) + else: + sys.exit(255) + # The following only makes sense when the env is defined, and assumes it is if 'env' in locals(): screen = sys.stdout diff --git a/core/bind/core_bind.cpp b/core/bind/core_bind.cpp index 34bbdb2c75..b41b84ab1e 100644 --- a/core/bind/core_bind.cpp +++ b/core/bind/core_bind.cpp @@ -2941,6 +2941,10 @@ float _Engine::get_physics_jitter_fix() const { return Engine::get_singleton()->get_physics_jitter_fix(); } +float _Engine::get_physics_interpolation_fraction() const { + return Engine::get_singleton()->get_physics_interpolation_fraction(); +} + void _Engine::set_target_fps(int p_fps) { Engine::get_singleton()->set_target_fps(p_fps); } @@ -3029,6 +3033,7 @@ void _Engine::_bind_methods() { ClassDB::bind_method(D_METHOD("get_iterations_per_second"), &_Engine::get_iterations_per_second); ClassDB::bind_method(D_METHOD("set_physics_jitter_fix", "physics_jitter_fix"), &_Engine::set_physics_jitter_fix); ClassDB::bind_method(D_METHOD("get_physics_jitter_fix"), &_Engine::get_physics_jitter_fix); + ClassDB::bind_method(D_METHOD("get_physics_interpolation_fraction"), &_Engine::get_physics_interpolation_fraction); ClassDB::bind_method(D_METHOD("set_target_fps", "target_fps"), &_Engine::set_target_fps); ClassDB::bind_method(D_METHOD("get_target_fps"), &_Engine::get_target_fps); diff --git a/core/bind/core_bind.h b/core/bind/core_bind.h index 3be5a08752..f0f86e003f 100644 --- a/core/bind/core_bind.h +++ b/core/bind/core_bind.h @@ -746,6 +746,7 @@ public: void set_physics_jitter_fix(float p_threshold); float get_physics_jitter_fix() const; + float get_physics_interpolation_fraction() const; void set_target_fps(int p_fps); int get_target_fps() const; diff --git a/core/engine.cpp b/core/engine.cpp index 2d8473fbd9..0dd0459403 100644 --- a/core/engine.cpp +++ b/core/engine.cpp @@ -227,6 +227,7 @@ Engine::Engine() { frames_drawn = 0; ips = 60; physics_jitter_fix = 0.5; + _physics_interpolation_fraction = 0.0f; _frame_delay = 0; _fps = 1; _target_fps = 0; diff --git a/core/engine.h b/core/engine.h index 15665fee29..192e8e67a0 100644 --- a/core/engine.h +++ b/core/engine.h @@ -63,6 +63,7 @@ private: float _time_scale; bool _pixel_snap; uint64_t _physics_frames; + float _physics_interpolation_fraction; uint64_t _idle_frames; bool _in_physics; @@ -95,6 +96,7 @@ public: bool is_in_physics_frame() const { return _in_physics; } uint64_t get_idle_frame_ticks() const { return _frame_ticks; } float get_idle_frame_step() const { return _frame_step; } + float get_physics_interpolation_fraction() const { return _physics_interpolation_fraction; } void set_time_scale(float p_scale); float get_time_scale() const; diff --git a/doc/classes/Engine.xml b/doc/classes/Engine.xml index 60a807c304..187e13d7bd 100644 --- a/doc/classes/Engine.xml +++ b/doc/classes/Engine.xml @@ -72,6 +72,13 @@ Returns the main loop object (see [MainLoop] and [SceneTree]). </description> </method> + <method name="get_physics_interpolation_fraction" qualifiers="const"> + <return type="float"> + </return> + <description> + Returns the fraction through the current physics tick we are at the time of rendering the frame. This can be used to implement fixed timestep interpolation. + </description> + </method> <method name="get_singleton" qualifiers="const"> <return type="Object"> </return> diff --git a/doc/classes/InputEvent.xml b/doc/classes/InputEvent.xml index f2c00908f6..4c8d83adba 100644 --- a/doc/classes/InputEvent.xml +++ b/doc/classes/InputEvent.xml @@ -17,6 +17,8 @@ <argument index="0" name="with_event" type="InputEvent"> </argument> <description> + Returns [code]true[/code] if the given input event and this input event can be added together (only for events of type [InputEventMouseMotion]). + The given input event's position, global position and speed will be copied. The resulting [code]relative[/code] is a sum of both events. Both events' modifiers have to be identical. </description> </method> <method name="as_text" qualifiers="const"> @@ -32,6 +34,7 @@ <argument index="0" name="action" type="String"> </argument> <description> + Returns a value between 0.0 and 1.0 depending on the given actions' state. Useful for getting the value of events of type [InputEventJoypadMotion]. </description> </method> <method name="is_action" qualifiers="const"> @@ -88,6 +91,7 @@ <argument index="0" name="event" type="InputEvent"> </argument> <description> + Returns [code]true[/code] if the given input event is checking for the same key ([InputEventKey]), button ([InputEventJoypadButton]) or action ([InputEventAction]). </description> </method> <method name="xformed_by" qualifiers="const"> @@ -98,6 +102,7 @@ <argument index="1" name="local_ofs" type="Vector2" default="Vector2( 0, 0 )"> </argument> <description> + Returns a copy of the given input event which has been offset by [code]local_ofs[/code] and transformed by [code]xform[/code]. Relevant for events of type [InputEventMouseButton], [InputEventMouseMotion], [InputEventScreenTouch], [InputEventScreenDrag], [InputEventMagnifyGesture] and [InputEventPanGesture]. </description> </method> </methods> diff --git a/doc/classes/Tree.xml b/doc/classes/Tree.xml index 22c74d4ca5..c2b7901c05 100644 --- a/doc/classes/Tree.xml +++ b/doc/classes/Tree.xml @@ -182,7 +182,7 @@ <argument index="1" name="expand" type="bool"> </argument> <description> - If [code]true[/code], the column will have the "Expand" flag of [Control]. + If [code]true[/code], the column will have the "Expand" flag of [Control]. Columns that have the "Expand" flag will use their "min_width" in a similar fashion to [member Control.size_flags_stretch_ratio]. </description> </method> <method name="set_column_min_width"> @@ -193,7 +193,7 @@ <argument index="1" name="min_width" type="int"> </argument> <description> - Sets the minimum width of a column. + Sets the minimum width of a column. Columns that have the "Expand" flag will use their "min_width" in a similar fashion to [member Control.size_flags_stretch_ratio]. </description> </method> <method name="set_column_title"> diff --git a/doc/classes/TreeItem.xml b/doc/classes/TreeItem.xml index 3a4acb351d..56b4b21525 100644 --- a/doc/classes/TreeItem.xml +++ b/doc/classes/TreeItem.xml @@ -347,6 +347,7 @@ <argument index="2" name="disabled" type="bool"> </argument> <description> + If [code]true[/code], disables the button at index [code]button_idx[/code] in column [code]column[/code]. </description> </method> <method name="set_cell_mode"> diff --git a/drivers/gles2/rasterizer_scene_gles2.cpp b/drivers/gles2/rasterizer_scene_gles2.cpp index ea29af7d9e..c8ceca8d97 100644 --- a/drivers/gles2/rasterizer_scene_gles2.cpp +++ b/drivers/gles2/rasterizer_scene_gles2.cpp @@ -1133,8 +1133,8 @@ void RasterizerSceneGLES2::_add_geometry_with_material(RasterizerStorageGLES2::G LightInstance *li = light_instance_owner.getornull(e->instance->light_instances[i]); - if (li->light_index >= render_light_instance_count) { - continue; // too many + if (li->light_index >= render_light_instance_count || render_light_instances[li->light_index] != li) { + continue; // too many or light_index did not correspond to the light instances to be rendered } if (copy) { diff --git a/drivers/png/resource_saver_png.cpp b/drivers/png/resource_saver_png.cpp index 89e8ee32cc..43a30f055b 100644 --- a/drivers/png/resource_saver_png.cpp +++ b/drivers/png/resource_saver_png.cpp @@ -79,7 +79,7 @@ bool ResourceSaverPNG::recognize(const RES &p_resource) const { void ResourceSaverPNG::get_recognized_extensions(const RES &p_resource, List<String> *p_extensions) const { - if (Object::cast_to<Texture>(*p_resource)) { + if (Object::cast_to<ImageTexture>(*p_resource)) { p_extensions->push_back("png"); } } diff --git a/drivers/unix/dir_access_unix.cpp b/drivers/unix/dir_access_unix.cpp index 251bab5783..d5582d00ed 100644 --- a/drivers/unix/dir_access_unix.cpp +++ b/drivers/unix/dir_access_unix.cpp @@ -136,27 +136,31 @@ String DirAccessUnix::get_next() { return ""; } - //typedef struct stat Stat; - struct stat flags; - String fname = fix_unicode_name(entry->d_name); - String f = current_dir.plus_file(fname); + if (entry->d_type == DT_UNKNOWN) { + //typedef struct stat Stat; + struct stat flags; + + String f = current_dir.plus_file(fname); + + if (stat(f.utf8().get_data(), &flags) == 0) { - if (stat(f.utf8().get_data(), &flags) == 0) { + if (S_ISDIR(flags.st_mode)) { - if (S_ISDIR(flags.st_mode)) { + _cisdir = true; - _cisdir = true; + } else { + + _cisdir = false; + } } else { _cisdir = false; } - } else { - - _cisdir = false; + _cisdir = (entry->d_type == DT_DIR); } _cishidden = (fname != "." && fname != ".." && fname.begins_with(".")); diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp index 4862d4bb5b..e45ff3fee2 100644 --- a/editor/code_editor.cpp +++ b/editor/code_editor.cpp @@ -1426,17 +1426,17 @@ void CodeTextEditor::_on_settings_change() { // AUTO BRACE COMPLETION text_editor->set_auto_brace_completion( - EDITOR_DEF("text_editor/completion/auto_brace_complete", true)); + EDITOR_GET("text_editor/completion/auto_brace_complete")); code_complete_timer->set_wait_time( - EDITOR_DEF("text_editor/completion/code_complete_delay", .3f)); + EDITOR_GET("text_editor/completion/code_complete_delay")); // call hint settings text_editor->set_callhint_settings( - EDITOR_DEF("text_editor/completion/put_callhint_tooltip_below_current_line", true), - EDITOR_DEF("text_editor/completion/callhint_tooltip_offset", Vector2())); + EDITOR_GET("text_editor/completion/put_callhint_tooltip_below_current_line"), + EDITOR_GET("text_editor/completion/callhint_tooltip_offset")); - idle->set_wait_time(EDITOR_DEF("text_editor/completion/idle_parse_delay", 2.0)); + idle->set_wait_time(EDITOR_GET("text_editor/completion/idle_parse_delay")); } void CodeTextEditor::_text_changed_idle_timeout() { @@ -1622,12 +1622,12 @@ CodeTextEditor::CodeTextEditor() { idle = memnew(Timer); add_child(idle); idle->set_one_shot(true); - idle->set_wait_time(EDITOR_DEF("text_editor/completion/idle_parse_delay", 2.0)); + idle->set_wait_time(EDITOR_GET("text_editor/completion/idle_parse_delay")); code_complete_timer = memnew(Timer); add_child(code_complete_timer); code_complete_timer->set_one_shot(true); - code_complete_timer->set_wait_time(EDITOR_DEF("text_editor/completion/code_complete_delay", .3f)); + code_complete_timer->set_wait_time(EDITOR_GET("text_editor/completion/code_complete_delay")); error_line = 0; error_column = 0; diff --git a/editor/editor_asset_installer.cpp b/editor/editor_asset_installer.cpp index 12df91a501..b706f2cae6 100644 --- a/editor/editor_asset_installer.cpp +++ b/editor/editor_asset_installer.cpp @@ -111,18 +111,14 @@ void EditorAssetInstaller::open(const String &p_path, int p_depth) { Map<String, Ref<Texture> > extension_guess; { - extension_guess["png"] = get_icon("Texture", "EditorIcons"); - extension_guess["jpg"] = get_icon("Texture", "EditorIcons"); - extension_guess["tex"] = get_icon("Texture", "EditorIcons"); - extension_guess["atlastex"] = get_icon("Texture", "EditorIcons"); - extension_guess["dds"] = get_icon("Texture", "EditorIcons"); + extension_guess["png"] = get_icon("ImageTexture", "EditorIcons"); + extension_guess["jpg"] = get_icon("ImageTexture", "EditorIcons"); + extension_guess["atlastex"] = get_icon("AtlasTexture", "EditorIcons"); extension_guess["scn"] = get_icon("PackedScene", "EditorIcons"); extension_guess["tscn"] = get_icon("PackedScene", "EditorIcons"); - extension_guess["xml"] = get_icon("PackedScene", "EditorIcons"); - extension_guess["xscn"] = get_icon("PackedScene", "EditorIcons"); - extension_guess["material"] = get_icon("Material", "EditorIcons"); - extension_guess["shd"] = get_icon("Shader", "EditorIcons"); + extension_guess["shader"] = get_icon("Shader", "EditorIcons"); extension_guess["gd"] = get_icon("GDScript", "EditorIcons"); + extension_guess["vs"] = get_icon("VisualScript", "EditorIcons"); } Ref<Texture> generic_extension = get_icon("Object", "EditorIcons"); diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index 2c0449398e..223ca7a108 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -483,7 +483,9 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { // Completion _initial_set("text_editor/completion/idle_parse_delay", 2.0); hints["text_editor/completion/idle_parse_delay"] = PropertyInfo(Variant::REAL, "text_editor/completion/idle_parse_delay", PROPERTY_HINT_RANGE, "0.1, 10, 0.01"); - _initial_set("text_editor/completion/auto_brace_complete", false); + _initial_set("text_editor/completion/auto_brace_complete", true); + _initial_set("text_editor/completion/code_complete_delay", 0.3); + hints["text_editor/completion/code_complete_delay"] = PropertyInfo(Variant::REAL, "text_editor/completion/code_complete_delay", PROPERTY_HINT_RANGE, "0.01, 5, 0.01"); _initial_set("text_editor/completion/put_callhint_tooltip_below_current_line", true); _initial_set("text_editor/completion/callhint_tooltip_offset", Vector2()); _initial_set("text_editor/completion/complete_file_paths", true); diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index 947d96f897..8e332ad20e 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -1298,7 +1298,7 @@ void FileSystemDock::_rename_operation_confirm() { _try_move_item(to_rename, new_path, file_renames, folder_renames); int current_tab = editor->get_current_tab(); - + _save_scenes_after_move(file_renames); // save scenes before updating _update_dependencies_after_move(file_renames); _update_resource_paths_after_move(file_renames); _update_project_settings_after_move(file_renames); @@ -1407,7 +1407,7 @@ void FileSystemDock::_move_operation_confirm(const String &p_to_path, bool overw if (is_moved) { int current_tab = editor->get_current_tab(); - + _save_scenes_after_move(file_renames); //save scenes before updating _update_dependencies_after_move(file_renames); _update_resource_paths_after_move(file_renames); _update_project_settings_after_move(file_renames); diff --git a/editor/plugins/asset_library_editor_plugin.cpp b/editor/plugins/asset_library_editor_plugin.cpp index 1503258ff5..31b11d8bea 100644 --- a/editor/plugins/asset_library_editor_plugin.cpp +++ b/editor/plugins/asset_library_editor_plugin.cpp @@ -177,6 +177,8 @@ void EditorAssetLibraryItemDescription::set_image(int p_type, int p_index, const tex->create_from_image(thumbnail); preview_images[i].button->set_icon(tex); + // Make it clearer that clicking it will open an external link + preview_images[i].button->set_default_cursor_shape(CURSOR_POINTING_HAND); } else { preview_images[i].button->set_icon(p_image); } @@ -403,54 +405,60 @@ void EditorAssetLibraryItemDownload::configure(const String &p_title, int p_asse void EditorAssetLibraryItemDownload::_notification(int p_what) { - if (p_what == NOTIFICATION_PROCESS) { + switch (p_what) { - // Make the progress bar visible again when retrying the download - progress->set_modulate(Color(1, 1, 1, 1)); + case NOTIFICATION_READY: { - if (download->get_downloaded_bytes() > 0) { - progress->set_max(download->get_body_size()); - progress->set_value(download->get_downloaded_bytes()); - } + add_style_override("panel", get_stylebox("panel", "TabContainer")); + } break; + case NOTIFICATION_PROCESS: { - int cstatus = download->get_http_client_status(); + // Make the progress bar visible again when retrying the download. + progress->set_modulate(Color(1, 1, 1, 1)); - if (cstatus == HTTPClient::STATUS_BODY) { - if (download->get_body_size() > 0) { - status->set_text( - vformat( - TTR("Downloading (%s / %s)..."), - String::humanize_size(download->get_downloaded_bytes()), - String::humanize_size(download->get_body_size()))); - } else { - // Total file size is unknown, so it cannot be displayed - status->set_text(TTR("Downloading...")); + if (download->get_downloaded_bytes() > 0) { + progress->set_max(download->get_body_size()); + progress->set_value(download->get_downloaded_bytes()); } - } - if (cstatus != prev_status) { - switch (cstatus) { + int cstatus = download->get_http_client_status(); + + if (cstatus == HTTPClient::STATUS_BODY) { + if (download->get_body_size() > 0) { + status->set_text(vformat( + TTR("Downloading (%s / %s)..."), + String::humanize_size(download->get_downloaded_bytes()), + String::humanize_size(download->get_body_size()))); + } else { + // Total file size is unknown, so it cannot be displayed. + status->set_text(TTR("Downloading...")); + } + } - case HTTPClient::STATUS_RESOLVING: { - status->set_text(TTR("Resolving...")); - progress->set_max(1); - progress->set_value(0); - } break; - case HTTPClient::STATUS_CONNECTING: { - status->set_text(TTR("Connecting...")); - progress->set_max(1); - progress->set_value(0); - } break; - case HTTPClient::STATUS_REQUESTING: { - status->set_text(TTR("Requesting...")); - progress->set_max(1); - progress->set_value(0); - } break; - default: { + if (cstatus != prev_status) { + switch (cstatus) { + + case HTTPClient::STATUS_RESOLVING: { + status->set_text(TTR("Resolving...")); + progress->set_max(1); + progress->set_value(0); + } break; + case HTTPClient::STATUS_CONNECTING: { + status->set_text(TTR("Connecting...")); + progress->set_max(1); + progress->set_value(0); + } break; + case HTTPClient::STATUS_REQUESTING: { + status->set_text(TTR("Requesting...")); + progress->set_max(1); + progress->set_value(0); + } break; + default: { + } } + prev_status = cstatus; } - prev_status = cstatus; - } + } break; } } void EditorAssetLibraryItemDownload::_close() { @@ -531,7 +539,7 @@ EditorAssetLibraryItemDownload::EditorAssetLibraryItemDownload() { hb2->add_spacer(); install = memnew(Button); - install->set_text(TTR("Install")); + install->set_text(TTR("Install...")); install->set_disabled(true); install->connect("pressed", this, "_install"); @@ -564,6 +572,7 @@ EditorAssetLibraryItemDownload::EditorAssetLibraryItemDownload() { void EditorAssetLibrary::_notification(int p_what) { switch (p_what) { + case NOTIFICATION_READY: { error_tr->set_texture(get_icon("Error", "EditorIcons")); @@ -573,14 +582,12 @@ void EditorAssetLibrary::_notification(int p_what) { error_label->raise(); } break; - case NOTIFICATION_VISIBILITY_CHANGED: { if (is_visible()) { - _repository_changed(0); // Update when shown for the first time + _repository_changed(0); // Update when shown for the first time. } } break; - case NOTIFICATION_PROCESS: { HTTPClient::Status s = request->get_http_client_status(); @@ -619,6 +626,7 @@ void EditorAssetLibrary::_notification(int p_what) { case NOTIFICATION_THEME_CHANGED: { library_scroll_bg->add_style_override("panel", get_stylebox("bg", "Tree")); + downloads_scroll->add_style_override("bg", get_stylebox("bg", "Tree")); error_tr->set_texture(get_icon("Error", "EditorIcons")); reverse->set_icon(get_icon("Sort", "EditorIcons")); filter->set_right_icon(get_icon("Search", "EditorIcons")); @@ -749,7 +757,7 @@ void EditorAssetLibrary::_image_update(bool use_cache, bool final, const PoolByt float scale_ratio = max_height / (image->get_height() * EDSCALE); if (scale_ratio < 1) { - image->resize(image->get_width() * EDSCALE * scale_ratio, image->get_height() * EDSCALE * scale_ratio, Image::INTERPOLATE_CUBIC); + image->resize(image->get_width() * EDSCALE * scale_ratio, image->get_height() * EDSCALE * scale_ratio, Image::INTERPOLATE_LANCZOS); } } break; case IMAGE_QUEUE_SCREENSHOT: { @@ -757,7 +765,7 @@ void EditorAssetLibrary::_image_update(bool use_cache, bool final, const PoolByt float scale_ratio = max_height / (image->get_height() * EDSCALE); if (scale_ratio < 1) { - image->resize(image->get_width() * EDSCALE * scale_ratio, image->get_height() * EDSCALE * scale_ratio, Image::INTERPOLATE_CUBIC); + image->resize(image->get_width() * EDSCALE * scale_ratio, image->get_height() * EDSCALE * scale_ratio, Image::INTERPOLATE_LANCZOS); } } break; } @@ -1238,9 +1246,6 @@ void EditorAssetLibrary::_http_request_completed(int p_status, int p_code, const description->connect("confirmed", this, "_install_asset"); description->configure(r["title"], r["asset_id"], category_map[r["category_id"]], r["category_id"], r["author"], r["author_id"], r["cost"], r["version"], r["version_string"], r["description"], r["download_url"], r["browse_url"], r["download_hash"]); - /*item->connect("asset_selected",this,"_select_asset"); - item->connect("author_selected",this,"_select_author"); - item->connect("category_selected",this,"_category_selected");*/ if (r.has("icon_url") && r["icon_url"] != "") { _request_image(description->get_instance_id(), r["icon_url"], IMAGE_QUEUE_ICON, 0); @@ -1267,9 +1272,8 @@ void EditorAssetLibrary::_http_request_completed(int p_status, int p_code, const if (p.has("thumbnail")) { _request_image(description->get_instance_id(), p["thumbnail"], IMAGE_QUEUE_THUMBNAIL, i); } - if (is_video) { - //_request_image(description->get_instance_id(),p["link"],IMAGE_QUEUE_SCREENSHOT,i); - } else { + + if (!is_video) { _request_image(description->get_instance_id(), p["link"], IMAGE_QUEUE_SCREENSHOT, i); } } @@ -1390,19 +1394,16 @@ EditorAssetLibrary::EditorAssetLibrary(bool p_templates_only) { reverse = memnew(ToolButton); reverse->set_toggle_mode(true); reverse->connect("toggled", this, "_rerun_search"); - //reverse->set_text(TTR("Reverse")); + reverse->set_tooltip(TTR("Reverse sorting.")); search_hb2->add_child(reverse); search_hb2->add_child(memnew(VSeparator)); - //search_hb2->add_spacer(); - search_hb2->add_child(memnew(Label(TTR("Category:") + " "))); categories = memnew(OptionButton); categories->add_item(TTR("All")); search_hb2->add_child(categories); categories->set_h_size_flags(SIZE_EXPAND_FILL); - //search_hb2->add_spacer(); categories->connect("item_selected", this, "_rerun_search"); search_hb2->add_child(memnew(VSeparator)); diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index 915fc5ba4c..d4eab888cc 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -2246,8 +2246,6 @@ void CanvasItemEditor::_gui_input_viewport(const Ref<InputEvent> &p_event) { //printf("Plugin\n"); } else if ((accepted = _gui_input_open_scene_on_double_click(p_event))) { //printf("Open scene on double click\n"); - } else if ((accepted = _gui_input_anchors(p_event))) { - //printf("Anchors\n"); } else if ((accepted = _gui_input_scale(p_event))) { //printf("Set scale\n"); } else if ((accepted = _gui_input_pivot(p_event))) { @@ -2258,6 +2256,8 @@ void CanvasItemEditor::_gui_input_viewport(const Ref<InputEvent> &p_event) { //printf("Rotate\n"); } else if ((accepted = _gui_input_move(p_event))) { //printf("Move\n"); + } else if ((accepted = _gui_input_anchors(p_event))) { + //printf("Anchors\n"); } else if ((accepted = _gui_input_select(p_event))) { //printf("Selection\n"); } else { diff --git a/editor/project_settings_editor.cpp b/editor/project_settings_editor.cpp index 001846604c..f05b6b5890 100644 --- a/editor/project_settings_editor.cpp +++ b/editor/project_settings_editor.cpp @@ -52,8 +52,8 @@ static const char *_button_names[JOY_BUTTON_MAX] = { "R2", "L3", "R3", - "Select, Nintendo -", - "Start, Nintendo +", + "Select, DualShock Share, Nintendo -", + "Start, DualShock Options, Nintendo +", "D-Pad Up", "D-Pad Down", "D-Pad Left", diff --git a/main/main.cpp b/main/main.cpp index ef5c4109db..7e69864e1e 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -1880,6 +1880,7 @@ bool Main::iteration() { double scaled_step = step * time_scale; Engine::get_singleton()->_frame_step = step; + Engine::get_singleton()->_physics_interpolation_fraction = advance.interpolation_fraction; uint64_t physics_process_ticks = 0; uint64_t idle_process_ticks = 0; diff --git a/main/main_timer_sync.cpp b/main/main_timer_sync.cpp index f7388c8517..edacb20f28 100644 --- a/main/main_timer_sync.cpp +++ b/main/main_timer_sync.cpp @@ -178,6 +178,10 @@ MainFrameTime MainTimerSync::advance_checked(float p_frame_slice, int p_iteratio // track deficit time_deficit = p_idle_step - ret.idle_step; + // p_frame_slice is 1.0 / iterations_per_sec + // i.e. the time in seconds taken by a physics tick + ret.interpolation_fraction = time_accum / p_frame_slice; + return ret; } diff --git a/main/main_timer_sync.h b/main/main_timer_sync.h index 179119edce..93d335b27f 100644 --- a/main/main_timer_sync.h +++ b/main/main_timer_sync.h @@ -36,6 +36,7 @@ struct MainFrameTime { float idle_step; // time to advance idles for (argument to process()) int physics_steps; // number of times to iterate the physics engine + float interpolation_fraction; // fraction through the current physics tick void clamp_idle(float min_idle_step, float max_idle_step); }; diff --git a/modules/bmp/image_loader_bmp.cpp b/modules/bmp/image_loader_bmp.cpp index a7e8dec11e..88732dff33 100644 --- a/modules/bmp/image_loader_bmp.cpp +++ b/modules/bmp/image_loader_bmp.cpp @@ -47,9 +47,6 @@ Error ImageLoaderBMP::convert_to_image(Ref<Image> p_image, size_t height = (size_t)p_header.bmp_info_header.bmp_height; size_t bits_per_pixel = (size_t)p_header.bmp_info_header.bmp_bit_count; - if (p_header.bmp_info_header.bmp_compression != BI_RGB) { - err = FAILED; - } // Check whether we can load it if (bits_per_pixel == 1) { @@ -238,11 +235,16 @@ Error ImageLoaderBMP::load_image(Ref<Image> p_image, FileAccess *f, bmp_header.bmp_info_header.bmp_colors_used = f->get_32(); bmp_header.bmp_info_header.bmp_important_colors = f->get_32(); - // Compressed bitmaps not supported, stop parsing - if (bmp_header.bmp_info_header.bmp_compression != BI_RGB) { - ERR_EXPLAIN("Unsupported bmp file: " + f->get_path()); - f->close(); - ERR_FAIL_V(ERR_UNAVAILABLE); + switch (bmp_header.bmp_info_header.bmp_compression) { + case BI_RLE8: + case BI_RLE4: + case BI_CMYKRLE8: + case BI_CMYKRLE4: { + // Stop parsing + ERR_EXPLAIN("Compressed BMP files are not supported: " + f->get_path()); + f->close(); + ERR_FAIL_V(ERR_UNAVAILABLE); + } break; } // Don't rely on sizeof(bmp_file_header) as structure padding // adds 2 bytes offset leading to misaligned color table reading @@ -257,8 +259,8 @@ Error ImageLoaderBMP::load_image(Ref<Image> p_image, FileAccess *f, if (bmp_header.bmp_info_header.bmp_bit_count <= 8) { // Support 256 colors max color_table_size = 1 << bmp_header.bmp_info_header.bmp_bit_count; + ERR_FAIL_COND_V(color_table_size == 0, ERR_BUG); } - ERR_FAIL_COND_V(color_table_size == 0, ERR_BUG); PoolVector<uint8_t> bmp_color_table; // Color table is usually 4 bytes per color -> [B][G][R][0] diff --git a/modules/bmp/image_loader_bmp.h b/modules/bmp/image_loader_bmp.h index 0082cf778a..2debb19a1c 100644 --- a/modules/bmp/image_loader_bmp.h +++ b/modules/bmp/image_loader_bmp.h @@ -42,15 +42,15 @@ protected: enum bmp_compression_s { BI_RGB = 0x00, - BI_RLE8 = 0x01, - BI_RLE4 = 0x02, + BI_RLE8 = 0x01, // compressed + BI_RLE4 = 0x02, // compressed BI_BITFIELDS = 0x03, BI_JPEG = 0x04, BI_PNG = 0x05, BI_ALPHABITFIELDS = 0x06, BI_CMYK = 0x0b, - BI_CMYKRLE8 = 0x0c, - BI_CMYKRLE4 = 0x0d + BI_CMYKRLE8 = 0x0c, // compressed + BI_CMYKRLE4 = 0x0d // compressed }; struct bmp_header_s { diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index c4937f023b..521b5ed538 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -159,11 +159,11 @@ bool GDScriptLanguage::validate(const String &p_script, int &r_line_error, int & for (int i = 0; i < cl->subclasses.size(); i++) { for (int j = 0; j < cl->subclasses[i]->functions.size(); j++) { - funcs[cl->subclasses[i]->functions[j]->line] = String(cl->subclasses[i]->name) + "." + String(cl->subclasses[i]->functions[j]->name); + funcs[cl->subclasses[i]->functions[j]->line] = String(cl->subclasses[i]->name) + "." + cl->subclasses[i]->functions[j]->name; } for (int j = 0; j < cl->subclasses[i]->static_functions.size(); j++) { - funcs[cl->subclasses[i]->static_functions[j]->line] = String(cl->subclasses[i]->name) + "." + String(cl->subclasses[i]->static_functions[j]->name); + funcs[cl->subclasses[i]->static_functions[j]->line] = String(cl->subclasses[i]->name) + "." + cl->subclasses[i]->static_functions[j]->name; } } diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp index d5e74c07c9..42f349ffc0 100644 --- a/modules/gdscript/gdscript_function.cpp +++ b/modules/gdscript/gdscript_function.cpp @@ -1784,20 +1784,9 @@ GDScriptFunction::~GDScriptFunction() { Variant GDScriptFunctionState::_signal_callback(const Variant **p_args, int p_argcount, Variant::CallError &r_error) { - if (state.instance_id && !ObjectDB::get_instance(state.instance_id)) { -#ifdef DEBUG_ENABLED - ERR_EXPLAIN("Resumed function '" + String(function->get_name()) + "()' after yield, but class instance is gone. At script: " + state.script->get_path() + ":" + itos(state.line)); - ERR_FAIL_V(Variant()); -#else - return Variant(); -#endif - } - Variant arg; r_error.error = Variant::CallError::CALL_OK; - ERR_FAIL_COND_V(!function, Variant()); - if (p_argcount == 0) { r_error.error = Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; r_error.argument = 1; @@ -1823,44 +1812,7 @@ Variant GDScriptFunctionState::_signal_callback(const Variant **p_args, int p_ar return Variant(); } - state.result = arg; - Variant ret = function->call(NULL, NULL, 0, r_error, &state); - - bool completed = true; - - // If the return value is a GDScriptFunctionState reference, - // then the function did yield again after resuming. - if (ret.is_ref()) { - GDScriptFunctionState *gdfs = Object::cast_to<GDScriptFunctionState>(ret); - if (gdfs && gdfs->function == function) { - completed = false; - gdfs->first_state = first_state.is_valid() ? first_state : Ref<GDScriptFunctionState>(this); - } - } - - function = NULL; //cleaned up; - state.result = Variant(); - - if (completed) { - if (first_state.is_valid()) { - first_state->emit_signal("completed", ret); - } else { - emit_signal("completed", ret); - } - } - -#ifdef DEBUG_ENABLED - if (ScriptDebugger::get_singleton()) - GDScriptLanguage::get_singleton()->exit_function(); - if (state.stack_size) { - //free stack - Variant *stack = (Variant *)state.stack.ptr(); - for (int i = 0; i < state.stack_size; i++) - stack[i].~Variant(); - } -#endif - - return ret; + return resume(arg); } bool GDScriptFunctionState::is_valid(bool p_extended_check) const { diff --git a/modules/visual_script/visual_script_func_nodes.cpp b/modules/visual_script/visual_script_func_nodes.cpp index 0413bbf303..c330fa1bc0 100644 --- a/modules/visual_script/visual_script_func_nodes.cpp +++ b/modules/visual_script/visual_script_func_nodes.cpp @@ -1026,7 +1026,6 @@ void VisualScriptPropertySet::_adjust_input_index(PropertyInfo &pinfo) const { } PropertyInfo VisualScriptPropertySet::get_input_value_port_info(int p_idx) const { - if (call_mode == CALL_MODE_INSTANCE || call_mode == CALL_MODE_BASIC_TYPE) { if (p_idx == 0) { PropertyInfo pi; @@ -1037,6 +1036,16 @@ PropertyInfo VisualScriptPropertySet::get_input_value_port_info(int p_idx) const } } + List<PropertyInfo> props; + ClassDB::get_property_list(_get_base_type(), &props, true); + for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { + if (E->get().name == property) { + PropertyInfo pinfo = PropertyInfo(E->get().type, "value", PROPERTY_HINT_TYPE_STRING, E->get().hint_string); + _adjust_input_index(pinfo); + return pinfo; + } + } + PropertyInfo pinfo = type_cache; pinfo.name = "value"; _adjust_input_index(pinfo); @@ -1047,6 +1056,13 @@ PropertyInfo VisualScriptPropertySet::get_output_value_port_info(int p_idx) cons if (call_mode == CALL_MODE_BASIC_TYPE) { return PropertyInfo(basic_type, "out"); } else if (call_mode == CALL_MODE_INSTANCE) { + List<PropertyInfo> props; + ClassDB::get_property_list(_get_base_type(), &props, true); + for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { + if (E->get().name == property) { + return PropertyInfo(E->get().type, "pass", PROPERTY_HINT_TYPE_STRING, E->get().hint_string); + } + } return PropertyInfo(Variant::OBJECT, "pass", PROPERTY_HINT_TYPE_STRING, get_base_type()); } else { return PropertyInfo(); @@ -1796,14 +1812,12 @@ PropertyInfo VisualScriptPropertyGet::get_input_value_port_info(int p_idx) const } PropertyInfo VisualScriptPropertyGet::get_output_value_port_info(int p_idx) const { - - if (index != StringName()) { - - Variant v; - Variant::CallError ce; - v = Variant::construct(type_cache, NULL, 0, ce); - Variant i = v.get(index); - return PropertyInfo(i.get_type(), "value." + String(index)); + List<PropertyInfo> props; + ClassDB::get_property_list(_get_base_type(), &props, true); + for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { + if (E->get().name == property) { + return PropertyInfo(E->get().type, "value." + String(index)); + } } return PropertyInfo(type_cache, "value"); diff --git a/platform/server/os_server.cpp b/platform/server/os_server.cpp index 12e53054bc..87dc6421ac 100644 --- a/platform/server/os_server.cpp +++ b/platform/server/os_server.cpp @@ -88,6 +88,8 @@ Error OS_Server::initialize(const VideoMode &p_desired, int p_video_driver, int visual_server = memnew(VisualServerRaster); visual_server->init(); + camera_server = memnew(CameraServer); + AudioDriverManager::initialize(p_audio_driver); input = memnew(InputDefault); @@ -117,6 +119,8 @@ void OS_Server::finalize() { memdelete(input); + memdelete(camera_server); + memdelete(power_manager); ResourceLoader::remove_resource_format_loader(resource_loader_dummy); diff --git a/platform/server/os_server.h b/platform/server/os_server.h index e3488a693d..dbdae6afb1 100644 --- a/platform/server/os_server.h +++ b/platform/server/os_server.h @@ -74,6 +74,7 @@ class OS_Server : public OS_Unix { #endif CrashHandler crash_handler; + CameraServer *camera_server; int video_driver_index; diff --git a/scene/3d/light.cpp b/scene/3d/light.cpp index 4ef945ab8d..91595657b1 100644 --- a/scene/3d/light.cpp +++ b/scene/3d/light.cpp @@ -413,7 +413,7 @@ DirectionalLight::DirectionalLight() : set_param(PARAM_SHADOW_NORMAL_BIAS, 0.8); set_param(PARAM_SHADOW_BIAS, 0.1); - set_param(PARAM_SHADOW_MAX_DISTANCE, 200); + set_param(PARAM_SHADOW_MAX_DISTANCE, 100); set_param(PARAM_SHADOW_BIAS_SPLIT_SCALE, 0.25); set_shadow_mode(SHADOW_PARALLEL_4_SPLITS); set_shadow_depth_range(SHADOW_DEPTH_RANGE_STABLE); diff --git a/scene/resources/audio_stream_sample.cpp b/scene/resources/audio_stream_sample.cpp index 4b3e392013..5b61654c5d 100644 --- a/scene/resources/audio_stream_sample.cpp +++ b/scene/resources/audio_stream_sample.cpp @@ -564,7 +564,8 @@ Error AudioStreamSample::save_to_wav(const String &p_path) { file->store_32(sub_chunk_2_size); //Subchunk2Size // Add data - PoolVector<uint8_t>::Read read_data = get_data().read(); + PoolVector<uint8_t> data = get_data(); + PoolVector<uint8_t>::Read read_data = data.read(); switch (format) { case AudioStreamSample::FORMAT_8_BITS: for (unsigned int i = 0; i < data_bytes; i++) { diff --git a/thirdparty/README.md b/thirdparty/README.md index cb29eadeca..c6817e2389 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -511,7 +511,7 @@ folder. ## xatlas - Upstream: https://github.com/jpcy/xatlas -- Version: git (b7d7bb, 2019) +- Version: git (f65a664, 2019) - License: MIT Files extracted from upstream source: diff --git a/thirdparty/xatlas/xatlas.cpp b/thirdparty/xatlas/xatlas.cpp index c62be4e73a..1b30305cd4 100644 --- a/thirdparty/xatlas/xatlas.cpp +++ b/thirdparty/xatlas/xatlas.cpp @@ -299,29 +299,30 @@ static void *Realloc(void *ptr, size_t size, int /*tag*/, const char * /*file*/, #if XA_PROFILE #define XA_PROFILE_START(var) const clock_t var##Start = clock(); #define XA_PROFILE_END(var) internal::s_profile.var += clock() - var##Start; -#define XA_PROFILE_PRINT(label, var) XA_PRINT("%s%.2f seconds (%g ms)\n", label, internal::clockToSeconds(internal::s_profile.var), internal::clockToMs(internal::s_profile.var)); +#define XA_PROFILE_PRINT_AND_RESET(label, var) XA_PRINT("%s%.2f seconds (%g ms)\n", label, internal::clockToSeconds(internal::s_profile.var), internal::clockToMs(internal::s_profile.var)); internal::s_profile.var = 0; struct ProfileData { - clock_t addMeshConcurrent; - std::atomic<clock_t> addMesh; + clock_t addMeshReal; + std::atomic<clock_t> addMeshThread; std::atomic<clock_t> addMeshCreateColocals; std::atomic<clock_t> addMeshCreateFaceGroups; std::atomic<clock_t> addMeshCreateBoundaries; - std::atomic<clock_t> addMeshCreateChartGroupsConcurrent; - std::atomic<clock_t> addMeshCreateChartGroups; - clock_t computeChartsConcurrent; - std::atomic<clock_t> computeCharts; + std::atomic<clock_t> addMeshCreateChartGroupsReal; + std::atomic<clock_t> addMeshCreateChartGroupsThread; + clock_t computeChartsReal; + std::atomic<clock_t> computeChartsThread; std::atomic<clock_t> atlasBuilder; std::atomic<clock_t> atlasBuilderInit; std::atomic<clock_t> atlasBuilderCreateInitialCharts; std::atomic<clock_t> atlasBuilderGrowCharts; std::atomic<clock_t> atlasBuilderMergeCharts; - std::atomic<clock_t> createChartMeshes; + std::atomic<clock_t> createChartMeshesReal; + std::atomic<clock_t> createChartMeshesThread; std::atomic<clock_t> fixChartMeshTJunctions; std::atomic<clock_t> closeChartMeshHoles; - clock_t parameterizeChartsConcurrent; - std::atomic<clock_t> parameterizeCharts; + clock_t parameterizeChartsReal; + std::atomic<clock_t> parameterizeChartsThread; std::atomic<clock_t> parameterizeChartsOrthogonal; std::atomic<clock_t> parameterizeChartsLSCM; std::atomic<clock_t> parameterizeChartsEvaluateQuality; @@ -329,6 +330,7 @@ struct ProfileData clock_t packChartsRasterize; clock_t packChartsDilate; clock_t packChartsFindLocation; + std::atomic<clock_t> packChartsFindLocationThread; clock_t packChartsBlit; }; @@ -346,7 +348,7 @@ static double clockToSeconds(clock_t c) #else #define XA_PROFILE_START(var) #define XA_PROFILE_END(var) -#define XA_PROFILE_PRINT(label, var) +#define XA_PROFILE_PRINT_AND_RESET(label, var) #endif static constexpr float kPi = 3.14159265358979323846f; @@ -641,6 +643,7 @@ static bool linesIntersect(const Vector2 &a1, const Vector2 &a2, const Vector2 & struct Vector2i { + Vector2i() {} Vector2i(int32_t x, int32_t y) : x(x), y(y) {} int32_t x, y; @@ -3528,6 +3531,15 @@ private: std::mutex m_mutex; }; +struct Spinlock +{ + void lock() { while(m_lock.test_and_set(std::memory_order_acquire)) {} } + void unlock() { m_lock.clear(std::memory_order_release); } + +private: + std::atomic_flag m_lock = ATOMIC_FLAG_INIT; +}; + struct TaskGroupHandle { uint32_t value = UINT32_MAX; @@ -3545,6 +3557,14 @@ class TaskScheduler public: TaskScheduler() : m_shutdown(false) { + // Max with current task scheduler usage is 1 per thread + 1 deep nesting, but allow for some slop. + m_maxGroups = std::thread::hardware_concurrency() * 4; + m_groups = XA_ALLOC_ARRAY(MemTag::Default, TaskGroup, m_maxGroups); + for (uint32_t i = 0; i < m_maxGroups; i++) { + new (&m_groups[i]) TaskGroup(); + m_groups[i].free = true; + m_groups[i].ref = 0; + } m_workers.resize(std::thread::hardware_concurrency() <= 1 ? 1 : std::thread::hardware_concurrency() - 1); for (uint32_t i = 0; i < m_workers.size(); i++) { m_workers[i].wakeup = false; @@ -3565,36 +3585,42 @@ public: worker.thread->~thread(); XA_FREE(worker.thread); } - for (uint32_t i = 0; i < m_groups.size(); i++) - destroyGroup(i); + for (uint32_t i = 0; i < m_maxGroups; i++) + m_groups[i].~TaskGroup(); + XA_FREE(m_groups); } - void run(TaskGroupHandle *handle, Task task) + TaskGroupHandle createTaskGroup(uint32_t reserveSize = 0) { - // Allocate a task group if this is the first time using this handle. - TaskGroup *group; - if (handle->value == UINT32_MAX) { - group = XA_NEW(MemTag::Default, TaskGroup); - group->ref = 0; - std::lock_guard<std::mutex> lock(m_groupsMutex); - for (uint32_t i = 0; i < m_groups.size(); i++) { - if (!m_groups[i]) { - m_groups[i] = group; - handle->value = i; - break; - } - } - if (handle->value == UINT32_MAX) { - m_groups.push_back(group); - handle->value = m_groups.size() - 1; - } - } - group = m_groups[handle->value]; - { - std::lock_guard<std::mutex> lock(group->queueMutex); - group->queue.push_back(task); - } - group->ref++; + // Claim the first free group. + for (uint32_t i = 0; i < m_maxGroups; i++) { + TaskGroup &group = m_groups[i]; + bool expected = true; + if (!group.free.compare_exchange_strong(expected, false)) + continue; + group.queueLock.lock(); + group.queueHead = 0; + group.queue.clear(); + group.queue.reserve(reserveSize); + group.queueLock.unlock(); + TaskGroupHandle handle; + handle.value = i; + return handle; + } + XA_DEBUG_ASSERT(false); + TaskGroupHandle handle; + handle.value = UINT32_MAX; + return handle; + } + + void run(TaskGroupHandle handle, Task task) + { + XA_DEBUG_ASSERT(handle.value != UINT32_MAX); + TaskGroup &group = m_groups[handle.value]; + group.queueLock.lock(); + group.queue.push_back(task); + group.queueLock.unlock(); + group.ref++; // Wake up a worker to run this task. for (uint32_t i = 0; i < m_workers.size(); i++) { m_workers[i].wakeup = true; @@ -3609,33 +3635,32 @@ public: return; } // Run tasks from the group queue until empty. - TaskGroup *group = m_groups[handle->value]; + TaskGroup &group = m_groups[handle->value]; for (;;) { Task *task = nullptr; - { - std::lock_guard<std::mutex> lock(group->queueMutex); - if (group->queueHead < group->queue.size()) - task = &group->queue[group->queueHead++]; - } + group.queueLock.lock(); + if (group.queueHead < group.queue.size()) + task = &group.queue[group.queueHead++]; + group.queueLock.unlock(); if (!task) break; task->func(task->userData); - group->ref--; + group.ref--; } // Even though the task queue is empty, workers can still be running tasks. - while (group->ref > 0) + while (group.ref > 0) std::this_thread::yield(); - std::lock_guard<std::mutex> lock(m_groupsMutex); - destroyGroup(handle->value); + group.free = true; handle->value = UINT32_MAX; } private: struct TaskGroup { + std::atomic<bool> free; Array<Task> queue; // Items are never removed. queueHead is incremented to pop items. uint32_t queueHead = 0; - std::mutex queueMutex; + Spinlock queueLock; std::atomic<uint32_t> ref; // Increment when a task is enqueued, decrement when a task finishes. }; @@ -3647,21 +3672,11 @@ private: std::atomic<bool> wakeup; }; - Array<TaskGroup *> m_groups; - std::mutex m_groupsMutex; + TaskGroup *m_groups; + uint32_t m_maxGroups; Array<Worker> m_workers; std::atomic<bool> m_shutdown; - void destroyGroup(uint32_t index) - { - TaskGroup *group = m_groups[index]; - m_groups[index] = nullptr; - if (group) { - group->~TaskGroup(); - XA_FREE(group); - } - } - static void workerThread(TaskScheduler *scheduler, Worker *worker) { std::unique_lock<std::mutex> lock(worker->mutex); @@ -3674,18 +3689,17 @@ private: // Look for a task in any of the groups and run it. TaskGroup *group = nullptr; Task *task = nullptr; - { - std::lock_guard<std::mutex> groupsLock(scheduler->m_groupsMutex); - for (uint32_t i = 0; i < scheduler->m_groups.size(); i++) { - group = scheduler->m_groups[i]; - if (!group) - continue; - std::lock_guard<std::mutex> queueLock(group->queueMutex); - if (group->queueHead < group->queue.size()) { - task = &group->queue[group->queueHead++]; - break; - } + for (uint32_t i = 0; i < scheduler->m_maxGroups; i++) { + group = &scheduler->m_groups[i]; + if (group->free || group->ref == 0) + continue; + group->queueLock.lock(); + if (group->queueHead < group->queue.size()) { + task = &group->queue[group->queueHead++]; + group->queueLock.unlock(); + break; } + group->queueLock.unlock(); } if (!task) break; @@ -3705,23 +3719,19 @@ public: destroyGroup({ i }); } - void run(TaskGroupHandle *handle, Task task) + TaskGroupHandle createTaskGroup(uint32_t reserveSize = 0) { - if (handle->value == UINT32_MAX) { - TaskGroup *group = XA_NEW(MemTag::Default, TaskGroup); - for (uint32_t i = 0; i < m_groups.size(); i++) { - if (!m_groups[i]) { - m_groups[i] = group; - handle->value = i; - break; - } - } - if (handle->value == UINT32_MAX) { - m_groups.push_back(group); - handle->value = m_groups.size() - 1; - } - } - m_groups[handle->value]->queue.push_back(task); + TaskGroup *group = XA_NEW(MemTag::Default, TaskGroup); + group->queue.reserve(reserveSize); + m_groups.push_back(group); + TaskGroupHandle handle; + handle.value = m_groups.size() - 1; + return handle; + } + + void run(TaskGroupHandle handle, Task task) + { + m_groups[handle.value]->queue.push_back(task); } void wait(TaskGroupHandle *handle) @@ -5967,6 +5977,58 @@ private: #endif }; +struct CreateChartTaskArgs +{ + const Mesh *mesh; + const Array<uint32_t> *faceArray; + const Basis *basis; + uint32_t meshId; + uint32_t chartGroupId; + uint32_t chartId; + Chart **chart; +}; + +static void runCreateChartTask(void *userData) +{ + XA_PROFILE_START(createChartMeshesThread) + auto args = (CreateChartTaskArgs *)userData; + *(args->chart) = XA_NEW(MemTag::Default, Chart, args->mesh, *(args->faceArray), *(args->basis), args->meshId, args->chartGroupId, args->chartId); + XA_PROFILE_END(createChartMeshesThread) +} + +struct ParameterizeChartTaskArgs +{ + Chart *chart; + ParameterizeFunc func; +}; + +static void runParameterizeChartTask(void *userData) +{ + auto args = (ParameterizeChartTaskArgs *)userData; + Mesh *mesh = args->chart->unifiedMesh(); + XA_PROFILE_START(parameterizeChartsOrthogonal) +#if 1 + computeOrthogonalProjectionMap(mesh); +#else + for (uint32_t i = 0; i < vertexCount; i++) + mesh->texcoord(i) = Vector2(dot(args->chart->basis().tangent, mesh->position(i)), dot(args->chart->basis().bitangent, mesh->position(i))); +#endif + XA_PROFILE_END(parameterizeChartsOrthogonal) + args->chart->evaluateOrthoParameterizationQuality(); + if (!args->chart->isOrtho() && !args->chart->isPlanar()) { + XA_PROFILE_START(parameterizeChartsLSCM) + if (args->func) + args->func(&mesh->position(0).x, &mesh->texcoord(0).x, mesh->vertexCount(), mesh->indices(), mesh->indexCount()); + else if (args->chart->isDisk()) + computeLeastSquaresConformalMap(mesh); + XA_PROFILE_END(parameterizeChartsLSCM) + args->chart->evaluateParameterizationQuality(); + } + // @@ Check that parameterization quality is above a certain threshold. + // Transfer parameterization from unified mesh to chart mesh. + args->chart->transferParameterization(); +} + // Set of charts corresponding to mesh faces in the same face group. class ChartGroup { @@ -6107,7 +6169,7 @@ public: - emphasize roundness metrics to prevent those cases. - If interior self-overlaps: preserve boundary parameterization and use mean-value map. */ - void computeCharts(const ChartOptions &options) + void computeCharts(TaskScheduler *taskScheduler, const ChartOptions &options) { m_chartOptions = options; // This function may be called multiple times, so destroy existing charts. @@ -6128,13 +6190,30 @@ public: AtlasBuilder builder(m_mesh, nullptr, options); runAtlasBuilder(builder, options); XA_PROFILE_END(atlasBuilder) - XA_PROFILE_START(createChartMeshes) const uint32_t chartCount = builder.chartCount(); + m_chartArray.resize(chartCount); + Array<CreateChartTaskArgs> taskArgs; + taskArgs.resize(chartCount); + for (uint32_t i = 0; i < chartCount; i++) { + CreateChartTaskArgs &args = taskArgs[i]; + args.mesh = m_mesh; + args.faceArray = &builder.chartFaces(i); + args.basis = &builder.chartBasis(i); + args.meshId = m_sourceId; + args.chartGroupId = m_id; + args.chartId = i; + args.chart = &m_chartArray[i]; + } + XA_PROFILE_START(createChartMeshesReal) + TaskGroupHandle taskGroup = taskScheduler->createTaskGroup(chartCount); for (uint32_t i = 0; i < chartCount; i++) { - Chart *chart = XA_NEW(MemTag::Default, Chart, m_mesh, builder.chartFaces(i), builder.chartBasis(i), m_sourceId, m_id, i); - m_chartArray.push_back(chart); + Task task; + task.userData = &taskArgs[i]; + task.func = runCreateChartTask; + taskScheduler->run(taskGroup, task); } - XA_PROFILE_END(createChartMeshes) + taskScheduler->wait(&taskGroup); + XA_PROFILE_END(createChartMeshesReal) #endif #if XA_DEBUG_EXPORT_OBJ_CHARTS char filename[256]; @@ -6157,18 +6236,33 @@ public: #endif } - void parameterizeCharts(ParameterizeFunc func) + void parameterizeCharts(TaskScheduler *taskScheduler, ParameterizeFunc func) { + const uint32_t chartCount = m_chartArray.size(); + Array<ParameterizeChartTaskArgs> taskArgs; + taskArgs.resize(chartCount); + TaskGroupHandle taskGroup = taskScheduler->createTaskGroup(chartCount); + for (uint32_t i = 0; i < chartCount; i++) { + ParameterizeChartTaskArgs &args = taskArgs[i]; + args.chart = m_chartArray[i]; + args.func = func; + Task task; + task.userData = &args; + task.func = runParameterizeChartTask; + taskScheduler->run(taskGroup, task); + } + taskScheduler->wait(&taskGroup); #if XA_RECOMPUTE_CHARTS + // Find charts with invalid parameterizations. Array<Chart *> invalidCharts; - const uint32_t chartCount = m_chartArray.size(); for (uint32_t i = 0; i < chartCount; i++) { Chart *chart = m_chartArray[i]; - parameterizeChart(chart, func); const ParameterizationQuality &quality = chart->paramQuality(); if (quality.boundaryIntersection || quality.flippedTriangleCount > 0) invalidCharts.push_back(chart); } + if (invalidCharts.isEmpty()) + return; // Recompute charts with invalid parameterizations. Array<uint32_t> meshFaces; for (uint32_t i = 0; i < invalidCharts.size(); i++) { @@ -6211,8 +6305,18 @@ public: #endif } // Parameterize the new charts. - for (uint32_t i = chartCount; i < m_chartArray.size(); i++) - parameterizeChart(m_chartArray[i], func); + taskGroup = taskScheduler->createTaskGroup(m_chartArray.size() - chartCount); + taskArgs.resize(m_chartArray.size() - chartCount); + for (uint32_t i = chartCount; i < m_chartArray.size(); i++) { + ParameterizeChartTaskArgs &args = taskArgs[i - chartCount]; + args.chart = m_chartArray[i]; + args.func = func; + Task task; + task.userData = &args; + task.func = runParameterizeChartTask; + taskScheduler->run(taskGroup, task); + } + taskScheduler->wait(&taskGroup); // Remove and delete the invalid charts. for (uint32_t i = 0; i < invalidCharts.size(); i++) { Chart *chart = invalidCharts[i]; @@ -6221,12 +6325,6 @@ public: XA_FREE(chart); m_paramDeletedChartsCount++; } -#else - const uint32_t chartCount = m_chartArray.size(); - for (uint32_t i = 0; i < chartCount; i++) { - Chart *chart = m_chartArray[i]; - parameterizeChart(chart, func); - } #endif } @@ -6269,32 +6367,6 @@ private: XA_DEBUG_ASSERT(builder.facesLeft() == 0); } - void parameterizeChart(Chart *chart, ParameterizeFunc func) - { - Mesh *mesh = chart->unifiedMesh(); - XA_PROFILE_START(parameterizeChartsOrthogonal) -#if 1 - computeOrthogonalProjectionMap(mesh); -#else - for (uint32_t i = 0; i < vertexCount; i++) - mesh->texcoord(i) = Vector2(dot(chart->basis().tangent, mesh->position(i)), dot(chart->basis().bitangent, mesh->position(i))); -#endif - XA_PROFILE_END(parameterizeChartsOrthogonal) - chart->evaluateOrthoParameterizationQuality(); - if (!chart->isOrtho() && !chart->isPlanar()) { - XA_PROFILE_START(parameterizeChartsLSCM) - if (func) - func(&mesh->position(0).x, &mesh->texcoord(0).x, mesh->vertexCount(), mesh->indices(), mesh->indexCount()); - else if (chart->isDisk()) - computeLeastSquaresConformalMap(mesh); - XA_PROFILE_END(parameterizeChartsLSCM) - chart->evaluateParameterizationQuality(); - } - // @@ Check that parameterization quality is above a certain threshold. - // Transfer parameterization from unified mesh to chart mesh. - chart->transferParameterization(); - } - void removeChart(const Chart *chart) { for (uint32_t i = 0; i < m_chartArray.size(); i++) { @@ -6326,14 +6398,15 @@ struct CreateChartGroupTaskArgs static void runCreateChartGroupTask(void *userData) { - XA_PROFILE_START(addMeshCreateChartGroups) + XA_PROFILE_START(addMeshCreateChartGroupsThread) auto args = (CreateChartGroupTaskArgs *)userData; *(args->chartGroup) = XA_NEW(MemTag::Default, ChartGroup, args->groupId, args->mesh, args->faceGroup); - XA_PROFILE_END(addMeshCreateChartGroups) + XA_PROFILE_END(addMeshCreateChartGroupsThread) } struct ComputeChartsTaskArgs { + TaskScheduler *taskScheduler; ChartGroup *chartGroup; const ChartOptions *options; Progress *progress; @@ -6341,18 +6414,19 @@ struct ComputeChartsTaskArgs static void runComputeChartsJob(void *userData) { - ComputeChartsTaskArgs *args = (ComputeChartsTaskArgs *)userData; + auto args = (ComputeChartsTaskArgs *)userData; if (args->progress->cancel) return; - XA_PROFILE_START(computeCharts) - args->chartGroup->computeCharts(*args->options); - XA_PROFILE_END(computeCharts) + XA_PROFILE_START(computeChartsThread) + args->chartGroup->computeCharts(args->taskScheduler, *args->options); + XA_PROFILE_END(computeChartsThread) args->progress->value++; args->progress->update(); } struct ParameterizeChartsTaskArgs { + TaskScheduler *taskScheduler; ChartGroup *chartGroup; ParameterizeFunc func; Progress *progress; @@ -6360,12 +6434,12 @@ struct ParameterizeChartsTaskArgs static void runParameterizeChartsJob(void *userData) { - ParameterizeChartsTaskArgs *args = (ParameterizeChartsTaskArgs *)userData; + auto args = (ParameterizeChartsTaskArgs *)userData; if (args->progress->cancel) return; - XA_PROFILE_START(parameterizeCharts) - args->chartGroup->parameterizeCharts(args->func); - XA_PROFILE_END(parameterizeCharts) + XA_PROFILE_START(parameterizeChartsThread) + args->chartGroup->parameterizeCharts(args->taskScheduler, args->func); + XA_PROFILE_END(parameterizeChartsThread) args->progress->value++; args->progress->update(); } @@ -6460,12 +6534,12 @@ public: args.groupId = g; args.mesh = mesh; } - TaskGroupHandle taskGroup; + TaskGroupHandle taskGroup = taskScheduler->createTaskGroup(chartGroups.size()); for (uint32_t g = 0; g < chartGroups.size(); g++) { Task task; task.userData = &taskArgs[g]; task.func = runCreateChartGroupTask; - taskScheduler->run(&taskGroup, task); + taskScheduler->run(taskGroup, task); } taskScheduler->wait(&taskGroup); // Thread-safe append. @@ -6481,29 +6555,39 @@ public: { m_chartsComputed = false; m_chartsParameterized = false; - uint32_t taskCount = 0; + // Ignore vertex maps. + uint32_t chartGroupCount = 0; for (uint32_t i = 0; i < m_chartGroups.size(); i++) { if (!m_chartGroups[i]->isVertexMap()) - taskCount++; + chartGroupCount++; } - Progress progress(ProgressCategory::ComputeCharts, progressFunc, progressUserData, taskCount); + Progress progress(ProgressCategory::ComputeCharts, progressFunc, progressUserData, chartGroupCount); Array<ComputeChartsTaskArgs> taskArgs; - taskArgs.reserve(taskCount); + taskArgs.reserve(chartGroupCount); for (uint32_t i = 0; i < m_chartGroups.size(); i++) { if (!m_chartGroups[i]->isVertexMap()) { ComputeChartsTaskArgs args; + args.taskScheduler = taskScheduler; args.chartGroup = m_chartGroups[i]; args.options = &options; args.progress = &progress; taskArgs.push_back(args); } } - TaskGroupHandle taskGroup; - for (uint32_t i = 0; i < taskCount; i++) { + // Sort chart groups by mesh indexCount. + m_chartGroupsRadix = RadixSort(); + Array<float> chartGroupSortData; + chartGroupSortData.resize(chartGroupCount); + for (uint32_t i = 0; i < chartGroupCount; i++) + chartGroupSortData[i] = (float)taskArgs[i].chartGroup->mesh()->indexCount(); + m_chartGroupsRadix.sort(chartGroupSortData); + // Larger chart group meshes are added first to reduce the chance of thread starvation. + TaskGroupHandle taskGroup = taskScheduler->createTaskGroup(chartGroupCount); + for (uint32_t i = 0; i < chartGroupCount; i++) { Task task; - task.userData = &taskArgs[i]; + task.userData = &taskArgs[m_chartGroupsRadix.ranks()[chartGroupCount - i - 1]]; task.func = runComputeChartsJob; - taskScheduler->run(&taskGroup, task); + taskScheduler->run(taskGroup, task); } taskScheduler->wait(&taskGroup); if (progress.cancel) @@ -6515,29 +6599,32 @@ public: bool parameterizeCharts(TaskScheduler *taskScheduler, ParameterizeFunc func, ProgressFunc progressFunc, void *progressUserData) { m_chartsParameterized = false; - uint32_t taskCount = 0; + // Ignore vertex maps. + uint32_t chartGroupCount = 0; for (uint32_t i = 0; i < m_chartGroups.size(); i++) { if (!m_chartGroups[i]->isVertexMap()) - taskCount++; + chartGroupCount++; } - Progress progress(ProgressCategory::ParameterizeCharts, progressFunc, progressUserData, taskCount); + Progress progress(ProgressCategory::ParameterizeCharts, progressFunc, progressUserData, chartGroupCount); Array<ParameterizeChartsTaskArgs> taskArgs; - taskArgs.reserve(taskCount); + taskArgs.reserve(chartGroupCount); for (uint32_t i = 0; i < m_chartGroups.size(); i++) { if (!m_chartGroups[i]->isVertexMap()) { ParameterizeChartsTaskArgs args; + args.taskScheduler = taskScheduler; args.chartGroup = m_chartGroups[i]; args.func = func; args.progress = &progress; taskArgs.push_back(args); } } - TaskGroupHandle taskGroup; - for (uint32_t i = 0; i < taskCount; i++) { + // Larger chart group meshes are added first to reduce the chance of thread starvation. + TaskGroupHandle taskGroup = taskScheduler->createTaskGroup(chartGroupCount); + for (uint32_t i = 0; i < chartGroupCount; i++) { Task task; - task.userData = &taskArgs[i]; + task.userData = &taskArgs[m_chartGroupsRadix.ranks()[chartGroupCount - i - 1]]; task.func = runParameterizeChartsJob; - taskScheduler->run(&taskGroup, task); + taskScheduler->run(taskGroup, task); } taskScheduler->wait(&taskGroup); if (progress.cancel) @@ -6570,6 +6657,7 @@ private: bool m_chartsComputed; bool m_chartsParameterized; Array<ChartGroup *> m_chartGroups; + RadixSort m_chartGroupsRadix; // By mesh indexCount. Array<uint32_t> m_chartGroupSourceMeshes; Array<Array<Vector2> > m_originalChartTexcoords; }; @@ -6734,6 +6822,71 @@ struct Chart uint32_t uniqueVertexCount() const { return uniqueVertices.isEmpty() ? vertexCount : uniqueVertices.size(); } }; +struct FindChartLocationBruteForceTaskArgs +{ + std::atomic<bool> *finished; // One of the tasks found a location that doesn't expand the atlas. + Vector2i startPosition; + const BitImage *atlasBitImage; + const BitImage *chartBitImage; + const BitImage *chartBitImageRotated; + int w, h; + bool blockAligned, resizableAtlas, allowRotate; + // out + bool best_insideAtlas; + int best_metric, best_x, best_y, best_w, best_h, best_r; +}; + +static void runFindChartLocationBruteForceTask(void *userData) +{ + XA_PROFILE_START(packChartsFindLocationThread) + auto args = (FindChartLocationBruteForceTaskArgs *)userData; + args->best_metric = INT_MAX; + if (args->finished->load()) + return; + // Try two different orientations. + for (int r = 0; r < 2; r++) { + int cw = args->chartBitImage->width(); + int ch = args->chartBitImage->height(); + if (r == 1) { + if (args->allowRotate) + swap(cw, ch); + else + break; + } + const int y = args->startPosition.y; + const int stepSize = args->blockAligned ? 4 : 1; + for (int x = args->startPosition.x; x <= args->w + stepSize; x += stepSize) { // + 1 not really necessary here. + if (!args->resizableAtlas && (x > (int)args->atlasBitImage->width() - cw || y > (int)args->atlasBitImage->height() - ch)) + continue; + if (args->finished->load()) + break; + // Early out if metric not better. + const int area = max(args->w, x + cw) * max(args->h, y + ch); + const int extents = max(max(args->w, x + cw), max(args->h, y + ch)); + const int metric = extents * extents + area; + if (metric > args->best_metric) + continue; + // If metric is the same, pick the one closest to the origin. + if (metric == args->best_metric && max(x, y) >= max(args->best_x, args->best_y)) + continue; + if (!args->atlasBitImage->canBlit(r == 1 ? *(args->chartBitImageRotated) : *(args->chartBitImage), x, y)) + continue; + args->best_metric = metric; + args->best_insideAtlas = area == args->w * args->h; + args->best_x = x; + args->best_y = y; + args->best_w = cw; + args->best_h = ch; + args->best_r = r; + if (args->best_insideAtlas) { + args->finished->store(true); + break; + } + } + } + XA_PROFILE_END(packChartsFindLocationThread) +} + struct Atlas { ~Atlas() @@ -6854,7 +7007,7 @@ struct Atlas } // Pack charts in the smallest possible rectangle. - bool packCharts(const PackOptions &options, ProgressFunc progressFunc, void *progressUserData) + bool packCharts(TaskScheduler *taskScheduler, const PackOptions &options, ProgressFunc progressFunc, void *progressUserData) { if (progressFunc) { if (!progressFunc(ProgressCategory::PackCharts, 0, progressUserData)) @@ -7069,7 +7222,7 @@ struct Atlas chartStartPositions.push_back(Vector2i(0, 0)); } XA_PROFILE_START(packChartsFindLocation) - const bool foundLocation = findChartLocation(chartStartPositions[currentAtlas], options.bruteForce, m_bitImages[currentAtlas], &chartBitImage, &chartBitImageRotated, atlasWidth, atlasHeight, &best_x, &best_y, &best_cw, &best_ch, &best_r, options.blockAlign, resizableAtlas, chart->allowRotate); + const bool foundLocation = findChartLocation(taskScheduler, chartStartPositions[currentAtlas], options.bruteForce, m_bitImages[currentAtlas], &chartBitImage, &chartBitImageRotated, atlasWidth, atlasHeight, &best_x, &best_y, &best_cw, &best_ch, &best_r, options.blockAlign, resizableAtlas, chart->allowRotate); XA_PROFILE_END(packChartsFindLocation) if (firstChartInBitImage && !foundLocation) { // Chart doesn't fit in an empty, newly allocated bitImage. texelsPerUnit must be too large for the resolution. @@ -7181,65 +7334,66 @@ private: // is occupied at this point. At the end we have many small charts and a large atlas with sparse holes. Finding those holes randomly is slow. A better approach would be to // start stacking large charts as if they were tetris pieces. Once charts get small try to place them randomly. It may be interesting to try a intermediate strategy, first try // along one axis and then try exhaustively along that axis. - bool findChartLocation(const Vector2i &startPosition, bool bruteForce, const BitImage *atlasBitImage, const BitImage *chartBitImage, const BitImage *chartBitImageRotated, int w, int h, int *best_x, int *best_y, int *best_w, int *best_h, int *best_r, bool blockAligned, bool resizableAtlas, bool allowRotate) + bool findChartLocation(TaskScheduler *taskScheduler, const Vector2i &startPosition, bool bruteForce, const BitImage *atlasBitImage, const BitImage *chartBitImage, const BitImage *chartBitImageRotated, int w, int h, int *best_x, int *best_y, int *best_w, int *best_h, int *best_r, bool blockAligned, bool resizableAtlas, bool allowRotate) { const int attempts = 4096; if (bruteForce || attempts >= w * h) - return findChartLocation_bruteForce(startPosition, atlasBitImage, chartBitImage, chartBitImageRotated, w, h, best_x, best_y, best_w, best_h, best_r, blockAligned, resizableAtlas, allowRotate); + return findChartLocation_bruteForce(taskScheduler, startPosition, atlasBitImage, chartBitImage, chartBitImageRotated, w, h, best_x, best_y, best_w, best_h, best_r, blockAligned, resizableAtlas, allowRotate); return findChartLocation_random(atlasBitImage, chartBitImage, chartBitImageRotated, w, h, best_x, best_y, best_w, best_h, best_r, attempts, blockAligned, resizableAtlas, allowRotate); } - bool findChartLocation_bruteForce(const Vector2i &startPosition, const BitImage *atlasBitImage, const BitImage *chartBitImage, const BitImage *chartBitImageRotated, int w, int h, int *best_x, int *best_y, int *best_w, int *best_h, int *best_r, bool blockAligned, bool resizableAtlas, bool allowRotate) + bool findChartLocation_bruteForce(TaskScheduler *taskScheduler, const Vector2i &startPosition, const BitImage *atlasBitImage, const BitImage *chartBitImage, const BitImage *chartBitImageRotated, int w, int h, int *best_x, int *best_y, int *best_w, int *best_h, int *best_r, bool blockAligned, bool resizableAtlas, bool allowRotate) { - bool result = false; - const int BLOCK_SIZE = 4; + const int stepSize = blockAligned ? 4 : 1; + uint32_t taskCount = 0; + for (int y = startPosition.y; y <= h + stepSize; y += stepSize) + taskCount++; + Array<FindChartLocationBruteForceTaskArgs> taskArgs; + taskArgs.resize(taskCount); + TaskGroupHandle taskGroup = taskScheduler->createTaskGroup(taskCount); + std::atomic<bool> finished(false); // One of the tasks found a location that doesn't expand the atlas. + uint32_t i = 0; + for (int y = startPosition.y; y <= h + stepSize; y += stepSize) { + FindChartLocationBruteForceTaskArgs &args = taskArgs[i]; + args.finished = &finished; + args.startPosition = Vector2i(y == startPosition.y ? startPosition.x : 0, y); + args.atlasBitImage = atlasBitImage; + args.chartBitImage = chartBitImage; + args.chartBitImageRotated = chartBitImageRotated; + args.w = w; + args.h = h; + args.blockAligned = blockAligned; + args.resizableAtlas = resizableAtlas; + args.allowRotate = allowRotate; + Task task; + task.userData = &taskArgs[i]; + task.func = runFindChartLocationBruteForceTask; + taskScheduler->run(taskGroup, task); + i++; + } + taskScheduler->wait(&taskGroup); + // Find the task result with the best metric. int best_metric = INT_MAX; - int step_size = blockAligned ? BLOCK_SIZE : 1; - // Try two different orientations. - for (int r = 0; r < 2; r++) { - int cw = chartBitImage->width(); - int ch = chartBitImage->height(); - if (r == 1) { - if (allowRotate) - swap(cw, ch); - else - break; - } - for (int y = startPosition.y; y <= h + step_size; y += step_size) { // + 1 to extend atlas in case atlas full. - for (int x = (y == startPosition.y ? startPosition.x : 0); x <= w + step_size; x += step_size) { // + 1 not really necessary here. - if (!resizableAtlas && (x > (int)atlasBitImage->width() - cw || y > (int)atlasBitImage->height() - ch)) - continue; - // Early out. - int area = max(w, x + cw) * max(h, y + ch); - //int perimeter = max(w, x+cw) + max(h, y+ch); - int extents = max(max(w, x + cw), max(h, y + ch)); - int metric = extents * extents + area; - if (metric > best_metric) { - continue; - } - if (metric == best_metric && max(x, y) >= max(*best_x, *best_y)) { - // If metric is the same, pick the one closest to the origin. - continue; - } - if (atlasBitImage->canBlit(r == 1 ? *chartBitImageRotated : *chartBitImage, x, y)) { - result = true; - best_metric = metric; - *best_x = x; - *best_y = y; - *best_w = cw; - *best_h = ch; - *best_r = r; - if (area == w * h) { - // Chart is completely inside, do not look at any other location. - goto done; - } - } - } - } + bool best_insideAtlas = false; + for (i = 0; i < taskCount; i++) { + FindChartLocationBruteForceTaskArgs &args = taskArgs[i]; + if (args.best_metric > best_metric) + continue; + // A location that doesn't expand the atlas is always preferred. + if (!args.best_insideAtlas && best_insideAtlas) + continue; + // If metric is the same, pick the one closest to the origin. + if (args.best_insideAtlas == best_insideAtlas && args.best_metric == best_metric && max(args.best_x, args.best_y) >= max(*best_x, *best_y)) + continue; + best_metric = args.best_metric; + best_insideAtlas = args.best_insideAtlas; + *best_x = args.best_x; + *best_y = args.best_y; + *best_w = args.best_w; + *best_h = args.best_h; + *best_r = args.best_r; } - done: - XA_DEBUG_ASSERT (best_metric != INT_MAX); - return result; + return best_metric != INT_MAX; } bool findChartLocation_random(const BitImage *atlasBitImage, const BitImage *chartBitImage, const BitImage *chartBitImageRotated, int w, int h, int *best_x, int *best_y, int *best_w, int *best_h, int *best_r, int minTrialCount, bool blockAligned, bool resizableAtlas, bool allowRotate) @@ -7439,25 +7593,31 @@ struct AddMeshTaskArgs static void runAddMeshTask(void *userData) { - XA_PROFILE_START(addMesh) + XA_PROFILE_START(addMeshThread) auto args = (AddMeshTaskArgs *)userData; // Responsible for freeing this. internal::Mesh *mesh = args->mesh; internal::Progress *progress = args->ctx->addMeshProgress; if (progress->cancel) goto cleanup; - XA_PROFILE_START(addMeshCreateColocals) - mesh->createColocals(); - XA_PROFILE_END(addMeshCreateColocals) + { + XA_PROFILE_START(addMeshCreateColocals) + mesh->createColocals(); + XA_PROFILE_END(addMeshCreateColocals) + } if (progress->cancel) goto cleanup; - XA_PROFILE_START(addMeshCreateFaceGroups) - mesh->createFaceGroups(); - XA_PROFILE_END(addMeshCreateFaceGroups) + { + XA_PROFILE_START(addMeshCreateFaceGroups) + mesh->createFaceGroups(); + XA_PROFILE_END(addMeshCreateFaceGroups) + } if (progress->cancel) goto cleanup; - XA_PROFILE_START(addMeshCreateBoundaries) - mesh->createBoundaries(); - XA_PROFILE_END(addMeshCreateBoundaries) + { + XA_PROFILE_START(addMeshCreateBoundaries) + mesh->createBoundaries(); + XA_PROFILE_END(addMeshCreateBoundaries) + } if (progress->cancel) goto cleanup; #if XA_DEBUG_EXPORT_OBJ_SOURCE_MESHES @@ -7491,9 +7651,11 @@ static void runAddMeshTask(void *userData) fclose(file); } #endif - XA_PROFILE_START(addMeshCreateChartGroupsConcurrent) - args->ctx->paramAtlas.addMesh(args->ctx->taskScheduler, mesh); // addMesh is thread safe - XA_PROFILE_END(addMeshCreateChartGroupsConcurrent) + { + XA_PROFILE_START(addMeshCreateChartGroupsReal) + args->ctx->paramAtlas.addMesh(args->ctx->taskScheduler, mesh); // addMesh is thread safe + XA_PROFILE_END(addMeshCreateChartGroupsReal) + } if (progress->cancel) goto cleanup; progress->value++; @@ -7503,7 +7665,7 @@ cleanup: XA_FREE(mesh); args->~AddMeshTaskArgs(); XA_FREE(args); - XA_PROFILE_END(addMesh) + XA_PROFILE_END(addMeshThread) } static internal::Vector3 DecodePosition(const MeshDecl &meshDecl, uint32_t index) @@ -7547,12 +7709,13 @@ AddMeshError::Enum AddMesh(Atlas *atlas, const MeshDecl &meshDecl, uint32_t mesh XA_PRINT_WARNING("AddMesh: Meshes and UV meshes cannot be added to the same atlas.\n"); return AddMeshError::Error; } +#if XA_PROFILE + if (ctx->meshCount == 0) + internal::s_profile.addMeshReal = clock(); +#endif // Don't know how many times AddMesh will be called, so progress needs to adjusted each time. if (!ctx->addMeshProgress) { ctx->addMeshProgress = XA_NEW(internal::MemTag::Default, internal::Progress, ProgressCategory::AddMesh, ctx->progressFunc, ctx->progressUserData, 1); -#if XA_PROFILE - internal::s_profile.addMeshConcurrent = clock(); -#endif } else { ctx->addMeshProgress->setMaxValue(internal::max(ctx->meshCount + 1, meshCountHint)); @@ -7560,7 +7723,6 @@ AddMeshError::Enum AddMesh(Atlas *atlas, const MeshDecl &meshDecl, uint32_t mesh bool decoded = (meshDecl.indexCount <= 0); uint32_t indexCount = decoded ? meshDecl.vertexCount : meshDecl.indexCount; XA_PRINT("Adding mesh %d: %u vertices, %u triangles\n", ctx->meshCount, meshDecl.vertexCount, indexCount / 3); - XA_PROFILE_START(addMesh) // Expecting triangle faces. if ((indexCount % 3) != 0) return AddMeshError::InvalidIndexCount; @@ -7629,15 +7791,16 @@ AddMeshError::Enum AddMesh(Atlas *atlas, const MeshDecl &meshDecl, uint32_t mesh ignore = true; mesh->addFace(tri[0], tri[1], tri[2], ignore); } + if (ctx->addMeshTaskGroup.value == UINT32_MAX) + ctx->addMeshTaskGroup = ctx->taskScheduler->createTaskGroup(); AddMeshTaskArgs *taskArgs = XA_NEW(internal::MemTag::Default, AddMeshTaskArgs); // The task frees this. taskArgs->ctx = ctx; taskArgs->mesh = mesh; internal::Task task; task.userData = taskArgs; task.func = runAddMeshTask; - ctx->taskScheduler->run(&ctx->addMeshTaskGroup, task); + ctx->taskScheduler->run(ctx->addMeshTaskGroup, task); ctx->meshCount++; - XA_PROFILE_END(addMesh) return AddMeshError::Success; } @@ -7657,15 +7820,15 @@ void AddMeshJoin(Atlas *atlas) ctx->addMeshProgress = nullptr; #if XA_PROFILE XA_PRINT("Added %u meshes\n", ctx->meshCount); - internal::s_profile.addMeshConcurrent = clock() - internal::s_profile.addMeshConcurrent; + internal::s_profile.addMeshReal = clock() - internal::s_profile.addMeshReal; #endif - XA_PROFILE_PRINT(" Total (concurrent): ", addMeshConcurrent) - XA_PROFILE_PRINT(" Total: ", addMesh) - XA_PROFILE_PRINT(" Create colocals: ", addMeshCreateColocals) - XA_PROFILE_PRINT(" Create face groups: ", addMeshCreateFaceGroups) - XA_PROFILE_PRINT(" Create boundaries: ", addMeshCreateBoundaries) - XA_PROFILE_PRINT(" Create chart groups (concurrent): ", addMeshCreateChartGroupsConcurrent) - XA_PROFILE_PRINT(" Create chart groups: ", addMeshCreateChartGroups) + XA_PROFILE_PRINT_AND_RESET(" Total (real): ", addMeshReal) + XA_PROFILE_PRINT_AND_RESET(" Total (thread): ", addMeshThread) + XA_PROFILE_PRINT_AND_RESET(" Create colocals: ", addMeshCreateColocals) + XA_PROFILE_PRINT_AND_RESET(" Create face groups: ", addMeshCreateFaceGroups) + XA_PROFILE_PRINT_AND_RESET(" Create boundaries: ", addMeshCreateBoundaries) + XA_PROFILE_PRINT_AND_RESET(" Create chart groups (real): ", addMeshCreateChartGroupsReal) + XA_PROFILE_PRINT_AND_RESET(" Create chart groups (thread): ", addMeshCreateChartGroupsThread) XA_PRINT_MEM_USAGE } @@ -7815,12 +7978,12 @@ void ComputeCharts(Atlas *atlas, ChartOptions chartOptions) } XA_PRINT("Computing charts\n"); uint32_t chartCount = 0, chartsWithHolesCount = 0, holesCount = 0, chartsWithTJunctionsCount = 0, tJunctionsCount = 0; - XA_PROFILE_START(computeChartsConcurrent) + XA_PROFILE_START(computeChartsReal) if (!ctx->paramAtlas.computeCharts(ctx->taskScheduler, chartOptions, ctx->progressFunc, ctx->progressUserData)) { XA_PRINT(" Cancelled by user\n"); return; } - XA_PROFILE_END(computeChartsConcurrent) + XA_PROFILE_END(computeChartsReal) // Count charts and print warnings. for (uint32_t i = 0; i < ctx->meshCount; i++) { for (uint32_t j = 0; j < ctx->paramAtlas.chartGroupCount(i); j++) { @@ -7854,16 +8017,17 @@ void ComputeCharts(Atlas *atlas, ChartOptions chartOptions) if (tJunctionsCount > 0) XA_PRINT(" Fixed %u t-junctions in %u charts\n", tJunctionsCount, chartsWithTJunctionsCount); XA_PRINT(" %u charts\n", chartCount); - XA_PROFILE_PRINT(" Total (concurrent): ", computeChartsConcurrent) - XA_PROFILE_PRINT(" Total: ", computeCharts) - XA_PROFILE_PRINT(" Atlas builder: ", atlasBuilder) - XA_PROFILE_PRINT(" Init: ", atlasBuilderInit) - XA_PROFILE_PRINT(" Create initial charts: ", atlasBuilderCreateInitialCharts) - XA_PROFILE_PRINT(" Grow charts: ", atlasBuilderGrowCharts) - XA_PROFILE_PRINT(" Merge charts: ", atlasBuilderMergeCharts) - XA_PROFILE_PRINT(" Create chart meshes: ", createChartMeshes) - XA_PROFILE_PRINT(" Fix t-junctions: ", fixChartMeshTJunctions); - XA_PROFILE_PRINT(" Close holes: ", closeChartMeshHoles) + XA_PROFILE_PRINT_AND_RESET(" Total (real): ", computeChartsReal) + XA_PROFILE_PRINT_AND_RESET(" Total (thread): ", computeChartsThread) + XA_PROFILE_PRINT_AND_RESET(" Atlas builder: ", atlasBuilder) + XA_PROFILE_PRINT_AND_RESET(" Init: ", atlasBuilderInit) + XA_PROFILE_PRINT_AND_RESET(" Create initial charts: ", atlasBuilderCreateInitialCharts) + XA_PROFILE_PRINT_AND_RESET(" Grow charts: ", atlasBuilderGrowCharts) + XA_PROFILE_PRINT_AND_RESET(" Merge charts: ", atlasBuilderMergeCharts) + XA_PROFILE_PRINT_AND_RESET(" Create chart meshes (real): ", createChartMeshesReal) + XA_PROFILE_PRINT_AND_RESET(" Create chart meshes (thread): ", createChartMeshesThread) + XA_PROFILE_PRINT_AND_RESET(" Fix t-junctions: ", fixChartMeshTJunctions) + XA_PROFILE_PRINT_AND_RESET(" Close holes: ", closeChartMeshHoles) XA_PRINT_MEM_USAGE } @@ -7896,12 +8060,12 @@ void ParameterizeCharts(Atlas *atlas, ParameterizeFunc func) } DestroyOutputMeshes(ctx); XA_PRINT("Parameterizing charts\n"); - XA_PROFILE_START(parameterizeChartsConcurrent) + XA_PROFILE_START(parameterizeChartsReal) if (!ctx->paramAtlas.parameterizeCharts(ctx->taskScheduler, func, ctx->progressFunc, ctx->progressUserData)) { XA_PRINT(" Cancelled by user\n"); return; } - XA_PROFILE_END(parameterizeChartsConcurrent) + XA_PROFILE_END(parameterizeChartsReal) uint32_t chartCount = 0, orthoChartsCount = 0, planarChartsCount = 0, chartsAddedCount = 0, chartsDeletedCount = 0; for (uint32_t i = 0; i < ctx->meshCount; i++) { for (uint32_t j = 0; j < ctx->paramAtlas.chartGroupCount(i); j++) { @@ -7982,11 +8146,11 @@ void ParameterizeCharts(Atlas *atlas, ParameterizeFunc func) } if (invalidParamCount > 0) XA_PRINT_WARNING(" %u charts with invalid parameterizations\n", invalidParamCount); - XA_PROFILE_PRINT(" Total (concurrent): ", parameterizeChartsConcurrent) - XA_PROFILE_PRINT(" Total: ", parameterizeCharts) - XA_PROFILE_PRINT(" Orthogonal: ", parameterizeChartsOrthogonal) - XA_PROFILE_PRINT(" LSCM: ", parameterizeChartsLSCM) - XA_PROFILE_PRINT(" Evaluate quality: ", parameterizeChartsEvaluateQuality) + XA_PROFILE_PRINT_AND_RESET(" Total (real): ", parameterizeChartsReal) + XA_PROFILE_PRINT_AND_RESET(" Total (thread): ", parameterizeChartsThread) + XA_PROFILE_PRINT_AND_RESET(" Orthogonal: ", parameterizeChartsOrthogonal) + XA_PROFILE_PRINT_AND_RESET(" LSCM: ", parameterizeChartsLSCM) + XA_PROFILE_PRINT_AND_RESET(" Evaluate quality: ", parameterizeChartsEvaluateQuality) XA_PRINT_MEM_USAGE } @@ -8039,7 +8203,7 @@ void PackCharts(Atlas *atlas, PackOptions packOptions) packAtlas.addChart(ctx->paramAtlas.chartAt(i)); } XA_PROFILE_START(packCharts) - if (!packAtlas.packCharts(packOptions, ctx->progressFunc, ctx->progressUserData)) + if (!packAtlas.packCharts(ctx->taskScheduler, packOptions, ctx->progressFunc, ctx->progressUserData)) return; XA_PROFILE_END(packCharts) // Populate atlas object with pack results. @@ -8058,19 +8222,13 @@ void PackCharts(Atlas *atlas, PackOptions packOptions) for (uint32_t i = 0; i < atlas->atlasCount; i++) packAtlas.getImages()[i]->copyTo(&atlas->image[atlas->width * atlas->height * i], atlas->width, atlas->height); } - XA_PROFILE_PRINT(" Total: ", packCharts) - XA_PROFILE_PRINT(" Rasterize: ", packChartsRasterize) - XA_PROFILE_PRINT(" Dilate (padding): ", packChartsDilate) - XA_PROFILE_PRINT(" Find location: ", packChartsFindLocation) - XA_PROFILE_PRINT(" Blit: ", packChartsBlit) + XA_PROFILE_PRINT_AND_RESET(" Total: ", packCharts) + XA_PROFILE_PRINT_AND_RESET(" Rasterize: ", packChartsRasterize) + XA_PROFILE_PRINT_AND_RESET(" Dilate (padding): ", packChartsDilate) + XA_PROFILE_PRINT_AND_RESET(" Find location (real): ", packChartsFindLocation) + XA_PROFILE_PRINT_AND_RESET(" Find location (thread): ", packChartsFindLocationThread) + XA_PROFILE_PRINT_AND_RESET(" Blit: ", packChartsBlit) XA_PRINT_MEM_USAGE -#if XA_PROFILE - internal::s_profile.packCharts = 0; - internal::s_profile.packChartsRasterize = 0; - internal::s_profile.packChartsDilate = 0; - internal::s_profile.packChartsFindLocation = 0; - internal::s_profile.packChartsBlit = 0; -#endif XA_PRINT("Building output meshes\n"); int progress = 0; if (ctx->progressFunc) { |