diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml
index a607d6b8ff7e..a22d05f5f019 100644
--- a/doc/classes/DisplayServer.xml
+++ b/doc/classes/DisplayServer.xml
@@ -123,7 +123,7 @@
Shows a text dialog which uses the operating system's native look-and-feel. [param callback] should accept a single [int] parameter which corresponds to the index of the pressed button.
- [b]Note:[/b] This method is implemented if the display server has the [constant FEATURE_NATIVE_DIALOG] feature. Supported platforms include macOS and Windows.
+ [b]Note:[/b] This method is implemented if the display server has the [constant FEATURE_NATIVE_DIALOG] feature. Supported platforms include macOS, Windows, and Android.
@@ -1937,7 +1937,7 @@
The display server supports all features of [constant FEATURE_NATIVE_DIALOG_FILE], with the added functionality of Options and native dialog file access to [code]res://[/code] and [code]user://[/code] paths. See [method file_dialog_show] and [method file_dialog_with_options_show]. [b]Windows, macOS, Linux (X11/Wayland)[/b]
- The display server supports initiating window drag operation on demand. See [method window_start_drag].
+ The display server supports initiating window drag and resize operations on demand. See [method window_start_drag] and [method window_start_resize].
Display server supports [constant WINDOW_FLAG_EXCLUDE_FROM_CAPTURE] window flag.
diff --git a/doc/classes/EditorSceneFormatImporter.xml b/doc/classes/EditorSceneFormatImporter.xml
index 886185ff9cdc..3440fdc140ab 100644
--- a/doc/classes/EditorSceneFormatImporter.xml
+++ b/doc/classes/EditorSceneFormatImporter.xml
@@ -16,11 +16,6 @@
Return supported file extensions for this scene importer.
-
-
-
-
-
diff --git a/doc/classes/NavigationRegion2D.xml b/doc/classes/NavigationRegion2D.xml
index 31ce1dba4a3f..731245fb4889 100644
--- a/doc/classes/NavigationRegion2D.xml
+++ b/doc/classes/NavigationRegion2D.xml
@@ -23,6 +23,12 @@
Bakes the [NavigationPolygon]. If [param on_thread] is set to [code]true[/code] (default), the baking is done on a separate thread.
+
+
+
+ Returns the axis-aligned rectangle for the region's transformed navigation mesh.
+
+
diff --git a/doc/classes/NavigationRegion3D.xml b/doc/classes/NavigationRegion3D.xml
index ad31fd06324b..019afce30bd2 100644
--- a/doc/classes/NavigationRegion3D.xml
+++ b/doc/classes/NavigationRegion3D.xml
@@ -23,6 +23,12 @@
Bakes the [NavigationMesh]. If [param on_thread] is set to [code]true[/code] (default), the baking is done on a separate thread. Baking on separate thread is useful because navigation baking is not a cheap operation. When it is completed, it automatically sets the new [NavigationMesh]. Please note that baking on separate thread may be very slow if geometry is parsed from meshes as async access to each mesh involves heavy synchronization. Also, please note that baking on a separate thread is automatically disabled on operating systems that cannot use threads (such as Web with threads disabled).
+
+
+
+ Returns the axis-aligned bounding box for the region's transformed navigation mesh.
+
+
diff --git a/doc/classes/NavigationServer2D.xml b/doc/classes/NavigationServer2D.xml
index 41ef298bc3bb..95db5591d055 100644
--- a/doc/classes/NavigationServer2D.xml
+++ b/doc/classes/NavigationServer2D.xml
@@ -784,6 +784,13 @@
Creates a new region.
+
+
+
+
+ Returns the axis-aligned rectangle for the [param region]'s transformed navigation mesh.
+
+
diff --git a/doc/classes/NavigationServer3D.xml b/doc/classes/NavigationServer3D.xml
index 6f0c68740a5b..64da1cdade7b 100644
--- a/doc/classes/NavigationServer3D.xml
+++ b/doc/classes/NavigationServer3D.xml
@@ -925,6 +925,13 @@
Creates a new region.
+
+
+
+
+ Returns the axis-aligned bounding box for the [param region]'s transformed navigation mesh.
+
+
diff --git a/doc/classes/ParticleProcessMaterial.xml b/doc/classes/ParticleProcessMaterial.xml
index e1c97a74bad4..8336899e8698 100644
--- a/doc/classes/ParticleProcessMaterial.xml
+++ b/doc/classes/ParticleProcessMaterial.xml
@@ -346,6 +346,10 @@
The amount of particles to spawn from the subemitter node when the particle expires.
[b]Note:[/b] This value shouldn't exceed [member GPUParticles2D.amount] or [member GPUParticles3D.amount] defined on the [i]subemitter node[/i] (not the main node), relative to the subemitter's particle lifetime. If the number of particles is exceeded, no new particles will spawn from the subemitter until enough particles have expired.
+
+ The amount of particles to spawn from the subemitter node when the particle spawns.
+ [b]Note:[/b] This value shouldn't exceed [member GPUParticles2D.amount] or [member GPUParticles3D.amount] defined on the [i]subemitter node[/i] (not the main node), relative to the subemitter's particle lifetime. If the number of particles is exceeded, no new particles will spawn from the subemitter until enough particles have expired.
+
The frequency at which particles should be emitted from the subemitter node. One particle will be spawned every [member sub_emitter_frequency] seconds.
[b]Note:[/b] This value shouldn't exceed [member GPUParticles2D.amount] or [member GPUParticles3D.amount] defined on the [i]subemitter node[/i] (not the main node), relative to the subemitter's particle lifetime. If the number of particles is exceeded, no new particles will spawn from the subemitter until enough particles have expired.
@@ -521,7 +525,9 @@
-
+
+
+
Represents the size of the [enum SubEmitterMode] enum.
diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml
index df2814fd8cf2..a3df48d099d8 100644
--- a/doc/classes/RenderingServer.xml
+++ b/doc/classes/RenderingServer.xml
@@ -2559,6 +2559,7 @@
+
@@ -2593,6 +2594,29 @@
Returns the [RenderingDevice] [RID] handle of the [MultiMesh], which can be used as any other buffer on the Rendering Device.
+
+
+
+
+ Returns the [RenderingDevice] [RID] handle of the [MultiMesh] command buffer. This [RID] is only valid if [code]use_indirect[/code] is set to [code]true[/code] when allocating data through [method multimesh_allocate_data]. It can be used to directly modify the instance count via buffer.
+ The data structure is dependent on both how many surfaces the mesh contains and whether it is indexed or not, the buffer has 5 integers in it, with the last unused if the mesh is not indexed.
+ Each of the values in the buffer correspond to these options:
+ [codeblock lang=text]
+ Indexed:
+ 0 - indexCount;
+ 1 - instanceCount;
+ 2 - firstIndex;
+ 3 - vertexOffset;
+ 4 - firstInstance;
+ Non Indexed:
+ 0 - vertexCount;
+ 1 - instanceCount;
+ 2 - firstVertex;
+ 3 - firstInstance;
+ 4 - unused;
+ [/codeblock]
+
+
diff --git a/drivers/gles3/storage/mesh_storage.cpp b/drivers/gles3/storage/mesh_storage.cpp
index a33629423205..428646aaf714 100644
--- a/drivers/gles3/storage/mesh_storage.cpp
+++ b/drivers/gles3/storage/mesh_storage.cpp
@@ -1518,7 +1518,7 @@ void MeshStorage::_multimesh_free(RID p_rid) {
multimesh_owner.free(p_rid);
}
-void MeshStorage::_multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors, bool p_use_custom_data) {
+void MeshStorage::_multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors, bool p_use_custom_data, bool p_use_indirect) {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL(multimesh);
@@ -2041,6 +2041,10 @@ void MeshStorage::_multimesh_set_buffer(RID p_multimesh, const Vector &p_
}
}
+RID MeshStorage::_multimesh_get_command_buffer_rd_rid(RID p_multimesh) const {
+ ERR_FAIL_V_MSG(RID(), "GLES3 does not implement indirect multimeshes.");
+}
+
RID MeshStorage::_multimesh_get_buffer_rd_rid(RID p_multimesh) const {
ERR_FAIL_V_MSG(RID(), "GLES3 does not contain a Rid for the multimesh buffer.");
}
diff --git a/drivers/gles3/storage/mesh_storage.h b/drivers/gles3/storage/mesh_storage.h
index 83ac0efc5c85..3b9084a04ce0 100644
--- a/drivers/gles3/storage/mesh_storage.h
+++ b/drivers/gles3/storage/mesh_storage.h
@@ -502,7 +502,7 @@ class MeshStorage : public RendererMeshStorage {
virtual RID _multimesh_allocate() override;
virtual void _multimesh_initialize(RID p_rid) override;
virtual void _multimesh_free(RID p_rid) override;
- virtual void _multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors = false, bool p_use_custom_data = false) override;
+ virtual void _multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors = false, bool p_use_custom_data = false, bool p_use_indirect = false) override;
virtual int _multimesh_get_instance_count(RID p_multimesh) const override;
virtual void _multimesh_set_mesh(RID p_multimesh, RID p_mesh) override;
@@ -521,6 +521,7 @@ class MeshStorage : public RendererMeshStorage {
virtual Color _multimesh_instance_get_color(RID p_multimesh, int p_index) const override;
virtual Color _multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const override;
virtual void _multimesh_set_buffer(RID p_multimesh, const Vector &p_buffer) override;
+ virtual RID _multimesh_get_command_buffer_rd_rid(RID p_multimesh) const override;
virtual RID _multimesh_get_buffer_rd_rid(RID p_multimesh) const override;
virtual Vector _multimesh_get_buffer(RID p_multimesh) const override;
diff --git a/drivers/wasapi/audio_driver_wasapi.cpp b/drivers/wasapi/audio_driver_wasapi.cpp
index b5cb8da249af..7d5680699f41 100644
--- a/drivers/wasapi/audio_driver_wasapi.cpp
+++ b/drivers/wasapi/audio_driver_wasapi.cpp
@@ -123,6 +123,8 @@ const IID IID_IAudioCaptureClient = __uuidof(IAudioCaptureClient);
static bool default_output_device_changed = false;
static bool default_input_device_changed = false;
+static int output_reinit_countdown = 0;
+static int input_reinit_countdown = 0;
// Silence warning due to a COM API weirdness (GH-35194).
#if defined(__GNUC__) && !defined(__clang__)
@@ -134,6 +136,8 @@ class CMMNotificationClient : public IMMNotificationClient {
LONG _cRef = 1;
public:
+ ComPtr enumerator = nullptr;
+
CMMNotificationClient() {}
virtual ~CMMNotificationClient() {}
@@ -199,6 +203,9 @@ class CMMNotificationClient : public IMMNotificationClient {
static CMMNotificationClient notif_client;
Error AudioDriverWASAPI::audio_device_init(AudioDeviceWASAPI *p_device, bool p_input, bool p_reinit, bool p_no_audio_client_3) {
+ // This function can be called recursively, so clean up before starting:
+ audio_device_finish(p_device);
+
WAVEFORMATEX *pwfex;
ComPtr enumerator = nullptr;
ComPtr output_device = nullptr;
@@ -225,21 +232,25 @@ Error AudioDriverWASAPI::audio_device_init(AudioDeviceWASAPI *p_device, bool p_i
ComPtr tmp_device = nullptr;
hr = devices->Item(i, &tmp_device);
- ERR_BREAK(hr != S_OK);
+ ERR_BREAK_MSG(hr != S_OK, "Cannot get devices item.");
ComPtr props = nullptr;
hr = tmp_device->OpenPropertyStore(STGM_READ, &props);
- ERR_BREAK(hr != S_OK);
+ ERR_BREAK_MSG(hr != S_OK, "Cannot open property store.");
PROPVARIANT propvar;
PropVariantInit(&propvar);
hr = props->GetValue(PKEY_Device_FriendlyNameGodot, &propvar);
- ERR_BREAK(hr != S_OK);
+ ERR_BREAK_MSG(hr != S_OK, "Cannot get value.");
if (p_device->device_name == String(propvar.pwszVal)) {
hr = tmp_device->GetId(&strId);
- ERR_BREAK(hr != S_OK);
+ if (unlikely(hr != S_OK)) {
+ PropVariantClear(&propvar);
+ ERR_PRINT("Cannot get device ID string.");
+ break;
+ }
found = true;
}
@@ -270,9 +281,14 @@ Error AudioDriverWASAPI::audio_device_init(AudioDeviceWASAPI *p_device, bool p_i
ERR_FAIL_COND_V(hr != S_OK, ERR_CANT_OPEN);
}
+ if (notif_client.enumerator != nullptr) {
+ notif_client.enumerator->UnregisterEndpointNotificationCallback(¬if_client);
+ notif_client.enumerator = nullptr;
+ }
hr = enumerator->RegisterEndpointNotificationCallback(¬if_client);
-
- if (hr != S_OK) {
+ if (hr == S_OK) {
+ notif_client.enumerator = enumerator;
+ } else {
ERR_PRINT("WASAPI: RegisterEndpointNotificationCallback error");
}
@@ -317,6 +333,7 @@ Error AudioDriverWASAPI::audio_device_init(AudioDeviceWASAPI *p_device, bool p_i
hr = p_device->audio_client->GetMixFormat(&pwfex);
ERR_FAIL_COND_V(hr != S_OK, ERR_CANT_OPEN);
+ // From this point onward, CoTaskMemFree(pwfex) must be called before returning or pwfex will leak!
print_verbose("WASAPI: wFormatTag = " + itos(pwfex->wFormatTag));
print_verbose("WASAPI: nChannels = " + itos(pwfex->nChannels));
@@ -340,6 +357,7 @@ Error AudioDriverWASAPI::audio_device_init(AudioDeviceWASAPI *p_device, bool p_i
print_verbose("WASAPI: closest->cbSize = " + itos(closest->cbSize));
WARN_PRINT("WASAPI: Using closest match instead");
+ CoTaskMemFree(pwfex);
pwfex = closest;
}
}
@@ -359,11 +377,13 @@ Error AudioDriverWASAPI::audio_device_init(AudioDeviceWASAPI *p_device, bool p_i
p_device->format_tag = WAVE_FORMAT_IEEE_FLOAT;
} else {
ERR_PRINT("WASAPI: Format not supported");
+ CoTaskMemFree(pwfex);
ERR_FAIL_V(ERR_CANT_OPEN);
}
} else {
if (p_device->format_tag != WAVE_FORMAT_PCM && p_device->format_tag != WAVE_FORMAT_IEEE_FLOAT) {
ERR_PRINT("WASAPI: Format not supported");
+ CoTaskMemFree(pwfex);
ERR_FAIL_V(ERR_CANT_OPEN);
}
}
@@ -376,10 +396,28 @@ Error AudioDriverWASAPI::audio_device_init(AudioDeviceWASAPI *p_device, bool p_i
pwfex->nAvgBytesPerSec = pwfex->nSamplesPerSec * pwfex->nChannels * (pwfex->wBitsPerSample / 8);
}
hr = p_device->audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED, streamflags, p_input ? REFTIMES_PER_SEC : 0, 0, pwfex, nullptr);
- ERR_FAIL_COND_V_MSG(hr != S_OK, ERR_CANT_OPEN, "WASAPI: Initialize failed with error 0x" + String::num_uint64(hr, 16) + ".");
+
+ if (p_reinit) {
+ // In case we're trying to re-initialize the device, prevent throwing this error on the console,
+ // otherwise if there is currently no device available this will spam the console.
+ if (hr != S_OK) {
+ print_verbose("WASAPI: Initialize failed with error 0x" + String::num_uint64(hr, 16) + ".");
+ CoTaskMemFree(pwfex);
+ return ERR_CANT_OPEN;
+ }
+ } else {
+ if (unlikely(hr != S_OK)) {
+ CoTaskMemFree(pwfex);
+ ERR_FAIL_V_MSG(ERR_CANT_OPEN, "WASAPI: Initialize failed with error 0x" + String::num_uint64(hr, 16) + ".");
+ }
+ }
+
UINT32 max_frames;
hr = p_device->audio_client->GetBufferSize(&max_frames);
- ERR_FAIL_COND_V(hr != S_OK, ERR_CANT_OPEN);
+ if (unlikely(hr != S_OK)) {
+ CoTaskMemFree(pwfex);
+ ERR_FAIL_V(ERR_CANT_OPEN);
+ }
// Due to WASAPI Shared Mode we have no control of the buffer size
if (!p_input) {
@@ -453,7 +491,10 @@ Error AudioDriverWASAPI::audio_device_init(AudioDeviceWASAPI *p_device, bool p_i
} else {
hr = p_device->audio_client->GetService(IID_IAudioRenderClient, (void **)&p_device->render_client);
}
- ERR_FAIL_COND_V(hr != S_OK, ERR_CANT_OPEN);
+ if (unlikely(hr != S_OK)) {
+ CoTaskMemFree(pwfex);
+ ERR_FAIL_V(ERR_CANT_OPEN);
+ }
// Free memory
CoTaskMemFree(pwfex);
@@ -464,6 +505,11 @@ Error AudioDriverWASAPI::audio_device_init(AudioDeviceWASAPI *p_device, bool p_i
Error AudioDriverWASAPI::init_output_device(bool p_reinit) {
Error err = audio_device_init(&audio_output, false, p_reinit);
if (err != OK) {
+ // We've tried to init the device, but have failed. Time to clean up.
+ Error finish_err = finish_output_device();
+ if (finish_err != OK) {
+ ERR_PRINT("WASAPI: finish_output_device error after failed output audio_device_init");
+ }
return err;
}
@@ -504,6 +550,11 @@ Error AudioDriverWASAPI::init_output_device(bool p_reinit) {
Error AudioDriverWASAPI::init_input_device(bool p_reinit) {
Error err = audio_device_init(&audio_input, true, p_reinit);
if (err != OK) {
+ // We've tried to init the device, but have failed. Time to clean up.
+ Error finish_err = finish_input_device();
+ if (finish_err != OK) {
+ ERR_PRINT("WASAPI: finish_input_device error after failed input audio_device_init");
+ }
return err;
}
@@ -826,9 +877,15 @@ void AudioDriverWASAPI::thread_func(void *p_udata) {
}
if (!ad->audio_output.audio_client) {
- Error err = ad->init_output_device(true);
- if (err == OK) {
- ad->start();
+ if (output_reinit_countdown < 1) {
+ Error err = ad->init_output_device(true);
+ if (err == OK) {
+ ad->start();
+ } else {
+ output_reinit_countdown = 1000;
+ }
+ } else {
+ output_reinit_countdown--;
}
avail_frames = 0;
@@ -899,9 +956,15 @@ void AudioDriverWASAPI::thread_func(void *p_udata) {
}
if (!ad->audio_input.audio_client) {
- Error err = ad->init_input_device(true);
- if (err == OK) {
- ad->input_start();
+ if (input_reinit_countdown < 1) {
+ Error err = ad->init_input_device(true);
+ if (err == OK) {
+ ad->input_start();
+ } else {
+ input_reinit_countdown = 1000;
+ }
+ } else {
+ input_reinit_countdown--;
}
}
}
diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp
index 468f58d52855..0caabffe56f6 100644
--- a/editor/editor_help.cpp
+++ b/editor/editor_help.cpp
@@ -46,6 +46,7 @@
#include "editor/editor_property_name_processor.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
+#include "editor/filesystem_dock.h"
#include "editor/gui/editor_toaster.h"
#include "editor/plugins/script_editor_plugin.h"
#include "editor/themes/editor_scale.h"
@@ -3870,6 +3871,37 @@ void EditorHelpBit::_update_labels() {
_add_text_to_rt(help_data.description.replace("", comment_color.to_html()), content, this, symbol_class_name);
}
+ if (!help_data.resource_path.is_empty()) {
+ if (has_prev_text) {
+ content->add_newline();
+ content->add_newline();
+ }
+ has_prev_text = true;
+
+ const String ext = help_data.resource_path.get_extension();
+ const bool is_dir = ext.is_empty();
+ const bool is_valid = is_dir || EditorFileSystem::get_singleton()->get_valid_extensions().has(ext);
+ if (!is_dir && is_valid) {
+ content->push_meta("open-res:" + help_data.resource_path, RichTextLabel::META_UNDERLINE_ON_HOVER);
+ content->add_image(get_editor_theme_icon(SNAME("Load")));
+ content->add_text(nbsp + TTR("Open"));
+ content->pop(); // meta
+ content->add_newline();
+ }
+
+ if (is_valid) {
+ content->push_meta("show:" + help_data.resource_path, RichTextLabel::META_UNDERLINE_ON_HOVER);
+ content->add_image(get_editor_theme_icon(SNAME("Filesystem")));
+ content->add_text(nbsp + TTR("Show in FileSystem"));
+ content->pop(); // meta
+ } else {
+ content->push_meta("open-file:" + help_data.resource_path, RichTextLabel::META_UNDERLINE_ON_HOVER);
+ content->add_image(get_editor_theme_icon(SNAME("Filesystem")));
+ content->add_text(nbsp + TTR("Open in File Manager"));
+ content->pop(); // meta
+ }
+ }
+
if (is_inside_tree()) {
update_content_height();
}
@@ -3951,6 +3983,17 @@ void EditorHelpBit::_meta_clicked(const String &p_select) {
} else {
_go_to_help(topic + ":" + symbol_class_name + ":" + link);
}
+ } else if (p_select.begins_with("open-file:")) {
+ String path = ProjectSettings::get_singleton()->globalize_path(p_select.trim_prefix("open-file:"));
+ OS::get_singleton()->shell_show_in_file_manager(path, true);
+ } else if (p_select.begins_with("open-res:")) {
+ if (help_data.doc_type.type == "PackedScene") {
+ EditorNode::get_singleton()->load_scene(p_select.trim_prefix("open-res:"));
+ } else {
+ EditorNode::get_singleton()->load_resource(p_select.trim_prefix("open-res:"));
+ }
+ } else if (p_select.begins_with("show:")) {
+ FileSystemDock::get_singleton()->navigate_to_path(p_select.trim_prefix("show:"));
} else if (p_select.begins_with("http:") || p_select.begins_with("https:")) {
OS::get_singleton()->shell_open(p_select);
} else if (p_select.begins_with("^")) { // Copy button.
@@ -4074,6 +4117,46 @@ void EditorHelpBit::parse_symbol(const String &p_symbol, const String &p_prologu
help_data.doc_type.enumeration = item_data.get("enumeration", "");
help_data.doc_type.is_bitfield = item_data.get("is_bitfield", false);
help_data.value = item_data.get("value", "");
+ } else if (item_type == "resource") {
+ String path = item_name.simplify_path();
+ const bool is_uid = path.begins_with("uid://");
+ if (is_uid) {
+ if (ResourceUID::get_singleton()->has_id(ResourceUID::get_singleton()->text_to_id(path))) {
+ path = ResourceUID::uid_to_path(path);
+ } else {
+ path = "";
+ }
+ }
+ help_data.resource_path = path;
+
+ Ref da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
+ if (da->file_exists(path)) {
+ help_data.doc_type.type = ResourceLoader::get_resource_type(path);
+ if (help_data.doc_type.type.is_empty()) {
+ const Vector textfile_ext = ((String)(EDITOR_GET("docks/filesystem/textfile_extensions"))).split(",", false);
+ symbol_type = textfile_ext.has(path.get_extension()) ? TTR("TextFile") : TTR("File");
+ } else {
+ symbol_type = TTR("Resource");
+ symbol_hint = SYMBOL_HINT_ASSIGNABLE;
+ if (is_uid) {
+ help_data.description = vformat("%s: [color=]%s[/color]", TTR("Path"), path);
+ }
+ }
+ symbol_name = path.get_file();
+ } else if (!is_uid && da->dir_exists(path)) {
+ symbol_type = TTR("Directory");
+ symbol_name = path;
+ } else {
+ help_data.resource_path = "";
+ symbol_name = "";
+ if (is_uid) {
+ symbol_type = TTR("Invalid UID");
+ help_data.description = "[color=][i]" + TTR("This UID does not point to any valid Resource.") + "[/i][/color]";
+ } else {
+ symbol_type = TTR("Invalid path");
+ help_data.description = "[color=][i]" + TTR("This path does not exist.") + "[/i][/color]";
+ }
+ }
} else {
ERR_FAIL_MSG("Invalid doc id: Unknown item type " + item_type.quote() + ".");
}
@@ -4091,7 +4174,7 @@ void EditorHelpBit::parse_symbol(const String &p_symbol, const String &p_prologu
}
}
- if (help_data.description.is_empty()) {
+ if (help_data.description.is_empty() && item_type != "resource") {
help_data.description = "[color=][i]" + TTR("No description available.") + "[/i][/color]";
}
diff --git a/editor/editor_help.h b/editor/editor_help.h
index eb687e9c5687..9686d6e1abe6 100644
--- a/editor/editor_help.h
+++ b/editor/editor_help.h
@@ -277,6 +277,7 @@ class EditorHelpBit : public VBoxContainer {
String value;
Vector arguments;
String qualifiers;
+ String resource_path;
};
inline static HashMap doc_class_cache;
diff --git a/editor/import/3d/editor_import_collada.cpp b/editor/import/3d/editor_import_collada.cpp
index b60c5eecb00c..238aa94095d2 100644
--- a/editor/import/3d/editor_import_collada.cpp
+++ b/editor/import/3d/editor_import_collada.cpp
@@ -1796,10 +1796,6 @@ void ColladaImport::create_animation(int p_clip, bool p_import_value_tracks) {
/*************************************** SCENE ***********************************/
/*********************************************************************************/
-uint32_t EditorSceneFormatImporterCollada::get_import_flags() const {
- return IMPORT_SCENE | IMPORT_ANIMATION;
-}
-
void EditorSceneFormatImporterCollada::get_extensions(List *r_extensions) const {
r_extensions->push_back("dae");
}
diff --git a/editor/import/3d/editor_import_collada.h b/editor/import/3d/editor_import_collada.h
index e0214c2fd0a4..4467c1ec04c0 100644
--- a/editor/import/3d/editor_import_collada.h
+++ b/editor/import/3d/editor_import_collada.h
@@ -37,7 +37,6 @@ class EditorSceneFormatImporterCollada : public EditorSceneFormatImporter {
GDCLASS(EditorSceneFormatImporterCollada, EditorSceneFormatImporter);
public:
- virtual uint32_t get_import_flags() const override;
virtual void get_extensions(List *r_extensions) const override;
virtual Node *import_scene(const String &p_path, uint32_t p_flags, const HashMap &p_options, List *r_missing_deps = nullptr, Error *r_err = nullptr) override;
diff --git a/editor/import/3d/resource_importer_obj.cpp b/editor/import/3d/resource_importer_obj.cpp
index 750584b3cfe8..efada9f2ecae 100644
--- a/editor/import/3d/resource_importer_obj.cpp
+++ b/editor/import/3d/resource_importer_obj.cpp
@@ -38,10 +38,6 @@
#include "scene/resources/mesh.h"
#include "scene/resources/surface_tool.h"
-uint32_t EditorOBJImporter::get_import_flags() const {
- return IMPORT_SCENE;
-}
-
static Error _parse_material_library(const String &p_path, HashMap> &material_map, List *r_missing_deps) {
Ref f = FileAccess::open(p_path, FileAccess::READ);
ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_OPEN, vformat("Couldn't open MTL file '%s', it may not exist or not be readable.", p_path));
diff --git a/editor/import/3d/resource_importer_obj.h b/editor/import/3d/resource_importer_obj.h
index c4a99428efdc..46f6a551f1e3 100644
--- a/editor/import/3d/resource_importer_obj.h
+++ b/editor/import/3d/resource_importer_obj.h
@@ -37,7 +37,6 @@ class EditorOBJImporter : public EditorSceneFormatImporter {
GDCLASS(EditorOBJImporter, EditorSceneFormatImporter);
public:
- virtual uint32_t get_import_flags() const override;
virtual void get_extensions(List *r_extensions) const override;
virtual Node *import_scene(const String &p_path, uint32_t p_flags, const HashMap &p_options, List *r_missing_deps, Error *r_err = nullptr) override;
diff --git a/editor/import/3d/resource_importer_scene.cpp b/editor/import/3d/resource_importer_scene.cpp
index 9baa2bf916f2..25ef0d3f0de7 100644
--- a/editor/import/3d/resource_importer_scene.cpp
+++ b/editor/import/3d/resource_importer_scene.cpp
@@ -57,15 +57,6 @@
#include "scene/resources/packed_scene.h"
#include "scene/resources/resource_format_text.h"
-uint32_t EditorSceneFormatImporter::get_import_flags() const {
- uint32_t ret;
- if (GDVIRTUAL_CALL(_get_import_flags, ret)) {
- return ret;
- }
-
- ERR_FAIL_V(0);
-}
-
void EditorSceneFormatImporter::get_extensions(List *r_extensions) const {
Vector arr;
if (GDVIRTUAL_CALL(_get_extensions, arr)) {
@@ -118,7 +109,6 @@ void EditorSceneFormatImporter::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_import_option", "name", "value"), &EditorSceneFormatImporter::add_import_option);
ClassDB::bind_method(D_METHOD("add_import_option_advanced", "type", "name", "default_value", "hint", "hint_string", "usage_flags"), &EditorSceneFormatImporter::add_import_option_advanced, DEFVAL(PROPERTY_HINT_NONE), DEFVAL(""), DEFVAL(PROPERTY_USAGE_DEFAULT));
- GDVIRTUAL_BIND(_get_import_flags);
GDVIRTUAL_BIND(_get_extensions);
GDVIRTUAL_BIND(_import_scene, "path", "flags", "options");
GDVIRTUAL_BIND(_get_import_options, "path");
@@ -3281,10 +3271,6 @@ void ResourceImporterScene::get_scene_importer_extensions(List *p_extens
///////////////////////////////////////
-uint32_t EditorSceneFormatImporterESCN::get_import_flags() const {
- return IMPORT_SCENE;
-}
-
void EditorSceneFormatImporterESCN::get_extensions(List *r_extensions) const {
r_extensions->push_back("escn");
}
diff --git a/editor/import/3d/resource_importer_scene.h b/editor/import/3d/resource_importer_scene.h
index df2fe3d4827c..f25a2231d951 100644
--- a/editor/import/3d/resource_importer_scene.h
+++ b/editor/import/3d/resource_importer_scene.h
@@ -58,7 +58,6 @@ class EditorSceneFormatImporter : public RefCounted {
Node *import_scene_wrapper(const String &p_path, uint32_t p_flags, const Dictionary &p_options);
Ref import_animation_wrapper(const String &p_path, uint32_t p_flags, const Dictionary &p_options);
- GDVIRTUAL0RC(uint32_t, _get_import_flags)
GDVIRTUAL0RC(Vector, _get_extensions)
GDVIRTUAL3R(Object *, _import_scene, String, uint32_t, Dictionary)
GDVIRTUAL1(_get_import_options, String)
@@ -77,7 +76,6 @@ class EditorSceneFormatImporter : public RefCounted {
void add_import_option(const String &p_name, const Variant &p_default_value);
void add_import_option_advanced(Variant::Type p_type, const String &p_name, const Variant &p_default_value, PropertyHint p_hint = PROPERTY_HINT_NONE, const String &p_hint_string = String(), int p_usage_flags = PROPERTY_USAGE_DEFAULT);
- virtual uint32_t get_import_flags() const;
virtual void get_extensions(List *r_extensions) const;
virtual Node *import_scene(const String &p_path, uint32_t p_flags, const HashMap &p_options, List *r_missing_deps, Error *r_err = nullptr);
virtual void get_import_options(const String &p_path, List *r_options);
@@ -322,7 +320,6 @@ class EditorSceneFormatImporterESCN : public EditorSceneFormatImporter {
GDCLASS(EditorSceneFormatImporterESCN, EditorSceneFormatImporter);
public:
- virtual uint32_t get_import_flags() const override;
virtual void get_extensions(List *r_extensions) const override;
virtual Node *import_scene(const String &p_path, uint32_t p_flags, const HashMap &p_options, List *r_missing_deps, Error *r_err = nullptr) override;
};
diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp
index fa16aba6b91c..ece9616b69b2 100644
--- a/editor/plugins/script_text_editor.cpp
+++ b/editor/plugins/script_text_editor.cpp
@@ -1102,6 +1102,11 @@ void ScriptTextEditor::_validate_symbol(const String &p_symbol) {
}
void ScriptTextEditor::_show_symbol_tooltip(const String &p_symbol, int p_row, int p_column) {
+ if (p_symbol.begins_with("res://") || p_symbol.begins_with("uid://")) {
+ EditorHelpBitTooltip::show_tooltip(code_editor->get_text_editor(), "resource||" + p_symbol);
+ return;
+ }
+
Node *base = get_tree()->get_edited_scene_root();
if (base) {
base = _find_node_for_script(base, base, script);
diff --git a/editor/plugins/sprite_frames_editor_plugin.cpp b/editor/plugins/sprite_frames_editor_plugin.cpp
index d3b051fc7aa4..5cea634e6df8 100644
--- a/editor/plugins/sprite_frames_editor_plugin.cpp
+++ b/editor/plugins/sprite_frames_editor_plugin.cpp
@@ -38,6 +38,7 @@
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
#include "editor/editor_undo_redo_manager.h"
+#include "editor/filesystem_dock.h"
#include "editor/gui/editor_bottom_panel.h"
#include "editor/gui/editor_file_dialog.h"
#include "editor/scene_tree_dock.h"
@@ -1309,10 +1310,39 @@ void SpriteFramesEditor::_frame_list_gui_input(const Ref &p_event) {
_zoom_out();
// Don't scroll down after zooming out.
accept_event();
+ } else if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::RIGHT) {
+ Point2 pos = mb->get_position();
+ right_clicked_frame = frame_list->get_item_at_position(pos, true);
+ if (right_clicked_frame != -1) {
+ if (!menu) {
+ menu = memnew(PopupMenu);
+ add_child(menu);
+ menu->connect(SceneStringName(id_pressed), callable_mp(this, &SpriteFramesEditor::_menu_selected));
+ menu->add_icon_item(get_editor_theme_icon(SNAME("ShowInFileSystem")), TTRC("Show in FileSystem"));
+ }
+
+ menu->set_position(get_screen_position() + get_local_mouse_position());
+ menu->popup();
+ }
}
}
}
+void SpriteFramesEditor::_menu_selected(int p_index) {
+ switch (p_index) {
+ case MENU_SHOW_IN_FILESYSTEM: {
+ String path = frames->get_frame_texture(edited_anim, right_clicked_frame)->get_path();
+ // Check if the file is an atlas resource, if it is find the source texture.
+ Ref at = frames->get_frame_texture(edited_anim, right_clicked_frame);
+ while (at.is_valid() && at->get_atlas().is_valid()) {
+ path = at->get_atlas()->get_path();
+ at = at->get_atlas();
+ }
+ FileSystemDock::get_singleton()->navigate_to_path(path);
+ } break;
+ }
+}
+
void SpriteFramesEditor::_frame_list_item_selected(int p_index, bool p_selected) {
if (updating) {
return;
diff --git a/editor/plugins/sprite_frames_editor_plugin.h b/editor/plugins/sprite_frames_editor_plugin.h
index 9f5f9579a4f3..896294482c15 100644
--- a/editor/plugins/sprite_frames_editor_plugin.h
+++ b/editor/plugins/sprite_frames_editor_plugin.h
@@ -87,6 +87,12 @@ class SpriteFramesEditor : public HSplitContainer {
FRAME_ORDER_BOTTOM_TOP_RIGHT_LEFT,
};
+ enum {
+ MENU_SHOW_IN_FILESYSTEM,
+ };
+
+ int right_clicked_frame = -1;
+
bool read_only = false;
Ref autoplay_icon;
@@ -137,6 +143,8 @@ class SpriteFramesEditor : public HSplitContainer {
AcceptDialog *dialog = nullptr;
+ PopupMenu *menu = nullptr;
+
StringName edited_anim;
ConfirmationDialog *delete_dialog = nullptr;
@@ -219,6 +227,8 @@ class SpriteFramesEditor : public HSplitContainer {
void _frame_list_gui_input(const Ref &p_event);
void _frame_list_item_selected(int p_index, bool p_selected);
+ void _menu_selected(int p_index);
+
void _zoom_in();
void _zoom_out();
void _zoom_reset();
diff --git a/misc/extension_api_validation/4.3-stable.expected b/misc/extension_api_validation/4.3-stable.expected
index a2bf8530a8de..7b5889f464b9 100644
--- a/misc/extension_api_validation/4.3-stable.expected
+++ b/misc/extension_api_validation/4.3-stable.expected
@@ -217,6 +217,13 @@ Validate extension JSON: Error: Field 'classes/Control/properties/offset_top': t
Property type changed to float to match the actual internal API and documentation.
+GH-99455
+--------
+Validate extension JSON: Error: Field 'classes/RenderingServer/methods/multimesh_allocate_data/arguments': size changed value in new API, from 5 to 6.
+
+Optional argument added to allow setting indirect draw mode on Multimesh. Compatibility method registered.
+
+
GH-100129
---------
Validate extension JSON: Error: Field 'classes/NavigationServer2D/methods/query_path': is_const changed value in new API, from true to false.
@@ -294,3 +301,11 @@ Validate extension JSON: Error: Field 'classes/RenderingDevice/methods/uniform_b
Validate extension JSON: Error: Field 'classes/RenderingDevice/methods/vertex_buffer_create/arguments': size changed value in new API, from 3 to 4.
Optional argument added. Compatibility methods registered.
+
+
+GH-101531
+---------
+Validate extension JSON: API was removed: classes/EditorSceneFormatImporter/methods/_get_import_flags
+
+This virtual method, and the internal public `get_import_flags`, were never used by the engine, since it was open sourced.
+So we're removing it despite the compat breakage as there's no way for users to rely on this affecting engine behavior.
diff --git a/modules/fbx/editor/editor_scene_importer_fbx2gltf.cpp b/modules/fbx/editor/editor_scene_importer_fbx2gltf.cpp
index 288ed1450745..3881fcdf6c11 100644
--- a/modules/fbx/editor/editor_scene_importer_fbx2gltf.cpp
+++ b/modules/fbx/editor/editor_scene_importer_fbx2gltf.cpp
@@ -38,10 +38,6 @@
#include "modules/gltf/gltf_document.h"
-uint32_t EditorSceneFormatImporterFBX2GLTF::get_import_flags() const {
- return ImportFlags::IMPORT_SCENE | ImportFlags::IMPORT_ANIMATION;
-}
-
void EditorSceneFormatImporterFBX2GLTF::get_extensions(List *r_extensions) const {
r_extensions->push_back("fbx");
}
diff --git a/modules/fbx/editor/editor_scene_importer_fbx2gltf.h b/modules/fbx/editor/editor_scene_importer_fbx2gltf.h
index ce2bac6fcf39..1b68ccfed304 100644
--- a/modules/fbx/editor/editor_scene_importer_fbx2gltf.h
+++ b/modules/fbx/editor/editor_scene_importer_fbx2gltf.h
@@ -42,7 +42,6 @@ class EditorSceneFormatImporterFBX2GLTF : public EditorSceneFormatImporter {
GDCLASS(EditorSceneFormatImporterFBX2GLTF, EditorSceneFormatImporter);
public:
- virtual uint32_t get_import_flags() const override;
virtual void get_extensions(List *r_extensions) const override;
virtual Node *import_scene(const String &p_path, uint32_t p_flags,
const HashMap &p_options,
diff --git a/modules/fbx/editor/editor_scene_importer_ufbx.cpp b/modules/fbx/editor/editor_scene_importer_ufbx.cpp
index 64075c06643e..9da03a5048d6 100644
--- a/modules/fbx/editor/editor_scene_importer_ufbx.cpp
+++ b/modules/fbx/editor/editor_scene_importer_ufbx.cpp
@@ -37,10 +37,6 @@
#include "core/config/project_settings.h"
-uint32_t EditorSceneFormatImporterUFBX::get_import_flags() const {
- return ImportFlags::IMPORT_SCENE | ImportFlags::IMPORT_ANIMATION;
-}
-
void EditorSceneFormatImporterUFBX::get_extensions(List *r_extensions) const {
r_extensions->push_back("fbx");
}
diff --git a/modules/fbx/editor/editor_scene_importer_ufbx.h b/modules/fbx/editor/editor_scene_importer_ufbx.h
index 6e3eafc10026..f81b3a82db63 100644
--- a/modules/fbx/editor/editor_scene_importer_ufbx.h
+++ b/modules/fbx/editor/editor_scene_importer_ufbx.h
@@ -46,7 +46,6 @@ class EditorSceneFormatImporterUFBX : public EditorSceneFormatImporter {
FBX_IMPORTER_UFBX,
FBX_IMPORTER_FBX2GLTF,
};
- virtual uint32_t get_import_flags() const override;
virtual void get_extensions(List *r_extensions) const override;
virtual Node *import_scene(const String &p_path, uint32_t p_flags,
const HashMap &p_options,
diff --git a/modules/gltf/editor/editor_scene_importer_blend.cpp b/modules/gltf/editor/editor_scene_importer_blend.cpp
index e64a571a614a..c9540220bf38 100644
--- a/modules/gltf/editor/editor_scene_importer_blend.cpp
+++ b/modules/gltf/editor/editor_scene_importer_blend.cpp
@@ -102,10 +102,6 @@ static bool _get_blender_version(const String &p_path, int &r_major, int &r_mino
return true;
}
-uint32_t EditorSceneFormatImporterBlend::get_import_flags() const {
- return ImportFlags::IMPORT_SCENE | ImportFlags::IMPORT_ANIMATION;
-}
-
void EditorSceneFormatImporterBlend::get_extensions(List *r_extensions) const {
r_extensions->push_back("blend");
}
diff --git a/modules/gltf/editor/editor_scene_importer_blend.h b/modules/gltf/editor/editor_scene_importer_blend.h
index 17eb9e570958..a4f40143ceef 100644
--- a/modules/gltf/editor/editor_scene_importer_blend.h
+++ b/modules/gltf/editor/editor_scene_importer_blend.h
@@ -67,7 +67,6 @@ class EditorSceneFormatImporterBlend : public EditorSceneFormatImporter {
BLEND_MODIFIERS_ALL
};
- virtual uint32_t get_import_flags() const override;
virtual void get_extensions(List *r_extensions) const override;
virtual Node *import_scene(const String &p_path, uint32_t p_flags,
const HashMap &p_options,
diff --git a/modules/gltf/editor/editor_scene_importer_gltf.cpp b/modules/gltf/editor/editor_scene_importer_gltf.cpp
index 3e75017fe65b..e63b3ec95628 100644
--- a/modules/gltf/editor/editor_scene_importer_gltf.cpp
+++ b/modules/gltf/editor/editor_scene_importer_gltf.cpp
@@ -35,10 +35,6 @@
#include "../gltf_defines.h"
#include "../gltf_document.h"
-uint32_t EditorSceneFormatImporterGLTF::get_import_flags() const {
- return ImportFlags::IMPORT_SCENE | ImportFlags::IMPORT_ANIMATION;
-}
-
void EditorSceneFormatImporterGLTF::get_extensions(List *r_extensions) const {
r_extensions->push_back("gltf");
r_extensions->push_back("glb");
diff --git a/modules/gltf/editor/editor_scene_importer_gltf.h b/modules/gltf/editor/editor_scene_importer_gltf.h
index e17b6f3f2e6b..1dee456cb327 100644
--- a/modules/gltf/editor/editor_scene_importer_gltf.h
+++ b/modules/gltf/editor/editor_scene_importer_gltf.h
@@ -42,7 +42,6 @@ class EditorSceneFormatImporterGLTF : public EditorSceneFormatImporter {
GDCLASS(EditorSceneFormatImporterGLTF, EditorSceneFormatImporter);
public:
- virtual uint32_t get_import_flags() const override;
virtual void get_extensions(List *r_extensions) const override;
virtual Node *import_scene(const String &p_path, uint32_t p_flags,
const HashMap &p_options,
diff --git a/modules/jsonrpc/jsonrpc.cpp b/modules/jsonrpc/jsonrpc.cpp
index a3c0ce88bf5d..0f03e49fc8b7 100644
--- a/modules/jsonrpc/jsonrpc.cpp
+++ b/modules/jsonrpc/jsonrpc.cpp
@@ -122,6 +122,11 @@ Variant JSONRPC::process_action(const Variant &p_action, bool p_process_arr_elem
Variant id;
if (dict.has("id")) {
id = dict["id"];
+
+ // Account for implementations that discern between int and float on the json serialization level, by using an int if there is a .0 fraction. See #100914
+ if (id.get_type() == Variant::FLOAT && id.operator float() == (float)(id.operator int())) {
+ id = id.operator int();
+ }
}
if (object == nullptr || !object->has_method(method)) {
diff --git a/modules/ktx/texture_loader_ktx.cpp b/modules/ktx/texture_loader_ktx.cpp
index 7c73f9cb8a6e..c088abcde993 100644
--- a/modules/ktx/texture_loader_ktx.cpp
+++ b/modules/ktx/texture_loader_ktx.cpp
@@ -208,32 +208,33 @@ static Ref load_from_file_access(Ref f, Error *r_error) {
case GL_COMPRESSED_RGBA_BPTC_UNORM:
format = Image::FORMAT_BPTC_RGBA;
break;
-#if 0 // TODO: ETC compression is bogus.
case GL_ETC1_RGB8_OES:
format = Image::FORMAT_ETC;
break;
case GL_COMPRESSED_R11_EAC:
format = Image::FORMAT_ETC2_R11;
break;
- case GL_COMPRESSED_SIGNED_R11_EAC:
+ // Decompression is not supported for this format.
+ /*case GL_COMPRESSED_SIGNED_R11_EAC:
format = Image::FORMAT_ETC2_R11S;
- break;
+ break;*/
case GL_COMPRESSED_RG11_EAC:
format = Image::FORMAT_ETC2_RG11;
break;
- case GL_COMPRESSED_SIGNED_RG11_EAC:
+ // Decompression is not supported for this format.
+ /*case GL_COMPRESSED_SIGNED_RG11_EAC:
format = Image::FORMAT_ETC2_RG11S;
- break;
+ break;*/
case GL_COMPRESSED_RGB8_ETC2:
format = Image::FORMAT_ETC2_RGB8;
break;
case GL_COMPRESSED_RGBA8_ETC2_EAC:
format = Image::FORMAT_ETC2_RGBA8;
break;
- case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
+ // Decompression is not supported for this format.
+ /*case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
format = Image::FORMAT_ETC2_RGB8A1;
- break;
-#endif
+ break;*/
case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR:
format = Image::FORMAT_ASTC_4x4;
break;
@@ -406,29 +407,30 @@ static Ref load_from_file_access(Ref f, Error *r_error) {
case VK_FORMAT_BC7_UNORM_BLOCK:
format = Image::FORMAT_BPTC_RGBA;
break;
-#if 0 // TODO: ETC compression is bogus.
case VK_FORMAT_EAC_R11_UNORM_BLOCK:
format = Image::FORMAT_ETC2_R11;
break;
- case VK_FORMAT_EAC_R11_SNORM_BLOCK:
+ // Decompression is not supported for this format.
+ /*case VK_FORMAT_EAC_R11_SNORM_BLOCK:
format = Image::FORMAT_ETC2_R11S;
- break;
+ break;*/
case VK_FORMAT_EAC_R11G11_UNORM_BLOCK:
format = Image::FORMAT_ETC2_RG11;
break;
- case VK_FORMAT_EAC_R11G11_SNORM_BLOCK:
+ // Decompression is not supported for this format.
+ /*case VK_FORMAT_EAC_R11G11_SNORM_BLOCK:
format = Image::FORMAT_ETC2_RG11S;
- break;
+ break;*/
case VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK:
format = Image::FORMAT_ETC2_RGB8;
break;
case VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK:
format = Image::FORMAT_ETC2_RGBA8;
break;
- case VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK:
+ // Decompression is not supported for this format.
+ /*case VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK:
format = Image::FORMAT_ETC2_RGB8A1;
- break;
-#endif
+ break;*/
case VK_FORMAT_ASTC_4x4_SRGB_BLOCK:
format = Image::FORMAT_ASTC_4x4;
break;
diff --git a/modules/navigation/2d/godot_navigation_server_2d.cpp b/modules/navigation/2d/godot_navigation_server_2d.cpp
index f6876b31e2a7..89e4de3b0221 100644
--- a/modules/navigation/2d/godot_navigation_server_2d.cpp
+++ b/modules/navigation/2d/godot_navigation_server_2d.cpp
@@ -158,6 +158,13 @@ static Ref poly_to_mesh(Ref d) {
}
}
+static Rect2 aabb_to_rect2(AABB aabb) {
+ Rect2 rect2;
+ rect2.position = Vector2(aabb.position.x, aabb.position.z);
+ rect2.size = Vector2(aabb.size.x, aabb.size.z);
+ return rect2;
+}
+
void GodotNavigationServer2D::init() {
#ifdef CLIPPER2_ENABLED
navmesh_generator_2d = memnew(NavMeshGenerator2D);
@@ -332,6 +339,11 @@ Vector2 GodotNavigationServer2D::region_get_random_point(RID p_region, uint32_t
return v3_to_v2(result);
}
+Rect2 GodotNavigationServer2D::region_get_bounds(RID p_region) const {
+ AABB bounds = NavigationServer3D::get_singleton()->region_get_bounds(p_region);
+ return aabb_to_rect2(bounds);
+}
+
RID FORWARD_0(link_create);
void FORWARD_2(link_set_map, RID, p_link, RID, p_map, rid_to_rid, rid_to_rid);
diff --git a/modules/navigation/2d/godot_navigation_server_2d.h b/modules/navigation/2d/godot_navigation_server_2d.h
index f4a5023a0df5..361b812bdf39 100644
--- a/modules/navigation/2d/godot_navigation_server_2d.h
+++ b/modules/navigation/2d/godot_navigation_server_2d.h
@@ -105,6 +105,7 @@ class GodotNavigationServer2D : public NavigationServer2D {
virtual Vector2 region_get_connection_pathway_end(RID p_region, int p_connection_id) const override;
virtual Vector2 region_get_closest_point(RID p_region, const Vector2 &p_point) const override;
virtual Vector2 region_get_random_point(RID p_region, uint32_t p_navigation_layers, bool p_uniformly) const override;
+ virtual Rect2 region_get_bounds(RID p_region) const override;
virtual RID link_create() override;
diff --git a/modules/navigation/3d/godot_navigation_server_3d.cpp b/modules/navigation/3d/godot_navigation_server_3d.cpp
index e2b8d0509b74..ef15bcd6a3df 100644
--- a/modules/navigation/3d/godot_navigation_server_3d.cpp
+++ b/modules/navigation/3d/godot_navigation_server_3d.cpp
@@ -595,6 +595,13 @@ Vector3 GodotNavigationServer3D::region_get_random_point(RID p_region, uint32_t
return region->get_random_point(p_navigation_layers, p_uniformly);
}
+AABB GodotNavigationServer3D::region_get_bounds(RID p_region) const {
+ const NavRegion *region = region_owner.get_or_null(p_region);
+ ERR_FAIL_NULL_V(region, AABB());
+
+ return region->get_bounds();
+}
+
RID GodotNavigationServer3D::link_create() {
MutexLock lock(operations_mutex);
diff --git a/modules/navigation/3d/godot_navigation_server_3d.h b/modules/navigation/3d/godot_navigation_server_3d.h
index 268dbee8e737..c595fbc02e4f 100644
--- a/modules/navigation/3d/godot_navigation_server_3d.h
+++ b/modules/navigation/3d/godot_navigation_server_3d.h
@@ -187,6 +187,7 @@ class GodotNavigationServer3D : public NavigationServer3D {
virtual Vector3 region_get_closest_point(RID p_region, const Vector3 &p_point) const override;
virtual Vector3 region_get_closest_point_normal(RID p_region, const Vector3 &p_point) const override;
virtual Vector3 region_get_random_point(RID p_region, uint32_t p_navigation_layers, bool p_uniformly) const override;
+ virtual AABB region_get_bounds(RID p_region) const override;
virtual RID link_create() override;
COMMAND_2(link_set_map, RID, p_link, RID, p_map);
diff --git a/platform/android/SCsub b/platform/android/SCsub
index 66c955252bd5..d0928a937b32 100644
--- a/platform/android/SCsub
+++ b/platform/android/SCsub
@@ -29,6 +29,7 @@ android_files = [
"plugin/godot_plugin_jni.cpp",
"rendering_context_driver_vulkan_android.cpp",
"variant/callable_jni.cpp",
+ "dialog_utils_jni.cpp",
]
env_android = env.Clone()
diff --git a/platform/android/dialog_utils_jni.cpp b/platform/android/dialog_utils_jni.cpp
new file mode 100644
index 000000000000..c1f8cea06d64
--- /dev/null
+++ b/platform/android/dialog_utils_jni.cpp
@@ -0,0 +1,52 @@
+/**************************************************************************/
+/* dialog_utils_jni.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "dialog_utils_jni.h"
+
+#include "display_server_android.h"
+#include "jni_utils.h"
+
+extern "C" {
+
+JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_DialogUtils_dialogCallback(JNIEnv *env, jclass clazz, jint p_button_index) {
+ DisplayServerAndroid *ds = (DisplayServerAndroid *)DisplayServer::get_singleton();
+ if (ds) {
+ ds->emit_dialog_callback(p_button_index);
+ }
+}
+
+JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_DialogUtils_inputDialogCallback(JNIEnv *env, jclass clazz, jstring p_text) {
+ DisplayServerAndroid *ds = (DisplayServerAndroid *)DisplayServer::get_singleton();
+ if (ds) {
+ String text = jstring_to_string(p_text, env);
+ ds->emit_input_dialog_callback(text);
+ }
+}
+}
diff --git a/platform/android/dialog_utils_jni.h b/platform/android/dialog_utils_jni.h
new file mode 100644
index 000000000000..7b2da46dc57f
--- /dev/null
+++ b/platform/android/dialog_utils_jni.h
@@ -0,0 +1,41 @@
+/**************************************************************************/
+/* dialog_utils_jni.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef DIALOG_UTILS_JNI_H
+#define DIALOG_UTILS_JNI_H
+
+#include
+
+extern "C" {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_DialogUtils_dialogCallback(JNIEnv *env, jclass clazz, jint p_button_index);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_DialogUtils_inputDialogCallback(JNIEnv *env, jclass clazz, jstring p_text);
+}
+
+#endif // DIALOG_UTILS_JNI_H
diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp
index 77b879133edf..06973f289c5c 100644
--- a/platform/android/display_server_android.cpp
+++ b/platform/android/display_server_android.cpp
@@ -70,7 +70,7 @@ bool DisplayServerAndroid::has_feature(Feature p_feature) const {
//case FEATURE_IME:
case FEATURE_MOUSE:
//case FEATURE_MOUSE_WARP:
- //case FEATURE_NATIVE_DIALOG:
+ case FEATURE_NATIVE_DIALOG:
case FEATURE_NATIVE_DIALOG_INPUT:
case FEATURE_NATIVE_DIALOG_FILE:
//case FEATURE_NATIVE_DIALOG_FILE_EXTRA:
@@ -178,6 +178,19 @@ bool DisplayServerAndroid::clipboard_has() const {
}
}
+Error DisplayServerAndroid::dialog_show(String p_title, String p_description, Vector p_buttons, const Callable &p_callback) {
+ GodotJavaWrapper *godot_java = OS_Android::get_singleton()->get_godot_java();
+ ERR_FAIL_NULL_V(godot_java, FAILED);
+ dialog_callback = p_callback;
+ return godot_java->show_dialog(p_title, p_description, p_buttons);
+}
+
+void DisplayServerAndroid::emit_dialog_callback(int p_button_index) {
+ if (dialog_callback.is_valid()) {
+ dialog_callback.call_deferred(p_button_index);
+ }
+}
+
Error DisplayServerAndroid::dialog_input_text(String p_title, String p_description, String p_partial, const Callable &p_callback) {
GodotJavaWrapper *godot_java = OS_Android::get_singleton()->get_godot_java();
ERR_FAIL_NULL_V(godot_java, FAILED);
diff --git a/platform/android/display_server_android.h b/platform/android/display_server_android.h
index 582f722fa5b8..0d2caa07b7c7 100644
--- a/platform/android/display_server_android.h
+++ b/platform/android/display_server_android.h
@@ -87,7 +87,9 @@ class DisplayServerAndroid : public DisplayServer {
Callable system_theme_changed;
+ Callable dialog_callback;
Callable input_dialog_callback;
+
Callable file_picker_callback;
void _window_callback(const Callable &p_callable, const Variant &p_arg, bool p_deferred = false) const;
@@ -119,6 +121,9 @@ class DisplayServerAndroid : public DisplayServer {
virtual String clipboard_get() const override;
virtual bool clipboard_has() const override;
+ virtual Error dialog_show(String p_title, String p_description, Vector p_buttons, const Callable &p_callback) override;
+ void emit_dialog_callback(int p_button_index);
+
virtual Error dialog_input_text(String p_title, String p_description, String p_partial, const Callable &p_callback) override;
void emit_input_dialog_callback(String p_text);
diff --git a/platform/android/java/lib/res/values/dimens.xml b/platform/android/java/lib/res/values/dimens.xml
index 287d1c892048..cc0f0788affe 100644
--- a/platform/android/java/lib/res/values/dimens.xml
+++ b/platform/android/java/lib/res/values/dimens.xml
@@ -1,6 +1,8 @@
48dp
- 10dp
- 5dp
+ 48dp
+ 10dp
+ 16dp
+ 8dp
diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
index fcbb8830f901..3fc3caed9114 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
@@ -44,7 +44,6 @@ import android.os.*
import android.util.Log
import android.util.TypedValue
import android.view.*
-import android.widget.EditText
import android.widget.FrameLayout
import androidx.annotation.Keep
import androidx.annotation.StringRes
@@ -65,6 +64,7 @@ import org.godotengine.godot.plugin.GodotPlugin
import org.godotengine.godot.plugin.GodotPluginRegistry
import org.godotengine.godot.tts.GodotTTS
import org.godotengine.godot.utils.CommandLineFileParser
+import org.godotengine.godot.utils.DialogUtils
import org.godotengine.godot.utils.GodotNetUtils
import org.godotengine.godot.utils.PermissionsUtil
import org.godotengine.godot.utils.PermissionsUtil.requestPermission
@@ -903,27 +903,27 @@ class Godot(private val context: Context) {
}
/**
- * Popup a dialog to input text.
+ * This method shows a dialog with multiple buttons.
+ *
+ * @param title The title of the dialog.
+ * @param message The message displayed in the dialog.
+ * @param buttons An array of button labels to display.
+ */
+ @Keep
+ private fun showDialog(title: String, message: String, buttons: Array) {
+ getActivity()?.let { DialogUtils.showDialog(it, title, message, buttons) }
+ }
+
+ /**
+ * This method shows a dialog with a text input field, allowing the user to input text.
+ *
+ * @param title The title of the input dialog.
+ * @param message The message displayed in the input dialog.
+ * @param existingText The existing text that will be pre-filled in the input field.
*/
@Keep
private fun showInputDialog(title: String, message: String, existingText: String) {
- val activity: Activity = getActivity() ?: return
- val inputField = EditText(activity)
- val paddingHorizontal = activity.resources.getDimensionPixelSize(R.dimen.input_dialog_padding_horizontal)
- val paddingVertical = activity.resources.getDimensionPixelSize(R.dimen.input_dialog_padding_vertical)
- inputField.setPadding(paddingHorizontal, paddingVertical, paddingHorizontal, paddingVertical)
- inputField.setText(existingText)
- runOnUiThread {
- val builder = AlertDialog.Builder(activity)
- builder.setMessage(message).setTitle(title).setView(inputField)
- builder.setPositiveButton(R.string.dialog_ok) {
- dialog: DialogInterface, id: Int ->
- GodotLib.inputDialogCallback(inputField.text.toString())
- dialog.dismiss()
- }
- val dialog = builder.create()
- dialog.show()
- }
+ getActivity()?.let { DialogUtils.showInputDialog(it, title, message, existingText) }
}
@Keep
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java
index 9af91924704a..67aa3283be5c 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java
@@ -235,11 +235,6 @@ public static void calldeferred(long p_id, String p_method, Object[] p_params) {
*/
public static native void onNightModeChanged();
- /**
- * Invoked on the input dialog submitted.
- */
- public static native void inputDialogCallback(String p_text);
-
/**
* Invoked on the file picker closed.
*/
diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/DialogUtils.kt b/platform/android/java/lib/src/org/godotengine/godot/utils/DialogUtils.kt
new file mode 100644
index 000000000000..45ec72b74339
--- /dev/null
+++ b/platform/android/java/lib/src/org/godotengine/godot/utils/DialogUtils.kt
@@ -0,0 +1,185 @@
+/**************************************************************************/
+/* DialogUtils.kt */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+package org.godotengine.godot.utils
+
+import android.app.Activity
+import android.app.AlertDialog
+import android.content.DialogInterface
+import android.widget.Button
+import android.widget.EditText
+import android.widget.LinearLayout
+
+import org.godotengine.godot.R
+
+/**
+ * Utility class for managing dialogs.
+ */
+internal class DialogUtils {
+ companion object {
+ private val TAG = DialogUtils::class.java.simpleName
+
+ /**
+ * Invoked on dialog button press.
+ */
+ @JvmStatic
+ private external fun dialogCallback(buttonIndex: Int)
+
+ /**
+ * Invoked on the input dialog submitted.
+ */
+ @JvmStatic
+ private external fun inputDialogCallback(text: String)
+
+ /**
+ * Displays a dialog with dynamically arranged buttons based on their text length.
+ *
+ * The buttons are laid out in rows, with a maximum of 2 buttons per row. If a button's text
+ * is too long to fit within half the screen width, it occupies the entire row.
+ *
+ * @param activity The activity where the dialog will be displayed.
+ * @param title The title of the dialog.
+ * @param message The message displayed in the dialog.
+ * @param buttons An array of button labels to display.
+ */
+ fun showDialog(activity: Activity, title: String, message: String, buttons: Array) {
+ var dismissDialog: () -> Unit = {} // Helper to dismiss the Dialog when a button is clicked.
+ activity.runOnUiThread {
+ val builder = AlertDialog.Builder(activity)
+ builder.setTitle(title)
+ builder.setMessage(message)
+
+ val buttonHeight = activity.resources.getDimensionPixelSize(R.dimen.button_height)
+ val paddingHorizontal = activity.resources.getDimensionPixelSize(R.dimen.dialog_padding_horizontal)
+ val paddingVertical = activity.resources.getDimensionPixelSize(R.dimen.dialog_padding_vertical)
+ val buttonPadding = activity.resources.getDimensionPixelSize(R.dimen.button_padding)
+
+ // Create a vertical parent layout to hold all rows of buttons.
+ val parentLayout = LinearLayout(activity)
+ parentLayout.orientation = LinearLayout.VERTICAL
+ parentLayout.setPadding(paddingHorizontal, paddingVertical, paddingHorizontal, paddingVertical)
+
+ // Horizontal row layout for arranging buttons.
+ var rowLayout = LinearLayout(activity)
+ rowLayout.orientation = LinearLayout.HORIZONTAL
+ rowLayout.layoutParams = LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT
+ )
+
+ // Calculate the maximum width for a button to allow two buttons per row.
+ val screenWidth = activity.resources.displayMetrics.widthPixels
+ val availableWidth = screenWidth - (2 * paddingHorizontal)
+ val maxButtonWidth = availableWidth / 2
+
+ buttons.forEachIndexed { index, buttonLabel ->
+ val button = Button(activity)
+ button.text = buttonLabel
+ button.isSingleLine = true
+ button.setPadding(buttonPadding, buttonPadding, buttonPadding, buttonPadding)
+
+ // Measure the button to determine its width.
+ button.measure(0, 0)
+ val buttonWidth = button.measuredWidth
+
+ val params = LinearLayout.LayoutParams(
+ if (buttonWidth > maxButtonWidth) LinearLayout.LayoutParams.MATCH_PARENT else 0,
+ buttonHeight
+ )
+ params.weight = if (buttonWidth > maxButtonWidth) 0f else 1f
+ button.layoutParams = params
+
+ // Handle full-width buttons by finalizing the current row, if needed.
+ if (buttonWidth > maxButtonWidth) {
+ if (rowLayout.childCount > 0) {
+ parentLayout.addView(rowLayout)
+ rowLayout = LinearLayout(activity)
+ rowLayout.orientation = LinearLayout.HORIZONTAL
+ }
+ // Add the full-width button directly to the parent layout.
+ parentLayout.addView(button)
+ } else {
+ rowLayout.addView(button)
+
+ // Finalize the row if it reaches 2 buttons.
+ if (rowLayout.childCount == 2) {
+ parentLayout.addView(rowLayout)
+ rowLayout = LinearLayout(activity)
+ rowLayout.orientation = LinearLayout.HORIZONTAL
+ }
+
+ // Handle the last button with incomplete row.
+ if (index == buttons.size - 1 && rowLayout.childCount > 0) {
+ parentLayout.addView(rowLayout)
+ }
+ }
+
+ button.setOnClickListener {
+ dialogCallback(index)
+ dismissDialog()
+ }
+ }
+
+ // Attach the parent layout to the dialog.
+ builder.setView(parentLayout)
+ val dialog = builder.create()
+ dismissDialog = {dialog.dismiss()}
+ dialog.show()
+ }
+ }
+
+ /**
+ * This method shows a dialog with a text input field, allowing the user to input text.
+ *
+ * @param activity The activity where the input dialog will be displayed.
+ * @param title The title of the input dialog.
+ * @param message The message displayed in the input dialog.
+ * @param existingText The existing text that will be pre-filled in the input field.
+ */
+ fun showInputDialog(activity: Activity, title: String, message: String, existingText: String) {
+ val inputField = EditText(activity)
+ val paddingHorizontal = activity.resources.getDimensionPixelSize(R.dimen.dialog_padding_horizontal)
+ val paddingVertical = activity.resources.getDimensionPixelSize(R.dimen.dialog_padding_vertical)
+ inputField.setPadding(paddingHorizontal, paddingVertical, paddingHorizontal, paddingVertical)
+ inputField.setText(existingText)
+ activity.runOnUiThread {
+ val builder = AlertDialog.Builder(activity)
+ builder.setMessage(message).setTitle(title).setView(inputField)
+ builder.setPositiveButton(R.string.dialog_ok) {
+ dialog: DialogInterface, id: Int ->
+ inputDialogCallback(inputField.text.toString())
+ dialog.dismiss()
+ }
+ val dialog = builder.create()
+ dialog.show()
+ }
+ }
+ }
+}
diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp
index 023832a2d345..b291473e29d3 100644
--- a/platform/android/java_godot_lib_jni.cpp
+++ b/platform/android/java_godot_lib_jni.cpp
@@ -494,14 +494,6 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onNightModeChanged(JN
}
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_inputDialogCallback(JNIEnv *env, jclass clazz, jstring p_text) {
- DisplayServerAndroid *ds = (DisplayServerAndroid *)DisplayServer::get_singleton();
- if (ds) {
- String text = jstring_to_string(p_text, env);
- ds->emit_input_dialog_callback(text);
- }
-}
-
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_filePickerCallback(JNIEnv *env, jclass clazz, jboolean p_ok, jobjectArray p_selected_paths) {
DisplayServerAndroid *ds = (DisplayServerAndroid *)DisplayServer::get_singleton();
if (ds) {
diff --git a/platform/android/java_godot_lib_jni.h b/platform/android/java_godot_lib_jni.h
index 48d91795c7be..6feaec47c599 100644
--- a/platform/android/java_godot_lib_jni.h
+++ b/platform/android/java_godot_lib_jni.h
@@ -65,7 +65,6 @@ JNIEXPORT jstring JNICALL Java_org_godotengine_godot_GodotLib_getEditorSetting(J
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setVirtualKeyboardHeight(JNIEnv *env, jclass clazz, jint p_height);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_requestPermissionResult(JNIEnv *env, jclass clazz, jstring p_permission, jboolean p_result);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onNightModeChanged(JNIEnv *env, jclass clazz);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_inputDialogCallback(JNIEnv *env, jclass clazz, jstring p_text);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_filePickerCallback(JNIEnv *env, jclass clazz, jboolean p_ok, jobjectArray p_selected_paths);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererResumed(JNIEnv *env, jclass clazz);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererPaused(JNIEnv *env, jclass clazz);
diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp
index 42249b9f5882..8c75e666a0a9 100644
--- a/platform/android/java_godot_wrapper.cpp
+++ b/platform/android/java_godot_wrapper.cpp
@@ -69,6 +69,7 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_
_get_clipboard = p_env->GetMethodID(godot_class, "getClipboard", "()Ljava/lang/String;");
_set_clipboard = p_env->GetMethodID(godot_class, "setClipboard", "(Ljava/lang/String;)V");
_has_clipboard = p_env->GetMethodID(godot_class, "hasClipboard", "()Z");
+ _show_dialog = p_env->GetMethodID(godot_class, "showDialog", "(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;)V");
_show_input_dialog = p_env->GetMethodID(godot_class, "showInputDialog", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
_show_file_picker = p_env->GetMethodID(godot_class, "showFilePicker", "(Ljava/lang/String;Ljava/lang/String;I[Ljava/lang/String;)V");
_request_permission = p_env->GetMethodID(godot_class, "requestPermission", "(Ljava/lang/String;)Z");
@@ -303,6 +304,28 @@ bool GodotJavaWrapper::has_clipboard() {
}
}
+Error GodotJavaWrapper::show_dialog(const String &p_title, const String &p_description, const Vector &p_buttons) {
+ if (_show_input_dialog) {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL_V(env, ERR_UNCONFIGURED);
+ jstring j_title = env->NewStringUTF(p_title.utf8().get_data());
+ jstring j_description = env->NewStringUTF(p_description.utf8().get_data());
+ jobjectArray j_buttons = env->NewObjectArray(p_buttons.size(), env->FindClass("java/lang/String"), nullptr);
+ for (int i = 0; i < p_buttons.size(); ++i) {
+ jstring j_button = env->NewStringUTF(p_buttons[i].utf8().get_data());
+ env->SetObjectArrayElement(j_buttons, i, j_button);
+ env->DeleteLocalRef(j_button);
+ }
+ env->CallVoidMethod(godot_instance, _show_dialog, j_title, j_description, j_buttons);
+ env->DeleteLocalRef(j_title);
+ env->DeleteLocalRef(j_description);
+ env->DeleteLocalRef(j_buttons);
+ return OK;
+ } else {
+ return ERR_UNCONFIGURED;
+ }
+}
+
Error GodotJavaWrapper::show_input_dialog(const String &p_title, const String &p_message, const String &p_existing_text) {
if (_show_input_dialog) {
JNIEnv *env = get_jni_env();
diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h
index 50612abf94a4..1c74072e1a86 100644
--- a/platform/android/java_godot_wrapper.h
+++ b/platform/android/java_godot_wrapper.h
@@ -60,6 +60,7 @@ class GodotJavaWrapper {
jmethodID _get_clipboard = nullptr;
jmethodID _set_clipboard = nullptr;
jmethodID _has_clipboard = nullptr;
+ jmethodID _show_dialog = nullptr;
jmethodID _show_input_dialog = nullptr;
jmethodID _show_file_picker = nullptr;
jmethodID _request_permission = nullptr;
@@ -109,6 +110,7 @@ class GodotJavaWrapper {
void set_clipboard(const String &p_text);
bool has_has_clipboard();
bool has_clipboard();
+ Error show_dialog(const String &p_title, const String &p_description, const Vector &p_buttons);
Error show_input_dialog(const String &p_title, const String &p_message, const String &p_existing_text);
Error show_file_picker(const String &p_current_directory, const String &p_filename, int p_mode, const Vector &p_filters);
bool request_permission(const String &p_name);
diff --git a/platform/linuxbsd/wayland/display_server_wayland.cpp b/platform/linuxbsd/wayland/display_server_wayland.cpp
index d5dc8c2a7837..0ad29405fba7 100644
--- a/platform/linuxbsd/wayland/display_server_wayland.cpp
+++ b/platform/linuxbsd/wayland/display_server_wayland.cpp
@@ -209,6 +209,7 @@ bool DisplayServerWayland::has_feature(Feature p_feature) const {
case FEATURE_SWAP_BUFFERS:
case FEATURE_KEEP_SCREEN_ON:
case FEATURE_IME:
+ case FEATURE_WINDOW_DRAG:
case FEATURE_CLIPBOARD_PRIMARY: {
return true;
} break;
diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp
index 77f08095b1ca..b17f72dc5c04 100644
--- a/platform/linuxbsd/x11/display_server_x11.cpp
+++ b/platform/linuxbsd/x11/display_server_x11.cpp
@@ -152,6 +152,7 @@ bool DisplayServerX11::has_feature(Feature p_feature) const {
case FEATURE_CLIPBOARD_PRIMARY:
case FEATURE_TEXT_TO_SPEECH:
case FEATURE_WINDOW_EMBEDDING:
+ case FEATURE_WINDOW_DRAG:
return true;
case FEATURE_SCREEN_CAPTURE:
return !xwayland;
diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp
index 4d9e7b3965de..f35624d890aa 100644
--- a/platform/windows/display_server_windows.cpp
+++ b/platform/windows/display_server_windows.cpp
@@ -138,6 +138,7 @@ bool DisplayServerWindows::has_feature(Feature p_feature) const {
case FEATURE_SCREEN_CAPTURE:
case FEATURE_STATUS_INDICATOR:
case FEATURE_WINDOW_EMBEDDING:
+ case FEATURE_WINDOW_DRAG:
case FEATURE_SCREEN_EXCLUDE_FROM_CAPTURE:
return true;
case FEATURE_EMOJI_AND_SYMBOL_PICKER:
diff --git a/scene/2d/navigation_region_2d.cpp b/scene/2d/navigation_region_2d.cpp
index e33c7c08f978..365d5ba4f68e 100644
--- a/scene/2d/navigation_region_2d.cpp
+++ b/scene/2d/navigation_region_2d.cpp
@@ -200,6 +200,9 @@ void NavigationRegion2D::set_navigation_polygon(const Ref &p_
#ifdef DEBUG_ENABLED
debug_mesh_dirty = true;
#endif // DEBUG_ENABLED
+
+ _update_bounds();
+
NavigationServer2D::get_singleton()->region_set_navigation_polygon(region, p_navigation_polygon);
if (navigation_polygon.is_valid()) {
@@ -341,6 +344,8 @@ void NavigationRegion2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("_navigation_polygon_changed"), &NavigationRegion2D::_navigation_polygon_changed);
+ ClassDB::bind_method(D_METHOD("get_bounds"), &NavigationRegion2D::get_bounds);
+
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "navigation_polygon", PROPERTY_HINT_RESOURCE_TYPE, "NavigationPolygon"), "set_navigation_polygon", "get_navigation_polygon");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "is_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_edge_connections"), "set_use_edge_connections", "get_use_edge_connections");
@@ -648,3 +653,26 @@ void NavigationRegion2D::_set_debug_visible(bool p_visible) {
}
}
#endif // DEBUG_ENABLED
+
+void NavigationRegion2D::_update_bounds() {
+ if (navigation_polygon.is_null()) {
+ bounds = Rect2();
+ return;
+ }
+
+ const Vector &vertices = navigation_polygon->get_vertices();
+ if (vertices.is_empty()) {
+ bounds = Rect2();
+ return;
+ }
+
+ const Transform2D gt = is_inside_tree() ? get_global_transform() : get_transform();
+
+ Rect2 new_bounds;
+ new_bounds.position = gt.xform(vertices[0]);
+
+ for (const Vector2 &vertex : vertices) {
+ new_bounds.expand_to(gt.xform(vertex));
+ }
+ bounds = new_bounds;
+}
diff --git a/scene/2d/navigation_region_2d.h b/scene/2d/navigation_region_2d.h
index 761eaef5ce07..9ea87b1d9002 100644
--- a/scene/2d/navigation_region_2d.h
+++ b/scene/2d/navigation_region_2d.h
@@ -50,6 +50,8 @@ class NavigationRegion2D : public Node2D {
void _navigation_polygon_changed();
+ Rect2 bounds;
+
#ifdef DEBUG_ENABLED
private:
RID debug_mesh_rid;
@@ -113,10 +115,13 @@ class NavigationRegion2D : public Node2D {
void _bake_finished(Ref p_navigation_polygon);
bool is_baking() const;
+ Rect2 get_bounds() const { return bounds; }
+
NavigationRegion2D();
~NavigationRegion2D();
private:
+ void _update_bounds();
void _region_enter_navigation_map();
void _region_exit_navigation_map();
void _region_update_transform();
diff --git a/scene/3d/gpu_particles_3d.cpp b/scene/3d/gpu_particles_3d.cpp
index 17e1b7f93dbd..2b748e88b55f 100644
--- a/scene/3d/gpu_particles_3d.cpp
+++ b/scene/3d/gpu_particles_3d.cpp
@@ -557,7 +557,6 @@ void GPUParticles3D::_notification(int p_what) {
Ref material = get_process_material();
ERR_FAIL_COND(material.is_null());
- material->disconnect("emission_shape_changed", callable_mp((Node3D *)this, &GPUParticles3D::update_gizmos));
} break;
case NOTIFICATION_SUSPENDED:
diff --git a/scene/3d/navigation_region_3d.cpp b/scene/3d/navigation_region_3d.cpp
index ce8cef569900..34be901eef83 100644
--- a/scene/3d/navigation_region_3d.cpp
+++ b/scene/3d/navigation_region_3d.cpp
@@ -193,6 +193,8 @@ void NavigationRegion3D::set_navigation_mesh(const Ref &p_naviga
navigation_mesh->connect_changed(callable_mp(this, &NavigationRegion3D::_navigation_mesh_changed));
}
+ _update_bounds();
+
NavigationServer3D::get_singleton()->region_set_navigation_mesh(region, p_navigation_mesh);
#ifdef DEBUG_ENABLED
@@ -314,6 +316,8 @@ void NavigationRegion3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("bake_navigation_mesh", "on_thread"), &NavigationRegion3D::bake_navigation_mesh, DEFVAL(true));
ClassDB::bind_method(D_METHOD("is_baking"), &NavigationRegion3D::is_baking);
+ ClassDB::bind_method(D_METHOD("get_bounds"), &NavigationRegion3D::get_bounds);
+
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "navigation_mesh", PROPERTY_HINT_RESOURCE_TYPE, "NavigationMesh"), "set_navigation_mesh", "get_navigation_mesh");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "is_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_edge_connections"), "set_use_edge_connections", "get_use_edge_connections");
@@ -740,3 +744,26 @@ void NavigationRegion3D::_update_debug_edge_connections_mesh() {
}
}
#endif // DEBUG_ENABLED
+
+void NavigationRegion3D::_update_bounds() {
+ if (navigation_mesh.is_null()) {
+ bounds = AABB();
+ return;
+ }
+
+ const Vector &vertices = navigation_mesh->get_vertices();
+ if (vertices.is_empty()) {
+ bounds = AABB();
+ return;
+ }
+
+ const Transform3D gt = is_inside_tree() ? get_global_transform() : get_transform();
+
+ AABB new_bounds;
+ new_bounds.position = gt.xform(vertices[0]);
+
+ for (const Vector3 &vertex : vertices) {
+ new_bounds.expand_to(gt.xform(vertex));
+ }
+ bounds = new_bounds;
+}
diff --git a/scene/3d/navigation_region_3d.h b/scene/3d/navigation_region_3d.h
index 82468627deb0..97abe3979ca6 100644
--- a/scene/3d/navigation_region_3d.h
+++ b/scene/3d/navigation_region_3d.h
@@ -51,6 +51,8 @@ class NavigationRegion3D : public Node3D {
void _navigation_mesh_changed();
+ AABB bounds;
+
#ifdef DEBUG_ENABLED
RID debug_instance;
RID debug_edge_connections_instance;
@@ -110,10 +112,13 @@ class NavigationRegion3D : public Node3D {
PackedStringArray get_configuration_warnings() const override;
+ AABB get_bounds() const { return bounds; }
+
NavigationRegion3D();
~NavigationRegion3D();
private:
+ void _update_bounds();
void _region_enter_navigation_map();
void _region_exit_navigation_map();
void _region_update_transform();
diff --git a/scene/gui/tab_bar.cpp b/scene/gui/tab_bar.cpp
index e8ff6420018e..65e3d23c0bf1 100644
--- a/scene/gui/tab_bar.cpp
+++ b/scene/gui/tab_bar.cpp
@@ -1488,8 +1488,8 @@ int TabBar::get_tab_width(int p_idx) const {
style = theme_cache.tab_disabled_style;
} else if (current == p_idx) {
style = theme_cache.tab_selected_style;
- // Use the unselected style's width if the hovered one is shorter, to avoid an infinite loop when switching tabs with the mouse.
- } else if (hover == p_idx && theme_cache.tab_hovered_style->get_minimum_size().width >= theme_cache.tab_unselected_style->get_minimum_size().width) {
+ // Always pick the widest style between hovered and unselected, to avoid an infinite loop when switching tabs with the mouse.
+ } else if (theme_cache.tab_hovered_style->get_minimum_size().width > theme_cache.tab_unselected_style->get_minimum_size().width) {
style = theme_cache.tab_hovered_style;
} else {
style = theme_cache.tab_unselected_style;
diff --git a/scene/resources/particle_process_material.cpp b/scene/resources/particle_process_material.cpp
index 13bc02014b01..2c54564ff7b3 100644
--- a/scene/resources/particle_process_material.cpp
+++ b/scene/resources/particle_process_material.cpp
@@ -132,6 +132,7 @@ void ParticleProcessMaterial::init_shaders() {
shader_names->sub_emitter_frequency = "sub_emitter_frequency";
shader_names->sub_emitter_amount_at_end = "sub_emitter_amount_at_end";
shader_names->sub_emitter_amount_at_collision = "sub_emitter_amount_at_collision";
+ shader_names->sub_emitter_amount_at_start = "sub_emitter_amount_at_start";
shader_names->sub_emitter_keep_velocity = "sub_emitter_keep_velocity";
shader_names->collision_friction = "collision_friction";
@@ -287,6 +288,9 @@ void ParticleProcessMaterial::_update_shader() {
if (sub_emitter_mode == SUB_EMITTER_AT_COLLISION) {
code += "uniform int sub_emitter_amount_at_collision;\n";
}
+ if (sub_emitter_mode == SUB_EMITTER_AT_START) {
+ code += "uniform int sub_emitter_amount_at_start;\n";
+ }
code += "uniform bool sub_emitter_keep_velocity;\n";
}
@@ -923,6 +927,10 @@ void ParticleProcessMaterial::_update_shader() {
code += " float pi = 3.14159;\n";
code += " float degree_to_rad = pi / 180.0;\n\n";
+ if (sub_emitter_mode == SUB_EMITTER_AT_START && !RenderingServer::get_singleton()->is_low_end()) {
+ code += " bool just_spawned = CUSTOM.y == 0.0;\n";
+ }
+
code += " CUSTOM.y += DELTA / LIFETIME;\n";
code += " CUSTOM.y = mix(CUSTOM.y, 1.0, INTERPOLATE_TO_END);\n";
code += " float lifetime_percent = CUSTOM.y / params.lifetime;\n";
@@ -1139,6 +1147,11 @@ void ParticleProcessMaterial::_update_shader() {
code += " emit_count = sub_emitter_amount_at_end;\n";
code += " }\n";
} break;
+ case SUB_EMITTER_AT_START: {
+ code += " if (just_spawned) {\n";
+ code += " emit_count = sub_emitter_amount_at_start;\n";
+ code += " }\n";
+ } break;
default: {
}
}
@@ -1858,6 +1871,10 @@ void ParticleProcessMaterial::_validate_property(PropertyInfo &p_property) const
p_property.usage = PROPERTY_USAGE_NONE;
}
+ if (p_property.name == "sub_emitter_amount_at_start" && sub_emitter_mode != SUB_EMITTER_AT_START) {
+ p_property.usage = PROPERTY_USAGE_NONE;
+ }
+
if (!turbulence_enabled) {
if (p_property.name == "turbulence_noise_strength" ||
p_property.name == "turbulence_noise_scale" ||
@@ -1932,6 +1949,15 @@ int ParticleProcessMaterial::get_sub_emitter_amount_at_collision() const {
return sub_emitter_amount_at_collision;
}
+void ParticleProcessMaterial::set_sub_emitter_amount_at_start(int p_amount) {
+ sub_emitter_amount_at_start = p_amount;
+ RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->sub_emitter_amount_at_start, p_amount);
+}
+
+int ParticleProcessMaterial::get_sub_emitter_amount_at_start() const {
+ return sub_emitter_amount_at_start;
+}
+
void ParticleProcessMaterial::set_sub_emitter_keep_velocity(bool p_enable) {
sub_emitter_keep_velocity = p_enable;
RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->sub_emitter_keep_velocity, p_enable);
@@ -2113,6 +2139,9 @@ void ParticleProcessMaterial::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_sub_emitter_amount_at_collision"), &ParticleProcessMaterial::get_sub_emitter_amount_at_collision);
ClassDB::bind_method(D_METHOD("set_sub_emitter_amount_at_collision", "amount"), &ParticleProcessMaterial::set_sub_emitter_amount_at_collision);
+ ClassDB::bind_method(D_METHOD("get_sub_emitter_amount_at_start"), &ParticleProcessMaterial::get_sub_emitter_amount_at_start);
+ ClassDB::bind_method(D_METHOD("set_sub_emitter_amount_at_start", "amount"), &ParticleProcessMaterial::set_sub_emitter_amount_at_start);
+
ClassDB::bind_method(D_METHOD("get_sub_emitter_keep_velocity"), &ParticleProcessMaterial::get_sub_emitter_keep_velocity);
ClassDB::bind_method(D_METHOD("set_sub_emitter_keep_velocity", "enable"), &ParticleProcessMaterial::set_sub_emitter_keep_velocity);
@@ -2242,10 +2271,11 @@ void ParticleProcessMaterial::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision_bounce", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_collision_bounce", "get_collision_bounce");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collision_use_scale"), "set_collision_use_scale", "is_collision_using_scale");
ADD_GROUP("Sub Emitter", "sub_emitter_");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "sub_emitter_mode", PROPERTY_HINT_ENUM, "Disabled,Constant,At End,At Collision"), "set_sub_emitter_mode", "get_sub_emitter_mode");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "sub_emitter_mode", PROPERTY_HINT_ENUM, "Disabled:0,Constant:1,At Start:4,At End:2,At Collision:3"), "set_sub_emitter_mode", "get_sub_emitter_mode");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sub_emitter_frequency", PROPERTY_HINT_RANGE, "0.01,100,0.01,suffix:Hz"), "set_sub_emitter_frequency", "get_sub_emitter_frequency");
ADD_PROPERTY(PropertyInfo(Variant::INT, "sub_emitter_amount_at_end", PROPERTY_HINT_RANGE, "1,32,1"), "set_sub_emitter_amount_at_end", "get_sub_emitter_amount_at_end");
ADD_PROPERTY(PropertyInfo(Variant::INT, "sub_emitter_amount_at_collision", PROPERTY_HINT_RANGE, "1,32,1"), "set_sub_emitter_amount_at_collision", "get_sub_emitter_amount_at_collision");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "sub_emitter_amount_at_start", PROPERTY_HINT_RANGE, "1,32,1"), "set_sub_emitter_amount_at_start", "get_sub_emitter_amount_at_start");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sub_emitter_keep_velocity"), "set_sub_emitter_keep_velocity", "get_sub_emitter_keep_velocity");
ADD_SIGNAL(MethodInfo("emission_shape_changed"));
@@ -2290,6 +2320,7 @@ void ParticleProcessMaterial::_bind_methods() {
BIND_ENUM_CONSTANT(SUB_EMITTER_CONSTANT);
BIND_ENUM_CONSTANT(SUB_EMITTER_AT_END);
BIND_ENUM_CONSTANT(SUB_EMITTER_AT_COLLISION);
+ BIND_ENUM_CONSTANT(SUB_EMITTER_AT_START);
BIND_ENUM_CONSTANT(SUB_EMITTER_MAX);
BIND_ENUM_CONSTANT(COLLISION_DISABLED);
@@ -2361,6 +2392,7 @@ ParticleProcessMaterial::ParticleProcessMaterial() :
set_sub_emitter_frequency(4);
set_sub_emitter_amount_at_end(1);
set_sub_emitter_amount_at_collision(1);
+ set_sub_emitter_amount_at_start(1);
set_sub_emitter_keep_velocity(false);
set_attractor_interaction_enabled(true);
diff --git a/scene/resources/particle_process_material.h b/scene/resources/particle_process_material.h
index 12e3fbb64e49..30bf9fc313f0 100644
--- a/scene/resources/particle_process_material.h
+++ b/scene/resources/particle_process_material.h
@@ -96,6 +96,7 @@ class ParticleProcessMaterial : public Material {
SUB_EMITTER_CONSTANT,
SUB_EMITTER_AT_END,
SUB_EMITTER_AT_COLLISION,
+ SUB_EMITTER_AT_START,
SUB_EMITTER_MAX
};
@@ -117,7 +118,7 @@ class ParticleProcessMaterial : public Material {
uint64_t emission_shape : 3;
uint64_t invalid_key : 1;
uint64_t has_emission_color : 1;
- uint64_t sub_emitter : 2;
+ uint64_t sub_emitter : 3;
uint64_t attractor_enabled : 1;
uint64_t collision_mode : 2;
uint64_t collision_scale : 1;
@@ -282,6 +283,7 @@ class ParticleProcessMaterial : public Material {
StringName sub_emitter_frequency;
StringName sub_emitter_amount_at_end;
StringName sub_emitter_amount_at_collision;
+ StringName sub_emitter_amount_at_start;
StringName sub_emitter_keep_velocity;
StringName collision_friction;
@@ -349,6 +351,7 @@ class ParticleProcessMaterial : public Material {
double sub_emitter_frequency = 0.0;
int sub_emitter_amount_at_end = 0;
int sub_emitter_amount_at_collision = 0;
+ int sub_emitter_amount_at_start = 0;
bool sub_emitter_keep_velocity = false;
//do not save emission points here
@@ -487,6 +490,9 @@ class ParticleProcessMaterial : public Material {
void set_sub_emitter_amount_at_collision(int p_amount);
int get_sub_emitter_amount_at_collision() const;
+ void set_sub_emitter_amount_at_start(int p_amount);
+ int get_sub_emitter_amount_at_start() const;
+
void set_sub_emitter_keep_velocity(bool p_enable);
bool get_sub_emitter_keep_velocity() const;
diff --git a/servers/navigation_server_2d.cpp b/servers/navigation_server_2d.cpp
index a24a3312684c..7994cd1758e6 100644
--- a/servers/navigation_server_2d.cpp
+++ b/servers/navigation_server_2d.cpp
@@ -91,6 +91,7 @@ void NavigationServer2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("region_get_connection_pathway_end", "region", "connection"), &NavigationServer2D::region_get_connection_pathway_end);
ClassDB::bind_method(D_METHOD("region_get_closest_point", "region", "to_point"), &NavigationServer2D::region_get_closest_point);
ClassDB::bind_method(D_METHOD("region_get_random_point", "region", "navigation_layers", "uniformly"), &NavigationServer2D::region_get_random_point);
+ ClassDB::bind_method(D_METHOD("region_get_bounds", "region"), &NavigationServer2D::region_get_bounds);
ClassDB::bind_method(D_METHOD("link_create"), &NavigationServer2D::link_create);
ClassDB::bind_method(D_METHOD("link_set_map", "link", "map"), &NavigationServer2D::link_set_map);
diff --git a/servers/navigation_server_2d.h b/servers/navigation_server_2d.h
index 510cf3965255..e53d84aef1a4 100644
--- a/servers/navigation_server_2d.h
+++ b/servers/navigation_server_2d.h
@@ -154,6 +154,7 @@ class NavigationServer2D : public Object {
virtual Vector2 region_get_closest_point(RID p_region, const Vector2 &p_point) const = 0;
virtual Vector2 region_get_random_point(RID p_region, uint32_t p_navigation_layers, bool p_uniformly) const = 0;
+ virtual Rect2 region_get_bounds(RID p_region) const = 0;
/// Creates a new link between positions in the nav map.
virtual RID link_create() = 0;
diff --git a/servers/navigation_server_2d_dummy.h b/servers/navigation_server_2d_dummy.h
index dd6e618e0b4b..4e16c00e4b91 100644
--- a/servers/navigation_server_2d_dummy.h
+++ b/servers/navigation_server_2d_dummy.h
@@ -87,6 +87,7 @@ class NavigationServer2DDummy : public NavigationServer2D {
Vector2 region_get_connection_pathway_end(RID p_region, int p_connection_id) const override { return Vector2(); }
Vector2 region_get_closest_point(RID p_region, const Vector2 &p_point) const override { return Vector2(); }
Vector2 region_get_random_point(RID p_region, uint32_t p_navigation_layers, bool p_uniformly) const override { return Vector2(); }
+ Rect2 region_get_bounds(RID p_region) const override { return Rect2(); }
RID link_create() override { return RID(); }
void link_set_map(RID p_link, RID p_map) override {}
diff --git a/servers/navigation_server_3d.cpp b/servers/navigation_server_3d.cpp
index 702439e4af62..a34eeb36f9d3 100644
--- a/servers/navigation_server_3d.cpp
+++ b/servers/navigation_server_3d.cpp
@@ -106,6 +106,7 @@ void NavigationServer3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("region_get_closest_point", "region", "to_point"), &NavigationServer3D::region_get_closest_point);
ClassDB::bind_method(D_METHOD("region_get_closest_point_normal", "region", "to_point"), &NavigationServer3D::region_get_closest_point_normal);
ClassDB::bind_method(D_METHOD("region_get_random_point", "region", "navigation_layers", "uniformly"), &NavigationServer3D::region_get_random_point);
+ ClassDB::bind_method(D_METHOD("region_get_bounds", "region"), &NavigationServer3D::region_get_bounds);
ClassDB::bind_method(D_METHOD("link_create"), &NavigationServer3D::link_create);
ClassDB::bind_method(D_METHOD("link_set_map", "link", "map"), &NavigationServer3D::link_set_map);
diff --git a/servers/navigation_server_3d.h b/servers/navigation_server_3d.h
index 5a9896a86a3b..e94ca5a415cc 100644
--- a/servers/navigation_server_3d.h
+++ b/servers/navigation_server_3d.h
@@ -176,6 +176,8 @@ class NavigationServer3D : public Object {
virtual Vector3 region_get_closest_point_normal(RID p_region, const Vector3 &p_point) const = 0;
virtual Vector3 region_get_random_point(RID p_region, uint32_t p_navigation_layers, bool p_uniformly) const = 0;
+ virtual AABB region_get_bounds(RID p_region) const = 0;
+
/// Creates a new link between positions in the nav map.
virtual RID link_create() = 0;
diff --git a/servers/navigation_server_3d_dummy.h b/servers/navigation_server_3d_dummy.h
index 2ab4c6e6e2c9..c3f1420bfacf 100644
--- a/servers/navigation_server_3d_dummy.h
+++ b/servers/navigation_server_3d_dummy.h
@@ -99,6 +99,7 @@ class NavigationServer3DDummy : public NavigationServer3D {
Vector3 region_get_closest_point(RID p_region, const Vector3 &p_point) const override { return Vector3(); }
Vector3 region_get_closest_point_normal(RID p_region, const Vector3 &p_point) const override { return Vector3(); }
Vector3 region_get_random_point(RID p_region, uint32_t p_navigation_layers, bool p_uniformly) const override { return Vector3(); }
+ AABB region_get_bounds(RID p_region) const override { return AABB(); }
RID link_create() override { return RID(); }
void link_set_map(RID p_link, RID p_map) override {}
diff --git a/servers/rendering/dummy/storage/mesh_storage.h b/servers/rendering/dummy/storage/mesh_storage.h
index 97789d3a82db..ffffa4ef5349 100644
--- a/servers/rendering/dummy/storage/mesh_storage.h
+++ b/servers/rendering/dummy/storage/mesh_storage.h
@@ -151,7 +151,7 @@ class MeshStorage : public RendererMeshStorage {
virtual void _multimesh_initialize(RID p_rid) override;
virtual void _multimesh_free(RID p_rid) override;
- virtual void _multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors = false, bool p_use_custom_data = false) override {}
+ virtual void _multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors = false, bool p_use_custom_data = false, bool p_use_indirect = false) override {}
virtual int _multimesh_get_instance_count(RID p_multimesh) const override { return 0; }
virtual void _multimesh_set_mesh(RID p_multimesh, RID p_mesh) override {}
@@ -171,6 +171,7 @@ class MeshStorage : public RendererMeshStorage {
virtual Color _multimesh_instance_get_color(RID p_multimesh, int p_index) const override { return Color(); }
virtual Color _multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const override { return Color(); }
virtual void _multimesh_set_buffer(RID p_multimesh, const Vector &p_buffer) override;
+ virtual RID _multimesh_get_command_buffer_rd_rid(RID p_multimesh) const override { return RID(); }
virtual RID _multimesh_get_buffer_rd_rid(RID p_multimesh) const override { return RID(); }
virtual Vector _multimesh_get_buffer(RID p_multimesh) const override;
diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
index 207538c71043..70b3a89c4a68 100644
--- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
+++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
@@ -599,7 +599,11 @@ void RenderForwardClustered::_render_list_template(RenderingDevice::DrawListID p
instance_count /= surf->owner->trail_steps;
}
- RD::get_singleton()->draw_list_draw(draw_list, index_array_rd.is_valid(), instance_count);
+ if (bool(surf->owner->base_flags & INSTANCE_DATA_FLAG_MULTIMESH_INDIRECT)) {
+ RD::get_singleton()->draw_list_draw_indirect(draw_list, index_array_rd.is_valid(), mesh_storage->_multimesh_get_command_buffer_rd_rid(surf->owner->data->base), surf->surface_index * sizeof(uint32_t) * mesh_storage->INDIRECT_MULTIMESH_COMMAND_STRIDE, 1, 0);
+ } else {
+ RD::get_singleton()->draw_list_draw(draw_list, index_array_rd.is_valid(), instance_count);
+ }
}
i += element_info.repeat - 1; //skip equal elements
@@ -1083,6 +1087,7 @@ void RenderForwardClustered::_fill_render_list(RenderListType p_render_list, con
} else {
surf->sort.lod_index = 0;
if (p_render_data->render_info) {
+ // This does not include primitives rendered via indirect draw calls.
uint32_t to_draw = mesh_storage->mesh_surface_get_vertices_drawn_count(surf->surface);
to_draw = _indices_to_primitives(surf->primitive, to_draw);
to_draw *= inst->instance_count;
@@ -4205,9 +4210,9 @@ void RenderForwardClustered::_geometry_instance_update(RenderGeometryInstance *p
ginstance->base_flags = 0;
bool store_transform = true;
-
if (ginstance->data->base_type == RS::INSTANCE_MULTIMESH) {
ginstance->base_flags |= INSTANCE_DATA_FLAG_MULTIMESH;
+
if (mesh_storage->multimesh_get_transform_format(ginstance->data->base) == RS::MULTIMESH_TRANSFORM_2D) {
ginstance->base_flags |= INSTANCE_DATA_FLAG_MULTIMESH_FORMAT_2D;
}
@@ -4217,6 +4222,9 @@ void RenderForwardClustered::_geometry_instance_update(RenderGeometryInstance *p
if (mesh_storage->multimesh_uses_custom_data(ginstance->data->base)) {
ginstance->base_flags |= INSTANCE_DATA_FLAG_MULTIMESH_HAS_CUSTOM_DATA;
}
+ if (mesh_storage->multimesh_uses_indirect(ginstance->data->base)) {
+ ginstance->base_flags |= INSTANCE_DATA_FLAG_MULTIMESH_INDIRECT;
+ }
ginstance->transforms_uniform_set = mesh_storage->multimesh_get_3d_uniform_set(ginstance->data->base, scene_shader.default_shader_rd, TRANSFORMS_UNIFORM_SET);
diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h
index 42c1da1a9711..567ca5d80fa4 100644
--- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h
+++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h
@@ -255,6 +255,7 @@ class RenderForwardClustered : public RendererSceneRenderRD {
// When changing any of these enums, remember to change the corresponding enums in the shader files as well.
enum {
+ INSTANCE_DATA_FLAG_MULTIMESH_INDIRECT = 1 << 2,
INSTANCE_DATA_FLAGS_DYNAMIC = 1 << 3,
INSTANCE_DATA_FLAGS_NON_UNIFORM_SCALE = 1 << 4,
INSTANCE_DATA_FLAG_USE_GI_BUFFERS = 1 << 5,
diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
index 6d217964366e..1651e82276dd 100644
--- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
+++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
@@ -2380,7 +2380,11 @@ void RenderForwardMobile::_render_list_template(RenderingDevice::DrawListID p_dr
instance_count /= surf->owner->trail_steps;
}
- RD::get_singleton()->draw_list_draw(draw_list, index_array_rd.is_valid(), instance_count);
+ if (bool(surf->owner->base_flags & INSTANCE_DATA_FLAG_MULTIMESH_INDIRECT)) {
+ RD::get_singleton()->draw_list_draw_indirect(draw_list, index_array_rd.is_valid(), mesh_storage->_multimesh_get_command_buffer_rd_rid(surf->owner->data->base), surf->surface_index * sizeof(uint32_t) * mesh_storage->INDIRECT_MULTIMESH_COMMAND_STRIDE, 1, 0);
+ } else {
+ RD::get_singleton()->draw_list_draw(draw_list, index_array_rd.is_valid(), instance_count);
+ }
}
}
@@ -2801,6 +2805,7 @@ void RenderForwardMobile::_geometry_instance_update(RenderGeometryInstance *p_ge
if (ginstance->data->base_type == RS::INSTANCE_MULTIMESH) {
ginstance->base_flags |= INSTANCE_DATA_FLAG_MULTIMESH;
+
if (mesh_storage->multimesh_get_transform_format(ginstance->data->base) == RS::MULTIMESH_TRANSFORM_2D) {
ginstance->base_flags |= INSTANCE_DATA_FLAG_MULTIMESH_FORMAT_2D;
}
@@ -2810,6 +2815,9 @@ void RenderForwardMobile::_geometry_instance_update(RenderGeometryInstance *p_ge
if (mesh_storage->multimesh_uses_custom_data(ginstance->data->base)) {
ginstance->base_flags |= INSTANCE_DATA_FLAG_MULTIMESH_HAS_CUSTOM_DATA;
}
+ if (mesh_storage->multimesh_uses_indirect(ginstance->data->base)) {
+ ginstance->base_flags |= INSTANCE_DATA_FLAG_MULTIMESH_INDIRECT;
+ }
ginstance->transforms_uniform_set = mesh_storage->multimesh_get_3d_uniform_set(ginstance->data->base, scene_shader.default_shader_rd, TRANSFORMS_UNIFORM_SET);
diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h
index 77242c8c93cc..e6aec9752eca 100644
--- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h
+++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h
@@ -358,6 +358,7 @@ class RenderForwardMobile : public RendererSceneRenderRD {
// When changing any of these enums, remember to change the corresponding enums in the shader files as well.
enum {
+ INSTANCE_DATA_FLAG_MULTIMESH_INDIRECT = 1 << 2,
INSTANCE_DATA_FLAGS_DYNAMIC = 1 << 3,
INSTANCE_DATA_FLAGS_NON_UNIFORM_SCALE = 1 << 4,
INSTANCE_DATA_FLAG_USE_GI_BUFFERS = 1 << 5,
diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp
index bd5f8dc787ee..84f4b452996f 100644
--- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp
+++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp
@@ -1485,7 +1485,7 @@ void MeshStorage::_multimesh_free(RID p_rid) {
multimesh_owner.free(p_rid);
}
-void MeshStorage::_multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors, bool p_use_custom_data) {
+void MeshStorage::_multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors, bool p_use_custom_data, bool p_use_indirect) {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL(multimesh);
@@ -1521,6 +1521,9 @@ void MeshStorage::_multimesh_allocate_data(RID p_multimesh, int p_instances, RS:
multimesh->stride_cache = multimesh->custom_data_offset_cache + (p_use_custom_data ? 4 : 0);
multimesh->buffer_set = false;
+ multimesh->indirect = p_use_indirect;
+ multimesh->command_buffer = RID();
+
//print_line("allocate, elements: " + itos(p_instances) + " 2D: " + itos(p_transform_format == RS::MULTIMESH_TRANSFORM_2D) + " colors " + itos(multimesh->uses_colors) + " data " + itos(multimesh->uses_custom_data) + " stride " + itos(multimesh->stride_cache) + " total size " + itos(multimesh->stride_cache * multimesh->instances));
multimesh->data_cache = Vector();
multimesh->aabb = AABB();
@@ -1609,6 +1612,30 @@ void MeshStorage::_multimesh_set_mesh(RID p_multimesh, RID p_mesh) {
}
multimesh->mesh = p_mesh;
+ if (multimesh->indirect) {
+ Mesh *mesh = mesh_owner.get_or_null(p_mesh);
+ ERR_FAIL_NULL(mesh);
+ if (mesh->surface_count > 0) {
+ if (multimesh->command_buffer.is_valid()) {
+ RD::get_singleton()->free(multimesh->command_buffer);
+ }
+
+ Vector newVector;
+ newVector.resize_zeroed(sizeof(uint32_t) * INDIRECT_MULTIMESH_COMMAND_STRIDE * mesh->surface_count);
+
+ for (uint32_t i = 0; i < mesh->surface_count; i++) {
+ uint32_t count = mesh_surface_get_vertices_drawn_count(mesh->surfaces[i]);
+ newVector.set(i * sizeof(uint32_t) * INDIRECT_MULTIMESH_COMMAND_STRIDE, static_cast(count));
+ newVector.set(i * sizeof(uint32_t) * INDIRECT_MULTIMESH_COMMAND_STRIDE + 1, static_cast(count >> 8));
+ newVector.set(i * sizeof(uint32_t) * INDIRECT_MULTIMESH_COMMAND_STRIDE + 2, static_cast(count >> 16));
+ newVector.set(i * sizeof(uint32_t) * INDIRECT_MULTIMESH_COMMAND_STRIDE + 3, static_cast(count >> 24));
+ }
+
+ RID newBuffer = RD::get_singleton()->storage_buffer_create(sizeof(uint32_t) * INDIRECT_MULTIMESH_COMMAND_STRIDE * mesh->surface_count, newVector, RD::STORAGE_BUFFER_USAGE_DISPATCH_INDIRECT);
+ multimesh->command_buffer = newBuffer;
+ }
+ }
+
if (multimesh->instances == 0) {
return;
}
@@ -2064,6 +2091,12 @@ void MeshStorage::_multimesh_set_buffer(RID p_multimesh, const Vector &p_
}
}
+RID MeshStorage::_multimesh_get_command_buffer_rd_rid(RID p_multimesh) const {
+ MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
+ ERR_FAIL_NULL_V(multimesh, RID());
+ return multimesh->command_buffer;
+}
+
RID MeshStorage::_multimesh_get_buffer_rd_rid(RID p_multimesh) const {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL_V(multimesh, RID());
@@ -2111,6 +2144,15 @@ void MeshStorage::_multimesh_set_visible_instances(RID p_multimesh, int p_visibl
multimesh->visible_instances = p_visible;
+ if (multimesh->indirect) { //we have to update the command buffer for the instance counts, in each stride this will be the second integer.
+ Mesh *mesh = mesh_owner.get_or_null(multimesh->mesh);
+ if (mesh != nullptr) {
+ for (uint32_t i = 0; i < mesh->surface_count; i++) {
+ RD::get_singleton()->buffer_update(multimesh->command_buffer, (i * sizeof(uint32_t) * INDIRECT_MULTIMESH_COMMAND_STRIDE) + sizeof(uint32_t), sizeof(uint32_t), &p_visible);
+ }
+ }
+ }
+
multimesh->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_MULTIMESH_VISIBLE_INSTANCES);
}
diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.h b/servers/rendering/renderer_rd/storage_rd/mesh_storage.h
index 7a8f0c8e6089..5b8ed3438578 100644
--- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.h
+++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.h
@@ -59,6 +59,10 @@ class MeshStorage : public RendererMeshStorage {
DEFAULT_RD_BUFFER_MAX,
};
+ enum IndirectMultiMesh : uint32_t {
+ INDIRECT_MULTIMESH_COMMAND_STRIDE = 5
+ };
+
private:
static MeshStorage *singleton;
@@ -226,6 +230,7 @@ class MeshStorage : public RendererMeshStorage {
AABB custom_aabb;
bool aabb_dirty = false;
bool buffer_set = false;
+ bool indirect = false;
bool motion_vectors_enabled = false;
uint32_t motion_vectors_current_offset = 0;
uint32_t motion_vectors_previous_offset = 0;
@@ -243,6 +248,7 @@ class MeshStorage : public RendererMeshStorage {
RID buffer; //storage buffer
RID uniform_set_3d;
RID uniform_set_2d;
+ RID command_buffer; //used if indirect setting is used
bool dirty = false;
MultiMesh *dirty_list = nullptr;
@@ -637,7 +643,7 @@ class MeshStorage : public RendererMeshStorage {
virtual void _multimesh_initialize(RID p_multimesh) override;
virtual void _multimesh_free(RID p_rid) override;
- virtual void _multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors = false, bool p_use_custom_data = false) override;
+ virtual void _multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors = false, bool p_use_custom_data = false, bool p_use_indirect = false) override;
virtual int _multimesh_get_instance_count(RID p_multimesh) const override;
virtual void _multimesh_set_mesh(RID p_multimesh, RID p_mesh) override;
@@ -654,6 +660,7 @@ class MeshStorage : public RendererMeshStorage {
virtual Color _multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const override;
virtual void _multimesh_set_buffer(RID p_multimesh, const Vector &p_buffer) override;
+ virtual RID _multimesh_get_command_buffer_rd_rid(RID p_multimesh) const override;
virtual RID _multimesh_get_buffer_rd_rid(RID p_multimesh) const override;
virtual Vector _multimesh_get_buffer(RID p_multimesh) const override;
@@ -672,6 +679,11 @@ class MeshStorage : public RendererMeshStorage {
bool _multimesh_uses_motion_vectors_offsets(RID p_multimesh);
bool _multimesh_uses_motion_vectors(RID p_multimesh);
+ _FORCE_INLINE_ bool multimesh_uses_indirect(RID p_multimesh) const {
+ MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
+ return multimesh->indirect;
+ }
+
_FORCE_INLINE_ RS::MultimeshTransformFormat multimesh_get_transform_format(RID p_multimesh) const {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
return multimesh->xform_format;
diff --git a/servers/rendering/rendering_server_default.h b/servers/rendering/rendering_server_default.h
index 16d32f00a465..14794f7bab79 100644
--- a/servers/rendering/rendering_server_default.h
+++ b/servers/rendering/rendering_server_default.h
@@ -382,7 +382,7 @@ class RenderingServerDefault : public RenderingServer {
FUNCRIDSPLIT(multimesh)
- FUNC5(multimesh_allocate_data, RID, int, MultimeshTransformFormat, bool, bool)
+ FUNC6(multimesh_allocate_data, RID, int, MultimeshTransformFormat, bool, bool, bool)
FUNC1RC(int, multimesh_get_instance_count, RID)
FUNC2(multimesh_set_mesh, RID, RID)
@@ -403,6 +403,7 @@ class RenderingServerDefault : public RenderingServer {
FUNC2RC(Color, multimesh_instance_get_custom_data, RID, int)
FUNC2(multimesh_set_buffer, RID, const Vector &)
+ FUNC1RC(RID, multimesh_get_command_buffer_rd_rid, RID)
FUNC1RC(RID, multimesh_get_buffer_rd_rid, RID)
FUNC1RC(Vector, multimesh_get_buffer, RID)
diff --git a/servers/rendering/storage/mesh_storage.cpp b/servers/rendering/storage/mesh_storage.cpp
index 4a6746e6c916..5b899878039b 100644
--- a/servers/rendering/storage/mesh_storage.cpp
+++ b/servers/rendering/storage/mesh_storage.cpp
@@ -48,7 +48,7 @@ void RendererMeshStorage::multimesh_free(RID p_rid) {
_multimesh_free(p_rid);
}
-void RendererMeshStorage::multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors, bool p_use_custom_data) {
+void RendererMeshStorage::multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors, bool p_use_custom_data, bool p_use_indirect) {
MultiMeshInterpolator *mmi = _multimesh_get_interpolator(p_multimesh);
if (mmi) {
mmi->_transform_format = p_transform_format;
@@ -68,7 +68,7 @@ void RendererMeshStorage::multimesh_allocate_data(RID p_multimesh, int p_instanc
mmi->_data_interpolated.resize_zeroed(size_in_floats);
}
- _multimesh_allocate_data(p_multimesh, p_instances, p_transform_format, p_use_colors, p_use_custom_data);
+ _multimesh_allocate_data(p_multimesh, p_instances, p_transform_format, p_use_colors, p_use_custom_data, p_use_indirect);
}
int RendererMeshStorage::multimesh_get_instance_count(RID p_multimesh) const {
@@ -223,6 +223,10 @@ void RendererMeshStorage::multimesh_set_buffer(RID p_multimesh, const Vector &p_buffer);
+ virtual RID multimesh_get_command_buffer_rd_rid(RID p_multimesh) const;
virtual RID multimesh_get_buffer_rd_rid(RID p_multimesh) const;
virtual Vector multimesh_get_buffer(RID p_multimesh) const;
@@ -159,7 +160,7 @@ class RendererMeshStorage {
virtual void _multimesh_initialize(RID p_rid) = 0;
virtual void _multimesh_free(RID p_rid) = 0;
- virtual void _multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors = false, bool p_use_custom_data = false) = 0;
+ virtual void _multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors = false, bool p_use_custom_data = false, bool p_use_indirect = false) = 0;
virtual int _multimesh_get_instance_count(RID p_multimesh) const = 0;
@@ -180,6 +181,7 @@ class RendererMeshStorage {
virtual Color _multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const = 0;
virtual void _multimesh_set_buffer(RID p_multimesh, const Vector &p_buffer) = 0;
+ virtual RID _multimesh_get_command_buffer_rd_rid(RID p_multimesh) const = 0;
virtual RID _multimesh_get_buffer_rd_rid(RID p_multimesh) const = 0;
virtual Vector _multimesh_get_buffer(RID p_multimesh) const = 0;
diff --git a/servers/rendering_server.compat.inc b/servers/rendering_server.compat.inc
index 99f2de9a9a31..2ae2f3c88449 100644
--- a/servers/rendering_server.compat.inc
+++ b/servers/rendering_server.compat.inc
@@ -30,6 +30,10 @@
#ifndef DISABLE_DEPRECATED
+void RenderingServer::_multimesh_allocate_data_bind_compat_99455(RID p_multimesh, int p_instances, MultimeshTransformFormat p_transform_format, bool p_use_colors, bool p_use_custom_data) {
+ multimesh_allocate_data(p_multimesh, p_instances, p_transform_format, p_use_colors, p_use_custom_data, false);
+}
+
void RenderingServer::_environment_set_fog_bind_compat_84792(RID p_env, bool p_enable, const Color &p_light_color, float p_light_energy, float p_sun_scatter, float p_density, float p_height, float p_height_density, float p_aerial_perspective, float p_sky_affect) {
environment_set_fog(p_env, p_enable, p_light_color, p_light_energy, p_sun_scatter, p_density, p_height, p_height_density, p_aerial_perspective, p_sky_affect, RS::EnvironmentFogMode::ENV_FOG_MODE_EXPONENTIAL);
}
@@ -47,6 +51,7 @@ void RenderingServer::_canvas_item_add_circle_bind_compat_84523(RID p_item, cons
}
void RenderingServer::_bind_compatibility_methods() {
+ ClassDB::bind_compatibility_method(D_METHOD("multimesh_allocate_data", "multimesh", "instances", "transform_format", "color_format", "custom_data_format"), &RenderingServer::_multimesh_allocate_data_bind_compat_99455, DEFVAL(false), DEFVAL(false));
ClassDB::bind_compatibility_method(D_METHOD("environment_set_fog", "env", "enable", "light_color", "light_energy", "sun_scatter", "density", "height", "height_density", "aerial_perspective", "sky_affect"), &RenderingServer::_environment_set_fog_bind_compat_84792);
ClassDB::bind_compatibility_method(D_METHOD("canvas_item_add_multiline", "item", "points", "colors", "width"), &RenderingServer::_canvas_item_add_multiline_bind_compat_84523, DEFVAL(-1.0));
ClassDB::bind_compatibility_method(D_METHOD("canvas_item_add_rect", "item", "rect", "color"), &RenderingServer::_canvas_item_add_rect_bind_compat_84523);
diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp
index a38d9a4b05ce..422faa13a093 100644
--- a/servers/rendering_server.cpp
+++ b/servers/rendering_server.cpp
@@ -2441,7 +2441,7 @@ void RenderingServer::_bind_methods() {
/* MULTIMESH API */
ClassDB::bind_method(D_METHOD("multimesh_create"), &RenderingServer::multimesh_create);
- ClassDB::bind_method(D_METHOD("multimesh_allocate_data", "multimesh", "instances", "transform_format", "color_format", "custom_data_format"), &RenderingServer::multimesh_allocate_data, DEFVAL(false), DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("multimesh_allocate_data", "multimesh", "instances", "transform_format", "color_format", "custom_data_format", "use_indirect"), &RenderingServer::multimesh_allocate_data, DEFVAL(false), DEFVAL(false), DEFVAL(false));
ClassDB::bind_method(D_METHOD("multimesh_get_instance_count", "multimesh"), &RenderingServer::multimesh_get_instance_count);
ClassDB::bind_method(D_METHOD("multimesh_set_mesh", "multimesh", "mesh"), &RenderingServer::multimesh_set_mesh);
ClassDB::bind_method(D_METHOD("multimesh_instance_set_transform", "multimesh", "index", "transform"), &RenderingServer::multimesh_instance_set_transform);
@@ -2459,6 +2459,7 @@ void RenderingServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("multimesh_set_visible_instances", "multimesh", "visible"), &RenderingServer::multimesh_set_visible_instances);
ClassDB::bind_method(D_METHOD("multimesh_get_visible_instances", "multimesh"), &RenderingServer::multimesh_get_visible_instances);
ClassDB::bind_method(D_METHOD("multimesh_set_buffer", "multimesh", "buffer"), &RenderingServer::multimesh_set_buffer);
+ ClassDB::bind_method(D_METHOD("multimesh_get_command_buffer_rd_rid", "multimesh"), &RenderingServer::multimesh_get_command_buffer_rd_rid);
ClassDB::bind_method(D_METHOD("multimesh_get_buffer_rd_rid", "multimesh"), &RenderingServer::multimesh_get_buffer_rd_rid);
ClassDB::bind_method(D_METHOD("multimesh_get_buffer", "multimesh"), &RenderingServer::multimesh_get_buffer);
diff --git a/servers/rendering_server.h b/servers/rendering_server.h
index 94447baf67c8..1206911e4b22 100644
--- a/servers/rendering_server.h
+++ b/servers/rendering_server.h
@@ -457,7 +457,12 @@ class RenderingServer : public Object {
MULTIMESH_INTERP_QUALITY_HIGH,
};
- virtual void multimesh_allocate_data(RID p_multimesh, int p_instances, MultimeshTransformFormat p_transform_format, bool p_use_colors = false, bool p_use_custom_data = false) = 0;
+protected:
+#ifndef DISABLE_DEPRECATED
+ void _multimesh_allocate_data_bind_compat_99455(RID p_multimesh, int p_instances, MultimeshTransformFormat p_transform_format, bool p_use_colors, bool p_use_custom_data);
+#endif
+public:
+ virtual void multimesh_allocate_data(RID p_multimesh, int p_instances, MultimeshTransformFormat p_transform_format, bool p_use_colors = false, bool p_use_custom_data = false, bool p_use_indirect = false) = 0;
virtual int multimesh_get_instance_count(RID p_multimesh) const = 0;
virtual void multimesh_set_mesh(RID p_multimesh, RID p_mesh) = 0;
@@ -478,6 +483,7 @@ class RenderingServer : public Object {
virtual Color multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const = 0;
virtual void multimesh_set_buffer(RID p_multimesh, const Vector &p_buffer) = 0;
+ virtual RID multimesh_get_command_buffer_rd_rid(RID p_multimesh) const = 0;
virtual RID multimesh_get_buffer_rd_rid(RID p_multimesh) const = 0;
virtual Vector multimesh_get_buffer(RID p_multimesh) const = 0;
diff --git a/version.py b/version.py
index 22704a08cb7e..d176efb476fb 100644
--- a/version.py
+++ b/version.py
@@ -3,7 +3,7 @@
major = 4
minor = 4
patch = 0
-status = "dev"
+status = "beta"
module_config = ""
website = "https://godotengine.org"
docs = "latest"