diff options
62 files changed, 924 insertions, 256 deletions
diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index 4fd6ab9028..d3cbae2f29 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -1430,6 +1430,9 @@ ProjectSettings::ProjectSettings() { GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true); GLOBAL_DEF("display/window/energy_saving/keep_screen_on.editor", false); + GLOBAL_DEF("animation/warnings/check_invalid_track_paths", true); + GLOBAL_DEF("animation/warnings/check_angle_interpolation_type_conflicting", true); + GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "audio/buses/default_bus_layout", PROPERTY_HINT_FILE, "*.tres"), "res://default_bus_layout.tres"); GLOBAL_DEF_RST("audio/general/text_to_speech", false); GLOBAL_DEF_RST(PropertyInfo(Variant::FLOAT, "audio/general/2d_panning_strength", PROPERTY_HINT_RANGE, "0,2,0.01"), 0.5f); diff --git a/doc/classes/CPUParticles3D.xml b/doc/classes/CPUParticles3D.xml index 27404b68bb..a6f85e7fe5 100644 --- a/doc/classes/CPUParticles3D.xml +++ b/doc/classes/CPUParticles3D.xml @@ -309,6 +309,10 @@ <member name="tangential_accel_min" type="float" setter="set_param_min" getter="get_param_min" default="0.0"> Minimum tangent acceleration. </member> + <member name="visibility_aabb" type="AABB" setter="set_visibility_aabb" getter="get_visibility_aabb" default="AABB(-4, -4, -4, 8, 8, 8)"> + The [AABB] that determines the node's region which needs to be visible on screen for the particle system to be active. + Grow the box if particles suddenly appear/disappear when the node enters/exits the screen. The [AABB] can be grown via code or with the [b]Particles → Generate AABB[/b] editor tool. + </member> </members> <signals> <signal name="finished"> diff --git a/doc/classes/MultiMesh.xml b/doc/classes/MultiMesh.xml index 5539213619..cd6397137b 100644 --- a/doc/classes/MultiMesh.xml +++ b/doc/classes/MultiMesh.xml @@ -92,7 +92,11 @@ </member> <member name="color_array" type="PackedColorArray" setter="_set_color_array" getter="_get_color_array" deprecated="Use [method set_instance_color] instead."> </member> + <member name="custom_aabb" type="AABB" setter="set_custom_aabb" getter="get_custom_aabb" default="AABB(0, 0, 0, 0, 0, 0)"> + Custom AABB for this MultiMesh resource. Setting this manually prevents costly runtime AABB recalculations. + </member> <member name="custom_data_array" type="PackedColorArray" setter="_set_custom_data_array" getter="_get_custom_data_array" deprecated="Use [method set_instance_custom_data] instead."> + See [method set_instance_custom_data]. </member> <member name="instance_count" type="int" setter="set_instance_count" getter="get_instance_count" default="0"> Number of instances that will get drawn. This clears and (re)sizes the buffers. Setting data format or flags afterwards will have no effect. diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index f8e91c94a0..2df34b0488 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -238,6 +238,12 @@ </method> </methods> <members> + <member name="animation/warnings/check_angle_interpolation_type_conflicting" type="bool" setter="" getter="" default="true"> + If [code]true[/code], [AnimationMixer] prints the warning of interpolation being forced to choose the shortest rotation path due to multiple angle interpolation types being mixed in the [AnimationMixer] cache. + </member> + <member name="animation/warnings/check_invalid_track_paths" type="bool" setter="" getter="" default="true"> + If [code]true[/code], [AnimationMixer] prints the warning of no matching object of the track path in the scene. + </member> <member name="application/boot_splash/bg_color" type="Color" setter="" getter="" default="Color(0.14, 0.14, 0.14, 1)"> Background color for the boot splash. </member> diff --git a/doc/classes/PropertyTweener.xml b/doc/classes/PropertyTweener.xml index 73e594218d..d3875ddfc2 100644 --- a/doc/classes/PropertyTweener.xml +++ b/doc/classes/PropertyTweener.xml @@ -43,6 +43,25 @@ [/codeblock] </description> </method> + <method name="set_custom_interpolator"> + <return type="PropertyTweener" /> + <param index="0" name="interpolator_method" type="Callable" /> + <description> + Allows interpolating the value with a custom easing function. The provided [param interpolator_method] will be called with a value ranging from [code]0.0[/code] to [code]1.0[/code] and is expected to return a value within the same range (values outside the range can be used for overshoot). The return value of the method is then used for interpolation between initial and final value. Note that the parameter passed to the method is still subject to the tweener's own easing. + [b]Example:[/b] + [codeblock] + @export var curve: Curve + + func _ready(): + var tween = create_tween() + # Interpolate the value using a custom curve. + tween.tween_property(self, "position:x", 300, 1).as_relative().set_custom_interpolator(tween_curve) + + func tween_curve(v): + return curve.sample_baked(v) + [/codeblock] + </description> + </method> <method name="set_delay"> <return type="PropertyTweener" /> <param index="0" name="delay" type="float" /> diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml index 4597e50f37..54a11af629 100644 --- a/doc/classes/RenderingServer.xml +++ b/doc/classes/RenderingServer.xml @@ -2332,6 +2332,13 @@ [b]Note:[/b] If the buffer is in the engine's internal cache, it will have to be fetched from GPU memory and possibly decompressed. This means [method multimesh_get_buffer] is potentially a slow operation and should be avoided whenever possible. </description> </method> + <method name="multimesh_get_custom_aabb" qualifiers="const"> + <return type="AABB" /> + <param index="0" name="multimesh" type="RID" /> + <description> + Returns the custom AABB defined for this MultiMesh resource. + </description> + </method> <method name="multimesh_get_instance_count" qualifiers="const"> <return type="int" /> <param index="0" name="multimesh" type="RID" /> @@ -2442,6 +2449,14 @@ [/codeblock] </description> </method> + <method name="multimesh_set_custom_aabb"> + <return type="void" /> + <param index="0" name="multimesh" type="RID" /> + <param index="1" name="aabb" type="AABB" /> + <description> + Sets the custom AABB for this MultiMesh resource. + </description> + </method> <method name="multimesh_set_mesh"> <return type="void" /> <param index="0" name="multimesh" type="RID" /> diff --git a/drivers/gles3/storage/mesh_storage.cpp b/drivers/gles3/storage/mesh_storage.cpp index 928e4e23ce..6c4bef10d5 100644 --- a/drivers/gles3/storage/mesh_storage.cpp +++ b/drivers/gles3/storage/mesh_storage.cpp @@ -1607,6 +1607,9 @@ void MeshStorage::_multimesh_mark_all_dirty(MultiMesh *multimesh, bool p_data, b void MeshStorage::_multimesh_re_create_aabb(MultiMesh *multimesh, const float *p_data, int p_instances) { ERR_FAIL_COND(multimesh->mesh.is_null()); + if (multimesh->custom_aabb != AABB()) { + return; + } AABB aabb; AABB mesh_aabb = mesh_get_aabb(multimesh->mesh); for (int i = 0; i < p_instances; i++) { @@ -1749,9 +1752,25 @@ RID MeshStorage::multimesh_get_mesh(RID p_multimesh) const { return multimesh->mesh; } +void MeshStorage::multimesh_set_custom_aabb(RID p_multimesh, const AABB &p_aabb) { + MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh); + ERR_FAIL_NULL(multimesh); + multimesh->custom_aabb = p_aabb; + multimesh->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_AABB); +} + +AABB MeshStorage::multimesh_get_custom_aabb(RID p_multimesh) const { + MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh); + ERR_FAIL_NULL_V(multimesh, AABB()); + return multimesh->custom_aabb; +} + AABB MeshStorage::multimesh_get_aabb(RID p_multimesh) const { MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh); ERR_FAIL_NULL_V(multimesh, AABB()); + if (multimesh->custom_aabb != AABB()) { + return multimesh->custom_aabb; + } if (multimesh->aabb_dirty) { const_cast<MeshStorage *>(this)->_update_dirty_multimeshes(); } @@ -1943,8 +1962,10 @@ void MeshStorage::multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_b //if we have a mesh set, we need to re-generate the AABB from the new data const float *data = p_buffer.ptr(); - _multimesh_re_create_aabb(multimesh, data, multimesh->instances); - multimesh->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_AABB); + if (multimesh->custom_aabb != AABB()) { + _multimesh_re_create_aabb(multimesh, data, multimesh->instances); + multimesh->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_AABB); + } } } @@ -2091,9 +2112,11 @@ void MeshStorage::_update_dirty_multimeshes() { } if (multimesh->aabb_dirty && multimesh->mesh.is_valid()) { - _multimesh_re_create_aabb(multimesh, data, visible_instances); multimesh->aabb_dirty = false; - multimesh->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_AABB); + if (multimesh->custom_aabb != AABB()) { + _multimesh_re_create_aabb(multimesh, data, visible_instances); + multimesh->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_AABB); + } } } diff --git a/drivers/gles3/storage/mesh_storage.h b/drivers/gles3/storage/mesh_storage.h index cea81baa0b..d246e7725c 100644 --- a/drivers/gles3/storage/mesh_storage.h +++ b/drivers/gles3/storage/mesh_storage.h @@ -189,6 +189,7 @@ struct MultiMesh { bool uses_custom_data = false; int visible_instances = -1; AABB aabb; + AABB custom_aabb; bool aabb_dirty = false; bool buffer_set = false; uint32_t stride_cache = 0; @@ -505,6 +506,8 @@ public: virtual void multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_color) override; virtual RID multimesh_get_mesh(RID p_multimesh) const override; + virtual void multimesh_set_custom_aabb(RID p_multimesh, const AABB &p_aabb) override; + virtual AABB multimesh_get_custom_aabb(RID p_multimesh) const override; virtual AABB multimesh_get_aabb(RID p_multimesh) const override; virtual Transform3D multimesh_instance_get_transform(RID p_multimesh, int p_index) const override; diff --git a/drivers/gles3/storage/utilities.cpp b/drivers/gles3/storage/utilities.cpp index 8a9e61c725..793b3f64f0 100644 --- a/drivers/gles3/storage/utilities.cpp +++ b/drivers/gles3/storage/utilities.cpp @@ -355,19 +355,16 @@ bool Utilities::has_os_feature(const String &p_feature) const { if (p_feature == "rgtc") { return config->rgtc_supported; } - if (p_feature == "s3tc") { return config->s3tc_supported; } - if (p_feature == "bptc") { return config->bptc_supported; } if (p_feature == "astc") { return config->astc_supported; } - - if (p_feature == "etc" || p_feature == "etc2") { + if (p_feature == "etc2") { return config->etc2_supported; } diff --git a/drivers/vulkan/rendering_device_driver_vulkan.cpp b/drivers/vulkan/rendering_device_driver_vulkan.cpp index 1eab2922d1..f48e6eb7ed 100644 --- a/drivers/vulkan/rendering_device_driver_vulkan.cpp +++ b/drivers/vulkan/rendering_device_driver_vulkan.cpp @@ -2244,19 +2244,31 @@ Error RenderingDeviceDriverVulkan::command_queue_present(CommandQueueID p_cmd_qu for (uint32_t i = 0; i < p_swap_chains.size(); i++) { SwapChain *swap_chain = (SwapChain *)(p_swap_chains[i].id); swap_chain->image_index = UINT_MAX; - if (results[i] == VK_ERROR_OUT_OF_DATE_KHR || results[i] == VK_SUBOPTIMAL_KHR) { + if (results[i] == VK_ERROR_OUT_OF_DATE_KHR) { context_driver->surface_set_needs_resize(swap_chain->surface, true); any_result_is_out_of_date = true; } } - if (any_result_is_out_of_date || err == VK_ERROR_OUT_OF_DATE_KHR || err == VK_SUBOPTIMAL_KHR) { + if (any_result_is_out_of_date || err == VK_ERROR_OUT_OF_DATE_KHR) { // It is possible for presentation to fail with out of date while acquire might've succeeded previously. This case // will be considered a silent failure as it can be triggered easily by resizing a window in the OS natively. return FAILED; } - ERR_FAIL_COND_V(err != VK_SUCCESS, FAILED); + // Handling VK_SUBOPTIMAL_KHR the same as VK_SUCCESS is completely intentional. + // + // Godot does not currently support native rotation in Android when creating the swap chain. It intentionally uses + // VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR instead of the current transform bits available in the surface capabilities. + // Choosing the transform that leads to optimal presentation leads to distortion that makes the application unusable, + // as the rotation of all the content is not handled at the moment. + // + // VK_SUBOPTIMAL_KHR is accepted as a successful case even if it's not the most efficient solution to work around this + // problem. This behavior should not be changed unless the swap chain recreation uses the current transform bits, as + // it'll lead to very low performance in Android by entering an endless loop where it'll always resize the swap chain + // every frame. + + ERR_FAIL_COND_V(err != VK_SUCCESS && err != VK_SUBOPTIMAL_KHR, FAILED); return OK; } @@ -2581,6 +2593,8 @@ Error RenderingDeviceDriverVulkan::swap_chain_resize(CommandQueueID p_cmd_queue, } // Prefer identity transform if it's supported, use the current transform otherwise. + // This behavior is intended as Godot does not supported native rotation in platforms that use these bits. + // Refer to the comment in command_queue_present() for more details. VkSurfaceTransformFlagBitsKHR surface_transform_bits; if (surface_capabilities.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) { surface_transform_bits = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; @@ -2718,18 +2732,17 @@ RDD::FramebufferID RenderingDeviceDriverVulkan::swap_chain_acquire_framebuffer(C swap_chain->command_queues_acquired_semaphores.push_back(semaphore_index); err = device_functions.AcquireNextImageKHR(vk_device, swap_chain->vk_swapchain, UINT64_MAX, semaphore, VK_NULL_HANDLE, &swap_chain->image_index); - if (err == VK_ERROR_OUT_OF_DATE_KHR || err == VK_SUBOPTIMAL_KHR) { - // We choose to treat out of date and suboptimal as the same case, as they both need to be recreated and - // we don't get much use out of presenting a suboptimal image anyway. Either case leaves the semaphore in - // a signaled state that will never finish, so it's necessary to recreate it. + if (err == VK_ERROR_OUT_OF_DATE_KHR) { + // Out of date leaves the semaphore in a signaled state that will never finish, so it's necessary to recreate it. bool semaphore_recreated = _recreate_image_semaphore(command_queue, semaphore_index, true); ERR_FAIL_COND_V(!semaphore_recreated, FramebufferID()); // Swap chain is out of date and must be recreated. r_resize_required = true; return FramebufferID(); - } else if (err != VK_SUCCESS) { + } else if (err != VK_SUCCESS && err != VK_SUBOPTIMAL_KHR) { // Swap chain failed to present but the reason is unknown. + // Refer to the comment in command_queue_present() as to why VK_SUBOPTIMAL_KHR is handled the same as VK_SUCCESS. return FramebufferID(); } diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index 1ff403291f..b7757eb435 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -77,10 +77,10 @@ void AnimationTrackKeyEdit::_fix_node_path(Variant &value) { Node *root = EditorNode::get_singleton()->get_tree()->get_root(); - Node *np_node = root->get_node(np); + Node *np_node = root->get_node_or_null(np); ERR_FAIL_NULL(np_node); - Node *edited_node = root->get_node(base); + Node *edited_node = root->get_node_or_null(base); ERR_FAIL_NULL(edited_node); value = edited_node->get_path_to(np_node); @@ -601,8 +601,8 @@ void AnimationTrackKeyEdit::_get_property_list(List<PropertyInfo> *p_list) const case Animation::TYPE_ANIMATION: { String animations; - if (root_path && root_path->has_node(animation->track_get_path(track))) { - AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(root_path->get_node(animation->track_get_path(track))); + if (root_path) { + AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(root_path->get_node_or_null(animation->track_get_path(track))); if (ap) { List<StringName> anims; ap->get_animation_list(&anims); @@ -663,10 +663,10 @@ void AnimationMultiTrackKeyEdit::_fix_node_path(Variant &value, NodePath &base) Node *root = EditorNode::get_singleton()->get_tree()->get_root(); - Node *np_node = root->get_node(np); + Node *np_node = root->get_node_or_null(np); ERR_FAIL_NULL(np_node); - Node *edited_node = root->get_node(base); + Node *edited_node = root->get_node_or_null(base); ERR_FAIL_NULL(edited_node); value = edited_node->get_path_to(np_node); @@ -1207,8 +1207,8 @@ void AnimationMultiTrackKeyEdit::_get_property_list(List<PropertyInfo> *p_list) String animations; - if (root_path && root_path->has_node(animation->track_get_path(first_track))) { - AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(root_path->get_node(animation->track_get_path(first_track))); + if (root_path) { + AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(root_path->get_node_or_null(animation->track_get_path(first_track))); if (ap) { List<StringName> anims; ap->get_animation_list(&anims); @@ -1940,8 +1940,8 @@ void AnimationTrackEdit::_notification(int p_what) { NodePath anim_path = animation->track_get_path(track); Node *node = nullptr; - if (root && root->has_node(anim_path)) { - node = root->get_node(anim_path); + if (root) { + node = root->get_node_or_null(anim_path); } String text; @@ -2481,10 +2481,9 @@ void AnimationTrackEdit::_path_submitted(const String &p_text) { } bool AnimationTrackEdit::_is_value_key_valid(const Variant &p_key_value, Variant::Type &r_valid_type) const { - if (root == nullptr) { + if (root == nullptr || !root->has_node_and_resource(animation->track_get_path(track))) { return false; } - Ref<Resource> res; Vector<StringName> leftover_path; Node *node = root->get_node_and_resource(animation->track_get_path(track), res, leftover_path); @@ -2774,11 +2773,11 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) { AnimationPlayer *ap = ape->get_player(); if (ap) { NodePath npath = animation->track_get_path(track); - Node *a_ap_root_node = ap->get_node(ap->get_root_node()); + Node *a_ap_root_node = ap->get_node_or_null(ap->get_root_node()); Node *nd = nullptr; // We must test that we have a valid a_ap_root_node before trying to access its content to init the nd Node. if (a_ap_root_node) { - nd = a_ap_root_node->get_node(NodePath(npath.get_concatenated_names())); + nd = a_ap_root_node->get_node_or_null(NodePath(npath.get_concatenated_names())); } if (nd) { StringName prop = npath.get_concatenated_subnames(); @@ -3310,8 +3309,8 @@ void AnimationTrackEditGroup::_notification(int p_what) { int separation = get_theme_constant(SNAME("h_separation"), SNAME("ItemList")); Color color = get_theme_color(SNAME("font_color"), SNAME("Label")); - if (root && root->has_node(node)) { - Node *n = root->get_node(node); + if (root) { + Node *n = root->get_node_or_null(node); if (n && EditorNode::get_singleton()->get_editor_selection()->is_selected(n)) { color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor)); } @@ -3353,7 +3352,10 @@ void AnimationTrackEditGroup::gui_input(const Ref<InputEvent> &p_event) { if (node_name_rect.has_point(pos)) { EditorSelection *editor_selection = EditorNode::get_singleton()->get_editor_selection(); editor_selection->clear(); - editor_selection->add_node(root->get_node(node)); + Node *n = root->get_node_or_null(node); + if (n) { + editor_selection->add_node(n); + } } } } @@ -4474,8 +4476,8 @@ void AnimationTrackEditor::_update_tracks() { if (use_filter) { NodePath path = animation->track_get_path(i); - if (root && root->has_node(path)) { - Node *node = root->get_node(path); + if (root) { + Node *node = root->get_node_or_null(path); if (!node) { continue; // No node, no filter. } @@ -4527,8 +4529,8 @@ void AnimationTrackEditor::_update_tracks() { NodePath path = animation->track_get_path(i); Node *node = nullptr; - if (root && root->has_node(path)) { - node = root->get_node(path); + if (root) { + node = root->get_node_or_null(path); } if (node && Object::cast_to<AnimationPlayer>(node)) { @@ -4557,8 +4559,8 @@ void AnimationTrackEditor::_update_tracks() { Ref<Texture2D> icon = get_editor_theme_icon(SNAME("Node")); String name = base_path; String tooltip; - if (root && root->has_node(base_path)) { - Node *n = root->get_node(base_path); + if (root) { + Node *n = root->get_node_or_null(base_path); if (n) { icon = EditorNode::get_singleton()->get_object_icon(n, "Node"); name = n->get_name(); @@ -4808,7 +4810,7 @@ void AnimationTrackEditor::_dropped_track(int p_from_track, int p_to_track) { void AnimationTrackEditor::_new_track_node_selected(NodePath p_path) { ERR_FAIL_NULL(root); - Node *node = get_node(p_path); + Node *node = get_node_or_null(p_path); ERR_FAIL_NULL(node); NodePath path_to = root->get_path_to(node, true); @@ -5129,7 +5131,8 @@ void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) { EditorNode::get_singleton()->show_warning(TTR("Track path is invalid, so can't add a method key.")); return; } - Node *base = root->get_node(animation->track_get_path(p_track)); + Node *base = root->get_node_or_null(animation->track_get_path(p_track)); + ERR_FAIL_NULL(base); method_selector->select_method_from_instance(base); @@ -5182,7 +5185,8 @@ void AnimationTrackEditor::_add_method_key(const String &p_method) { EditorNode::get_singleton()->show_warning(TTR("Track path is invalid, so can't add a method key.")); return; } - Node *base = root->get_node(animation->track_get_path(insert_key_from_track_call_track)); + Node *base = root->get_node_or_null(animation->track_get_path(insert_key_from_track_call_track)); + ERR_FAIL_NULL(base); List<MethodInfo> minfo; base->get_method_list(&minfo); @@ -5963,8 +5967,8 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { NodePath path = animation->track_get_path(i); Node *node = nullptr; - if (root && root->has_node(path)) { - node = root->get_node(path); + if (root) { + node = root->get_node_or_null(path); } String text; @@ -6085,7 +6089,7 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { if (root) { NodePath np = track_clipboard[i].full_path; - exists = root->get_node(np); + exists = root->get_node_or_null(np); if (exists) { path = NodePath(root->get_path_to(exists).get_names(), track_clipboard[i].full_path.get_subnames(), false); } @@ -6587,15 +6591,17 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { void AnimationTrackEditor::_cleanup_animation(Ref<Animation> p_animation) { for (int i = 0; i < p_animation->get_track_count(); i++) { - bool prop_exists = false; - Variant::Type valid_type = Variant::NIL; - Object *obj = nullptr; - + if (!root->has_node_and_resource(p_animation->track_get_path(i))) { + continue; + } Ref<Resource> res; Vector<StringName> leftover_path; - Node *node = root->get_node_and_resource(p_animation->track_get_path(i), res, leftover_path); + bool prop_exists = false; + Variant::Type valid_type = Variant::NIL; + Object *obj = nullptr; + if (res.is_valid()) { obj = res.ptr(); } else if (node) { @@ -6772,9 +6778,9 @@ void AnimationTrackEditor::_pick_track_select_recursive(TreeItem *p_item, const } NodePath np = p_item->get_metadata(0); - Node *node = get_node(np); + Node *node = get_node_or_null(np); - if (!p_filter.is_empty() && ((String)node->get_name()).findn(p_filter) != -1) { + if (node && !p_filter.is_empty() && ((String)node->get_name()).findn(p_filter) != -1) { p_select_candidates.push_back(node); } diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp index 6ad8dae3b4..41a3437f20 100644 --- a/editor/connections_dialog.cpp +++ b/editor/connections_dialog.cpp @@ -591,6 +591,18 @@ bool ConnectDialog::is_editing() const { return edit_mode; } +void ConnectDialog::shortcut_input(const Ref<InputEvent> &p_event) { + const Ref<InputEventKey> &key = p_event; + + if (key.is_valid() && key->is_pressed() && !key->is_echo()) { + if (ED_IS_SHORTCUT("editor/open_search", p_event)) { + filter_nodes->grab_focus(); + filter_nodes->select_all(); + filter_nodes->accept_event(); + } + } +} + /* * Initialize ConnectDialog and populate fields with expected data. * If creating a connection from scratch, sensible defaults are used. @@ -1554,6 +1566,7 @@ ConnectionsDock::ConnectionsDock() { connect_dialog = memnew(ConnectDialog); connect_dialog->connect("connected", callable_mp(NodeDock::get_singleton(), &NodeDock::restore_last_valid_node), CONNECT_DEFERRED); + connect_dialog->set_process_shortcut_input(true); add_child(connect_dialog); disconnect_all_dialog = memnew(ConfirmationDialog); diff --git a/editor/connections_dialog.h b/editor/connections_dialog.h index 4f628d5685..fb163fbb5f 100644 --- a/editor/connections_dialog.h +++ b/editor/connections_dialog.h @@ -180,6 +180,8 @@ public: bool get_one_shot() const; bool is_editing() const; + virtual void shortcut_input(const Ref<InputEvent> &p_event) override; + void init(const ConnectionData &p_cd, const PackedStringArray &p_signal_args, bool p_edit = false); void popup_dialog(const String p_for_signal); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 663cddc0da..4cb9367bfe 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -170,6 +170,9 @@ static const String META_TEXT_TO_COPY = "text_to_copy"; static const String EDITOR_NODE_CONFIG_SECTION = "EditorNode"; +static const String REMOVE_ANDROID_BUILD_TEMPLATE_MESSAGE = "The Android build template is already installed in this project and it won't be overwritten.\nRemove the \"%s\" directory manually before attempting this operation again."; +static const String INSTALL_ANDROID_BUILD_TEMPLATE_MESSAGE = "This will set up your project for gradle Android builds by installing the source template to \"%s\".\nNote that in order to make gradle builds instead of using pre-built APKs, the \"Use Gradle Build\" option should be enabled in the Android export preset."; + void EditorNode::disambiguate_filenames(const Vector<String> p_full_paths, Vector<String> &r_filenames) { ERR_FAIL_COND_MSG(p_full_paths.size() != r_filenames.size(), vformat("disambiguate_filenames requires two string vectors of same length (%d != %d).", p_full_paths.size(), r_filenames.size())); @@ -966,7 +969,7 @@ void EditorNode::_fs_changed() { String config_error; bool missing_templates; if (export_defer.android_build_template) { - export_template_manager->install_android_template(); + export_template_manager->install_android_template(export_preset); } if (!platform->can_export(export_preset, config_error, missing_templates, export_defer.debug)) { ERR_PRINT(vformat("Cannot export project with preset \"%s\" due to configuration errors:\n%s", preset_name, config_error)); @@ -2525,7 +2528,16 @@ void EditorNode::_edit_current(bool p_skip_foreign, bool p_skip_inspector_update } void EditorNode::_android_build_source_selected(const String &p_file) { - export_template_manager->install_android_template_from_file(p_file); + export_template_manager->install_android_template_from_file(p_file, android_export_preset); +} + +void EditorNode::_android_export_preset_selected(int p_index) { + if (p_index >= 0) { + android_export_preset = EditorExport::get_singleton()->get_export_preset(choose_android_export_profile->get_item_id(p_index)); + } else { + android_export_preset.unref(); + } + install_android_build_template_message->set_text(vformat(TTR(INSTALL_ANDROID_BUILD_TEMPLATE_MESSAGE), export_template_manager->get_android_build_directory(android_export_preset))); } void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { @@ -2811,14 +2823,45 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { } break; case FILE_INSTALL_ANDROID_SOURCE: { if (p_confirmed) { - export_template_manager->install_android_template(); - } else { - if (DirAccess::exists("res://android/build")) { + if (export_template_manager->is_android_template_installed(android_export_preset)) { + remove_android_build_template->set_text(vformat(TTR(REMOVE_ANDROID_BUILD_TEMPLATE_MESSAGE), export_template_manager->get_android_build_directory(android_export_preset))); remove_android_build_template->popup_centered(); - } else if (export_template_manager->can_install_android_template()) { + } else if (!export_template_manager->can_install_android_template(android_export_preset)) { + gradle_build_manage_templates->popup_centered(); + } else { + export_template_manager->install_android_template(android_export_preset); + } + } else { + bool has_custom_gradle_build = false; + choose_android_export_profile->clear(); + for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); i++) { + Ref<EditorExportPreset> export_preset = EditorExport::get_singleton()->get_export_preset(i); + if (export_preset->get_platform()->get_class_name() == "EditorExportPlatformAndroid" && (bool)export_preset->get("gradle_build/use_gradle_build")) { + choose_android_export_profile->add_item(export_preset->get_name(), i); + String gradle_build_directory = export_preset->get("gradle_build/gradle_build_directory"); + String android_source_template = export_preset->get("gradle_build/android_source_template"); + if (!android_source_template.is_empty() || (gradle_build_directory != "" && gradle_build_directory != "res://android")) { + has_custom_gradle_build = true; + } + } + } + _android_export_preset_selected(choose_android_export_profile->get_item_count() >= 1 ? 0 : -1); + + if (choose_android_export_profile->get_item_count() > 1 && has_custom_gradle_build) { + // If there's multiple options and at least one of them uses a custom gradle build then prompt the user to choose. + choose_android_export_profile->show(); install_android_build_template->popup_centered(); } else { - gradle_build_manage_templates->popup_centered(); + choose_android_export_profile->hide(); + + if (export_template_manager->is_android_template_installed(android_export_preset)) { + remove_android_build_template->set_text(vformat(TTR(REMOVE_ANDROID_BUILD_TEMPLATE_MESSAGE), export_template_manager->get_android_build_directory(android_export_preset))); + remove_android_build_template->popup_centered(); + } else if (export_template_manager->can_install_android_template(android_export_preset)) { + install_android_build_template->popup_centered(); + } else { + gradle_build_manage_templates->popup_centered(); + } } } } break; @@ -2831,7 +2874,7 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { OS::get_singleton()->shell_show_in_file_manager(OS::get_singleton()->get_user_data_dir(), true); } break; case FILE_EXPLORE_ANDROID_BUILD_TEMPLATES: { - OS::get_singleton()->shell_show_in_file_manager(ProjectSettings::get_singleton()->get_resource_path().path_join("android"), true); + OS::get_singleton()->shell_show_in_file_manager(ProjectSettings::get_singleton()->globalize_path(export_template_manager->get_android_build_directory(android_export_preset).get_base_dir()), true); } break; case FILE_QUIT: case RUN_PROJECT_MANAGER: @@ -7216,14 +7259,26 @@ EditorNode::EditorNode() { file_android_build_source->connect("file_selected", callable_mp(this, &EditorNode::_android_build_source_selected)); gui_base->add_child(file_android_build_source); - install_android_build_template = memnew(ConfirmationDialog); - install_android_build_template->set_text(TTR("This will set up your project for gradle Android builds by installing the source template to \"res://android/build\".\nYou can then apply modifications and build your own custom APK on export (adding modules, changing the AndroidManifest.xml, etc.).\nNote that in order to make gradle builds instead of using pre-built APKs, the \"Use Gradle Build\" option should be enabled in the Android export preset.")); - install_android_build_template->set_ok_button_text(TTR("Install")); - install_android_build_template->connect("confirmed", callable_mp(this, &EditorNode::_menu_confirm_current)); - gui_base->add_child(install_android_build_template); + { + VBoxContainer *vbox = memnew(VBoxContainer); + install_android_build_template_message = memnew(Label); + install_android_build_template_message->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART); + install_android_build_template_message->set_custom_minimum_size(Size2(300 * EDSCALE, 1)); + vbox->add_child(install_android_build_template_message); + + choose_android_export_profile = memnew(OptionButton); + choose_android_export_profile->connect("item_selected", callable_mp(this, &EditorNode::_android_export_preset_selected)); + vbox->add_child(choose_android_export_profile); + + install_android_build_template = memnew(ConfirmationDialog); + install_android_build_template->set_ok_button_text(TTR("Install")); + install_android_build_template->connect("confirmed", callable_mp(this, &EditorNode::_menu_confirm_current)); + install_android_build_template->add_child(vbox); + install_android_build_template->set_min_size(Vector2(500.0 * EDSCALE, 0)); + gui_base->add_child(install_android_build_template); + } remove_android_build_template = memnew(ConfirmationDialog); - remove_android_build_template->set_text(TTR("The Android build template is already installed in this project and it won't be overwritten.\nRemove the \"res://android/build\" directory manually before attempting this operation again.")); remove_android_build_template->set_ok_button_text(TTR("Show in File Manager")); remove_android_build_template->connect("confirmed", callable_mp(this, &EditorNode::_menu_option).bind(FILE_EXPLORE_ANDROID_BUILD_TEMPLATES)); gui_base->add_child(remove_android_build_template); diff --git a/editor/editor_node.h b/editor/editor_node.h index 0f6e031424..8a880a00cc 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -80,6 +80,7 @@ class EditorBuildProfileManager; class EditorCommandPalette; class EditorDockManager; class EditorExport; +class EditorExportPreset; class EditorExtensionManager; class EditorFeatureProfileManager; class EditorFileDialog; @@ -382,6 +383,9 @@ private: ConfirmationDialog *gradle_build_manage_templates = nullptr; ConfirmationDialog *install_android_build_template = nullptr; ConfirmationDialog *remove_android_build_template = nullptr; + Label *install_android_build_template_message = nullptr; + OptionButton *choose_android_export_profile = nullptr; + Ref<EditorExportPreset> android_export_preset; PopupMenu *vcs_actions_menu = nullptr; EditorFileDialog *file = nullptr; @@ -528,6 +532,7 @@ private: void _menu_option_confirm(int p_option, bool p_confirmed); void _android_build_source_selected(const String &p_file); + void _android_export_preset_selected(int p_index); void _request_screenshot(); void _screenshot(bool p_use_utc = false); diff --git a/editor/editor_property_name_processor.cpp b/editor/editor_property_name_processor.cpp index 5dc211bf55..a892ea0f85 100644 --- a/editor/editor_property_name_processor.cpp +++ b/editor/editor_property_name_processor.cpp @@ -160,7 +160,6 @@ EditorPropertyNameProcessor::EditorPropertyNameProcessor() { capitalize_string_remaps["dtls"] = "DTLS"; capitalize_string_remaps["eol"] = "EOL"; capitalize_string_remaps["erp"] = "ERP"; - capitalize_string_remaps["etc"] = "ETC"; capitalize_string_remaps["etc2"] = "ETC2"; capitalize_string_remaps["fabrik"] = "FABRIK"; capitalize_string_remaps["fbx"] = "FBX"; diff --git a/editor/export/editor_export_platform_pc.cpp b/editor/export/editor_export_platform_pc.cpp index 42df0d93f6..413db1595b 100644 --- a/editor/export/editor_export_platform_pc.cpp +++ b/editor/export/editor_export_platform_pc.cpp @@ -34,17 +34,13 @@ #include "scene/resources/image_texture.h" void EditorExportPlatformPC::get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) const { - if (p_preset->get("texture_format/bptc")) { - r_features->push_back("bptc"); - } - if (p_preset->get("texture_format/s3tc")) { + if (p_preset->get("texture_format/s3tc_bptc")) { r_features->push_back("s3tc"); + r_features->push_back("bptc"); } - if (p_preset->get("texture_format/etc")) { - r_features->push_back("etc"); - } - if (p_preset->get("texture_format/etc2")) { + if (p_preset->get("texture_format/etc2_astc")) { r_features->push_back("etc2"); + r_features->push_back("astc"); } // PC platforms only have one architecture per export, since // we export a single executable instead of a bundle. @@ -60,10 +56,8 @@ void EditorExportPlatformPC::get_export_options(List<ExportOption> *r_options) c r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "binary_format/embed_pck"), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/bptc"), true)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/s3tc"), true)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/etc"), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/etc2"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/s3tc_bptc"), true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/etc2_astc"), false)); } String EditorExportPlatformPC::get_name() const { @@ -103,6 +97,14 @@ bool EditorExportPlatformPC::has_valid_export_configuration(const Ref<EditorExpo valid = dvalid || rvalid; r_missing_templates = !valid; + bool uses_s3tc_bptc = p_preset->get("texture_format/s3tc_bptc"); + bool uses_etc2_astc = p_preset->get("texture_format/etc2_astc"); + + if (!uses_s3tc_bptc && !uses_etc2_astc) { + valid = false; + err += TTR("A texture format must be selected to export the project. Please select at least one texture format."); + } + if (!err.is_empty()) { r_error = err; } @@ -237,9 +239,8 @@ void EditorExportPlatformPC::set_logo(const Ref<Texture2D> &p_logo) { } void EditorExportPlatformPC::get_platform_features(List<String> *r_features) const { - r_features->push_back("pc"); //all pcs support "pc" - r_features->push_back("s3tc"); //all pcs support "s3tc" compression - r_features->push_back(get_os_name().to_lower()); //OS name is a feature + r_features->push_back("pc"); // Identify PC platforms as such. + r_features->push_back(get_os_name().to_lower()); // OS name is a feature. } void EditorExportPlatformPC::resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, HashSet<String> &p_features) { diff --git a/editor/export/export_template_manager.cpp b/editor/export/export_template_manager.cpp index b3951d2c28..c3e48820b2 100644 --- a/editor/export/export_template_manager.cpp +++ b/editor/export/export_template_manager.cpp @@ -38,6 +38,7 @@ #include "editor/editor_paths.h" #include "editor/editor_settings.h" #include "editor/editor_string_names.h" +#include "editor/export/editor_export.h" #include "editor/progress_dialog.h" #include "editor/themes/editor_scale.h" #include "scene/gui/file_dialog.h" @@ -655,39 +656,78 @@ void ExportTemplateManager::_hide_dialog() { hide(); } -bool ExportTemplateManager::can_install_android_template() { +String ExportTemplateManager::get_android_build_directory(const Ref<EditorExportPreset> &p_preset) { + if (p_preset.is_valid()) { + String gradle_build_dir = p_preset->get("gradle_build/gradle_build_directory"); + if (!gradle_build_dir.is_empty()) { + return gradle_build_dir.path_join("build"); + } + } + return "res://android/build"; +} + +String ExportTemplateManager::get_android_source_zip(const Ref<EditorExportPreset> &p_preset) { + if (p_preset.is_valid()) { + String android_source_zip = p_preset->get("gradle_build/android_source_template"); + if (!android_source_zip.is_empty()) { + return android_source_zip; + } + } + const String templates_dir = EditorPaths::get_singleton()->get_export_templates_dir().path_join(VERSION_FULL_CONFIG); - return FileAccess::exists(templates_dir.path_join("android_source.zip")); + return templates_dir.path_join("android_source.zip"); } -Error ExportTemplateManager::install_android_template() { - const String &templates_path = EditorPaths::get_singleton()->get_export_templates_dir().path_join(VERSION_FULL_CONFIG); - const String &source_zip = templates_path.path_join("android_source.zip"); +String ExportTemplateManager::get_android_template_identifier(const Ref<EditorExportPreset> &p_preset) { + // The template identifier is the Godot version for the default template, and the full path plus md5 hash for custom templates. + if (p_preset.is_valid()) { + String android_source_zip = p_preset->get("gradle_build/android_source_template"); + if (!android_source_zip.is_empty()) { + return android_source_zip + String(" [") + FileAccess::get_md5(android_source_zip) + String("]"); + } + } + return VERSION_FULL_CONFIG; +} + +bool ExportTemplateManager::is_android_template_installed(const Ref<EditorExportPreset> &p_preset) { + return DirAccess::exists(get_android_build_directory(p_preset)); +} + +bool ExportTemplateManager::can_install_android_template(const Ref<EditorExportPreset> &p_preset) { + return FileAccess::exists(get_android_source_zip(p_preset)); +} + +Error ExportTemplateManager::install_android_template(const Ref<EditorExportPreset> &p_preset) { + const String source_zip = get_android_source_zip(p_preset); ERR_FAIL_COND_V(!FileAccess::exists(source_zip), ERR_CANT_OPEN); - return install_android_template_from_file(source_zip); + return install_android_template_from_file(source_zip, p_preset); } -Error ExportTemplateManager::install_android_template_from_file(const String &p_file) { + +Error ExportTemplateManager::install_android_template_from_file(const String &p_file, const Ref<EditorExportPreset> &p_preset) { // To support custom Android builds, we install the Java source code and buildsystem // from android_source.zip to the project's res://android folder. Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES); ERR_FAIL_COND_V(da.is_null(), ERR_CANT_CREATE); - // Make res://android dir (if it does not exist). - da->make_dir("android"); + String build_dir = get_android_build_directory(p_preset); + String parent_dir = build_dir.get_base_dir(); + + // Make parent of the build dir (if it does not exist). + da->make_dir_recursive(parent_dir); { - // Add version, to ensure building won't work if template and Godot version don't match. - Ref<FileAccess> f = FileAccess::open("res://android/.build_version", FileAccess::WRITE); + // Add identifier, to ensure building won't work if the current template doesn't match. + Ref<FileAccess> f = FileAccess::open(parent_dir.path_join(".build_version"), FileAccess::WRITE); ERR_FAIL_COND_V(f.is_null(), ERR_CANT_CREATE); - f->store_line(VERSION_FULL_CONFIG); + f->store_line(get_android_template_identifier(p_preset)); } // Create the android build directory. - Error err = da->make_dir_recursive("android/build"); + Error err = da->make_dir_recursive(build_dir); ERR_FAIL_COND_V(err != OK, err); { // Add an empty .gdignore file to avoid scan. - Ref<FileAccess> f = FileAccess::open("res://android/build/.gdignore", FileAccess::WRITE); + Ref<FileAccess> f = FileAccess::open(build_dir.path_join(".gdignore"), FileAccess::WRITE); ERR_FAIL_COND_V(f.is_null(), ERR_CANT_CREATE); f->store_line(""); } @@ -735,11 +775,11 @@ Error ExportTemplateManager::install_android_template_from_file(const String &p_ unzCloseCurrentFile(pkg); if (!dirs_tested.has(base_dir)) { - da->make_dir_recursive(String("android/build").path_join(base_dir)); + da->make_dir_recursive(build_dir.path_join(base_dir)); dirs_tested.insert(base_dir); } - String to_write = String("res://android/build").path_join(path); + String to_write = build_dir.path_join(path); Ref<FileAccess> f = FileAccess::open(to_write, FileAccess::WRITE); if (f.is_valid()) { f->store_buffer(uncomp_data.ptr(), uncomp_data.size()); diff --git a/editor/export/export_template_manager.h b/editor/export/export_template_manager.h index 8c26554d13..a00d874580 100644 --- a/editor/export/export_template_manager.h +++ b/editor/export/export_template_manager.h @@ -33,6 +33,7 @@ #include "scene/gui/dialogs.h" +class EditorExportPreset; class ExportTemplateVersion; class FileDialog; class HTTPRequest; @@ -121,10 +122,15 @@ protected: static void _bind_methods(); public: - bool can_install_android_template(); - Error install_android_template(); + static String get_android_build_directory(const Ref<EditorExportPreset> &p_preset); + static String get_android_source_zip(const Ref<EditorExportPreset> &p_preset); + static String get_android_template_identifier(const Ref<EditorExportPreset> &p_preset); - Error install_android_template_from_file(const String &p_file); + bool is_android_template_installed(const Ref<EditorExportPreset> &p_preset); + bool can_install_android_template(const Ref<EditorExportPreset> &p_preset); + Error install_android_template(const Ref<EditorExportPreset> &p_preset); + + Error install_android_template_from_file(const String &p_file, const Ref<EditorExportPreset> &p_preset); void popup_manager(); diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index dde7ddd32d..ac83460542 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -584,7 +584,7 @@ void FileSystemDock::_notification(int p_what) { file_list_search_box->set_right_icon(get_editor_theme_icon(SNAME("Search"))); file_list_button_sort->set_icon(get_editor_theme_icon(SNAME("Sort"))); - button_dock_placement->set_icon(get_editor_theme_icon(SNAME("GuiTabMenu"))); + button_dock_placement->set_icon(get_editor_theme_icon(SNAME("GuiTabMenuHl"))); if (is_layout_rtl()) { button_hist_next->set_icon(get_editor_theme_icon(SNAME("Back"))); diff --git a/editor/gui/editor_spin_slider.cpp b/editor/gui/editor_spin_slider.cpp index dd1440fe0b..fab5784f16 100644 --- a/editor/gui/editor_spin_slider.cpp +++ b/editor/gui/editor_spin_slider.cpp @@ -68,27 +68,15 @@ void EditorSpinSlider::gui_input(const Ref<InputEvent> &p_event) { } return; } else { - grabbing_spinner_attempt = true; - grabbing_spinner_dist_cache = 0; - pre_grab_value = get_value(); - grabbing_spinner = false; - grabbing_spinner_mouse_pos = get_global_mouse_position(); - emit_signal("grabbed"); + _grab_start(); } } else { - if (grabbing_spinner_attempt) { - if (grabbing_spinner) { - Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE); - Input::get_singleton()->warp_mouse(grabbing_spinner_mouse_pos); - queue_redraw(); - emit_signal("ungrabbed"); - } else { - _focus_entered(); - } - - grabbing_spinner = false; - grabbing_spinner_attempt = false; - } + _grab_end(); + } + } else if (mb->get_button_index() == MouseButton::RIGHT) { + if (mb->is_pressed() && is_grabbing()) { + _grab_end(); + set_value(pre_grab_value); } } else if (mb->get_button_index() == MouseButton::WHEEL_UP || mb->get_button_index() == MouseButton::WHEEL_DOWN) { if (grabber->is_visible()) { @@ -142,8 +130,47 @@ void EditorSpinSlider::gui_input(const Ref<InputEvent> &p_event) { } Ref<InputEventKey> k = p_event; - if (k.is_valid() && k->is_pressed() && k->is_action("ui_accept", true)) { - _focus_entered(); + if (k.is_valid() && k->is_pressed()) { + if (k->is_action("ui_accept", true)) { + _focus_entered(); + } else if (is_grabbing()) { + if (k->is_action("ui_cancel", true)) { + _grab_end(); + set_value(pre_grab_value); + } + accept_event(); + } + } +} + +void EditorSpinSlider::_grab_start() { + grabbing_spinner_attempt = true; + grabbing_spinner_dist_cache = 0; + pre_grab_value = get_value(); + grabbing_spinner = false; + grabbing_spinner_mouse_pos = get_global_mouse_position(); + emit_signal("grabbed"); +} + +void EditorSpinSlider::_grab_end() { + if (grabbing_spinner_attempt) { + if (grabbing_spinner) { + Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE); + Input::get_singleton()->warp_mouse(grabbing_spinner_mouse_pos); + queue_redraw(); + grabbing_spinner = false; + emit_signal("ungrabbed"); + } else { + _focus_entered(); + } + + grabbing_spinner_attempt = false; + } + + if (grabbing_grabber) { + grabbing_grabber = false; + mousewheel_over_grabber = false; + emit_signal("ungrabbed"); } } @@ -173,16 +200,25 @@ void EditorSpinSlider::_grabber_gui_input(const Ref<InputEvent> &p_event) { if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT) { if (mb->is_pressed()) { grabbing_grabber = true; + pre_grab_value = get_value(); if (!mousewheel_over_grabber) { grabbing_ratio = get_as_ratio(); grabbing_from = grabber->get_transform().xform(mb->get_position()).x; } + grab_focus(); emit_signal("grabbed"); } else { grabbing_grabber = false; mousewheel_over_grabber = false; emit_signal("ungrabbed"); } + } else if (mb.is_valid() && mb->get_button_index() == MouseButton::RIGHT) { + if (mb->is_pressed() && grabbing_grabber) { + grabbing_grabber = false; + mousewheel_over_grabber = false; + set_value(pre_grab_value); + emit_signal("ungrabbed"); + } } Ref<InputEventMouseMotion> mm = p_event; diff --git a/editor/gui/editor_spin_slider.h b/editor/gui/editor_spin_slider.h index 8c643157f1..a999f5c48f 100644 --- a/editor/gui/editor_spin_slider.h +++ b/editor/gui/editor_spin_slider.h @@ -72,6 +72,9 @@ class EditorSpinSlider : public Range { bool hide_slider = false; bool flat = false; + void _grab_start(); + void _grab_end(); + void _grabber_gui_input(const Ref<InputEvent> &p_event); void _value_input_closed(); void _value_input_submitted(const String &); diff --git a/editor/plugins/cpu_particles_3d_editor_plugin.cpp b/editor/plugins/cpu_particles_3d_editor_plugin.cpp index 7e5fa70f3f..0fd980ff10 100644 --- a/editor/plugins/cpu_particles_3d_editor_plugin.cpp +++ b/editor/plugins/cpu_particles_3d_editor_plugin.cpp @@ -77,7 +77,60 @@ void CPUParticles3DEditor::_menu_option(int p_option) { ur->commit_action(false); } break; + case MENU_OPTION_GENERATE_AABB: { + // Add one second to the default generation lifetime, since the progress is updated every second. + generate_seconds->set_value(MAX(1.0, trunc(node->get_lifetime()) + 1.0)); + + if (generate_seconds->get_value() >= 11.0 + CMP_EPSILON) { + // Only pop up the time dialog if the particle's lifetime is long enough to warrant shortening it. + generate_aabb->popup_centered(); + } else { + // Generate the visibility AABB immediately. + _generate_aabb(); + } + } break; + } +} + +void CPUParticles3DEditor::_generate_aabb() { + double time = generate_seconds->get_value(); + + double running = 0.0; + + EditorProgress ep("gen_aabb", TTR("Generating Visibility AABB (Waiting for Particle Simulation)"), int(time)); + + bool was_emitting = node->is_emitting(); + if (!was_emitting) { + node->set_emitting(true); + OS::get_singleton()->delay_usec(1000); + } + + AABB rect; + + while (running < time) { + uint64_t ticks = OS::get_singleton()->get_ticks_usec(); + ep.step("Generating...", int(running), true); + OS::get_singleton()->delay_usec(1000); + + AABB capture = node->capture_aabb(); + if (rect == AABB()) { + rect = capture; + } else { + rect.merge_with(capture); + } + + running += (OS::get_singleton()->get_ticks_usec() - ticks) / 1000000.0; + } + + if (!was_emitting) { + node->set_emitting(false); } + + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); + ur->create_action(TTR("Generate Visibility AABB")); + ur->add_do_method(node, "set_visibility_aabb", rect); + ur->add_undo_method(node, "set_visibility_aabb", node->get_visibility_aabb()); + ur->commit_action(); } void CPUParticles3DEditor::edit(CPUParticles3D *p_particles) { @@ -117,9 +170,24 @@ CPUParticles3DEditor::CPUParticles3DEditor() { options->set_text(TTR("CPUParticles3D")); options->get_popup()->add_item(TTR("Restart"), MENU_OPTION_RESTART); + options->get_popup()->add_item(TTR("Generate AABB"), MENU_OPTION_GENERATE_AABB); options->get_popup()->add_item(TTR("Create Emission Points From Node"), MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE); options->get_popup()->add_item(TTR("Convert to GPUParticles3D"), MENU_OPTION_CONVERT_TO_GPU_PARTICLES); options->get_popup()->connect("id_pressed", callable_mp(this, &CPUParticles3DEditor::_menu_option)); + + generate_aabb = memnew(ConfirmationDialog); + generate_aabb->set_title(TTR("Generate Visibility AABB")); + VBoxContainer *genvb = memnew(VBoxContainer); + generate_aabb->add_child(genvb); + generate_seconds = memnew(SpinBox); + genvb->add_margin_child(TTR("Generation Time (sec):"), generate_seconds); + generate_seconds->set_min(0.1); + generate_seconds->set_max(25); + generate_seconds->set_value(2); + + add_child(generate_aabb); + + generate_aabb->connect("confirmed", callable_mp(this, &CPUParticles3DEditor::_generate_aabb)); } void CPUParticles3DEditorPlugin::edit(Object *p_object) { diff --git a/editor/plugins/cpu_particles_3d_editor_plugin.h b/editor/plugins/cpu_particles_3d_editor_plugin.h index 6de23fc2b8..99178b7fde 100644 --- a/editor/plugins/cpu_particles_3d_editor_plugin.h +++ b/editor/plugins/cpu_particles_3d_editor_plugin.h @@ -38,14 +38,19 @@ class CPUParticles3DEditor : public GPUParticles3DEditorBase { GDCLASS(CPUParticles3DEditor, GPUParticles3DEditorBase); enum Menu { + MENU_OPTION_GENERATE_AABB, MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE, MENU_OPTION_CLEAR_EMISSION_VOLUME, MENU_OPTION_RESTART, MENU_OPTION_CONVERT_TO_GPU_PARTICLES, }; + ConfirmationDialog *generate_aabb = nullptr; + SpinBox *generate_seconds = nullptr; CPUParticles3D *node = nullptr; + void _generate_aabb(); + void _menu_option(int); friend class CPUParticles3DEditorPlugin; diff --git a/editor/plugins/gizmos/cpu_particles_3d_gizmo_plugin.cpp b/editor/plugins/gizmos/cpu_particles_3d_gizmo_plugin.cpp index 3745b407a3..fe5d8e92d1 100644 --- a/editor/plugins/gizmos/cpu_particles_3d_gizmo_plugin.cpp +++ b/editor/plugins/gizmos/cpu_particles_3d_gizmo_plugin.cpp @@ -31,11 +31,16 @@ #include "cpu_particles_3d_gizmo_plugin.h" #include "editor/editor_node.h" +#include "editor/editor_settings.h" #include "editor/editor_string_names.h" #include "editor/plugins/node_3d_editor_plugin.h" #include "scene/3d/cpu_particles_3d.h" CPUParticles3DGizmoPlugin::CPUParticles3DGizmoPlugin() { + Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/particles", Color(0.8, 0.7, 0.4)); + create_material("particles_material", gizmo_color); + gizmo_color.a = MAX((gizmo_color.a - 0.2) * 0.02, 0.0); + create_material("particles_solid_material", gizmo_color); create_icon_material("particles_icon", EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("GizmoCPUParticles3D"), EditorStringName(EditorIcons))); } @@ -56,6 +61,48 @@ bool CPUParticles3DGizmoPlugin::is_selectable_when_hidden() const { } void CPUParticles3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { + CPUParticles3D *particles = Object::cast_to<CPUParticles3D>(p_gizmo->get_node_3d()); + + p_gizmo->clear(); + + Vector<Vector3> lines; + AABB aabb = particles->get_visibility_aabb(); + + for (int i = 0; i < 12; i++) { + Vector3 a, b; + aabb.get_edge(i, a, b); + lines.push_back(a); + lines.push_back(b); + } + + Vector<Vector3> handles; + + for (int i = 0; i < 3; i++) { + Vector3 ax; + ax[i] = aabb.position[i] + aabb.size[i]; + ax[(i + 1) % 3] = aabb.position[(i + 1) % 3] + aabb.size[(i + 1) % 3] * 0.5; + ax[(i + 2) % 3] = aabb.position[(i + 2) % 3] + aabb.size[(i + 2) % 3] * 0.5; + handles.push_back(ax); + } + + Vector3 center = aabb.get_center(); + for (int i = 0; i < 3; i++) { + Vector3 ax; + ax[i] = 1.0; + handles.push_back(center + ax); + lines.push_back(center); + lines.push_back(center + ax); + } + + Ref<Material> material = get_material("particles_material", p_gizmo); + + p_gizmo->add_lines(lines, material); + + if (p_gizmo->is_selected()) { + Ref<Material> solid_material = get_material("particles_solid_material", p_gizmo); + p_gizmo->add_solid_box(solid_material, aabb.get_size(), aabb.get_center()); + } + Ref<Material> icon = get_material("particles_icon", p_gizmo); p_gizmo->add_unscaled_billboard(icon, 0.05); } diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index 92c1b1be1d..f0cb2aa3a5 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -1488,6 +1488,10 @@ Transform3D Node3DEditorViewport::_compute_transform(TransformMode p_mode, const } void Node3DEditorViewport::_surface_mouse_enter() { + if (Input::get_singleton()->get_mouse_mode() == Input::MOUSE_MODE_CAPTURED) { + return; + } + if (!surface->has_focus() && (!get_viewport()->gui_get_focus_owner() || !get_viewport()->gui_get_focus_owner()->is_text_field())) { surface->grab_focus(); } diff --git a/editor/project_settings_editor.cpp b/editor/project_settings_editor.cpp index e59bb76ff4..b7f28068b7 100644 --- a/editor/project_settings_editor.cpp +++ b/editor/project_settings_editor.cpp @@ -280,7 +280,6 @@ void ProjectSettingsEditor::_add_feature_overrides() { presets.insert("bptc"); presets.insert("s3tc"); - presets.insert("etc"); presets.insert("etc2"); presets.insert("editor"); presets.insert("template_debug"); diff --git a/modules/basis_universal/register_types.cpp b/modules/basis_universal/register_types.cpp index f538fc6676..c9ea67cb09 100644 --- a/modules/basis_universal/register_types.cpp +++ b/modules/basis_universal/register_types.cpp @@ -190,9 +190,9 @@ static Ref<Image> basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size } else if (RS::get_singleton()->has_os_feature("s3tc")) { format = basist::transcoder_texture_format::cTFBC1; // get this from renderer imgfmt = Image::FORMAT_DXT1; - } else if (RS::get_singleton()->has_os_feature("etc")) { + } else if (RS::get_singleton()->has_os_feature("etc2")) { format = basist::transcoder_texture_format::cTFETC1; // get this from renderer - imgfmt = Image::FORMAT_ETC; + imgfmt = Image::FORMAT_ETC2_RGB8; } else { format = basist::transcoder_texture_format::cTFBGR565; // get this from renderer imgfmt = Image::FORMAT_RGB565; diff --git a/modules/ktx/texture_loader_ktx.cpp b/modules/ktx/texture_loader_ktx.cpp index 155ed56bd0..026c0ce510 100644 --- a/modules/ktx/texture_loader_ktx.cpp +++ b/modules/ktx/texture_loader_ktx.cpp @@ -287,7 +287,7 @@ static Ref<Image> load_from_file_access(Ref<FileAccess> f, Error *r_error) { ktxfmt = KTX_TTF_BC7_RGBA; } else if (RS::get_singleton()->has_os_feature("s3tc")) { ktxfmt = KTX_TTF_BC1_RGB; - } else if (RS::get_singleton()->has_os_feature("etc")) { + } else if (RS::get_singleton()->has_os_feature("etc2")) { ktxfmt = KTX_TTF_ETC1_RGB; } else { ktxfmt = KTX_TTF_RGBA32; diff --git a/platform/android/doc_classes/EditorExportPlatformAndroid.xml b/platform/android/doc_classes/EditorExportPlatformAndroid.xml index a6f92158f9..976d64fd49 100644 --- a/platform/android/doc_classes/EditorExportPlatformAndroid.xml +++ b/platform/android/doc_classes/EditorExportPlatformAndroid.xml @@ -37,14 +37,22 @@ A list of additional command line arguments, exported project will receive when started. </member> <member name="custom_template/debug" type="String" setter="" getter=""> - Path to the custom export template. If left empty, default template is used. + Path to an APK file to use as a custom export template for debug exports. If left empty, default template is used. + [b]Note:[/b] This is only used if [member EditorExportPlatformAndroid.gradle_build/use_gradle_build] is disabled. </member> <member name="custom_template/release" type="String" setter="" getter=""> - Path to the custom export template. If left empty, default template is used. + Path to an APK file to use as a custom export template for release exports. If left empty, default template is used. + [b]Note:[/b] This is only used if [member EditorExportPlatformAndroid.gradle_build/use_gradle_build] is disabled. + </member> + <member name="gradle_build/android_source_template" type="String" setter="" getter=""> + Path to a ZIP file holding the source for the export template used in a Gradle build. If left empty, the default template is used. </member> <member name="gradle_build/export_format" type="int" setter="" getter=""> Export format for Gradle build. </member> + <member name="gradle_build/gradle_build_directory" type="String" setter="" getter=""> + Path to the Gradle build directory. If left empty, then [code]res://android[/code] will be used. + </member> <member name="gradle_build/min_sdk" type="String" setter="" getter=""> Minimal Android SDK version for Gradle build. </member> diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 459f5a5983..bd062401a4 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -46,6 +46,7 @@ #include "editor/editor_node.h" #include "editor/editor_paths.h" #include "editor/editor_settings.h" +#include "editor/export/export_template_manager.h" #include "editor/import/resource_importer_texture_settings.h" #include "editor/themes/editor_scale.h" #include "main/splash.gen.h" @@ -208,12 +209,14 @@ static const char *android_perms[] = { nullptr }; +static const char *MISMATCHED_VERSIONS_MESSAGE = "Android build version mismatch:\n| Template installed: %s\n| Requested version: %s\nPlease reinstall Android build template from 'Project' menu."; + static const char *SPLASH_IMAGE_EXPORT_PATH = "res/drawable-nodpi/splash.png"; static const char *LEGACY_BUILD_SPLASH_IMAGE_EXPORT_PATH = "res/drawable-nodpi-v4/splash.png"; static const char *SPLASH_BG_COLOR_PATH = "res/drawable-nodpi/splash_bg_color.png"; static const char *LEGACY_BUILD_SPLASH_BG_COLOR_PATH = "res/drawable-nodpi-v4/splash_bg_color.png"; -static const char *SPLASH_CONFIG_PATH = "res://android/build/res/drawable/splash_drawable.xml"; -static const char *GDEXTENSION_LIBS_PATH = "res://android/build/libs/gdextensionlibs.json"; +static const char *SPLASH_CONFIG_PATH = "res/drawable/splash_drawable.xml"; +static const char *GDEXTENSION_LIBS_PATH = "libs/gdextensionlibs.json"; static const int icon_densities_count = 6; static const char *launcher_icon_option = PNAME("launcher_icons/main_192x192"); @@ -250,8 +253,8 @@ static const LauncherIcon launcher_adaptive_icon_backgrounds[icon_densities_coun static const int EXPORT_FORMAT_APK = 0; static const int EXPORT_FORMAT_AAB = 1; -static const char *APK_ASSETS_DIRECTORY = "res://android/build/assets"; -static const char *AAB_ASSETS_DIRECTORY = "res://android/build/assetPacks/installTime/src/main/assets"; +static const char *APK_ASSETS_DIRECTORY = "assets"; +static const char *AAB_ASSETS_DIRECTORY = "assetPacks/installTime/src/main/assets"; static const int OPENGL_MIN_SDK_VERSION = 21; // Should match the value in 'platform/android/java/app/config.gradle#minSdk' static const int VULKAN_MIN_SDK_VERSION = 24; @@ -474,7 +477,8 @@ String EditorExportPlatformAndroid::get_valid_basename() const { } String EditorExportPlatformAndroid::get_assets_directory(const Ref<EditorExportPreset> &p_preset, int p_export_format) const { - return p_export_format == EXPORT_FORMAT_AAB ? AAB_ASSETS_DIRECTORY : APK_ASSETS_DIRECTORY; + String gradle_build_directory = ExportTemplateManager::get_android_build_directory(p_preset); + return gradle_build_directory.path_join(p_export_format == EXPORT_FORMAT_AAB ? AAB_ASSETS_DIRECTORY : APK_ASSETS_DIRECTORY); } bool EditorExportPlatformAndroid::is_package_name_valid(const String &p_package, String *r_error) const { @@ -774,11 +778,10 @@ Error EditorExportPlatformAndroid::copy_gradle_so(void *p_userdata, const Shared } if (abi_index != -1) { exported = true; - String base = "res://android/build/libs"; String type = export_data->debug ? "debug" : "release"; String abi = abis[abi_index].abi; String filename = p_so.path.get_file(); - String dst_path = base.path_join(type).path_join(abi).path_join(filename); + String dst_path = export_data->libs_directory.path_join(type).path_join(abi).path_join(filename); Vector<uint8_t> data = FileAccess::get_file_as_bytes(p_so.path); print_verbose("Copying .so file from " + p_so.path + " to " + dst_path); Error err = store_file_at_path(dst_path, data); @@ -867,7 +870,7 @@ void EditorExportPlatformAndroid::_write_tmp_manifest(const Ref<EditorExportPres manifest_text += _get_application_tag(Ref<EditorExportPlatform>(this), p_preset, _has_read_write_storage_permission(perms), p_debug); manifest_text += "</manifest>\n"; - String manifest_path = vformat("res://android/build/src/%s/AndroidManifest.xml", (p_debug ? "debug" : "release")); + String manifest_path = ExportTemplateManager::get_android_build_directory(p_preset).path_join(vformat("src/%s/AndroidManifest.xml", (p_debug ? "debug" : "release"))); print_verbose("Storing manifest into " + manifest_path + ": " + "\n" + manifest_text); store_string_at_path(manifest_path, manifest_text); @@ -1628,15 +1631,6 @@ void EditorExportPlatformAndroid::load_icon_refs(const Ref<EditorExportPreset> & } } -void EditorExportPlatformAndroid::store_image(const LauncherIcon launcher_icon, const Vector<uint8_t> &data) { - store_image(launcher_icon.export_path, data); -} - -void EditorExportPlatformAndroid::store_image(const String &export_path, const Vector<uint8_t> &data) { - String img_path = export_path.insert(0, "res://android/build/"); - store_file_at_path(img_path, data); -} - void EditorExportPlatformAndroid::_copy_icons_to_gradle_project(const Ref<EditorExportPreset> &p_preset, const String &processed_splash_config_xml, const Ref<Image> &splash_image, @@ -1644,26 +1638,30 @@ void EditorExportPlatformAndroid::_copy_icons_to_gradle_project(const Ref<Editor const Ref<Image> &main_image, const Ref<Image> &foreground, const Ref<Image> &background) { + String gradle_build_dir = ExportTemplateManager::get_android_build_directory(p_preset); + // Store the splash configuration if (!processed_splash_config_xml.is_empty()) { print_verbose("Storing processed splash configuration: " + String("\n") + processed_splash_config_xml); - store_string_at_path(SPLASH_CONFIG_PATH, processed_splash_config_xml); + store_string_at_path(gradle_build_dir.path_join(SPLASH_CONFIG_PATH), processed_splash_config_xml); } // Store the splash image if (splash_image.is_valid() && !splash_image->is_empty()) { - print_verbose("Storing splash image in " + String(SPLASH_IMAGE_EXPORT_PATH)); + String splash_export_path = gradle_build_dir.path_join(SPLASH_IMAGE_EXPORT_PATH); + print_verbose("Storing splash image in " + splash_export_path); Vector<uint8_t> data; _load_image_data(splash_image, data); - store_image(SPLASH_IMAGE_EXPORT_PATH, data); + store_file_at_path(splash_export_path, data); } // Store the splash bg color image if (splash_bg_color_image.is_valid() && !splash_bg_color_image->is_empty()) { - print_verbose("Storing splash background image in " + String(SPLASH_BG_COLOR_PATH)); + String splash_bg_color_path = gradle_build_dir.path_join(SPLASH_BG_COLOR_PATH); + print_verbose("Storing splash background image in " + splash_bg_color_path); Vector<uint8_t> data; _load_image_data(splash_bg_color_image, data); - store_image(SPLASH_BG_COLOR_PATH, data); + store_file_at_path(splash_bg_color_path, data); } // Prepare images to be resized for the icons. If some image ends up being uninitialized, @@ -1674,7 +1672,7 @@ void EditorExportPlatformAndroid::_copy_icons_to_gradle_project(const Ref<Editor print_verbose("Processing launcher icon for dimension " + itos(launcher_icons[i].dimensions) + " into " + launcher_icons[i].export_path); Vector<uint8_t> data; _process_launcher_icons(launcher_icons[i].export_path, main_image, launcher_icons[i].dimensions, data); - store_image(launcher_icons[i], data); + store_file_at_path(gradle_build_dir.path_join(launcher_icons[i].export_path), data); } if (foreground.is_valid() && !foreground->is_empty()) { @@ -1682,7 +1680,7 @@ void EditorExportPlatformAndroid::_copy_icons_to_gradle_project(const Ref<Editor Vector<uint8_t> data; _process_launcher_icons(launcher_adaptive_icon_foregrounds[i].export_path, foreground, launcher_adaptive_icon_foregrounds[i].dimensions, data); - store_image(launcher_adaptive_icon_foregrounds[i], data); + store_file_at_path(gradle_build_dir.path_join(launcher_adaptive_icon_foregrounds[i].export_path), data); } if (background.is_valid() && !background->is_empty()) { @@ -1690,7 +1688,7 @@ void EditorExportPlatformAndroid::_copy_icons_to_gradle_project(const Ref<Editor Vector<uint8_t> data; _process_launcher_icons(launcher_adaptive_icon_backgrounds[i].export_path, background, launcher_adaptive_icon_backgrounds[i].dimensions, data); - store_image(launcher_adaptive_icon_backgrounds[i], data); + store_file_at_path(gradle_build_dir.path_join(launcher_adaptive_icon_backgrounds[i].export_path), data); } } } @@ -1798,7 +1796,9 @@ void EditorExportPlatformAndroid::get_export_options(List<ExportOption> *r_optio r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.apk"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.apk"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "gradle_build/use_gradle_build"), false, false, true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "gradle_build/use_gradle_build"), false, true, true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "gradle_build/gradle_build_directory", PROPERTY_HINT_PLACEHOLDER_TEXT, "res://android"), "", false, false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "gradle_build/android_source_template", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "gradle_build/export_format", PROPERTY_HINT_ENUM, "Export APK,Export AAB"), EXPORT_FORMAT_APK, false, true)); // Using String instead of int to default to an empty string (no override) with placeholder for instructions (see GH-62465). // This implies doing validation that the string is a proper int. @@ -1876,6 +1876,18 @@ void EditorExportPlatformAndroid::get_export_options(List<ExportOption> *r_optio } } +bool EditorExportPlatformAndroid::get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const { + if (p_option == "gradle_build/gradle_build_directory" || p_option == "gradle_build/android_source_template") { + // @todo These are experimental options - keep them hidden for now. + //return (bool)p_preset->get("gradle_build/use_gradle_build"); + return false; + } else if (p_option == "custom_template/debug" || p_option == "custom_template/release") { + // The APK templates are ignored if Gradle build is enabled. + return !p_preset->get("gradle_build/use_gradle_build"); + } + return true; +} + String EditorExportPlatformAndroid::get_name() const { return "Android"; } @@ -2345,7 +2357,7 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito err += template_err; } } else { - bool installed_android_build_template = FileAccess::exists("res://android/build/build.gradle"); + bool installed_android_build_template = FileAccess::exists(ExportTemplateManager::get_android_build_directory(p_preset).path_join("build.gradle")); if (!installed_android_build_template) { r_missing_templates = !exists_export_template("android_source.zip", &err); err += TTR("Android build template not installed in the project. Install it from the Project menu.") + "\n"; @@ -2492,6 +2504,19 @@ bool EditorExportPlatformAndroid::has_valid_project_configuration(const Ref<Edit valid = false; } + if (p_preset->get("gradle_build/use_gradle_build")) { + String build_version_path = ExportTemplateManager::get_android_build_directory(p_preset).get_base_dir().path_join(".build_version"); + Ref<FileAccess> f = FileAccess::open(build_version_path, FileAccess::READ); + if (f.is_valid()) { + String current_version = ExportTemplateManager::get_android_template_identifier(p_preset); + String installed_version = f->get_line().strip_edges(); + if (current_version != installed_version) { + err += vformat(TTR(MISMATCHED_VERSIONS_MESSAGE), installed_version, current_version); + err += "\n"; + } + } + } + String min_sdk_str = p_preset->get("gradle_build/min_sdk"); int min_sdk_int = VULKAN_MIN_SDK_VERSION; if (!min_sdk_str.is_empty()) { // Empty means no override, nothing to do. @@ -2734,34 +2759,37 @@ Error EditorExportPlatformAndroid::sign_apk(const Ref<EditorExportPreset> &p_pre return OK; } -void EditorExportPlatformAndroid::_clear_assets_directory() { +void EditorExportPlatformAndroid::_clear_assets_directory(const Ref<EditorExportPreset> &p_preset) { Ref<DirAccess> da_res = DirAccess::create(DirAccess::ACCESS_RESOURCES); + String gradle_build_directory = ExportTemplateManager::get_android_build_directory(p_preset); // Clear the APK assets directory - if (da_res->dir_exists(APK_ASSETS_DIRECTORY)) { + String apk_assets_directory = gradle_build_directory.path_join(APK_ASSETS_DIRECTORY); + if (da_res->dir_exists(apk_assets_directory)) { print_verbose("Clearing APK assets directory..."); - Ref<DirAccess> da_assets = DirAccess::open(APK_ASSETS_DIRECTORY); + Ref<DirAccess> da_assets = DirAccess::open(apk_assets_directory); ERR_FAIL_COND(da_assets.is_null()); da_assets->erase_contents_recursive(); - da_res->remove(APK_ASSETS_DIRECTORY); + da_res->remove(apk_assets_directory); } // Clear the AAB assets directory - if (da_res->dir_exists(AAB_ASSETS_DIRECTORY)) { + String aab_assets_directory = gradle_build_directory.path_join(AAB_ASSETS_DIRECTORY); + if (da_res->dir_exists(aab_assets_directory)) { print_verbose("Clearing AAB assets directory..."); - Ref<DirAccess> da_assets = DirAccess::open(AAB_ASSETS_DIRECTORY); + Ref<DirAccess> da_assets = DirAccess::open(aab_assets_directory); ERR_FAIL_COND(da_assets.is_null()); da_assets->erase_contents_recursive(); - da_res->remove(AAB_ASSETS_DIRECTORY); + da_res->remove(aab_assets_directory); } } -void EditorExportPlatformAndroid::_remove_copied_libs() { +void EditorExportPlatformAndroid::_remove_copied_libs(String p_gdextension_libs_path) { print_verbose("Removing previously installed libraries..."); Error error; - String libs_json = FileAccess::get_file_as_string(GDEXTENSION_LIBS_PATH, &error); + String libs_json = FileAccess::get_file_as_string(p_gdextension_libs_path, &error); if (error || libs_json.is_empty()) { print_verbose("No previously installed libraries found"); return; @@ -2777,7 +2805,7 @@ void EditorExportPlatformAndroid::_remove_copied_libs() { print_verbose("Removing previously installed library " + libs[i]); da->remove(libs[i]); } - da->remove(GDEXTENSION_LIBS_PATH); + da->remove(p_gdextension_libs_path); } String EditorExportPlatformAndroid::join_list(const List<String> &p_parts, const String &p_separator) { @@ -2836,6 +2864,8 @@ String EditorExportPlatformAndroid::_resolve_export_plugin_android_library_path( bool EditorExportPlatformAndroid::_is_clean_build_required(const Ref<EditorExportPreset> &p_preset) { bool first_build = last_gradle_build_time == 0; bool have_plugins_changed = false; + String gradle_build_dir = ExportTemplateManager::get_android_build_directory(p_preset); + bool has_build_dir_changed = last_gradle_build_dir != gradle_build_dir; String plugin_names = _get_plugins_names(p_preset); @@ -2855,9 +2885,10 @@ bool EditorExportPlatformAndroid::_is_clean_build_required(const Ref<EditorExpor } last_gradle_build_time = OS::get_singleton()->get_unix_time(); + last_gradle_build_dir = gradle_build_dir; last_plugin_names = plugin_names; - return have_plugins_changed || first_build; + return have_plugins_changed || has_build_dir_changed || first_build; } Error EditorExportPlatformAndroid::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { @@ -2881,6 +2912,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP EditorProgress ep("export", TTR("Exporting for Android"), 105, true); bool use_gradle_build = bool(p_preset->get("gradle_build/use_gradle_build")); + String gradle_build_directory = use_gradle_build ? ExportTemplateManager::get_android_build_directory(p_preset) : ""; bool p_give_internet = p_flags & (DEBUG_FLAG_DUMB_CLIENT | DEBUG_FLAG_REMOTE_DEBUG); bool apk_expansion = p_preset->get("apk_expansion/enable"); Vector<ABI> enabled_abis = get_enabled_abis(p_preset); @@ -2940,15 +2972,17 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP //test that installed build version is alright { print_verbose("Checking build version..."); - Ref<FileAccess> f = FileAccess::open("res://android/.build_version", FileAccess::READ); + String gradle_base_directory = gradle_build_directory.get_base_dir(); + Ref<FileAccess> f = FileAccess::open(gradle_base_directory.path_join(".build_version"), FileAccess::READ); if (f.is_null()) { add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Trying to build from a gradle built template, but no version info for it exists. Please reinstall from the 'Project' menu.")); return ERR_UNCONFIGURED; } - String version = f->get_line().strip_edges(); - print_verbose("- build version: " + version); - if (version != VERSION_FULL_CONFIG) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Android build version mismatch: Template installed: %s, Godot version: %s. Please reinstall Android build template from 'Project' menu."), version, VERSION_FULL_CONFIG)); + String current_version = ExportTemplateManager::get_android_template_identifier(p_preset); + String installed_version = f->get_line().strip_edges(); + print_verbose("- build version: " + installed_version); + if (installed_version != current_version) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR(MISMATCHED_VERSIONS_MESSAGE), installed_version, current_version)); return ERR_UNCONFIGURED; } } @@ -2969,9 +3003,9 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP // TODO: should we use "package/name" or "application/config/name"? String project_name = get_project_name(p_preset->get("package/name")); - err = _create_project_name_strings_files(p_preset, project_name); //project name localization. + err = _create_project_name_strings_files(p_preset, project_name, gradle_build_directory); //project name localization. if (err != OK) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Unable to overwrite res://android/build/res/*.xml files with project name.")); + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Unable to overwrite res/*.xml files with project name.")); } // Copies the project icon files into the appropriate Gradle project directory. _copy_icons_to_gradle_project(p_preset, processed_splash_config_xml, splash_image, splash_bg_color_image, main_image, foreground, background); @@ -2979,12 +3013,14 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP _write_tmp_manifest(p_preset, p_give_internet, p_debug); //stores all the project files inside the Gradle project directory. Also includes all ABIs - _clear_assets_directory(); - _remove_copied_libs(); + _clear_assets_directory(p_preset); + String gdextension_libs_path = gradle_build_directory.path_join(GDEXTENSION_LIBS_PATH); + _remove_copied_libs(gdextension_libs_path); if (!apk_expansion) { print_verbose("Exporting project files..."); CustomExportData user_data; user_data.assets_directory = assets_directory; + user_data.libs_directory = gradle_build_directory.path_join("libs"); user_data.debug = p_debug; if (p_flags & DEBUG_FLAG_DUMB_CLIENT) { err = export_project_files(p_preset, p_debug, ignore_apk_file, &user_data, copy_gradle_so); @@ -3023,7 +3059,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP build_command = "gradlew"; #endif - String build_path = ProjectSettings::get_singleton()->get_resource_path().path_join("android/build"); + String build_path = ProjectSettings::get_singleton()->globalize_path(gradle_build_directory); build_command = build_path.path_join(build_command); String package_name = get_package_name(p_preset->get("package/unique_name")); diff --git a/platform/android/export/export_plugin.h b/platform/android/export/export_plugin.h index c282055fba..abc462a691 100644 --- a/platform/android/export/export_plugin.h +++ b/platform/android/export/export_plugin.h @@ -90,6 +90,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { #endif // DISABLE_DEPRECATED String last_plugin_names; uint64_t last_gradle_build_time = 0; + String last_gradle_build_dir; Vector<Device> devices; SafeFlag devices_changed; @@ -174,10 +175,6 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { void load_icon_refs(const Ref<EditorExportPreset> &p_preset, Ref<Image> &icon, Ref<Image> &foreground, Ref<Image> &background); - void store_image(const LauncherIcon launcher_icon, const Vector<uint8_t> &data); - - void store_image(const String &export_path, const Vector<uint8_t> &data); - void _copy_icons_to_gradle_project(const Ref<EditorExportPreset> &p_preset, const String &processed_splash_config_xml, const Ref<Image> &splash_image, @@ -198,6 +195,8 @@ public: virtual void get_export_options(List<ExportOption> *r_options) const override; + virtual bool get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const override; + virtual String get_export_option_warning(const EditorExportPreset *p_preset, const StringName &p_name) const override; virtual String get_name() const override; @@ -248,9 +247,9 @@ public: Error sign_apk(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &export_path, EditorProgress &ep); - void _clear_assets_directory(); + void _clear_assets_directory(const Ref<EditorExportPreset> &p_preset); - void _remove_copied_libs(); + void _remove_copied_libs(String p_gdextension_libs_path); static String join_list(const List<String> &p_parts, const String &p_separator); static String join_abis(const Vector<ABI> &p_parts, const String &p_separator, bool p_use_arch); diff --git a/platform/android/export/gradle_export_util.cpp b/platform/android/export/gradle_export_util.cpp index 0915009235..9eddef6a4c 100644 --- a/platform/android/export/gradle_export_util.cpp +++ b/platform/android/export/gradle_export_util.cpp @@ -191,14 +191,14 @@ String _android_xml_escape(const String &p_string) { } // Creates strings.xml files inside the gradle project for different locales. -Error _create_project_name_strings_files(const Ref<EditorExportPreset> &p_preset, const String &project_name) { +Error _create_project_name_strings_files(const Ref<EditorExportPreset> &p_preset, const String &project_name, const String &p_gradle_build_dir) { print_verbose("Creating strings resources for supported locales for project " + project_name); // Stores the string into the default values directory. String processed_default_xml_string = vformat(godot_project_name_xml_string, _android_xml_escape(project_name)); - store_string_at_path("res://android/build/res/values/godot_project_name_string.xml", processed_default_xml_string); + store_string_at_path(p_gradle_build_dir.path_join("res/values/godot_project_name_string.xml"), processed_default_xml_string); // Searches the Gradle project res/ directory to find all supported locales - Ref<DirAccess> da = DirAccess::open("res://android/build/res"); + Ref<DirAccess> da = DirAccess::open(p_gradle_build_dir.path_join("res")); if (da.is_null()) { if (OS::get_singleton()->is_stdout_verbose()) { print_error("Unable to open Android resources directory."); @@ -217,7 +217,7 @@ Error _create_project_name_strings_files(const Ref<EditorExportPreset> &p_preset continue; } String locale = file.replace("values-", "").replace("-r", "_"); - String locale_directory = "res://android/build/res/" + file + "/godot_project_name_string.xml"; + String locale_directory = p_gradle_build_dir.path_join("res/" + file + "/godot_project_name_string.xml"); if (appnames.has(locale)) { String locale_project_name = appnames[locale]; String processed_xml_string = vformat(godot_project_name_xml_string, _android_xml_escape(locale_project_name)); diff --git a/platform/android/export/gradle_export_util.h b/platform/android/export/gradle_export_util.h index 2498394add..9f8e476f73 100644 --- a/platform/android/export/gradle_export_util.h +++ b/platform/android/export/gradle_export_util.h @@ -63,6 +63,7 @@ static const int XR_MODE_OPENXR = 1; struct CustomExportData { String assets_directory; + String libs_directory; bool debug; Vector<String> libs; }; @@ -94,7 +95,7 @@ Error store_string_at_path(const String &p_path, const String &p_data); Error rename_and_store_file_in_gradle_project(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key); // Creates strings.xml files inside the gradle project for different locales. -Error _create_project_name_strings_files(const Ref<EditorExportPreset> &p_preset, const String &project_name); +Error _create_project_name_strings_files(const Ref<EditorExportPreset> &p_preset, const String &project_name, const String &p_gradle_build_dir); String bool_to_string(bool v); diff --git a/platform/linuxbsd/doc_classes/EditorExportPlatformLinuxBSD.xml b/platform/linuxbsd/doc_classes/EditorExportPlatformLinuxBSD.xml index 07378566c3..a44c86202e 100644 --- a/platform/linuxbsd/doc_classes/EditorExportPlatformLinuxBSD.xml +++ b/platform/linuxbsd/doc_classes/EditorExportPlatformLinuxBSD.xml @@ -57,17 +57,11 @@ - [code]{exe_name}[/code] - Name of application executable. - [code]{cmd_args}[/code] - Array of the command line argument for the application. </member> - <member name="texture_format/bptc" type="bool" setter="" getter=""> - If [code]true[/code], project textures are exported in the BPTC format. + <member name="texture_format/etc2_astc" type="bool" setter="" getter=""> + If [code]true[/code], project textures are exported in the ETC2/ASTC format. </member> - <member name="texture_format/etc" type="bool" setter="" getter=""> - If [code]true[/code], project textures are exported in the ETC format. - </member> - <member name="texture_format/etc2" type="bool" setter="" getter=""> - If [code]true[/code], project textures are exported in the ETC2 format. - </member> - <member name="texture_format/s3tc" type="bool" setter="" getter=""> - If [code]true[/code], project textures are exported in the S3TC format. + <member name="texture_format/s3tc_bptc" type="bool" setter="" getter=""> + If [code]true[/code], project textures are exported in the S3TC/BPTC format. </member> </members> </class> diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp index 4e87dd42dd..85846335f7 100644 --- a/platform/linuxbsd/os_linuxbsd.cpp +++ b/platform/linuxbsd/os_linuxbsd.cpp @@ -127,14 +127,6 @@ void OS_LinuxBSD::alert(const String &p_alert, const String &p_title) { } } -int OS_LinuxBSD::get_low_processor_usage_mode_sleep_usec() const { - if (DisplayServer::get_singleton() == nullptr || DisplayServer::get_singleton()->get_name() != "Wayland" || is_in_low_processor_usage_mode()) { - return OS::get_low_processor_usage_mode_sleep_usec(); - } - - return 500; // Roughly 2000 FPS, improves frame time when emulating VSync. -} - void OS_LinuxBSD::initialize() { crash_handler.initialize(); diff --git a/platform/linuxbsd/os_linuxbsd.h b/platform/linuxbsd/os_linuxbsd.h index d4ffc0e9b4..9084061eb9 100644 --- a/platform/linuxbsd/os_linuxbsd.h +++ b/platform/linuxbsd/os_linuxbsd.h @@ -127,8 +127,6 @@ public: virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override; - virtual int get_low_processor_usage_mode_sleep_usec() const override; - virtual bool _check_internal_feature_support(const String &p_feature) override; void run(); diff --git a/platform/linuxbsd/wayland/display_server_wayland.cpp b/platform/linuxbsd/wayland/display_server_wayland.cpp index 85bbfe546a..c957dea32d 100644 --- a/platform/linuxbsd/wayland/display_server_wayland.cpp +++ b/platform/linuxbsd/wayland/display_server_wayland.cpp @@ -867,11 +867,11 @@ bool DisplayServerWayland::window_is_focused(WindowID p_window_id) const { } bool DisplayServerWayland::window_can_draw(DisplayServer::WindowID p_window_id) const { - return frame; + return !suspended; } bool DisplayServerWayland::can_any_window_draw() const { - return frame; + return !suspended; } void DisplayServerWayland::window_set_ime_active(const bool p_active, DisplayServer::WindowID p_window_id) { @@ -1143,7 +1143,32 @@ void DisplayServerWayland::process_events() { wayland_thread.keyboard_echo_keys(); - frame = wayland_thread.get_reset_frame(); + if (!suspended) { + if (emulate_vsync) { + // Due to various reasons, we manually handle display synchronization by + // waiting for a frame event (request to draw) or, if available, the actual + // window's suspend status. When a window is suspended, we can avoid drawing + // altogether, either because the compositor told us that we don't need to or + // because the pace of the frame events became unreliable. + bool frame = wayland_thread.wait_frame_suspend_ms(1000); + if (!frame) { + suspended = true; + } + } else { + if (wayland_thread.is_suspended()) { + suspended = true; + } + } + + if (suspended) { + DEBUG_LOG_WAYLAND("Window suspended."); + } + } else { + if (wayland_thread.get_reset_frame()) { + // At last, a sign of life! We're no longer suspended. + suspended = false; + } + } wayland_thread.mutex.unlock(); diff --git a/platform/linuxbsd/wayland/display_server_wayland.h b/platform/linuxbsd/wayland/display_server_wayland.h index d4da80a55f..e42967eb7b 100644 --- a/platform/linuxbsd/wayland/display_server_wayland.h +++ b/platform/linuxbsd/wayland/display_server_wayland.h @@ -117,7 +117,7 @@ class DisplayServerWayland : public DisplayServer { Context context; - bool frame = false; + bool suspended = false; bool emulate_vsync = false; String rendering_driver; diff --git a/platform/linuxbsd/wayland/wayland_thread.cpp b/platform/linuxbsd/wayland/wayland_thread.cpp index ae1d96a3b1..ebb21722e2 100644 --- a/platform/linuxbsd/wayland/wayland_thread.cpp +++ b/platform/linuxbsd/wayland/wayland_thread.cpp @@ -469,7 +469,7 @@ void WaylandThread::_wl_registry_on_global(void *data, struct wl_registry *wl_re } if (strcmp(interface, xdg_wm_base_interface.name) == 0) { - registry->xdg_wm_base = (struct xdg_wm_base *)wl_registry_bind(wl_registry, name, &xdg_wm_base_interface, MAX(2, MIN(5, (int)version))); + registry->xdg_wm_base = (struct xdg_wm_base *)wl_registry_bind(wl_registry, name, &xdg_wm_base_interface, MAX(2, MIN(6, (int)version))); registry->xdg_wm_base_name = name; xdg_wm_base_add_listener(registry->xdg_wm_base, &xdg_wm_base_listener, nullptr); @@ -1063,9 +1063,10 @@ void WaylandThread::_xdg_toplevel_on_configure(void *data, struct xdg_toplevel * WindowState *ws = (WindowState *)data; ERR_FAIL_NULL(ws); - // Expect the window to be in windowed mode. The mode will get overridden if - // the compositor reports otherwise. + // Expect the window to be in a plain state. It will get properly set if the + // compositor reports otherwise below. ws->mode = DisplayServer::WINDOW_MODE_WINDOWED; + ws->suspended = false; uint32_t *state = nullptr; wl_array_for_each(state, states) { @@ -1078,6 +1079,10 @@ void WaylandThread::_xdg_toplevel_on_configure(void *data, struct xdg_toplevel * ws->mode = DisplayServer::WINDOW_MODE_FULLSCREEN; } break; + case XDG_TOPLEVEL_STATE_SUSPENDED: { + ws->suspended = true; + } break; + default: { // We don't care about the other states (for now). } break; @@ -1176,9 +1181,10 @@ void WaylandThread::libdecor_frame_on_configure(struct libdecor_frame *frame, st libdecor_window_state window_state = LIBDECOR_WINDOW_STATE_NONE; - // Expect the window to be in windowed mode. The mode will get overridden if - // the compositor reports otherwise. + // Expect the window to be in a plain state. It will get properly set if the + // compositor reports otherwise below. ws->mode = DisplayServer::WINDOW_MODE_WINDOWED; + ws->suspended = false; if (libdecor_configuration_get_window_state(configuration, &window_state)) { if (window_state & LIBDECOR_WINDOW_STATE_MAXIMIZED) { @@ -1188,6 +1194,10 @@ void WaylandThread::libdecor_frame_on_configure(struct libdecor_frame *frame, st if (window_state & LIBDECOR_WINDOW_STATE_FULLSCREEN) { ws->mode = DisplayServer::WINDOW_MODE_FULLSCREEN; } + + if (window_state & LIBDECOR_WINDOW_STATE_SUSPENDED) { + ws->suspended = true; + } } window_state_update_size(ws, width, height); @@ -3872,6 +3882,102 @@ bool WaylandThread::get_reset_frame() { return old_frame; } +// Dispatches events until a frame event is received, a window is reported as +// suspended or the timeout expires. +bool WaylandThread::wait_frame_suspend_ms(int p_timeout) { + if (main_window.suspended) { + // The window is suspended! The compositor is telling us _explicitly_ that we + // don't need to draw, without letting us guess through the frame event's + // timing and stuff like that. Our job here is done. + return false; + } + + if (frame) { + // We already have a frame! Probably it got there while the caller locked :D + frame = false; + return true; + } + + struct pollfd poll_fd; + poll_fd.fd = wl_display_get_fd(wl_display); + poll_fd.events = POLLIN | POLLHUP; + + int begin_ms = OS::get_singleton()->get_ticks_msec(); + int remaining_ms = p_timeout; + + while (remaining_ms > 0) { + // Empty the event queue while it's full. + while (wl_display_prepare_read(wl_display) != 0) { + if (wl_display_dispatch_pending(wl_display) == -1) { + // Oh no. We'll check and handle any display error below. + break; + } + + if (main_window.suspended) { + return false; + } + + if (frame) { + // We had a frame event in the queue :D + frame = false; + return true; + } + } + + int werror = wl_display_get_error(wl_display); + + if (werror) { + if (werror == EPROTO) { + struct wl_interface *wl_interface = nullptr; + uint32_t id = 0; + + int error_code = wl_display_get_protocol_error(wl_display, (const struct wl_interface **)&wl_interface, &id); + CRASH_NOW_MSG(vformat("Wayland protocol error %d on interface %s@%d.", error_code, wl_interface ? wl_interface->name : "unknown", id)); + } else { + CRASH_NOW_MSG(vformat("Wayland client error code %d.", werror)); + } + } + + wl_display_flush(wl_display); + + // Wait for the event file descriptor to have new data. + poll(&poll_fd, 1, remaining_ms); + + if (poll_fd.revents | POLLIN) { + // Load the queues with fresh new data. + wl_display_read_events(wl_display); + } else { + // Oh well... Stop signaling that we want to read. + wl_display_cancel_read(wl_display); + + // We've got no new events :( + // We won't even bother with checking the frame flag. + return false; + } + + // Let's try dispatching now... + wl_display_dispatch_pending(wl_display); + + if (main_window.suspended) { + return false; + } + + if (frame) { + frame = false; + return true; + } + + remaining_ms -= OS::get_singleton()->get_ticks_msec() - begin_ms; + } + + DEBUG_LOG_WAYLAND_THREAD("Frame timeout."); + return false; +} + +bool WaylandThread::is_suspended() const { + return main_window.suspended; +} + void WaylandThread::destroy() { if (!initialized) { return; diff --git a/platform/linuxbsd/wayland/wayland_thread.h b/platform/linuxbsd/wayland/wayland_thread.h index 86033c1a09..f3e3c3a2ac 100644 --- a/platform/linuxbsd/wayland/wayland_thread.h +++ b/platform/linuxbsd/wayland/wayland_thread.h @@ -177,6 +177,7 @@ public: Rect2i rect; DisplayServer::WindowMode mode = DisplayServer::WINDOW_MODE_WINDOWED; + bool suspended = false; // These are true by default as it isn't guaranteed that we'll find an // xdg-shell implementation with wm_capabilities available. If and once we @@ -939,6 +940,9 @@ public: void set_frame(); bool get_reset_frame(); + bool wait_frame_suspend_ms(int p_timeout); + + bool is_suspended() const; Error init(); void destroy(); diff --git a/platform/windows/doc_classes/EditorExportPlatformWindows.xml b/platform/windows/doc_classes/EditorExportPlatformWindows.xml index c483d3380b..1239a2b32f 100644 --- a/platform/windows/doc_classes/EditorExportPlatformWindows.xml +++ b/platform/windows/doc_classes/EditorExportPlatformWindows.xml @@ -129,17 +129,11 @@ - [code]{exe_name}[/code] - Name of application executable. - [code]{cmd_args}[/code] - Array of the command line argument for the application. </member> - <member name="texture_format/bptc" type="bool" setter="" getter=""> - If [code]true[/code], project textures are exported in the BPTC format. + <member name="texture_format/etc2_astc" type="bool" setter="" getter=""> + If [code]true[/code], project textures are exported in the ETC2/ASTC format. </member> - <member name="texture_format/etc" type="bool" setter="" getter=""> - If [code]true[/code], project textures are exported in the ETC format. - </member> - <member name="texture_format/etc2" type="bool" setter="" getter=""> - If [code]true[/code], project textures are exported in the ETC2 format. - </member> - <member name="texture_format/s3tc" type="bool" setter="" getter=""> - If [code]true[/code], project textures are exported in the S3TC format. + <member name="texture_format/s3tc_bptc" type="bool" setter="" getter=""> + If [code]true[/code], project textures are exported in the S3TC/BPTC format. </member> </members> </class> diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index 9fc0477432..b8ca480e3b 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -213,7 +213,7 @@ void TileMap::add_layer(int p_to_pos) { // Must clear before adding the layer. TileMapLayer *new_layer = memnew(TileMapLayer); layers.insert(p_to_pos, new_layer); - add_child(new_layer); + add_child(new_layer, false, INTERNAL_MODE_FRONT); new_layer->force_parent_owned(); new_layer->set_name(vformat("Layer%d", p_to_pos)); move_child(new_layer, p_to_pos); @@ -538,7 +538,7 @@ bool TileMap::_set(const StringName &p_name, const Variant &p_value) { if (p_value.is_array()) { if (layers.size() == 0) { TileMapLayer *new_layer = memnew(TileMapLayer); - add_child(new_layer); + add_child(new_layer, false, INTERNAL_MODE_FRONT); new_layer->force_parent_owned(); new_layer->set_name("Layer0"); new_layer->set_layer_index_in_tile_map_node(0); @@ -564,7 +564,7 @@ bool TileMap::_set(const StringName &p_name, const Variant &p_value) { if (index >= (int)layers.size()) { while (index >= (int)layers.size()) { TileMapLayer *new_layer = memnew(TileMapLayer); - add_child(new_layer); + add_child(new_layer, false, INTERNAL_MODE_FRONT); new_layer->force_parent_owned(); new_layer->set_name(vformat("Layer%d", index)); new_layer->set_layer_index_in_tile_map_node(index); @@ -1002,7 +1002,7 @@ void TileMap::_bind_methods() { TileMap::TileMap() { TileMapLayer *new_layer = memnew(TileMapLayer); - add_child(new_layer); + add_child(new_layer, false, INTERNAL_MODE_FRONT); new_layer->set_name("Layer0"); new_layer->set_layer_index_in_tile_map_node(0); new_layer->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &TileMap::_emit_changed)); diff --git a/scene/3d/cpu_particles_3d.cpp b/scene/3d/cpu_particles_3d.cpp index fa8599a0a2..32d68f7f78 100644 --- a/scene/3d/cpu_particles_3d.cpp +++ b/scene/3d/cpu_particles_3d.cpp @@ -101,6 +101,12 @@ void CPUParticles3D::set_randomness_ratio(real_t p_ratio) { randomness_ratio = p_ratio; } +void CPUParticles3D::set_visibility_aabb(const AABB &p_aabb) { + RS::get_singleton()->multimesh_set_custom_aabb(multimesh, p_aabb); + visibility_aabb = p_aabb; + update_gizmos(); +} + void CPUParticles3D::set_lifetime_randomness(double p_random) { lifetime_randomness = p_random; } @@ -141,6 +147,10 @@ real_t CPUParticles3D::get_randomness_ratio() const { return randomness_ratio; } +AABB CPUParticles3D::get_visibility_aabb() const { + return visibility_aabb; +} + double CPUParticles3D::get_lifetime_randomness() const { return lifetime_randomness; } @@ -520,6 +530,11 @@ bool CPUParticles3D::get_split_scale() { return split_scale; } +AABB CPUParticles3D::capture_aabb() const { + RS::get_singleton()->multimesh_set_custom_aabb(multimesh, AABB()); + return RS::get_singleton()->multimesh_get_aabb(multimesh); +} + void CPUParticles3D::_validate_property(PropertyInfo &p_property) const { if (p_property.name == "emission_sphere_radius" && (emission_shape != EMISSION_SHAPE_SPHERE && emission_shape != EMISSION_SHAPE_SPHERE_SURFACE)) { p_property.usage = PROPERTY_USAGE_NONE; @@ -1341,6 +1356,7 @@ void CPUParticles3D::convert_from_particles(Node *p_particles) { set_pre_process_time(gpu_particles->get_pre_process_time()); set_explosiveness_ratio(gpu_particles->get_explosiveness_ratio()); set_randomness_ratio(gpu_particles->get_randomness_ratio()); + set_visibility_aabb(gpu_particles->get_visibility_aabb()); set_use_local_coordinates(gpu_particles->get_use_local_coordinates()); set_fixed_fps(gpu_particles->get_fixed_fps()); set_fractional_delta(gpu_particles->get_fractional_delta()); @@ -1420,6 +1436,7 @@ void CPUParticles3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_pre_process_time", "secs"), &CPUParticles3D::set_pre_process_time); ClassDB::bind_method(D_METHOD("set_explosiveness_ratio", "ratio"), &CPUParticles3D::set_explosiveness_ratio); ClassDB::bind_method(D_METHOD("set_randomness_ratio", "ratio"), &CPUParticles3D::set_randomness_ratio); + ClassDB::bind_method(D_METHOD("set_visibility_aabb", "aabb"), &CPUParticles3D::set_visibility_aabb); ClassDB::bind_method(D_METHOD("set_lifetime_randomness", "random"), &CPUParticles3D::set_lifetime_randomness); ClassDB::bind_method(D_METHOD("set_use_local_coordinates", "enable"), &CPUParticles3D::set_use_local_coordinates); ClassDB::bind_method(D_METHOD("set_fixed_fps", "fps"), &CPUParticles3D::set_fixed_fps); @@ -1433,6 +1450,7 @@ void CPUParticles3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_pre_process_time"), &CPUParticles3D::get_pre_process_time); ClassDB::bind_method(D_METHOD("get_explosiveness_ratio"), &CPUParticles3D::get_explosiveness_ratio); ClassDB::bind_method(D_METHOD("get_randomness_ratio"), &CPUParticles3D::get_randomness_ratio); + ClassDB::bind_method(D_METHOD("get_visibility_aabb"), &CPUParticles3D::get_visibility_aabb); ClassDB::bind_method(D_METHOD("get_lifetime_randomness"), &CPUParticles3D::get_lifetime_randomness); ClassDB::bind_method(D_METHOD("get_use_local_coordinates"), &CPUParticles3D::get_use_local_coordinates); ClassDB::bind_method(D_METHOD("get_fixed_fps"), &CPUParticles3D::get_fixed_fps); @@ -1461,6 +1479,7 @@ void CPUParticles3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "fixed_fps", PROPERTY_HINT_RANGE, "0,1000,1,suffix:FPS"), "set_fixed_fps", "get_fixed_fps"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fract_delta"), "set_fractional_delta", "get_fractional_delta"); ADD_GROUP("Drawing", ""); + ADD_PROPERTY(PropertyInfo(Variant::AABB, "visibility_aabb", PROPERTY_HINT_NONE, "suffix:m"), "set_visibility_aabb", "get_visibility_aabb"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "local_coords"), "set_use_local_coordinates", "get_use_local_coordinates"); ADD_PROPERTY(PropertyInfo(Variant::INT, "draw_order", PROPERTY_HINT_ENUM, "Index,Lifetime,View Depth"), "set_draw_order", "get_draw_order"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh"), "set_mesh", "get_mesh"); @@ -1665,6 +1684,7 @@ CPUParticles3D::CPUParticles3D() { set_emitting(true); set_amount(8); + set_visibility_aabb(AABB(Vector3(-4, -4, -4), Vector3(8, 8, 8))); set_param_min(PARAM_INITIAL_LINEAR_VELOCITY, 0); set_param_min(PARAM_ANGULAR_VELOCITY, 0); diff --git a/scene/3d/cpu_particles_3d.h b/scene/3d/cpu_particles_3d.h index e05bd65fba..e9b75d9140 100644 --- a/scene/3d/cpu_particles_3d.h +++ b/scene/3d/cpu_particles_3d.h @@ -138,6 +138,7 @@ private: real_t randomness_ratio = 0.0; double lifetime_randomness = 0.0; double speed_scale = 1.0; + AABB visibility_aabb; bool local_coords = false; int fixed_fps = 0; bool fractional_delta = true; @@ -210,6 +211,7 @@ public: void set_pre_process_time(double p_time); void set_explosiveness_ratio(real_t p_ratio); void set_randomness_ratio(real_t p_ratio); + void set_visibility_aabb(const AABB &p_aabb); void set_lifetime_randomness(double p_random); void set_use_local_coordinates(bool p_enable); void set_speed_scale(double p_scale); @@ -221,6 +223,7 @@ public: double get_pre_process_time() const; real_t get_explosiveness_ratio() const; real_t get_randomness_ratio() const; + AABB get_visibility_aabb() const; double get_lifetime_randomness() const; bool get_use_local_coordinates() const; double get_speed_scale() const; @@ -308,6 +311,8 @@ public: void convert_from_particles(Node *p_particles); + AABB capture_aabb() const; + CPUParticles3D(); ~CPUParticles3D(); }; diff --git a/scene/animation/animation_mixer.cpp b/scene/animation/animation_mixer.cpp index 9f954fd6c0..2f99bdda40 100644 --- a/scene/animation/animation_mixer.cpp +++ b/scene/animation/animation_mixer.cpp @@ -32,6 +32,7 @@ #include "animation_mixer.compat.inc" #include "core/config/engine.h" +#include "core/config/project_settings.h" #include "scene/3d/mesh_instance_3d.h" #include "scene/3d/node_3d.h" #include "scene/3d/skeleton_3d.h" @@ -597,6 +598,9 @@ bool AnimationMixer::_update_caches() { List<StringName> sname; get_animation_list(&sname); + bool check_path = GLOBAL_GET("animation/warnings/check_invalid_track_paths"); + bool check_angle_interpolation = GLOBAL_GET("animation/warnings/check_angle_interpolation_type_conflicting"); + Node *parent = get_node_or_null(root_node); if (!parent) { cache_valid = false; @@ -645,10 +649,19 @@ bool AnimationMixer::_update_caches() { if (!track) { Ref<Resource> resource; Vector<StringName> leftover_path; - Node *child = parent->get_node_and_resource(path, resource, leftover_path); + if (!parent->has_node_and_resource(path)) { + if (check_path) { + WARN_PRINT_ED(mixer_name + ": '" + String(E) + "', couldn't resolve track: '" + String(path) + "'. This warning can be disabled in Project Settings."); + } + continue; + } + + Node *child = parent->get_node_and_resource(path, resource, leftover_path); if (!child) { - ERR_PRINT("AnimationMixer: '" + String(E) + "', couldn't resolve track: '" + String(path) + "'."); + if (check_path) { + WARN_PRINT_ED(mixer_name + ": '" + String(E) + "', couldn't resolve track: '" + String(path) + "'. This warning can be disabled in Project Settings."); + } continue; } @@ -657,7 +670,7 @@ bool AnimationMixer::_update_caches() { case Animation::TYPE_VALUE: { // If a value track without a key is cached first, the initial value cannot be determined. // It is a corner case, but which may cause problems with blending. - ERR_CONTINUE_MSG(anim->track_get_key_count(i) == 0, "AnimationMixer: '" + String(E) + "', Value Track: '" + String(path) + "' must have at least one key to cache for blending."); + ERR_CONTINUE_MSG(anim->track_get_key_count(i) == 0, mixer_name + ": '" + String(E) + "', Value Track: '" + String(path) + "' must have at least one key to cache for blending."); TrackCacheValue *track_value = memnew(TrackCacheValue); @@ -698,7 +711,7 @@ bool AnimationMixer::_update_caches() { Node3D *node_3d = Object::cast_to<Node3D>(child); if (!node_3d) { - ERR_PRINT("AnimationMixer: '" + String(E) + "', transform track does not point to Node3D: '" + String(path) + "'."); + ERR_PRINT(mixer_name + ": '" + String(E) + "', transform track does not point to Node3D: '" + String(path) + "'."); continue; } @@ -764,20 +777,20 @@ bool AnimationMixer::_update_caches() { case Animation::TYPE_BLEND_SHAPE: { #ifndef _3D_DISABLED if (path.get_subname_count() != 1) { - ERR_PRINT("AnimationMixer: '" + String(E) + "', blend shape track does not contain a blend shape subname: '" + String(path) + "'."); + ERR_PRINT(mixer_name + ": '" + String(E) + "', blend shape track does not contain a blend shape subname: '" + String(path) + "'."); continue; } MeshInstance3D *mesh_3d = Object::cast_to<MeshInstance3D>(child); if (!mesh_3d) { - ERR_PRINT("AnimationMixer: '" + String(E) + "', blend shape track does not point to MeshInstance3D: '" + String(path) + "'."); + ERR_PRINT(mixer_name + ": '" + String(E) + "', blend shape track does not point to MeshInstance3D: '" + String(path) + "'."); continue; } StringName blend_shape_name = path.get_subname(0); int blend_shape_idx = mesh_3d->find_blend_shape_by_name(blend_shape_name); if (blend_shape_idx == -1) { - ERR_PRINT("AnimationMixer: '" + String(E) + "', blend shape track points to a non-existing name: '" + String(blend_shape_name) + "'."); + ERR_PRINT(mixer_name + ": '" + String(E) + "', blend shape track points to a non-existing name: '" + String(blend_shape_name) + "'."); continue; } @@ -855,7 +868,6 @@ bool AnimationMixer::_update_caches() { } else if (track_cache_type == Animation::TYPE_VALUE) { // If it has at least one angle interpolation, it also uses angle interpolation for blending. TrackCacheValue *track_value = static_cast<TrackCacheValue *>(track); - bool was_continuous = track_value->is_continuous; bool was_using_angle = track_value->is_using_angle; if (track_src_type == Animation::TYPE_VALUE) { @@ -868,23 +880,17 @@ bool AnimationMixer::_update_caches() { // TODO: Currently, misc type cannot be blended. // In the future, it should have a separate blend weight, just as bool is converted to 0 and 1. // Then, it should provide the correct precedence value. - bool skip_update_mode_warning = false; if (track_value->is_continuous) { if (!Animation::is_variant_interpolatable(track_value->init_value)) { WARN_PRINT_ONCE_ED(mixer_name + ": '" + String(E) + "', Value Track: '" + String(path) + "' uses a non-numeric type as key value with UpdateMode.UPDATE_CONTINUOUS. This will not be blended correctly, so it is forced to UpdateMode.UPDATE_DISCRETE."); track_value->is_continuous = false; - skip_update_mode_warning = true; } if (track_value->init_value.is_string()) { WARN_PRINT_ONCE_ED(mixer_name + ": '" + String(E) + "', Value Track: '" + String(path) + "' blends String types. This is an experimental algorithm."); } } - - if (!skip_update_mode_warning && was_continuous != track_value->is_continuous) { - WARN_PRINT_ONCE_ED(mixer_name + ": '" + String(E) + "', Value Track: '" + String(path) + "' has different update modes between some animations which may be blended together. Blending prioritizes UpdateMode.UPDATE_CONTINUOUS, so the process treats UpdateMode.UPDATE_DISCRETE as UpdateMode.UPDATE_CONTINUOUS with InterpolationType.INTERPOLATION_NEAREST."); - } - if (was_using_angle != track_value->is_using_angle) { - WARN_PRINT_ONCE_ED(mixer_name + ": '" + String(E) + "', Value Track: '" + String(path) + "' has different interpolation types for rotation between some animations which may be blended together. Blending prioritizes angle interpolation, so the blending result uses the shortest path referenced to the initial (RESET animation) value."); + if (check_angle_interpolation && (was_using_angle != track_value->is_using_angle)) { + WARN_PRINT_ED(mixer_name + ": '" + String(E) + "', Value Track: '" + String(path) + "' has different interpolation types for rotation between some animations which may be blended together. Blending prioritizes angle interpolation, so the blending result uses the shortest path referenced to the initial (RESET animation) value. If you do not want further warnings, you can turn off the checking for the angle interpolation type conflicting in Project Settings."); } } diff --git a/scene/animation/tween.cpp b/scene/animation/tween.cpp index 34fc83500f..2e5fd6180c 100644 --- a/scene/animation/tween.cpp +++ b/scene/animation/tween.cpp @@ -536,6 +536,11 @@ Ref<PropertyTweener> PropertyTweener::set_ease(Tween::EaseType p_ease) { return this; } +Ref<PropertyTweener> PropertyTweener::set_custom_interpolator(const Callable &p_method) { + custom_method = p_method; + return this; +} + Ref<PropertyTweener> PropertyTweener::set_delay(double p_delay) { delay = p_delay; return this; @@ -589,7 +594,23 @@ bool PropertyTweener::step(double &r_delta) { double time = MIN(elapsed_time - delay, duration); if (time < duration) { - target_instance->set_indexed(property, tween->interpolate_variant(initial_val, delta_val, time, duration, trans_type, ease_type)); + if (custom_method.is_valid()) { + const Variant t = tween->interpolate_variant(0.0, 1.0, time, duration, trans_type, ease_type); + const Variant *argptr = &t; + + Variant result; + Callable::CallError ce; + custom_method.callp(&argptr, 1, result, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_FAIL_V_MSG(false, "Error calling custom method from PropertyTweener: " + Variant::get_callable_error_text(custom_method, &argptr, 1, ce) + "."); + } else if (result.get_type() != Variant::FLOAT) { + ERR_FAIL_V_MSG(false, vformat("Wrong return type in PropertyTweener custom method. Expected float, got %s.", Variant::get_type_name(result.get_type()))); + } + + target_instance->set_indexed(property, Animation::interpolate_variant(initial_val, final_val, result)); + } else { + target_instance->set_indexed(property, tween->interpolate_variant(initial_val, delta_val, time, duration, trans_type, ease_type)); + } r_delta = 0; return true; } else { @@ -617,6 +638,7 @@ void PropertyTweener::_bind_methods() { ClassDB::bind_method(D_METHOD("as_relative"), &PropertyTweener::as_relative); ClassDB::bind_method(D_METHOD("set_trans", "trans"), &PropertyTweener::set_trans); ClassDB::bind_method(D_METHOD("set_ease", "ease"), &PropertyTweener::set_ease); + ClassDB::bind_method(D_METHOD("set_custom_interpolator", "interpolator_method"), &PropertyTweener::set_custom_interpolator); ClassDB::bind_method(D_METHOD("set_delay", "delay"), &PropertyTweener::set_delay); } @@ -693,7 +715,7 @@ bool CallbackTweener::step(double &r_delta) { Callable::CallError ce; callback.callp(nullptr, 0, result, ce); if (ce.error != Callable::CallError::CALL_OK) { - ERR_FAIL_V_MSG(false, "Error calling method from CallbackTweener: " + Variant::get_callable_error_text(callback, nullptr, 0, ce)); + ERR_FAIL_V_MSG(false, "Error calling method from CallbackTweener: " + Variant::get_callable_error_text(callback, nullptr, 0, ce) + "."); } finished = true; @@ -773,7 +795,7 @@ bool MethodTweener::step(double &r_delta) { Callable::CallError ce; callback.callp(argptr, 1, result, ce); if (ce.error != Callable::CallError::CALL_OK) { - ERR_FAIL_V_MSG(false, "Error calling method from MethodTweener: " + Variant::get_callable_error_text(callback, argptr, 1, ce)); + ERR_FAIL_V_MSG(false, "Error calling method from MethodTweener: " + Variant::get_callable_error_text(callback, argptr, 1, ce) + "."); } if (time < duration) { diff --git a/scene/animation/tween.h b/scene/animation/tween.h index 053b4fac46..8dcc3ad7b6 100644 --- a/scene/animation/tween.h +++ b/scene/animation/tween.h @@ -197,6 +197,7 @@ public: Ref<PropertyTweener> as_relative(); Ref<PropertyTweener> set_trans(Tween::TransitionType p_trans); Ref<PropertyTweener> set_ease(Tween::EaseType p_ease); + Ref<PropertyTweener> set_custom_interpolator(const Callable &p_method); Ref<PropertyTweener> set_delay(double p_delay); void set_tween(const Ref<Tween> &p_tween) override; @@ -222,6 +223,7 @@ private: double duration = 0; Tween::TransitionType trans_type = Tween::TRANS_MAX; // This is set inside set_tween(); Tween::EaseType ease_type = Tween::EASE_MAX; + Callable custom_method; double delay = 0; bool do_continue = true; diff --git a/scene/resources/compressed_texture.cpp b/scene/resources/compressed_texture.cpp index f4395d5c7d..588a2b967b 100644 --- a/scene/resources/compressed_texture.cpp +++ b/scene/resources/compressed_texture.cpp @@ -808,6 +808,7 @@ RID CompressedTextureLayered::get_rid() const { Ref<Image> CompressedTextureLayered::get_layer_data(int p_layer) const { if (texture.is_valid()) { + ERR_FAIL_INDEX_V(p_layer, get_layers(), Ref<Image>()); return RS::get_singleton()->texture_2d_layer_get(texture, p_layer); } else { return Ref<Image>(); diff --git a/scene/resources/multimesh.cpp b/scene/resources/multimesh.cpp index a9df766196..8cddfb5840 100644 --- a/scene/resources/multimesh.cpp +++ b/scene/resources/multimesh.cpp @@ -269,6 +269,16 @@ Color MultiMesh::get_instance_custom_data(int p_instance) const { return RenderingServer::get_singleton()->multimesh_instance_get_custom_data(multimesh, p_instance); } +void MultiMesh::set_custom_aabb(const AABB &p_custom) { + custom_aabb = p_custom; + RS::get_singleton()->multimesh_set_custom_aabb(multimesh, custom_aabb); + emit_changed(); +} + +AABB MultiMesh::get_custom_aabb() const { + return custom_aabb; +} + AABB MultiMesh::get_aabb() const { return RenderingServer::get_singleton()->multimesh_get_aabb(multimesh); } @@ -326,6 +336,8 @@ void MultiMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("get_instance_color", "instance"), &MultiMesh::get_instance_color); ClassDB::bind_method(D_METHOD("set_instance_custom_data", "instance", "custom_data"), &MultiMesh::set_instance_custom_data); ClassDB::bind_method(D_METHOD("get_instance_custom_data", "instance"), &MultiMesh::get_instance_custom_data); + ClassDB::bind_method(D_METHOD("set_custom_aabb", "aabb"), &MultiMesh::set_custom_aabb); + ClassDB::bind_method(D_METHOD("get_custom_aabb"), &MultiMesh::get_custom_aabb); ClassDB::bind_method(D_METHOD("get_aabb"), &MultiMesh::get_aabb); ClassDB::bind_method(D_METHOD("get_buffer"), &MultiMesh::get_buffer); @@ -334,6 +346,7 @@ void MultiMesh::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "transform_format", PROPERTY_HINT_ENUM, "2D,3D"), "set_transform_format", "get_transform_format"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_colors"), "set_use_colors", "is_using_colors"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_custom_data"), "set_use_custom_data", "is_using_custom_data"); + ADD_PROPERTY(PropertyInfo(Variant::AABB, "custom_aabb", PROPERTY_HINT_NONE, "suffix:m"), "set_custom_aabb", "get_custom_aabb"); ADD_PROPERTY(PropertyInfo(Variant::INT, "instance_count", PROPERTY_HINT_RANGE, "0,16384,1,or_greater"), "set_instance_count", "get_instance_count"); ADD_PROPERTY(PropertyInfo(Variant::INT, "visible_instance_count", PROPERTY_HINT_RANGE, "-1,16384,1,or_greater"), "set_visible_instance_count", "get_visible_instance_count"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh"), "set_mesh", "get_mesh"); diff --git a/scene/resources/multimesh.h b/scene/resources/multimesh.h index 98885db0cc..d7bcb13162 100644 --- a/scene/resources/multimesh.h +++ b/scene/resources/multimesh.h @@ -48,6 +48,7 @@ private: Ref<Mesh> mesh; RID multimesh; TransformFormat transform_format = TRANSFORM_2D; + AABB custom_aabb; bool use_colors = false; bool use_custom_data = false; int instance_count = 0; @@ -103,6 +104,9 @@ public: void set_instance_custom_data(int p_instance, const Color &p_custom_data); Color get_instance_custom_data(int p_instance) const; + void set_custom_aabb(const AABB &p_custom); + AABB get_custom_aabb() const; + virtual AABB get_aabb() const; virtual RID get_rid() const override; diff --git a/servers/rendering/dummy/storage/mesh_storage.h b/servers/rendering/dummy/storage/mesh_storage.h index ce0e80e8c8..d98b2e2ee7 100644 --- a/servers/rendering/dummy/storage/mesh_storage.h +++ b/servers/rendering/dummy/storage/mesh_storage.h @@ -87,6 +87,12 @@ public: s->index_count = p_surface.index_count; s->aabb = p_surface.aabb; s->skin_data = p_surface.skin_data; + s->lods = p_surface.lods; + s->bone_aabbs = p_surface.bone_aabbs; + s->mesh_to_skeleton_xform = p_surface.mesh_to_skeleton_xform; + s->blend_shape_data = p_surface.blend_shape_data; + s->uv_scale = p_surface.uv_scale; + s->material = p_surface.material; } virtual int mesh_get_blend_shape_count(RID p_mesh) const override { return 0; } @@ -153,6 +159,9 @@ public: virtual void multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color) override {} virtual void multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_color) override {} + virtual void multimesh_set_custom_aabb(RID p_multimesh, const AABB &p_aabb) override {} + virtual AABB multimesh_get_custom_aabb(RID p_multimesh) const override { return AABB(); } + virtual RID multimesh_get_mesh(RID p_multimesh) const override { return RID(); } virtual AABB multimesh_get_aabb(RID p_multimesh) const override { return AABB(); } diff --git a/servers/rendering/dummy/storage/utilities.h b/servers/rendering/dummy/storage/utilities.h index 4a640f9594..6e8af9afac 100644 --- a/servers/rendering/dummy/storage/utilities.h +++ b/servers/rendering/dummy/storage/utilities.h @@ -108,7 +108,7 @@ public: virtual void set_debug_generate_wireframes(bool p_generate) override {} virtual bool has_os_feature(const String &p_feature) const override { - return p_feature == "rgtc" || p_feature == "bptc" || p_feature == "s3tc" || p_feature == "etc" || p_feature == "etc2"; + return p_feature == "rgtc" || p_feature == "bptc" || p_feature == "s3tc" || p_feature == "etc2"; } virtual void update_memory_info() override {} diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp index 05929a862a..21787b3fcf 100644 --- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp +++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp @@ -1660,6 +1660,9 @@ void MeshStorage::_multimesh_mark_all_dirty(MultiMesh *multimesh, bool p_data, b void MeshStorage::_multimesh_re_create_aabb(MultiMesh *multimesh, const float *p_data, int p_instances) { ERR_FAIL_COND(multimesh->mesh.is_null()); + if (multimesh->custom_aabb != AABB()) { + return; + } AABB aabb; AABB mesh_aabb = mesh_get_aabb(multimesh->mesh); for (int i = 0; i < p_instances; i++) { @@ -1960,8 +1963,10 @@ void MeshStorage::multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_b //if we have a mesh set, we need to re-generate the AABB from the new data const float *data = p_buffer.ptr(); - _multimesh_re_create_aabb(multimesh, data, multimesh->instances); - multimesh->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_AABB); + if (multimesh->custom_aabb != AABB()) { + _multimesh_re_create_aabb(multimesh, data, multimesh->instances); + multimesh->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_AABB); + } } } @@ -2015,9 +2020,26 @@ int MeshStorage::multimesh_get_visible_instances(RID p_multimesh) const { return multimesh->visible_instances; } +void MeshStorage::multimesh_set_custom_aabb(RID p_multimesh, const AABB &p_aabb) { + MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh); + ERR_FAIL_NULL(multimesh); + multimesh->custom_aabb = p_aabb; + multimesh->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_AABB); +} + +AABB MeshStorage::multimesh_get_custom_aabb(RID p_multimesh) const { + MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh); + ERR_FAIL_NULL_V(multimesh, AABB()); + return multimesh->custom_aabb; +} + AABB MeshStorage::multimesh_get_aabb(RID p_multimesh) const { MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh); ERR_FAIL_NULL_V(multimesh, AABB()); + if (multimesh->custom_aabb != AABB()) { + return multimesh->custom_aabb; + } + if (multimesh->aabb_dirty) { const_cast<MeshStorage *>(this)->_update_dirty_multimeshes(); } @@ -2064,9 +2086,11 @@ void MeshStorage::_update_dirty_multimeshes() { if (multimesh->aabb_dirty) { //aabb is dirty.. - _multimesh_re_create_aabb(multimesh, data, visible_instances); multimesh->aabb_dirty = false; - multimesh->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_AABB); + if (multimesh->custom_aabb != AABB()) { + _multimesh_re_create_aabb(multimesh, data, visible_instances); + multimesh->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_AABB); + } } } diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.h b/servers/rendering/renderer_rd/storage_rd/mesh_storage.h index 771ac6d380..5491f637bc 100644 --- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.h +++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.h @@ -220,6 +220,7 @@ private: bool uses_custom_data = false; int visible_instances = -1; AABB aabb; + AABB custom_aabb; bool aabb_dirty = false; bool buffer_set = false; bool motion_vectors_enabled = false; @@ -646,6 +647,9 @@ public: virtual void multimesh_set_visible_instances(RID p_multimesh, int p_visible) override; virtual int multimesh_get_visible_instances(RID p_multimesh) const override; + virtual void multimesh_set_custom_aabb(RID p_multimesh, const AABB &p_aabb) override; + virtual AABB multimesh_get_custom_aabb(RID p_multimesh) const override; + virtual AABB multimesh_get_aabb(RID p_multimesh) const override; void _update_dirty_multimeshes(); diff --git a/servers/rendering/renderer_rd/storage_rd/utilities.cpp b/servers/rendering/renderer_rd/storage_rd/utilities.cpp index cb035c494c..8b780a6f7b 100644 --- a/servers/rendering/renderer_rd/storage_rd/utilities.cpp +++ b/servers/rendering/renderer_rd/storage_rd/utilities.cpp @@ -278,7 +278,7 @@ bool Utilities::has_os_feature(const String &p_feature) const { return true; } - if ((p_feature == "etc" || p_feature == "etc2") && RD::get_singleton()->texture_is_format_supported_for_usage(RD::DATA_FORMAT_ETC2_R8G8B8_UNORM_BLOCK, RD::TEXTURE_USAGE_SAMPLING_BIT)) { + if (p_feature == "etc2" && RD::get_singleton()->texture_is_format_supported_for_usage(RD::DATA_FORMAT_ETC2_R8G8B8_UNORM_BLOCK, RD::TEXTURE_USAGE_SAMPLING_BIT)) { return true; } diff --git a/servers/rendering/rendering_device_graph.cpp b/servers/rendering/rendering_device_graph.cpp index 83fb2d1918..3e4890e866 100644 --- a/servers/rendering/rendering_device_graph.cpp +++ b/servers/rendering/rendering_device_graph.cpp @@ -1430,7 +1430,13 @@ void RenderingDeviceGraph::add_compute_list_usage(ResourceTracker *p_tracker, Re compute_instruction_list.command_trackers.push_back(p_tracker); compute_instruction_list.command_tracker_usages.push_back(p_usage); p_tracker->compute_list_index = compute_instruction_list.index; + p_tracker->compute_list_usage = p_usage; } +#ifdef DEV_ENABLED + else if (p_tracker->compute_list_usage != p_usage) { + ERR_FAIL_MSG(vformat("Tracker can't have more than one type of usage in the same compute list. Compute list usage is %d and the requested usage is %d.", p_tracker->compute_list_usage, p_usage)); + } +#endif } void RenderingDeviceGraph::add_compute_list_usages(VectorView<ResourceTracker *> p_trackers, VectorView<ResourceUsage> p_usages) { @@ -1614,7 +1620,13 @@ void RenderingDeviceGraph::add_draw_list_usage(ResourceTracker *p_tracker, Resou draw_instruction_list.command_trackers.push_back(p_tracker); draw_instruction_list.command_tracker_usages.push_back(p_usage); p_tracker->draw_list_index = draw_instruction_list.index; + p_tracker->draw_list_usage = p_usage; } +#ifdef DEV_ENABLED + else if (p_tracker->draw_list_usage != p_usage) { + ERR_FAIL_MSG(vformat("Tracker can't have more than one type of usage in the same draw list. Draw list usage is %d and the requested usage is %d.", p_tracker->draw_list_usage, p_usage)); + } +#endif } void RenderingDeviceGraph::add_draw_list_usages(VectorView<ResourceTracker *> p_trackers, VectorView<ResourceUsage> p_usages) { diff --git a/servers/rendering/rendering_device_graph.h b/servers/rendering/rendering_device_graph.h index 995fdb27d1..9bb109ea0e 100644 --- a/servers/rendering/rendering_device_graph.h +++ b/servers/rendering/rendering_device_graph.h @@ -155,7 +155,9 @@ public: int32_t read_slice_command_list_index = -1; int32_t write_command_or_list_index = -1; int32_t draw_list_index = -1; + ResourceUsage draw_list_usage = RESOURCE_USAGE_NONE; int32_t compute_list_index = -1; + ResourceUsage compute_list_usage = RESOURCE_USAGE_NONE; ResourceUsage usage = RESOURCE_USAGE_NONE; BitField<RDD::BarrierAccessBits> usage_access; RDD::BufferID buffer_driver_id; diff --git a/servers/rendering/rendering_server_default.h b/servers/rendering/rendering_server_default.h index 3bb6ba1c51..577e9accc0 100644 --- a/servers/rendering/rendering_server_default.h +++ b/servers/rendering/rendering_server_default.h @@ -335,6 +335,9 @@ public: FUNC3(multimesh_instance_set_color, RID, int, const Color &) FUNC3(multimesh_instance_set_custom_data, RID, int, const Color &) + FUNC2(multimesh_set_custom_aabb, RID, const AABB &) + FUNC1RC(AABB, multimesh_get_custom_aabb, RID) + FUNC1RC(RID, multimesh_get_mesh, RID) FUNC1RC(AABB, multimesh_get_aabb, RID) diff --git a/servers/rendering/storage/mesh_storage.h b/servers/rendering/storage/mesh_storage.h index 3c1b2b495d..39fd4f393d 100644 --- a/servers/rendering/storage/mesh_storage.h +++ b/servers/rendering/storage/mesh_storage.h @@ -104,6 +104,9 @@ public: virtual void multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color) = 0; virtual void multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_color) = 0; + virtual void multimesh_set_custom_aabb(RID p_multimesh, const AABB &p_aabb) = 0; + virtual AABB multimesh_get_custom_aabb(RID p_multimesh) const = 0; + virtual RID multimesh_get_mesh(RID p_multimesh) const = 0; virtual Transform3D multimesh_instance_get_transform(RID p_multimesh, int p_index) const = 0; diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp index 99b0b1886c..d03f8113f8 100644 --- a/servers/rendering_server.cpp +++ b/servers/rendering_server.cpp @@ -2427,6 +2427,8 @@ void RenderingServer::_bind_methods() { ClassDB::bind_method(D_METHOD("multimesh_instance_set_custom_data", "multimesh", "index", "custom_data"), &RenderingServer::multimesh_instance_set_custom_data); ClassDB::bind_method(D_METHOD("multimesh_get_mesh", "multimesh"), &RenderingServer::multimesh_get_mesh); ClassDB::bind_method(D_METHOD("multimesh_get_aabb", "multimesh"), &RenderingServer::multimesh_get_aabb); + ClassDB::bind_method(D_METHOD("multimesh_set_custom_aabb", "multimesh", "aabb"), &RenderingServer::multimesh_set_custom_aabb); + ClassDB::bind_method(D_METHOD("multimesh_get_custom_aabb", "multimesh"), &RenderingServer::multimesh_get_custom_aabb); ClassDB::bind_method(D_METHOD("multimesh_instance_get_transform", "multimesh", "index"), &RenderingServer::multimesh_instance_get_transform); ClassDB::bind_method(D_METHOD("multimesh_instance_get_transform_2d", "multimesh", "index"), &RenderingServer::multimesh_instance_get_transform_2d); ClassDB::bind_method(D_METHOD("multimesh_instance_get_color", "multimesh", "index"), &RenderingServer::multimesh_instance_get_color); diff --git a/servers/rendering_server.h b/servers/rendering_server.h index d63283ddbe..19b6c35339 100644 --- a/servers/rendering_server.h +++ b/servers/rendering_server.h @@ -413,6 +413,9 @@ public: virtual RID multimesh_get_mesh(RID p_multimesh) const = 0; virtual AABB multimesh_get_aabb(RID p_multimesh) const = 0; + virtual void multimesh_set_custom_aabb(RID p_mesh, const AABB &p_aabb) = 0; + virtual AABB multimesh_get_custom_aabb(RID p_mesh) const = 0; + virtual Transform3D multimesh_instance_get_transform(RID p_multimesh, int p_index) const = 0; virtual Transform2D multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const = 0; virtual Color multimesh_instance_get_color(RID p_multimesh, int p_index) const = 0; |