summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--SConstruct16
-rw-r--r--core/bind/core_bind.cpp5
-rw-r--r--core/bind/core_bind.h1
-rw-r--r--core/engine.cpp1
-rw-r--r--core/engine.h2
-rw-r--r--doc/classes/Engine.xml7
-rw-r--r--doc/classes/InputEvent.xml5
-rw-r--r--doc/classes/Tree.xml4
-rw-r--r--doc/classes/TreeItem.xml1
-rw-r--r--drivers/gles2/rasterizer_scene_gles2.cpp4
-rw-r--r--drivers/png/resource_saver_png.cpp2
-rw-r--r--drivers/unix/dir_access_unix.cpp24
-rw-r--r--editor/code_editor.cpp14
-rw-r--r--editor/editor_asset_installer.cpp14
-rw-r--r--editor/editor_settings.cpp4
-rw-r--r--editor/filesystem_dock.cpp4
-rw-r--r--editor/plugins/asset_library_editor_plugin.cpp111
-rw-r--r--editor/plugins/canvas_item_editor_plugin.cpp4
-rw-r--r--editor/project_settings_editor.cpp4
-rw-r--r--main/main.cpp1
-rw-r--r--main/main_timer_sync.cpp4
-rw-r--r--main/main_timer_sync.h1
-rw-r--r--modules/bmp/image_loader_bmp.cpp20
-rw-r--r--modules/bmp/image_loader_bmp.h8
-rw-r--r--modules/gdscript/gdscript_editor.cpp4
-rw-r--r--modules/gdscript/gdscript_function.cpp50
-rw-r--r--modules/visual_script/visual_script_func_nodes.cpp32
-rw-r--r--platform/server/os_server.cpp4
-rw-r--r--platform/server/os_server.h1
-rw-r--r--scene/3d/light.cpp2
-rw-r--r--scene/resources/audio_stream_sample.cpp3
-rw-r--r--thirdparty/README.md2
-rw-r--r--thirdparty/xatlas/xatlas.cpp698
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) {