From ae04a3a5ddefd99d1b26e3f59dd4d5470a03b580 Mon Sep 17 00:00:00 2001 From: lawnjelly Date: Fri, 14 Mar 2025 10:02:43 +0000 Subject: [PATCH] Physics Interpolation - Move 3D FTI to `SceneTree` Moves 3D interpolation from server to the client code (`SceneTree`). Complete rework of 3D physics interpolation, but using the same user API. --- core/error/error_macros.h | 12 +- core/templates/interpolated_property.cpp | 49 ++ core/templates/interpolated_property.h | 107 ++++ doc/classes/BoneAttachment3D.xml | 1 + doc/classes/RenderingServer.xml | 16 - doc/classes/VehicleWheel3D.xml | 1 + .../4.4-stable.expected | 8 + scene/3d/bone_attachment_3d.cpp | 1 + scene/3d/camera_3d.cpp | 163 +++--- scene/3d/camera_3d.h | 35 +- scene/3d/cpu_particles_3d.cpp | 2 +- scene/3d/node_3d.cpp | 77 ++- scene/3d/node_3d.h | 46 +- scene/3d/physics/vehicle_body_3d.cpp | 1 + scene/3d/skeleton_ik_3d.cpp | 2 +- scene/3d/visual_instance_3d.cpp | 40 +- scene/3d/visual_instance_3d.h | 2 +- scene/debugger/scene_debugger.cpp | 5 - scene/main/node.cpp | 5 - scene/main/scene_tree.cpp | 19 + scene/main/scene_tree.h | 4 + scene/main/scene_tree_fti.cpp | 474 ++++++++++++++++++ scene/main/scene_tree_fti.h | 128 +++++ servers/rendering/renderer_scene_cull.cpp | 211 +------- servers/rendering/renderer_scene_cull.h | 33 -- servers/rendering/rendering_method.h | 2 - servers/rendering/rendering_server_default.h | 2 - servers/rendering_server.compat.inc | 10 + servers/rendering_server.cpp | 2 - servers/rendering_server.h | 4 +- 30 files changed, 1030 insertions(+), 432 deletions(-) create mode 100644 core/templates/interpolated_property.cpp create mode 100644 core/templates/interpolated_property.h create mode 100644 scene/main/scene_tree_fti.cpp create mode 100644 scene/main/scene_tree_fti.h diff --git a/core/error/error_macros.h b/core/error/error_macros.h index bedb84b4be8..bd4ec34cde0 100644 --- a/core/error/error_macros.h +++ b/core/error/error_macros.h @@ -826,10 +826,14 @@ void _physics_interpolation_warning(const char *p_function, const char *p_file, #endif #ifdef DEV_ENABLED -#define DEV_CHECK_ONCE(m_cond) \ - if (unlikely(!(m_cond))) { \ - ERR_PRINT_ONCE("DEV_CHECK_ONCE failed \"" _STR(m_cond) "\" is false."); \ - } else \ +#define DEV_CHECK_ONCE(m_cond) \ + if (true) { \ + static bool first_print = true; \ + if (first_print && unlikely(!(m_cond))) { \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "DEV_CHECK_ONCE failed \"" _STR(m_cond) "\" is false."); \ + first_print = false; \ + } \ + } else \ ((void)0) #else #define DEV_CHECK_ONCE(m_cond) diff --git a/core/templates/interpolated_property.cpp b/core/templates/interpolated_property.cpp new file mode 100644 index 00000000000..556ca0bacad --- /dev/null +++ b/core/templates/interpolated_property.cpp @@ -0,0 +1,49 @@ +/**************************************************************************/ +/* interpolated_property.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "interpolated_property.h" + +#include "core/math/vector2.h" + +namespace InterpolatedPropertyFuncs { + +float lerp(float p_a, float p_b, float p_fraction) { + return Math::lerp(p_a, p_b, p_fraction); +} + +double lerp(double p_a, double p_b, float p_fraction) { + return Math::lerp(p_a, p_b, (double)p_fraction); +} + +Vector2 lerp(const Vector2 &p_a, const Vector2 &p_b, float p_fraction) { + return p_a.lerp(p_b, p_fraction); +} + +} //namespace InterpolatedPropertyFuncs diff --git a/core/templates/interpolated_property.h b/core/templates/interpolated_property.h new file mode 100644 index 00000000000..d55c1f8d6a4 --- /dev/null +++ b/core/templates/interpolated_property.h @@ -0,0 +1,107 @@ +/**************************************************************************/ +/* interpolated_property.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#pragma once + +struct Vector2; + +namespace InterpolatedPropertyFuncs { +float lerp(float p_a, float p_b, float p_fraction); +double lerp(double p_a, double p_b, float p_fraction); +Vector2 lerp(const Vector2 &p_a, const Vector2 &p_b, float p_fraction); +} //namespace InterpolatedPropertyFuncs + +// This class is intended to reduce the boiler plate involved to +// support custom properties to be physics interpolated. + +template +class InterpolatedProperty { + // Only needs interpolating / updating the servers when + // curr and prev are different. + bool _needs_interpolating = false; + T _interpolated; + T curr; + T prev; + +public: + // FTI depends on the constant flow between current values + // (on the current tick) and stored previous values (on the previous tick). + // These should be updated both on each tick, and also on resets. + void pump() { + prev = curr; + _needs_interpolating = false; + } + void reset() { pump(); } + + void set_interpolated_value(const T &p_val) { + _interpolated = p_val; + } + const T &interpolated() const { return _interpolated; } + bool needs_interpolating() const { return _needs_interpolating; } + + bool interpolate(float p_interpolation_fraction) { + if (_needs_interpolating) { + _interpolated = InterpolatedPropertyFuncs::lerp(prev, curr, p_interpolation_fraction); + return true; + } + return false; + } + + operator T() const { + return curr; + } + + bool operator==(const T &p_o) const { + return p_o == curr; + } + + bool operator!=(const T &p_o) const { + return p_o != curr; + } + + InterpolatedProperty &operator=(T p_val) { + curr = p_val; + _interpolated = p_val; + _needs_interpolating = true; + return *this; + } + InterpolatedProperty(T p_val) { + curr = p_val; + _interpolated = p_val; + pump(); + } + InterpolatedProperty() { + // Ensure either the constructor is run, + // or the memory is zeroed if using a fundamental type. + _interpolated = T{}; + curr = T{}; + prev = T{}; + } +}; diff --git a/doc/classes/BoneAttachment3D.xml b/doc/classes/BoneAttachment3D.xml index 1e2f3089776..7ddded94102 100644 --- a/doc/classes/BoneAttachment3D.xml +++ b/doc/classes/BoneAttachment3D.xml @@ -59,5 +59,6 @@ Whether the BoneAttachment3D node will override the bone pose of the bone it is attached to. When set to [code]true[/code], the BoneAttachment3D node can change the pose of the bone. When set to [code]false[/code], the BoneAttachment3D will always be set to the bone's transform. [b]Note:[/b] This override performs interruptively in the skeleton update process using signals due to the old design. It may cause unintended behavior when used at the same time with [SkeletonModifier3D]. + diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml index 820563af0e8..00ecdde81f3 100644 --- a/doc/classes/RenderingServer.xml +++ b/doc/classes/RenderingServer.xml @@ -1931,14 +1931,6 @@ Sets the visibility range values for the given geometry instance. Equivalent to [member GeometryInstance3D.visibility_range_begin] and related properties. - - - - - Prevents physics interpolation for the current physics tick. - This is useful when moving an instance to a new location, to give an instantaneous change rather than interpolation from the previous location. - - @@ -1980,14 +1972,6 @@ If [code]true[/code], ignores both frustum and occlusion culling on the specified 3D geometry instance. This is not the same as [member GeometryInstance3D.ignore_occlusion_culling], which only ignores occlusion culling and leaves frustum culling intact. - - - - - - Turns on and off physics interpolation for the instance. - - diff --git a/doc/classes/VehicleWheel3D.xml b/doc/classes/VehicleWheel3D.xml index 621f32de6da..dee096a52e6 100644 --- a/doc/classes/VehicleWheel3D.xml +++ b/doc/classes/VehicleWheel3D.xml @@ -64,6 +64,7 @@ [b]Note:[/b] The simulation does not take the effect of gears into account, you will need to add logic for this if you wish to simulate gears. A negative value will result in the wheel reversing. + The steering angle for the wheel, in radians. Setting this to a non-zero value will result in the vehicle turning when it's moving. diff --git a/misc/extension_api_validation/4.4-stable.expected b/misc/extension_api_validation/4.4-stable.expected index 0c0e90c53c5..55b6156a9c6 100644 --- a/misc/extension_api_validation/4.4-stable.expected +++ b/misc/extension_api_validation/4.4-stable.expected @@ -82,3 +82,11 @@ Validate extension JSON: Error: Field 'classes/TextServerExtension/methods/_shap Validate extension JSON: Error: Field 'classes/TextServerExtension/methods/_shaped_text_draw_outline/arguments': size changed value in new API, from 7 to 8. Optional "oversmpling" argument added. Compatibility methods registered. + + +GH-104269 +--------- +Validate extension JSON: API was removed: classes/RenderingServer/methods/instance_set_interpolated +Validate extension JSON: API was removed: classes/RenderingServer/methods/instance_reset_physics_interpolation + +Functionality moved out of server. diff --git a/scene/3d/bone_attachment_3d.cpp b/scene/3d/bone_attachment_3d.cpp index 59647457d83..68aa8173ad3 100644 --- a/scene/3d/bone_attachment_3d.cpp +++ b/scene/3d/bone_attachment_3d.cpp @@ -366,6 +366,7 @@ void BoneAttachment3D::notify_rebind_required() { #endif // TOOLS_ENABLED BoneAttachment3D::BoneAttachment3D() { + set_physics_interpolation_mode(PHYSICS_INTERPOLATION_MODE_OFF); } void BoneAttachment3D::_bind_methods() { diff --git a/scene/3d/camera_3d.cpp b/scene/3d/camera_3d.cpp index 386711598a4..9e3c445c65d 100644 --- a/scene/3d/camera_3d.cpp +++ b/scene/3d/camera_3d.cpp @@ -41,6 +41,64 @@ void Camera3D::_request_camera_update() { _update_camera(); } +void Camera3D::fti_pump_property() { + switch (mode) { + default: + break; + case PROJECTION_PERSPECTIVE: { + fov.pump(); + } break; + case PROJECTION_ORTHOGONAL: { + size.pump(); + } break; + case PROJECTION_FRUSTUM: { + size.pump(); + frustum_offset.pump(); + } break; + } + _near.pump(); + _far.pump(); + + Node3D::fti_pump_property(); +} + +void Camera3D::fti_update_servers_property() { + if (camera.is_valid()) { + float f = Engine::get_singleton()->get_physics_interpolation_fraction(); + + switch (mode) { + default: + break; + case PROJECTION_PERSPECTIVE: { + // If there have been changes due to interpolation, update the servers. + if (fov.interpolate(f) || _near.interpolate(f) || _far.interpolate(f)) { + RS::get_singleton()->camera_set_perspective(camera, fov.interpolated(), _near.interpolated(), _far.interpolated()); + } + } break; + case PROJECTION_ORTHOGONAL: { + if (size.interpolate(f) || _near.interpolate(f) || _far.interpolate(f)) { + RS::get_singleton()->camera_set_orthogonal(camera, size.interpolated(), _near.interpolated(), _far.interpolated()); + } + } break; + case PROJECTION_FRUSTUM: { + if (size.interpolate(f) || frustum_offset.interpolate(f) || _near.interpolate(f) || _far.interpolate(f)) { + RS::get_singleton()->camera_set_frustum(camera, size.interpolated(), frustum_offset.interpolated(), _near.interpolated(), _far.interpolated()); + } + } break; + } + } + + Node3D::fti_update_servers_property(); +} + +void Camera3D::fti_update_servers_xform() { + if (camera.is_valid()) { + Transform3D tr = _get_adjusted_camera_transform(_get_cached_global_transform_interpolated()); + RS::get_singleton()->camera_set_transform(camera, tr); + } + Node3D::fti_update_servers_xform(); +} + void Camera3D::_update_camera_mode() { force_change = true; switch (mode) { @@ -55,6 +113,7 @@ void Camera3D::_update_camera_mode() { set_frustum(size, frustum_offset, _near, _far); } break; } + fti_notify_node_changed(false); } void Camera3D::_validate_property(PropertyInfo &p_property) const { @@ -92,12 +151,8 @@ void Camera3D::_update_camera() { if (!is_physics_interpolated_and_enabled()) { RenderingServer::get_singleton()->camera_set_transform(camera, get_camera_transform()); } else { - // Ideally we shouldn't be moving a physics interpolated camera within a frame, - // because it will break smooth interpolation, but it may occur on e.g. level load. - if (!Engine::get_singleton()->is_in_physics_frame() && camera.is_valid()) { - _physics_interpolation_ensure_transform_calculated(true); - RenderingServer::get_singleton()->camera_set_transform(camera, _interpolation_data.camera_xform_interpolated); - } + // Force a refresh next frame. + fti_notify_node_changed(); } if (is_part_of_edited_scene() || !is_current()) { @@ -111,40 +166,6 @@ void Camera3D::_physics_interpolated_changed() { _update_process_mode(); } -void Camera3D::_physics_interpolation_ensure_data_flipped() { - // The curr -> previous update can either occur - // on the INTERNAL_PHYSICS_PROCESS OR - // on NOTIFICATION_TRANSFORM_CHANGED, - // if NOTIFICATION_TRANSFORM_CHANGED takes place - // earlier than INTERNAL_PHYSICS_PROCESS on a tick. - // This is to ensure that the data keeps flowing, but the new data - // doesn't overwrite before prev has been set. - - // Keep the data flowing. - uint64_t tick = Engine::get_singleton()->get_physics_frames(); - if (_interpolation_data.last_update_physics_tick != tick) { - _interpolation_data.xform_prev = _interpolation_data.xform_curr; - _interpolation_data.last_update_physics_tick = tick; - physics_interpolation_flip_data(); - } -} - -void Camera3D::_physics_interpolation_ensure_transform_calculated(bool p_force) const { - DEV_CHECK_ONCE(!Engine::get_singleton()->is_in_physics_frame()); - - InterpolationData &id = _interpolation_data; - uint64_t frame = Engine::get_singleton()->get_frames_drawn(); - - if (id.last_update_frame != frame || p_force) { - id.last_update_frame = frame; - - TransformInterpolator::interpolate_transform_3d(id.xform_prev, id.xform_curr, id.xform_interpolated, Engine::get_singleton()->get_physics_interpolation_fraction()); - - Transform3D &tr = id.camera_xform_interpolated; - tr = _get_adjusted_camera_transform(id.xform_interpolated); - } -} - void Camera3D::set_desired_process_modes(bool p_process_internal, bool p_physics_process_internal) { _desired_process_internal = p_process_internal; _desired_physics_process_internal = p_physics_process_internal; @@ -152,17 +173,8 @@ void Camera3D::set_desired_process_modes(bool p_process_internal, bool p_physics } void Camera3D::_update_process_mode() { - bool process = _desired_process_internal; - bool physics_process = _desired_physics_process_internal; - - if (is_physics_interpolated_and_enabled()) { - if (is_current()) { - process = true; - physics_process = true; - } - } - set_process_internal(process); - set_physics_process_internal(physics_process); + set_process_internal(_desired_process_internal); + set_physics_process_internal(_desired_physics_process_internal); } void Camera3D::_notification(int p_what) { @@ -186,60 +198,18 @@ void Camera3D::_notification(int p_what) { #endif } break; - case NOTIFICATION_INTERNAL_PROCESS: { - if (is_physics_interpolated_and_enabled() && camera.is_valid()) { - _physics_interpolation_ensure_transform_calculated(); - -#ifdef RENDERING_SERVER_DEBUG_PHYSICS_INTERPOLATION - print_line("\t\tinterpolated Camera3D: " + rtos(_interpolation_data.xform_interpolated.origin.x) + "\t( prev " + rtos(_interpolation_data.xform_prev.origin.x) + ", curr " + rtos(_interpolation_data.xform_curr.origin.x) + " ) on tick " + itos(Engine::get_singleton()->get_physics_frames())); -#endif - - RenderingServer::get_singleton()->camera_set_transform(camera, _interpolation_data.camera_xform_interpolated); - } - } break; - - case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { - if (is_physics_interpolated_and_enabled()) { - _physics_interpolation_ensure_data_flipped(); - _interpolation_data.xform_curr = get_global_transform(); - } - } break; - case NOTIFICATION_TRANSFORM_CHANGED: { - if (is_physics_interpolated_and_enabled()) { - _physics_interpolation_ensure_data_flipped(); - _interpolation_data.xform_curr = get_global_transform(); #if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED) + if (is_physics_interpolated_and_enabled()) { if (!Engine::get_singleton()->is_in_physics_frame()) { PHYSICS_INTERPOLATION_NODE_WARNING(get_instance_id(), "Interpolated Camera3D triggered from outside physics process"); } -#endif } +#endif _request_camera_update(); if (doppler_tracking != DOPPLER_TRACKING_DISABLED) { velocity_tracker->update_position(get_global_transform().origin); } - // Allow auto-reset when first adding to the tree, as a convenience. - if (_is_physics_interpolation_reset_requested() && is_inside_tree()) { - _notification(NOTIFICATION_RESET_PHYSICS_INTERPOLATION); - _set_physics_interpolation_reset_requested(false); - } - } break; - - case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: { - if (is_inside_tree()) { - _interpolation_data.xform_curr = get_global_transform(); - _interpolation_data.xform_prev = _interpolation_data.xform_curr; - _update_process_mode(); - } - } break; - - case NOTIFICATION_SUSPENDED: - case NOTIFICATION_PAUSED: { - if (is_physics_interpolated_and_enabled() && is_inside_tree() && is_visible_in_tree()) { - _physics_interpolation_ensure_transform_calculated(true); - RenderingServer::get_singleton()->camera_set_transform(camera, _interpolation_data.camera_xform_interpolated); - } } break; case NOTIFICATION_EXIT_WORLD: { @@ -289,8 +259,7 @@ Transform3D Camera3D::_get_adjusted_camera_transform(const Transform3D &p_xform) Transform3D Camera3D::get_camera_transform() const { if (is_physics_interpolated_and_enabled() && !Engine::get_singleton()->is_in_physics_frame()) { - _physics_interpolation_ensure_transform_calculated(); - return _interpolation_data.camera_xform_interpolated; + return _get_adjusted_camera_transform(_get_cached_global_transform_interpolated()); } return _get_adjusted_camera_transform(get_global_transform()); diff --git a/scene/3d/camera_3d.h b/scene/3d/camera_3d.h index a015d413736..7fd88f412ed 100644 --- a/scene/3d/camera_3d.h +++ b/scene/3d/camera_3d.h @@ -30,6 +30,7 @@ #pragma once +#include "core/templates/interpolated_property.h" #include "scene/3d/node_3d.h" #include "scene/3d/velocity_tracker_3d.h" #include "scene/resources/camera_attributes.h" @@ -64,12 +65,12 @@ private: ProjectionType mode = PROJECTION_PERSPECTIVE; - real_t fov = 75.0; - real_t size = 1.0; - Vector2 frustum_offset; + InterpolatedProperty fov = 75.0; + InterpolatedProperty size = 1.0; + InterpolatedProperty frustum_offset; // _ prefix to avoid conflict with Windows defines. - real_t _near = 0.05; - real_t _far = 4000.0; + InterpolatedProperty _near = 0.05; + InterpolatedProperty _far = 4000.0; real_t v_offset = 0.0; real_t h_offset = 0.0; KeepAspect keep_aspect = KEEP_HEIGHT; @@ -99,25 +100,10 @@ private: Vector pyramid_shape_points; #endif // PHYSICS_3D_DISABLED - /////////////////////////////////////////////////////// - // INTERPOLATION FUNCTIONS - void _physics_interpolation_ensure_transform_calculated(bool p_force = false) const; - void _physics_interpolation_ensure_data_flipped(); - - // These can be set by derived Camera3Ds, if they wish to do processing - // (while still allowing physics interpolation to function). + // These can be set by derived Cameras. bool _desired_process_internal = false; bool _desired_physics_process_internal = false; - mutable struct InterpolationData { - Transform3D xform_curr; - Transform3D xform_prev; - Transform3D xform_interpolated; - Transform3D camera_xform_interpolated; // After modification according to camera type. - uint32_t last_update_physics_tick = 0; - uint32_t last_update_frame = UINT32_MAX; - } _interpolation_data; - void _update_process_mode(); protected: @@ -125,9 +111,6 @@ protected: // This is because physics interpolation may need to request process modes additionally. void set_desired_process_modes(bool p_process_internal, bool p_physics_process_internal); - // Opportunity for derived classes to interpolate extra attributes. - virtual void physics_interpolation_flip_data() {} - virtual void _physics_interpolated_changed() override; virtual Transform3D _get_adjusted_camera_transform(const Transform3D &p_xform) const; /////////////////////////////////////////////////////// @@ -136,6 +119,10 @@ protected: virtual void _request_camera_update(); void _update_camera_mode(); + virtual void fti_pump_property() override; + virtual void fti_update_servers_property() override; + virtual void fti_update_servers_xform() override; + void _notification(int p_what); void _validate_property(PropertyInfo &p_property) const; diff --git a/scene/3d/cpu_particles_3d.cpp b/scene/3d/cpu_particles_3d.cpp index 2d47c053fe7..6f941f42e96 100644 --- a/scene/3d/cpu_particles_3d.cpp +++ b/scene/3d/cpu_particles_3d.cpp @@ -738,7 +738,7 @@ void CPUParticles3D::_particles_process(double p_delta) { Transform3D emission_xform; Basis velocity_xform; if (!local_coords) { - emission_xform = get_global_transform(); + emission_xform = get_global_transform_interpolated(); velocity_xform = emission_xform.basis; } diff --git a/scene/3d/node_3d.cpp b/scene/3d/node_3d.cpp index 4d8037b1406..50537139031 100644 --- a/scene/3d/node_3d.cpp +++ b/scene/3d/node_3d.cpp @@ -126,7 +126,7 @@ void Node3D::_propagate_transform_changed(Node3D *p_origin) { callable_mp(this, &Node3D::_propagate_transform_changed_deferred).call_deferred(); } } - _set_dirty_bits(DIRTY_GLOBAL_TRANSFORM); + _set_dirty_bits(DIRTY_GLOBAL_TRANSFORM | DIRTY_GLOBAL_INTERPOLATED_TRANSFORM); } void Node3D::_notification(int p_what) { @@ -164,16 +164,40 @@ void Node3D::_notification(int p_what) { } } - _set_dirty_bits(DIRTY_GLOBAL_TRANSFORM); // Global is always dirty upon entering a scene. + _set_dirty_bits(DIRTY_GLOBAL_TRANSFORM | DIRTY_GLOBAL_INTERPOLATED_TRANSFORM); // Global is always dirty upon entering a scene. _notify_dirty(); notification(NOTIFICATION_ENTER_WORLD); _update_visibility_parent(true); + + if (is_inside_tree() && get_tree()->is_physics_interpolation_enabled()) { + // Always reset FTI when entering tree and update the servers, + // both for interpolated and non-interpolated nodes, + // to ensure the server xforms are up to date. + fti_pump_xform(); + + // No need to interpolate as we are doing a reset. + data.global_transform_interpolated = get_global_transform(); + + // Make sure servers are up to date. + fti_update_servers_xform(); + + // As well as a reset based on the transform when adding, the user may change + // the transform during the first tick / frame, and we want to reset automatically + // at the end of the frame / tick (unless the user manually called `reset_physics_interpolation()`). + if (is_physics_interpolated()) { + get_tree()->get_scene_tree_fti().node_3d_request_reset(this); + } + } } break; case NOTIFICATION_EXIT_TREE: { ERR_MAIN_THREAD_GUARD; + if (is_inside_tree()) { + get_tree()->get_scene_tree_fti().node_3d_notify_delete(this); + } + notification(NOTIFICATION_EXIT_WORLD, true); if (xform_change.in_list()) { get_tree()->xform_change_list.remove(&xform_change); @@ -240,6 +264,18 @@ void Node3D::_notification(int p_what) { if (data.client_physics_interpolation_data) { data.client_physics_interpolation_data->global_xform_prev = data.client_physics_interpolation_data->global_xform_curr; } + + // In most cases, nodes derived from Node3D will have to already have reset code available for SceneTreeFTI, + // so it makes sense for them to reuse this method rather than respond individually to NOTIFICATION_RESET_PHYSICS_INTERPOLATION, + // unless they need to perform specific tasks (like changing process modes). + fti_pump_xform(); + fti_pump_property(); + } break; + case NOTIFICATION_SUSPENDED: + case NOTIFICATION_PAUSED: { + if (is_physics_interpolated_and_enabled()) { + data.local_transform_prev = get_transform(); + } } break; } } @@ -251,6 +287,7 @@ void Node3D::set_basis(const Basis &p_basis) { } void Node3D::set_quaternion(const Quaternion &p_quaternion) { ERR_THREAD_GUARD; + fti_notify_node_changed(); if (_test_dirty_bits(DIRTY_EULER_ROTATION_AND_SCALE)) { // We need the scale part, so if these are dirty, update it @@ -317,8 +354,19 @@ void Node3D::set_global_rotation_degrees(const Vector3 &p_euler_degrees) { set_global_rotation(radians); } +void Node3D::fti_pump_xform() { + data.local_transform_prev = get_transform(); +} + +void Node3D::fti_notify_node_changed(bool p_transform_changed) { + if (is_inside_tree()) { + get_tree()->get_scene_tree_fti().node_3d_notify_changed(*this, p_transform_changed); + } +} + void Node3D::set_transform(const Transform3D &p_transform) { ERR_THREAD_GUARD; + fti_notify_node_changed(); data.local_transform = p_transform; _replace_dirty_mask(DIRTY_EULER_ROTATION_AND_SCALE); // Make rot/scale dirty. @@ -452,9 +500,19 @@ Transform3D Node3D::_get_global_transform_interpolated(real_t p_interpolation_fr } Transform3D Node3D::get_global_transform_interpolated() { +#if 1 // Pass through if physics interpolation is switched off. // This is a convenience, as it allows you to easy turn off interpolation // without changing any code. + if (data.fti_global_xform_interp_set && is_physics_interpolated_and_enabled() && !Engine::get_singleton()->is_in_physics_frame() && is_visible_in_tree()) { + return data.global_transform_interpolated; + } + + return get_global_transform(); +#else + // OLD METHOD - deprecated since moving to SceneTreeFTI, + // but leaving for reference and comparison for debugging. + if (!is_physics_interpolated_and_enabled()) { return get_global_transform(); } @@ -467,6 +525,7 @@ Transform3D Node3D::get_global_transform_interpolated() { } return _get_global_transform_interpolated(Engine::get_singleton()->get_physics_interpolation_fraction()); +#endif } Transform3D Node3D::get_global_transform() const { @@ -537,6 +596,7 @@ Transform3D Node3D::get_relative_transform(const Node *p_parent) const { void Node3D::set_position(const Vector3 &p_position) { ERR_THREAD_GUARD; + fti_notify_node_changed(); data.local_transform.origin = p_position; _propagate_transform_changed(this); if (data.notify_local_transform) { @@ -618,6 +678,7 @@ EulerOrder Node3D::get_rotation_order() const { void Node3D::set_rotation(const Vector3 &p_euler_rad) { ERR_THREAD_GUARD; + fti_notify_node_changed(); if (_test_dirty_bits(DIRTY_EULER_ROTATION_AND_SCALE)) { // Update scale only if rotation and scale are dirty, as rotation will be overridden. data.scale = data.local_transform.basis.get_scale(); @@ -640,6 +701,7 @@ void Node3D::set_rotation_degrees(const Vector3 &p_euler_degrees) { void Node3D::set_scale(const Vector3 &p_scale) { ERR_THREAD_GUARD; + fti_notify_node_changed(); if (_test_dirty_bits(DIRTY_EULER_ROTATION_AND_SCALE)) { // Update rotation only if rotation and scale are dirty, as scale will be overridden. data.euler_rotation = data.local_transform.basis.get_euler_normalized(data.euler_rotation_order); @@ -1380,6 +1442,13 @@ Node3D::Node3D() : data.disable_scale = false; data.vi_visible = true; + data.fti_on_frame_xform_list = false; + data.fti_on_frame_property_list = false; + data.fti_on_tick_xform_list = false; + data.fti_on_tick_property_list = false; + data.fti_global_xform_interp_set = false; + data.fti_frame_xform_force_update = false; + #ifdef TOOLS_ENABLED data.gizmos_disabled = false; data.gizmos_dirty = false; @@ -1389,4 +1458,8 @@ Node3D::Node3D() : Node3D::~Node3D() { _disable_client_physics_interpolation(); + + if (is_inside_tree()) { + get_tree()->get_scene_tree_fti().node_3d_notify_delete(this); + } } diff --git a/scene/3d/node_3d.h b/scene/3d/node_3d.h index e641d7b8db0..87363059489 100644 --- a/scene/3d/node_3d.h +++ b/scene/3d/node_3d.h @@ -50,6 +50,8 @@ public: class Node3D : public Node { GDCLASS(Node3D, Node); + friend class SceneTreeFTI; + public: // Edit mode for the rotation. // THIS MODE ONLY AFFECTS HOW DATA IS EDITED AND SAVED @@ -81,7 +83,8 @@ private: DIRTY_NONE = 0, DIRTY_EULER_ROTATION_AND_SCALE = 1, DIRTY_LOCAL_TRANSFORM = 2, - DIRTY_GLOBAL_TRANSFORM = 4 + DIRTY_GLOBAL_TRANSFORM = 4, + DIRTY_GLOBAL_INTERPOLATED_TRANSFORM = 8, }; struct ClientPhysicsInterpolationData { @@ -97,8 +100,19 @@ private: // This Data struct is to avoid namespace pollution in derived classes. struct Data { + // Interpolated global transform - correct on the frame only. + // Only used with FTI. + Transform3D global_transform_interpolated; + + // Current xforms are either + // * Used for everything (when not using FTI) + // * Correct on the physics tick (when using FTI) mutable Transform3D global_transform; mutable Transform3D local_transform; + + // Only used with FTI. + Transform3D local_transform_prev; + mutable EulerOrder euler_rotation_order = EulerOrder::YXZ; mutable Vector3 euler_rotation; mutable Vector3 scale = Vector3(1, 1, 1); @@ -122,6 +136,14 @@ private: bool visible : 1; bool disable_scale : 1; + // Scene tree interpolation. + bool fti_on_frame_xform_list : 1; + bool fti_on_frame_property_list : 1; + bool fti_on_tick_xform_list : 1; + bool fti_on_tick_property_list : 1; + bool fti_global_xform_interp_set : 1; + bool fti_frame_xform_force_update : 1; + RID visibility_parent; Node3D *parent = nullptr; @@ -166,8 +188,30 @@ protected: void _set_vi_visible(bool p_visible) { data.vi_visible = p_visible; } bool _is_vi_visible() const { return data.vi_visible; } Transform3D _get_global_transform_interpolated(real_t p_interpolation_fraction); + const Transform3D &_get_cached_global_transform_interpolated() const { return data.global_transform_interpolated; } void _disable_client_physics_interpolation(); + // Calling this announces to the FTI system that a node has been moved, + // or requires an update in terms of interpolation + // (e.g. changing Camera zoom even if position hasn't changed). + void fti_notify_node_changed(bool p_transform_changed = true); + + // Opportunity after FTI to update the servers + // with global_transform_interpolated, + // and any custom interpolated data in derived classes. + // Make sure to call the parent class fti_update_servers(), + // so all data is updated to the servers. + virtual void fti_update_servers_xform() {} + virtual void fti_update_servers_property() {} + + // Pump the FTI data, also gives a chance for inherited classes + // to pump custom data, but they *must* call the base class here too. + // This is the opportunity for classes to move current values for + // transforms and properties to stored previous values, + // and this should take place both on ticks, and during resets. + virtual void fti_pump_xform(); + virtual void fti_pump_property() {} + void _notification(int p_what); static void _bind_methods(); diff --git a/scene/3d/physics/vehicle_body_3d.cpp b/scene/3d/physics/vehicle_body_3d.cpp index 5289d287b0c..f02dc489f2d 100644 --- a/scene/3d/physics/vehicle_body_3d.cpp +++ b/scene/3d/physics/vehicle_body_3d.cpp @@ -353,6 +353,7 @@ real_t VehicleWheel3D::get_rpm() const { } VehicleWheel3D::VehicleWheel3D() { + set_physics_interpolation_mode(PHYSICS_INTERPOLATION_MODE_OFF); } void VehicleBody3D::_update_wheel_transform(VehicleWheel3D &wheel, PhysicsDirectBodyState3D *s) { diff --git a/scene/3d/skeleton_ik_3d.cpp b/scene/3d/skeleton_ik_3d.cpp index 3c3c3990e04..e65fbd5755c 100644 --- a/scene/3d/skeleton_ik_3d.cpp +++ b/scene/3d/skeleton_ik_3d.cpp @@ -245,7 +245,7 @@ void FabrikInverseKinematic::solve(Task *p_task, bool override_tip_basis, bool p Vector3 origin_pos = p_task->skeleton->get_bone_global_pose(p_task->chain.chain_root.bone).origin; - make_goal(p_task, p_task->skeleton->get_global_transform().affine_inverse()); + make_goal(p_task, p_task->skeleton->get_global_transform_interpolated().affine_inverse()); if (p_use_magnet && p_task->chain.middle_chain_item) { p_task->chain.magnet_position = p_magnet_position; diff --git a/scene/3d/visual_instance_3d.cpp b/scene/3d/visual_instance_3d.cpp index a40d76dacc6..41903f208a1 100644 --- a/scene/3d/visual_instance_3d.cpp +++ b/scene/3d/visual_instance_3d.cpp @@ -58,10 +58,6 @@ void VisualInstance3D::_update_visibility() { RS::get_singleton()->instance_set_visible(instance, visible); } -void VisualInstance3D::_physics_interpolated_changed() { - RenderingServer::get_singleton()->instance_set_interpolated(instance, is_physics_interpolated()); -} - void VisualInstance3D::set_instance_use_identity_transform(bool p_enable) { // Prevent sending instance transforms when using global coordinates. _set_use_identity_transform(p_enable); @@ -77,6 +73,12 @@ void VisualInstance3D::set_instance_use_identity_transform(bool p_enable) { } } +void VisualInstance3D::fti_update_servers_xform() { + if (!_is_using_identity_transform()) { + RS::get_singleton()->instance_set_transform(get_instance(), _get_cached_global_transform_interpolated()); + } +} + void VisualInstance3D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_WORLD: { @@ -86,33 +88,19 @@ void VisualInstance3D::_notification(int p_what) { } break; case NOTIFICATION_TRANSFORM_CHANGED: { - if (_is_vi_visible() || is_physics_interpolated_and_enabled()) { - if (!_is_using_identity_transform()) { - RenderingServer::get_singleton()->instance_set_transform(instance, get_global_transform()); - - // For instance when first adding to the tree, when the previous transform is - // unset, to prevent streaking from the origin. - if (_is_physics_interpolation_reset_requested() && is_physics_interpolated_and_enabled() && is_inside_tree()) { - if (_is_vi_visible()) { - _notification(NOTIFICATION_RESET_PHYSICS_INTERPOLATION); - } - _set_physics_interpolation_reset_requested(false); - } - } + // ToDo : Can we turn off notify transform for physics interpolated cases? + if (_is_vi_visible() && !(is_inside_tree() && get_tree()->is_physics_interpolation_enabled()) && !_is_using_identity_transform()) { + // Physics interpolation global off, always send. + RenderingServer::get_singleton()->instance_set_transform(instance, get_global_transform()); } } break; case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: { - if (_is_vi_visible() && is_physics_interpolated() && is_inside_tree()) { - // We must ensure the RenderingServer transform is up to date before resetting. - // This is because NOTIFICATION_TRANSFORM_CHANGED is deferred, - // and cannot be relied to be called in order before NOTIFICATION_RESET_PHYSICS_INTERPOLATION. - if (!_is_using_identity_transform()) { - RenderingServer::get_singleton()->instance_set_transform(instance, get_global_transform()); - } + if (_is_vi_visible() && is_inside_tree()) { + // Allow resetting motion vectors etc + // at the same time as resetting physics interpolation, + // giving users one common interface. RenderingServer::get_singleton()->instance_teleport(instance); - - RenderingServer::get_singleton()->instance_reset_physics_interpolation(instance); } } break; diff --git a/scene/3d/visual_instance_3d.h b/scene/3d/visual_instance_3d.h index 608352a7f29..727066a754b 100644 --- a/scene/3d/visual_instance_3d.h +++ b/scene/3d/visual_instance_3d.h @@ -44,8 +44,8 @@ class VisualInstance3D : public Node3D { protected: void _update_visibility(); - virtual void _physics_interpolated_changed() override; void set_instance_use_identity_transform(bool p_enable); + virtual void fti_update_servers_xform() override; void _notification(int p_what); static void _bind_methods(); diff --git a/scene/debugger/scene_debugger.cpp b/scene/debugger/scene_debugger.cpp index 6894158644b..ab098215aaa 100644 --- a/scene/debugger/scene_debugger.cpp +++ b/scene/debugger/scene_debugger.cpp @@ -2151,11 +2151,6 @@ void RuntimeNodeSelect::_update_selection() { RS::get_singleton()->instance_set_transform(sb->instance_ofs, t_offset); RS::get_singleton()->instance_set_transform(sb->instance_xray, t); RS::get_singleton()->instance_set_transform(sb->instance_xray_ofs, t_offset); - - RS::get_singleton()->instance_reset_physics_interpolation(sb->instance); - RS::get_singleton()->instance_reset_physics_interpolation(sb->instance_ofs); - RS::get_singleton()->instance_reset_physics_interpolation(sb->instance_xray); - RS::get_singleton()->instance_reset_physics_interpolation(sb->instance_xray_ofs); } #endif // _3D_DISABLED } diff --git a/scene/main/node.cpp b/scene/main/node.cpp index 59062228e70..19f6832bfe1 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -219,11 +219,6 @@ void Node::_notification(int p_notification) { get_tree()->nodes_in_tree_count++; orphan_node_count--; - // Allow physics interpolated nodes to automatically reset when added to the tree - // (this is to save the user from doing this manually each time). - if (get_tree()->is_physics_interpolation_enabled()) { - _set_physics_interpolation_reset_requested(true); - } } break; case NOTIFICATION_POST_ENTER_TREE: { diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index 70757523e1c..916e163e9b8 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -588,6 +588,8 @@ void SceneTree::set_physics_interpolation_enabled(bool p_enabled) { _physics_interpolation_enabled = p_enabled; RenderingServer::get_singleton()->set_physics_interpolation_enabled(p_enabled); + get_scene_tree_fti().set_enabled(get_root(), p_enabled); + // Perform an auto reset on the root node for convenience for the user. if (root) { root->reset_physics_interpolation(); @@ -615,6 +617,7 @@ void SceneTree::iteration_prepare() { // Make sure any pending transforms from the last tick / frame // are flushed before pumping the interpolation prev and currents. flush_transform_notifications(); + get_scene_tree_fti().tick_update(); RenderingServer::get_singleton()->tick(); } } @@ -669,6 +672,17 @@ void SceneTree::iteration_end() { } bool SceneTree::process(double p_time) { + // First pass of scene tree fixed timestep interpolation. + if (get_scene_tree_fti().is_enabled()) { + // Special, we need to ensure RenderingServer is up to date + // with *all* the pending xforms *before* updating it during + // the FTI update. + // If this is not done, we can end up with a deferred `set_transform()` + // overwriting the interpolated xform in the server. + flush_transform_notifications(); + get_scene_tree_fti().frame_update(get_root(), true); + } + if (MainLoop::process(p_time)) { _quit = true; } @@ -750,6 +764,11 @@ bool SceneTree::process(double p_time) { #endif // _3D_DISABLED #endif // TOOLS_ENABLED + // Second pass of scene tree fixed timestep interpolation. + // ToDo: Possibly needs another flush_transform_notifications here + // depending on whether there are side effects to _call_idle_callbacks(). + get_scene_tree_fti().frame_update(get_root(), false); + if (_physics_interpolation_enabled) { RenderingServer::get_singleton()->pre_draw(true); } diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h index 2695c1bbd83..dfff6551b4e 100644 --- a/scene/main/scene_tree.h +++ b/scene/main/scene_tree.h @@ -34,6 +34,7 @@ #include "core/os/thread_safe.h" #include "core/templates/paged_allocator.h" #include "core/templates/self_list.h" +#include "scene/main/scene_tree_fti.h" #include "scene/resources/mesh.h" #undef Window @@ -148,6 +149,7 @@ private: bool _quit = false; bool _physics_interpolation_enabled = false; + SceneTreeFTI scene_tree_fti; StringName tree_changed_name = "tree_changed"; StringName node_added_name = "node_added"; @@ -454,6 +456,8 @@ public: void client_physics_interpolation_remove_node_3d(SelfList *p_elem); #endif + SceneTreeFTI &get_scene_tree_fti() { return scene_tree_fti; } + SceneTree(); ~SceneTree(); }; diff --git a/scene/main/scene_tree_fti.cpp b/scene/main/scene_tree_fti.cpp new file mode 100644 index 00000000000..28d4c5adf05 --- /dev/null +++ b/scene/main/scene_tree_fti.cpp @@ -0,0 +1,474 @@ +/**************************************************************************/ +/* scene_tree_fti.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef _3D_DISABLED + +#include "scene_tree_fti.h" + +#include "core/config/engine.h" +#include "core/math/transform_interpolator.h" +#include "core/os/os.h" +#include "scene/3d/visual_instance_3d.h" + +// Uncomment this to enable some slow extra DEV_ENABLED +// checks to ensure there aren't more than one object added to the lists. +// #define GODOT_SCENE_TREE_FTI_EXTRA_CHECKS + +void SceneTreeFTI::_reset_flags(Node *p_node) { + Node3D *s = Object::cast_to(p_node); + + if (s) { + s->data.fti_on_tick_xform_list = false; + s->data.fti_on_tick_property_list = false; + s->data.fti_on_frame_xform_list = false; + s->data.fti_on_frame_property_list = false; + s->data.fti_global_xform_interp_set = false; + s->data.fti_frame_xform_force_update = false; + + // In most cases the later NOTIFICATION_RESET_PHYSICS_INTERPOLATION + // will reset this, but this should help cover hidden nodes. + s->data.local_transform_prev = s->get_transform(); + } + + for (int n = 0; n < p_node->get_child_count(); n++) { + _reset_flags(p_node->get_child(n)); + } +} + +void SceneTreeFTI::set_enabled(Node *p_root, bool p_enabled) { + if (data.enabled == p_enabled) { + return; + } + MutexLock(data.mutex); + + data.tick_xform_list[0].clear(); + data.tick_xform_list[1].clear(); + + // Node3D flags must be reset. + if (p_root) { + _reset_flags(p_root); + } + + data.enabled = p_enabled; +} + +void SceneTreeFTI::tick_update() { + if (!data.enabled) { + return; + } + MutexLock(data.mutex); + + _update_request_resets(); + + uint32_t curr_mirror = data.mirror; + uint32_t prev_mirror = curr_mirror ? 0 : 1; + + LocalVector &curr = data.tick_xform_list[curr_mirror]; + LocalVector &prev = data.tick_xform_list[prev_mirror]; + + // First detect on the previous list but not on this tick list. + for (uint32_t n = 0; n < prev.size(); n++) { + Node3D *s = prev[n]; + if (!s->data.fti_on_tick_xform_list) { + // Needs a reset so jittering will stop. + s->fti_pump_xform(); + + // This may not get updated so set it to the same as global xform. + // TODO: double check this is the best value. + s->data.global_transform_interpolated = s->get_global_transform(); + + // Remove from interpolation list. + if (s->data.fti_on_frame_xform_list) { + s->data.fti_on_frame_xform_list = false; + } + + // Ensure that the spatial gets at least ONE further + // update in the resting position in the next frame update. + s->data.fti_frame_xform_force_update = true; + } + } + + LocalVector &curr_prop = data.tick_property_list[curr_mirror]; + LocalVector &prev_prop = data.tick_property_list[prev_mirror]; + + // Detect on the previous property list but not on this tick list. + for (uint32_t n = 0; n < prev_prop.size(); n++) { + Node3D *s = prev_prop[n]; + + if (!s->data.fti_on_tick_property_list) { + // Needs a reset so jittering will stop. + s->fti_pump_xform(); + + // Ensure the servers are up to date with the final resting value. + s->fti_update_servers_property(); + + // Remove from interpolation list. + if (s->data.fti_on_frame_property_list) { + s->data.fti_on_frame_property_list = false; + data.frame_property_list.erase_unordered(s); + +#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS + DEV_CHECK_ONCE(data.frame_property_list.find(s) == -1); +#endif + } + } + } + + // Pump all on the property list that are NOT on the tick list. + for (uint32_t n = 0; n < curr_prop.size(); n++) { + Node3D *s = curr_prop[n]; + + // Reset, needs to be marked each tick. + s->data.fti_on_tick_property_list = false; + s->fti_pump_property(); + } + + // Now pump all on the current list. + for (uint32_t n = 0; n < curr.size(); n++) { + Node3D *s = curr[n]; + + // Reset, needs to be marked each tick. + s->data.fti_on_tick_xform_list = false; + + // Pump. + s->fti_pump_xform(); + } + + // Clear previous list and flip. + prev.clear(); + prev_prop.clear(); + data.mirror = prev_mirror; +} + +void SceneTreeFTI::_update_request_resets() { + // For instance when first adding to the tree, when the previous transform is + // unset, to prevent streaking from the origin. + for (uint32_t n = 0; n < data.request_reset_list.size(); n++) { + Node3D *s = data.request_reset_list[n]; + if (s->_is_physics_interpolation_reset_requested()) { + if (s->_is_vi_visible() && !s->_is_using_identity_transform()) { + s->notification(Node3D::NOTIFICATION_RESET_PHYSICS_INTERPOLATION); + } + + s->_set_physics_interpolation_reset_requested(false); + } + } + + data.request_reset_list.clear(); +} + +void SceneTreeFTI::node_3d_request_reset(Node3D *p_node) { + DEV_CHECK_ONCE(data.enabled); + DEV_ASSERT(p_node); + + MutexLock(data.mutex); + + if (!p_node->_is_physics_interpolation_reset_requested()) { + p_node->_set_physics_interpolation_reset_requested(true); +#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS + DEV_CHECK_ONCE(data.request_reset_list.find(p_node) == -1); +#endif + data.request_reset_list.push_back(p_node); + } +} + +void SceneTreeFTI::_node_3d_notify_set_property(Node3D &r_node) { + if (!r_node.is_physics_interpolated()) { + return; + } + + DEV_CHECK_ONCE(data.enabled); + + // Note that a Node3D can be on BOTH the transform list and the property list. + if (!r_node.data.fti_on_tick_property_list) { + r_node.data.fti_on_tick_property_list = true; + + // Should only appear once in the property list. +#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS + DEV_CHECK_ONCE(data.tick_property_list[data.mirror].find(&r_node) == -1); +#endif + data.tick_property_list[data.mirror].push_back(&r_node); + } + + if (!r_node.data.fti_on_frame_property_list) { + r_node.data.fti_on_frame_property_list = true; + + // Should only appear once in the property frame list. +#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS + DEV_CHECK_ONCE(data.frame_property_list.find(&r_node) == -1); +#endif + data.frame_property_list.push_back(&r_node); + } +} + +void SceneTreeFTI::_node_3d_notify_set_xform(Node3D &r_node) { + DEV_CHECK_ONCE(data.enabled); + + if (!r_node.is_physics_interpolated()) { + // Force an update of non-interpolated to servers + // on the next traversal. + r_node.data.fti_frame_xform_force_update = true; + return; + } + + if (!r_node.data.fti_on_tick_xform_list) { + r_node.data.fti_on_tick_xform_list = true; + + // Should only appear once in the xform list. +#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS + DEV_CHECK_ONCE(data.tick_xform_list[data.mirror].find(&r_node) == -1); +#endif + data.tick_xform_list[data.mirror].push_back(&r_node); + + // The following flag could have been previously set + // (for removal from the tick list). + // We no longer need this guarantee, + // however there is probably no downside to leaving it set + // as it will be cleared on the next frame anyway. + // This line is left for reference. + // r_spatial.data.fti_frame_xform_force_update = false; + } + + if (!r_node.data.fti_on_frame_xform_list) { + r_node.data.fti_on_frame_xform_list = true; + } +} + +void SceneTreeFTI::node_3d_notify_delete(Node3D *p_node) { + if (!data.enabled) { + return; + } + + ERR_FAIL_NULL(p_node); + + MutexLock(data.mutex); + + p_node->data.fti_on_frame_xform_list = false; + + // Ensure this is kept in sync with the lists, in case a node + // is removed and re-added to the scene tree multiple times + // on the same frame / tick. + p_node->_set_physics_interpolation_reset_requested(false); + + // This can potentially be optimized for large scenes with large churn, + // as it will be doing a linear search through the lists. + data.tick_xform_list[0].erase_unordered(p_node); + data.tick_xform_list[1].erase_unordered(p_node); + + data.tick_property_list[0].erase_unordered(p_node); + data.tick_property_list[1].erase_unordered(p_node); + + data.frame_property_list.erase_unordered(p_node); + data.request_reset_list.erase_unordered(p_node); + +#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS + // There should only be one occurrence on the lists. + // Check this in DEV_ENABLED builds. + DEV_CHECK_ONCE(data.tick_xform_list[0].find(p_node) == -1); + DEV_CHECK_ONCE(data.tick_xform_list[1].find(p_node) == -1); + + DEV_CHECK_ONCE(data.tick_property_list[0].find(p_node) == -1); + DEV_CHECK_ONCE(data.tick_property_list[1].find(p_node) == -1); + + DEV_CHECK_ONCE(data.frame_property_list.find(p_node) == -1); + DEV_CHECK_ONCE(data.request_reset_list.find(p_node) == -1); +#endif +} + +void SceneTreeFTI::_update_dirty_nodes(Node *p_node, uint32_t p_current_frame, float p_interpolation_fraction, bool p_active, const Transform3D *p_parent_global_xform, int p_depth) { + Node3D *s = Object::cast_to(p_node); + + // Don't recurse into hidden branches. + if (s && !s->is_visible()) { + // NOTE : If we change from recursing entire tree, we should do an is_visible_in_tree() + // check for the first of the branch. + return; + } + + // Not a Node3D. + // Could be e.g. a viewport or something + // so we should still recurse to children. + if (!s) { + for (int n = 0; n < p_node->get_child_count(); n++) { + _update_dirty_nodes(p_node->get_child(n), p_current_frame, p_interpolation_fraction, p_active, nullptr, p_depth + 1); + } + return; + } + + // We are going to be using data.global_transform, so + // we need to ensure data.global_transform is not dirty! + if (s->_test_dirty_bits(Node3D::DIRTY_GLOBAL_TRANSFORM)) { + _ALLOW_DISCARD_ s->get_global_transform(); + } + + // Start the active interpolation chain from here onwards + // as we recurse further into the SceneTree. + // Once we hit an active (interpolated) node, we have to fully + // process all ancestors because their xform will also change. + // Anything not moving (inactive) higher in the tree need not be processed. + if (!p_active) { + if (data.frame_start) { + // On the frame start, activate whenever we hit something that requests interpolation. + if (s->data.fti_on_frame_xform_list || s->data.fti_frame_xform_force_update) { + p_active = true; + } + } else { + // On the frame end, we want to re-interpolate *anything* that has moved + // since the frame start. + + if (s->_test_dirty_bits(Node3D::DIRTY_GLOBAL_INTERPOLATED_TRANSFORM)) { + p_active = true; + } + } + } + + if (data.frame_start) { + // Mark on the Node3D whether we have set global_transform_interp. + // This can later be used when calling `get_global_transform_interpolated()` + // to know which xform to return. + s->data.fti_global_xform_interp_set = p_active; + } + + if (p_active) { +#if 0 + bool dirty = s->data.dirty & Node3D::DIRTY_GLOBAL_INTERP; + + if (data.debug) { + String sz; + for (int n = 0; n < p_depth; n++) { + sz += "\t"; + } + print_line(sz + p_node->get_name() + (dirty ? " DIRTY" : "")); + } +#endif + + // First calculate our local xform. + // This will either use interpolation, or just use the current local if not interpolated. + Transform3D local_interp; + if (s->is_physics_interpolated()) { + // Make sure to call `get_transform()` rather than using local_transform directly, because + // local_transform may be dirty and need updating from rotation / scale. + TransformInterpolator::interpolate_transform_3d(s->data.local_transform_prev, s->get_transform(), local_interp, p_interpolation_fraction); + } else { + local_interp = s->get_transform(); + } + + // Concatenate parent xform. + if (!s->is_set_as_top_level()) { + if (p_parent_global_xform) { + s->data.global_transform_interpolated = (*p_parent_global_xform) * local_interp; + } else { + const Node3D *parent = s->get_parent_node_3d(); + + if (parent) { + const Transform3D &parent_glob = parent->data.fti_global_xform_interp_set ? parent->data.global_transform_interpolated : parent->data.global_transform; + s->data.global_transform_interpolated = parent_glob * local_interp; + } else { + s->data.global_transform_interpolated = local_interp; + } + } + } else { + s->data.global_transform_interpolated = local_interp; + } + + // Watch for this, disable_scale can cause incredibly confusing bugs + // and must be checked for when calculating global xforms. + if (s->data.disable_scale) { + s->data.global_transform_interpolated.basis.orthonormalize(); + } + + // Upload to RenderingServer the interpolated global xform. + s->fti_update_servers_xform(); + + // Only do this at most for one frame, + // it is used to catch objects being removed from the tick lists + // that have a deferred frame update. + s->data.fti_frame_xform_force_update = false; + + } // if active. + + // Remove the dirty interp flag from EVERYTHING as we go. + s->_clear_dirty_bits(Node3D::DIRTY_GLOBAL_INTERPOLATED_TRANSFORM); + + // Recurse to children. + for (int n = 0; n < p_node->get_child_count(); n++) { + _update_dirty_nodes(p_node->get_child(n), p_current_frame, p_interpolation_fraction, p_active, s->data.fti_global_xform_interp_set ? &s->data.global_transform_interpolated : &s->data.global_transform, p_depth + 1); + } +} + +void SceneTreeFTI::frame_update(Node *p_root, bool p_frame_start) { + if (!data.enabled || !p_root) { + return; + } + MutexLock(data.mutex); + + _update_request_resets(); + + data.frame_start = p_frame_start; + + float f = Engine::get_singleton()->get_physics_interpolation_fraction(); + uint32_t frame = Engine::get_singleton()->get_frames_drawn(); + +// #define SCENE_TREE_FTI_TAKE_TIMINGS +#ifdef SCENE_TREE_FTI_TAKE_TIMINGS + uint64_t before = OS::get_singleton()->get_ticks_usec(); +#endif + + if (data.debug) { + print_line(String("\nScene: ") + (data.frame_start ? "start" : "end") + "\n"); + } + + // Probably not the most optimal approach as we traverse the entire SceneTree + // but simple and foolproof. + // Can be optimized later. + _update_dirty_nodes(p_root, frame, f, false); + + if (!p_frame_start && data.debug) { + data.debug = false; + } + +#ifdef SCENE_TREE_FTI_TAKE_TIMINGS + uint64_t after = OS::get_singleton()->get_ticks_usec(); + if ((Engine::get_singleton()->get_frames_drawn() % 60) == 0) { + print_line("Took " + itos(after - before) + " usec " + (data.frame_start ? "start" : "end")); + } +#endif + + // Update the properties once off at the end of the frame. + // No need for two passes for properties. + if (!p_frame_start) { + for (uint32_t n = 0; n < data.frame_property_list.size(); n++) { + Node3D *s = data.frame_property_list[n]; + s->fti_update_servers_property(); + } + } +} + +#endif // ndef _3D_DISABLED diff --git a/scene/main/scene_tree_fti.h b/scene/main/scene_tree_fti.h new file mode 100644 index 00000000000..33be68158d0 --- /dev/null +++ b/scene/main/scene_tree_fti.h @@ -0,0 +1,128 @@ +/**************************************************************************/ +/* scene_tree_fti.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#pragma once + +#include "core/os/mutex.h" +#include "core/templates/local_vector.h" + +class Node3D; +class Node; +struct Transform3D; + +#ifdef _3D_DISABLED +// Stubs +class SceneTreeFTI { +public: + void frame_update(Node *p_root, bool p_frame_start) {} + void tick_update() {} + void set_enabled(Node *p_root, bool p_enabled) {} + bool is_enabled() const { return false; } + + void node_3d_notify_changed(Node3D &r_node, bool p_transform_changed) {} + void node_3d_notify_delete(Node3D *p_node) {} + void node_3d_request_reset(Node3D *p_node) {} +}; +#else + +// Important. +// This class uses raw pointers, so it is essential that on deletion, this class is notified +// so that any references can be cleared up to prevent dangling pointer access. + +// This class can be used from a custom SceneTree. + +// Note we could potentially make SceneTreeFTI static / global to avoid the lookup through scene tree, +// but this covers the custom case of multiple scene trees. + +class SceneTreeFTI { + struct Data { + // Prev / Curr lists of Node3Ds having local xforms pumped. + LocalVector tick_xform_list[2]; + + // Prev / Curr lists of Node3Ds having actively interpolated properties. + LocalVector tick_property_list[2]; + + LocalVector frame_property_list; + + LocalVector request_reset_list; + + uint32_t mirror = 0; + + bool enabled = false; + + // Whether we are in physics ticks, or in a frame. + bool in_frame = false; + + // Updating at the start of the frame, or the end on second pass. + bool frame_start = true; + + Mutex mutex; + + bool debug = false; + } data; + + void _update_dirty_nodes(Node *p_node, uint32_t p_current_frame, float p_interpolation_fraction, bool p_active, const Transform3D *p_parent_global_xform = nullptr, int p_depth = 0); + void _update_request_resets(); + + void _reset_flags(Node *p_node); + void _node_3d_notify_set_xform(Node3D &r_node); + void _node_3d_notify_set_property(Node3D &r_node); + +public: + // Hottest function, allow inlining the data.enabled check. + void node_3d_notify_changed(Node3D &r_node, bool p_transform_changed) { + if (!data.enabled) { + return; + } + MutexLock(data.mutex); + + if (p_transform_changed) { + _node_3d_notify_set_xform(r_node); + } else { + _node_3d_notify_set_property(r_node); + } + } + + void node_3d_request_reset(Node3D *p_node); + void node_3d_notify_delete(Node3D *p_node); + + // Calculate interpolated xforms, send to visual server. + void frame_update(Node *p_root, bool p_frame_start); + + // Update local xform pumps. + void tick_update(); + + void set_enabled(Node *p_root, bool p_enabled); + bool is_enabled() const { return data.enabled; } + + void set_debug_next_frame() { data.debug = true; } +}; + +#endif // ndef _3D_DISABLED diff --git a/servers/rendering/renderer_scene_cull.cpp b/servers/rendering/renderer_scene_cull.cpp index a00b9bfa0a9..277ada1e4d0 100644 --- a/servers/rendering/renderer_scene_cull.cpp +++ b/servers/rendering/renderer_scene_cull.cpp @@ -941,45 +941,8 @@ void RendererSceneCull::instance_set_transform(RID p_instance, const Transform3D Instance *instance = instance_owner.get_or_null(p_instance); ERR_FAIL_NULL(instance); -#ifdef RENDERING_SERVER_DEBUG_PHYSICS_INTERPOLATION - print_line("instance_set_transform " + rtos(p_transform.origin.x) + " .. tick " + itos(Engine::get_singleton()->get_physics_frames())); -#endif - - if (!_interpolation_data.interpolation_enabled || !instance->interpolated || !instance->scenario) { - if (instance->transform == p_transform) { - return; // Must be checked to avoid worst evil. - } - -#ifdef DEBUG_ENABLED - - for (int i = 0; i < 4; i++) { - const Vector3 &v = i < 3 ? p_transform.basis.rows[i] : p_transform.origin; - ERR_FAIL_COND(!v.is_finite()); - } - -#endif - instance->transform = p_transform; - _instance_queue_update(instance, true); - -#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED) - if (_interpolation_data.interpolation_enabled && !instance->interpolated && Engine::get_singleton()->is_in_physics_frame()) { - PHYSICS_INTERPOLATION_NODE_WARNING(instance->object_id, "Non-interpolated instance triggered from physics process"); - } -#endif - - return; - } - - float new_checksum = TransformInterpolator::checksum_transform_3d(p_transform); - bool checksums_match = (instance->transform_checksum_curr == new_checksum) && (instance->transform_checksum_prev == new_checksum); - - // We can't entirely reject no changes because we need the interpolation - // system to keep on stewing. - - // Optimized check. First checks the checksums. If they pass it does the slow check at the end. - // Alternatively we can do this non-optimized and ignore the checksum... if no change. - if (checksums_match && (instance->transform_curr == p_transform) && (instance->transform_prev == p_transform)) { - return; + if (instance->transform == p_transform) { + return; // Must be checked to avoid worst evil. } #ifdef DEBUG_ENABLED @@ -990,71 +953,8 @@ void RendererSceneCull::instance_set_transform(RID p_instance, const Transform3D } #endif - - instance->transform_curr = p_transform; - -#ifdef RENDERING_SERVER_DEBUG_PHYSICS_INTERPOLATION - print_line("\tprev " + rtos(instance->transform_prev.origin.x) + ", curr " + rtos(instance->transform_curr.origin.x)); -#endif - - // Keep checksums up to date. - instance->transform_checksum_curr = new_checksum; - - if (!instance->on_interpolate_transform_list) { - _interpolation_data.instance_transform_update_list_curr->push_back(p_instance); - instance->on_interpolate_transform_list = true; - } else { - DEV_ASSERT(_interpolation_data.instance_transform_update_list_curr->size()); - } - - // If the instance is invisible, then we are simply updating the data flow, there is no need to calculate the interpolated - // transform or anything else. - // Ideally we would not even call the VisualServer::set_transform() when invisible but that would entail having logic - // to keep track of the previous transform on the SceneTree side. The "early out" below is less efficient but a lot cleaner codewise. - if (!instance->visible) { - return; - } - - // Decide on the interpolation method... slerp if possible. - instance->interpolation_method = TransformInterpolator::find_method(instance->transform_prev.basis, instance->transform_curr.basis); - - if (!instance->on_interpolate_list) { - _interpolation_data.instance_interpolate_update_list.push_back(p_instance); - instance->on_interpolate_list = true; - } else { - DEV_ASSERT(_interpolation_data.instance_interpolate_update_list.size()); - } - + instance->transform = p_transform; _instance_queue_update(instance, true); - -#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED) - if (!Engine::get_singleton()->is_in_physics_frame()) { - PHYSICS_INTERPOLATION_NODE_WARNING(instance->object_id, "Interpolated instance triggered from outside physics process"); - } -#endif -} - -void RendererSceneCull::instance_set_interpolated(RID p_instance, bool p_interpolated) { - Instance *instance = instance_owner.get_or_null(p_instance); - ERR_FAIL_NULL(instance); - instance->interpolated = p_interpolated; -} - -void RendererSceneCull::instance_reset_physics_interpolation(RID p_instance) { - Instance *instance = instance_owner.get_or_null(p_instance); - ERR_FAIL_NULL(instance); - - instance->teleported = true; - - if (_interpolation_data.interpolation_enabled && instance->interpolated) { - instance->transform_prev = instance->transform_curr; - instance->transform_checksum_prev = instance->transform_checksum_curr; - -#ifdef RENDERING_SERVER_DEBUG_PHYSICS_INTERPOLATION - print_line("instance_reset_physics_interpolation .. tick " + itos(Engine::get_singleton()->get_physics_frames())); - print_line("\tprev " + rtos(instance->transform_prev.origin.x) + ", curr " + rtos(instance->transform_curr.origin.x)); -#endif - } } void RendererSceneCull::instance_attach_object_instance_id(RID p_instance, ObjectID p_id) { @@ -1107,23 +1007,6 @@ void RendererSceneCull::instance_set_visible(RID p_instance, bool p_visible) { if (p_visible) { if (instance->scenario != nullptr) { - // Special case for physics interpolation, we want to ensure the interpolated data is up to date - if (_interpolation_data.interpolation_enabled && instance->interpolated && !instance->on_interpolate_list) { - // Do all the extra work we normally do on instance_set_transform(), because this is optimized out for hidden instances. - // This prevents a glitch of stale interpolation transform data when unhiding before the next physics tick. - instance->interpolation_method = TransformInterpolator::find_method(instance->transform_prev.basis, instance->transform_curr.basis); - _interpolation_data.instance_interpolate_update_list.push_back(p_instance); - instance->on_interpolate_list = true; - - // We must also place on the transform update list for a tick, so the system - // can auto-detect if the instance is no longer moving, and remove from the interpolate lists again. - // If this step is ignored, an unmoving instance could remain on the interpolate lists indefinitely - // (or rather until the object is deleted) and cause unnecessary updates and drawcalls. - if (!instance->on_interpolate_transform_list) { - _interpolation_data.instance_transform_update_list_curr->push_back(p_instance); - instance->on_interpolate_transform_list = true; - } - } _instance_queue_update(instance, true, false); } } else if (instance->indexer_id.is_valid()) { @@ -4278,8 +4161,6 @@ bool RendererSceneCull::free(RID p_rid) { Instance *instance = instance_owner.get_or_null(p_rid); - _interpolation_data.notify_free_instance(p_rid, *instance); - instance_geometry_set_lightmap(p_rid, RID(), Rect2(), 0); instance_set_scenario(p_rid, RID()); instance_set_base(p_rid, RID()); @@ -4342,103 +4223,17 @@ void RendererSceneCull::set_scene_render(RendererSceneRender *p_scene_render) { void RendererSceneCull::update_interpolation_tick(bool p_process) { // MultiMesh: Update interpolation in storage. RSG::mesh_storage->update_interpolation_tick(p_process); - - // INSTANCES - - // Detect any that were on the previous transform list that are no longer active; - // we should remove them from the interpolate list. - - for (const RID &rid : *_interpolation_data.instance_transform_update_list_prev) { - Instance *instance = instance_owner.get_or_null(rid); - - bool active = true; - - // No longer active? (Either the instance deleted or no longer being transformed.) - if (instance && !instance->on_interpolate_transform_list) { - active = false; - instance->on_interpolate_list = false; - - // Make sure the most recent transform is set... - instance->transform = instance->transform_curr; - - // ... and that both prev and current are the same, just in case of any interpolations. - instance->transform_prev = instance->transform_curr; - - // Make sure instances are updated one more time to ensure the AABBs are correct. - _instance_queue_update(instance, true); - } - - if (!instance) { - active = false; - } - - if (!active) { - _interpolation_data.instance_interpolate_update_list.erase(rid); - } - } - - // Now for any in the transform list (being actively interpolated), keep the previous transform - // value up to date, ready for the next tick. - if (p_process) { - for (const RID &rid : *_interpolation_data.instance_transform_update_list_curr) { - Instance *instance = instance_owner.get_or_null(rid); - if (instance) { - instance->transform_prev = instance->transform_curr; - instance->transform_checksum_prev = instance->transform_checksum_curr; - instance->on_interpolate_transform_list = false; - } - } - } - - // We maintain a mirror list for the transform updates, so we can detect when an instance - // is no longer being transformed, and remove it from the interpolate list. - SWAP(_interpolation_data.instance_transform_update_list_curr, _interpolation_data.instance_transform_update_list_prev); - - // Prepare for the next iteration. - _interpolation_data.instance_transform_update_list_curr->clear(); } void RendererSceneCull::update_interpolation_frame(bool p_process) { // MultiMesh: Update interpolation in storage. RSG::mesh_storage->update_interpolation_frame(p_process); - - if (p_process) { - real_t f = Engine::get_singleton()->get_physics_interpolation_fraction(); - - for (const RID &rid : _interpolation_data.instance_interpolate_update_list) { - Instance *instance = instance_owner.get_or_null(rid); - if (instance) { - TransformInterpolator::interpolate_transform_3d_via_method(instance->transform_prev, instance->transform_curr, instance->transform, f, instance->interpolation_method); - -#ifdef RENDERING_SERVER_DEBUG_PHYSICS_INTERPOLATION - print_line("\t\tinterpolated: " + rtos(instance->transform.origin.x) + "\t( prev " + rtos(instance->transform_prev.origin.x) + ", curr " + rtos(instance->transform_curr.origin.x) + " ) on tick " + itos(Engine::get_singleton()->get_physics_frames())); -#endif - - // Make sure AABBs are constantly up to date through the interpolation. - _instance_queue_update(instance, true); - } - } - } } void RendererSceneCull::set_physics_interpolation_enabled(bool p_enabled) { _interpolation_data.interpolation_enabled = p_enabled; } -void RendererSceneCull::InterpolationData::notify_free_instance(RID p_rid, Instance &r_instance) { - r_instance.on_interpolate_list = false; - r_instance.on_interpolate_transform_list = false; - - if (!interpolation_enabled) { - return; - } - - // If the instance was on any of the lists, remove. - instance_interpolate_update_list.erase_multiple_unordered(p_rid); - instance_transform_update_list_curr->erase_multiple_unordered(p_rid); - instance_transform_update_list_prev->erase_multiple_unordered(p_rid); -} - RendererSceneCull::RendererSceneCull() { render_pass = 1; singleton = this; diff --git a/servers/rendering/renderer_scene_cull.h b/servers/rendering/renderer_scene_cull.h index 67f69dc06cc..42a75891966 100644 --- a/servers/rendering/renderer_scene_cull.h +++ b/servers/rendering/renderer_scene_cull.h @@ -408,17 +408,9 @@ public: RID mesh_instance; //only used for meshes and when skeleton/blendshapes exist - // This is the main transform to be drawn with ... - // This will either be the interpolated transform (when using fixed timestep interpolation) - // or the ONLY transform (when not using FTI). Transform3D transform; bool teleported = false; - // For interpolation we store the current transform (this physics tick) - // and the transform in the previous tick. - Transform3D transform_curr; - Transform3D transform_prev; - float lod_bias; bool ignore_occlusion_culling; @@ -437,16 +429,6 @@ public: bool dynamic_gi : 1; // Same as above for dynamic objects. bool redraw_if_visible : 1; - bool on_interpolate_list : 1; - bool on_interpolate_transform_list : 1; - bool interpolated : 1; - TransformInterpolator::Method interpolation_method : 3; - - // For fixed timestep interpolation. - // Note 32 bits is plenty for checksum, no need for real_t - float transform_checksum_curr; - float transform_checksum_prev; - Instance *lightmap = nullptr; Rect2 lightmap_uv_scale; int lightmap_slice_index; @@ -587,13 +569,6 @@ public: dynamic_gi = false; redraw_if_visible = false; - on_interpolate_list = false; - on_interpolate_transform_list = false; - interpolated = true; - interpolation_method = TransformInterpolator::INTERP_LERP; - transform_checksum_curr = 0.0; - transform_checksum_prev = 0.0; - lightmap_slice_index = 0; lightmap = nullptr; lightmap_cull_index = 0; @@ -1047,8 +1022,6 @@ public: virtual void instance_set_layer_mask(RID p_instance, uint32_t p_mask); virtual void instance_set_pivot_data(RID p_instance, float p_sorting_offset, bool p_use_aabb_center); virtual void instance_set_transform(RID p_instance, const Transform3D &p_transform); - virtual void instance_set_interpolated(RID p_instance, bool p_interpolated); - virtual void instance_reset_physics_interpolation(RID p_instance); virtual void instance_attach_object_instance_id(RID p_instance, ObjectID p_id); virtual void instance_set_blend_shape_weight(RID p_instance, int p_shape, float p_weight); virtual void instance_set_surface_override_material(RID p_instance, int p_surface, RID p_material); @@ -1430,12 +1403,6 @@ public: virtual void set_physics_interpolation_enabled(bool p_enabled); struct InterpolationData { - void notify_free_instance(RID p_rid, Instance &r_instance); - LocalVector instance_interpolate_update_list; - LocalVector instance_transform_update_lists[2]; - LocalVector *instance_transform_update_list_curr = &instance_transform_update_lists[0]; - LocalVector *instance_transform_update_list_prev = &instance_transform_update_lists[1]; - bool interpolation_enabled = false; } _interpolation_data; diff --git a/servers/rendering/rendering_method.h b/servers/rendering/rendering_method.h index 98286045def..38e173bf48f 100644 --- a/servers/rendering/rendering_method.h +++ b/servers/rendering/rendering_method.h @@ -82,8 +82,6 @@ public: virtual void instance_set_layer_mask(RID p_instance, uint32_t p_mask) = 0; virtual void instance_set_pivot_data(RID p_instance, float p_sorting_offset, bool p_use_aabb_center) = 0; virtual void instance_set_transform(RID p_instance, const Transform3D &p_transform) = 0; - virtual void instance_set_interpolated(RID p_instance, bool p_interpolated) = 0; - virtual void instance_reset_physics_interpolation(RID p_instance) = 0; virtual void instance_attach_object_instance_id(RID p_instance, ObjectID p_id) = 0; virtual void instance_set_blend_shape_weight(RID p_instance, int p_shape, float p_weight) = 0; virtual void instance_set_surface_override_material(RID p_instance, int p_surface, RID p_material) = 0; diff --git a/servers/rendering/rendering_server_default.h b/servers/rendering/rendering_server_default.h index 2ab5c24ae8f..f8321f0448d 100644 --- a/servers/rendering/rendering_server_default.h +++ b/servers/rendering/rendering_server_default.h @@ -882,8 +882,6 @@ public: FUNC2(instance_set_layer_mask, RID, uint32_t) FUNC3(instance_set_pivot_data, RID, float, bool) FUNC2(instance_set_transform, RID, const Transform3D &) - FUNC2(instance_set_interpolated, RID, bool) - FUNC1(instance_reset_physics_interpolation, RID) FUNC2(instance_attach_object_instance_id, RID, ObjectID) FUNC3(instance_set_blend_shape_weight, RID, int, float) FUNC3(instance_set_surface_override_material, RID, int, RID) diff --git a/servers/rendering_server.compat.inc b/servers/rendering_server.compat.inc index 2ae2f3c8844..b3ccaa2da70 100644 --- a/servers/rendering_server.compat.inc +++ b/servers/rendering_server.compat.inc @@ -50,12 +50,22 @@ void RenderingServer::_canvas_item_add_circle_bind_compat_84523(RID p_item, cons canvas_item_add_circle(p_item, p_pos, p_radius, p_color, false); } +void RenderingServer::_instance_set_interpolated_bind_compat_104269(RID p_instance, bool p_interpolated) { + WARN_PRINT_ONCE("instance_set_interpolated() is deprecated."); +} + +void RenderingServer::_instance_reset_physics_interpolation_bind_compat_104269(RID p_instance) { + WARN_PRINT_ONCE("instance_reset_physics_interpolation() is deprecated."); +} + void RenderingServer::_bind_compatibility_methods() { ClassDB::bind_compatibility_method(D_METHOD("multimesh_allocate_data", "multimesh", "instances", "transform_format", "color_format", "custom_data_format"), &RenderingServer::_multimesh_allocate_data_bind_compat_99455, DEFVAL(false), DEFVAL(false)); ClassDB::bind_compatibility_method(D_METHOD("environment_set_fog", "env", "enable", "light_color", "light_energy", "sun_scatter", "density", "height", "height_density", "aerial_perspective", "sky_affect"), &RenderingServer::_environment_set_fog_bind_compat_84792); ClassDB::bind_compatibility_method(D_METHOD("canvas_item_add_multiline", "item", "points", "colors", "width"), &RenderingServer::_canvas_item_add_multiline_bind_compat_84523, DEFVAL(-1.0)); ClassDB::bind_compatibility_method(D_METHOD("canvas_item_add_rect", "item", "rect", "color"), &RenderingServer::_canvas_item_add_rect_bind_compat_84523); ClassDB::bind_compatibility_method(D_METHOD("canvas_item_add_circle", "item", "pos", "radius", "color"), &RenderingServer::_canvas_item_add_circle_bind_compat_84523); + ClassDB::bind_compatibility_method(D_METHOD("instance_set_interpolated", "instance", "interpolated"), &RenderingServer::_instance_set_interpolated_bind_compat_104269); + ClassDB::bind_compatibility_method(D_METHOD("instance_reset_physics_interpolation", "instance"), &RenderingServer::_instance_reset_physics_interpolation_bind_compat_104269); } #endif diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp index d0cb25d3237..f0768c1ef8b 100644 --- a/servers/rendering_server.cpp +++ b/servers/rendering_server.cpp @@ -3164,8 +3164,6 @@ void RenderingServer::_bind_methods() { ClassDB::bind_method(D_METHOD("instance_set_layer_mask", "instance", "mask"), &RenderingServer::instance_set_layer_mask); ClassDB::bind_method(D_METHOD("instance_set_pivot_data", "instance", "sorting_offset", "use_aabb_center"), &RenderingServer::instance_set_pivot_data); ClassDB::bind_method(D_METHOD("instance_set_transform", "instance", "transform"), &RenderingServer::instance_set_transform); - ClassDB::bind_method(D_METHOD("instance_set_interpolated", "instance", "interpolated"), &RenderingServer::instance_set_interpolated); - ClassDB::bind_method(D_METHOD("instance_reset_physics_interpolation", "instance"), &RenderingServer::instance_reset_physics_interpolation); ClassDB::bind_method(D_METHOD("instance_attach_object_instance_id", "instance", "id"), &RenderingServer::instance_attach_object_instance_id); ClassDB::bind_method(D_METHOD("instance_set_blend_shape_weight", "instance", "shape", "weight"), &RenderingServer::instance_set_blend_shape_weight); ClassDB::bind_method(D_METHOD("instance_set_surface_override_material", "instance", "surface", "material"), &RenderingServer::instance_set_surface_override_material); diff --git a/servers/rendering_server.h b/servers/rendering_server.h index 1b678e2038f..c83309275ae 100644 --- a/servers/rendering_server.h +++ b/servers/rendering_server.h @@ -87,6 +87,8 @@ protected: void _canvas_item_add_multiline_bind_compat_84523(RID p_item, const Vector &p_points, const Vector &p_colors, float p_width = -1.0); void _canvas_item_add_rect_bind_compat_84523(RID p_item, const Rect2 &p_rect, const Color &p_color); void _canvas_item_add_circle_bind_compat_84523(RID p_item, const Point2 &p_pos, float p_radius, const Color &p_color); + void _instance_set_interpolated_bind_compat_104269(RID p_instance, bool p_interpolated); + void _instance_reset_physics_interpolation_bind_compat_104269(RID p_instance); static void _bind_compatibility_methods(); #endif @@ -1442,8 +1444,6 @@ public: virtual void instance_set_layer_mask(RID p_instance, uint32_t p_mask) = 0; virtual void instance_set_pivot_data(RID p_instance, float p_sorting_offset, bool p_use_aabb_center) = 0; virtual void instance_set_transform(RID p_instance, const Transform3D &p_transform) = 0; - virtual void instance_set_interpolated(RID p_instance, bool p_interpolated) = 0; - virtual void instance_reset_physics_interpolation(RID p_instance) = 0; virtual void instance_attach_object_instance_id(RID p_instance, ObjectID p_id) = 0; virtual void instance_set_blend_shape_weight(RID p_instance, int p_shape, float p_weight) = 0; virtual void instance_set_surface_override_material(RID p_instance, int p_surface, RID p_material) = 0;