Fixed Timestep Interpolation (2D)

Adds fixed timestep interpolation to the rendering server (2D only).
Switchable on and off with a project setting (default is off).

Co-authored-by: lawnjelly <lawnjelly@gmail.com>
This commit is contained in:
Ricardo Buring 2024-02-17 00:57:32 +01:00
parent fe01776f05
commit 2ed2ccc2d8
39 changed files with 1040 additions and 75 deletions

View File

@ -0,0 +1,76 @@
/**************************************************************************/
/* transform_interpolator.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 "transform_interpolator.h"
#include "core/math/transform_2d.h"
void TransformInterpolator::interpolate_transform_2d(const Transform2D &p_prev, const Transform2D &p_curr, Transform2D &r_result, real_t p_fraction) {
// Extract parameters.
Vector2 p1 = p_prev.get_origin();
Vector2 p2 = p_curr.get_origin();
// Special case for physics interpolation, if flipping, don't interpolate basis.
// If the determinant polarity changes, the handedness of the coordinate system changes.
if (_sign(p_prev.determinant()) != _sign(p_curr.determinant())) {
r_result.columns[0] = p_curr.columns[0];
r_result.columns[1] = p_curr.columns[1];
r_result.set_origin(p1.lerp(p2, p_fraction));
return;
}
real_t r1 = p_prev.get_rotation();
real_t r2 = p_curr.get_rotation();
Size2 s1 = p_prev.get_scale();
Size2 s2 = p_curr.get_scale();
// Slerp rotation.
Vector2 v1(Math::cos(r1), Math::sin(r1));
Vector2 v2(Math::cos(r2), Math::sin(r2));
real_t dot = v1.dot(v2);
dot = CLAMP(dot, -1, 1);
Vector2 v;
if (dot > 0.9995f) {
v = v1.lerp(v2, p_fraction).normalized(); // Linearly interpolate to avoid numerical precision issues.
} else {
real_t angle = p_fraction * Math::acos(dot);
Vector2 v3 = (v2 - v1 * dot).normalized();
v = v1 * Math::cos(angle) + v3 * Math::sin(angle);
}
// Construct matrix.
r_result = Transform2D(Math::atan2(v.y, v.x), p1.lerp(p2, p_fraction));
r_result.scale_basis(s1.lerp(s2, p_fraction));
}

View File

@ -0,0 +1,46 @@
/**************************************************************************/
/* transform_interpolator.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef TRANSFORM_INTERPOLATOR_H
#define TRANSFORM_INTERPOLATOR_H
#include "core/math/math_defs.h"
struct Transform2D;
class TransformInterpolator {
private:
static bool _sign(real_t p_val) { return p_val >= 0; }
public:
static void interpolate_transform_2d(const Transform2D &p_prev, const Transform2D &p_curr, Transform2D &r_result, real_t p_fraction);
};
#endif // TRANSFORM_INTERPOLATOR_H

View File

@ -62,6 +62,7 @@ public:
};
virtual void initialize();
virtual void iteration_prepare() {}
virtual bool physics_process(double p_time);
virtual bool process(double p_time);
virtual void finalize();

View File

@ -104,6 +104,22 @@ public:
return false;
}
U erase_multiple_unordered(const T &p_val) {
U from = 0;
U occurrences = 0;
while (true) {
int64_t idx = find(p_val, from);
if (idx == -1) {
break;
}
remove_at_unordered(idx);
from = idx;
occurrences++;
}
return occurrences;
}
void invert() {
for (U i = 0; i < count / 2; i++) {
SWAP(data[i], data[count - i - 1]);

View File

@ -1012,6 +1012,7 @@
Distance between the node's top edge and its parent control, based on [member anchor_top].
Offsets are often controlled by one or multiple parent [Container] nodes, so you should not modify them manually if your node is a direct child of a [Container]. Offsets update automatically when you move or resize the node.
</member>
<member name="physics_interpolation_mode" type="int" setter="set_physics_interpolation_mode" getter="get_physics_interpolation_mode" overrides="Node" enum="Node.PhysicsInterpolationMode" default="2" />
<member name="pivot_offset" type="Vector2" setter="set_pivot_offset" getter="get_pivot_offset" default="Vector2(0, 0)">
By default, the node's pivot is its top-left corner. When you change its [member rotation] or [member scale], it will rotate or scale around this pivot. Set this property to [member size] / 2 to pivot around the Control's center.
</member>

View File

@ -619,6 +619,21 @@
[method request_ready] resets it back to [code]false[/code].
</description>
</method>
<method name="is_physics_interpolated" qualifiers="const">
<return type="bool" />
<description>
Returns [code]true[/code] if physics interpolation is enabled for this node (see [member physics_interpolation_mode]).
[b]Note:[/b] Interpolation will only be active if both the flag is set [b]and[/b] physics interpolation is enabled within the [SceneTree]. This can be tested using [method is_physics_interpolated_and_enabled].
</description>
</method>
<method name="is_physics_interpolated_and_enabled" qualifiers="const">
<return type="bool" />
<description>
Returns [code]true[/code] if physics interpolation is enabled (see [member physics_interpolation_mode]) [b]and[/b] enabled in the [SceneTree].
This is a convenience version of [method is_physics_interpolated] that also checks whether physics interpolation is enabled globally.
See [member SceneTree.physics_interpolation] and [member ProjectSettings.physics/common/physics_interpolation].
</description>
</method>
<method name="is_physics_processing" qualifiers="const">
<return type="bool" />
<description>
@ -793,6 +808,15 @@
[b]Note:[/b] This method only affects the current node. If the node's children also need to request ready, this method needs to be called for each one of them. When the node and its children enter the tree again, the order of [method _ready] callbacks will be the same as normal.
</description>
</method>
<method name="reset_physics_interpolation">
<return type="void" />
<description>
When physics interpolation is active, moving a node to a radically different transform (such as placement within a level) can result in a visible glitch as the object is rendered moving from the old to new position over the physics tick.
That glitch can be prevented by calling this method, which temporarily disables interpolation until the physics tick is complete.
The notification [constant NOTIFICATION_RESET_PHYSICS_INTERPOLATION] will be received by the node and all children recursively.
[b]Note:[/b] This function should be called [b]after[/b] moving the node, rather than before.
</description>
</method>
<method name="rpc" qualifiers="vararg">
<return type="int" enum="Error" />
<param index="0" name="method" type="StringName" />
@ -964,6 +988,10 @@
The owner of this node. The owner must be an ancestor of this node. When packing the owner node in a [PackedScene], all the nodes it owns are also saved with it.
[b]Note:[/b] In the editor, nodes not owned by the scene root are usually not displayed in the Scene dock, and will [b]not[/b] be saved. To prevent this, remember to set the owner after calling [method add_child]. See also (see [member unique_name_in_owner])
</member>
<member name="physics_interpolation_mode" type="int" setter="set_physics_interpolation_mode" getter="get_physics_interpolation_mode" enum="Node.PhysicsInterpolationMode" default="0">
Allows enabling or disabling physics interpolation per node, offering a finer grain of control than turning physics interpolation on and off globally. See [member ProjectSettings.physics/common/physics_interpolation] and [member SceneTree.physics_interpolation] for the global setting.
[b]Note:[/b] When teleporting a node to a distant position you should temporarily disable interpolation with [method Node.reset_physics_interpolation].
</member>
<member name="process_mode" type="int" setter="set_process_mode" getter="get_process_mode" enum="Node.ProcessMode" default="0">
The node's processing behavior (see [enum ProcessMode]). To check if the node can process in its current mode, use [method can_process].
</member>
@ -1122,6 +1150,9 @@
<constant name="NOTIFICATION_ENABLED" value="29">
Notification received when the node is enabled again after being disabled. See [constant PROCESS_MODE_DISABLED].
</constant>
<constant name="NOTIFICATION_RESET_PHYSICS_INTERPOLATION" value="2001">
Notification received when [method reset_physics_interpolation] is called on the node or its ancestors.
</constant>
<constant name="NOTIFICATION_EDITOR_PRE_SAVE" value="9001">
Notification received right before the scene with the node is saved in the editor. This notification is only sent in the Godot editor and will not occur in exported projects.
</constant>
@ -1237,6 +1268,15 @@
<constant name="FLAG_PROCESS_THREAD_MESSAGES_ALL" value="3" enum="ProcessThreadMessages" is_bitfield="true">
Allows this node to process threaded messages created with [method call_deferred_thread_group] right before either [method _process] or [method _physics_process] are called.
</constant>
<constant name="PHYSICS_INTERPOLATION_MODE_INHERIT" value="0" enum="PhysicsInterpolationMode">
Inherits [member physics_interpolation_mode] from the node's parent. This is the default for any newly created node.
</constant>
<constant name="PHYSICS_INTERPOLATION_MODE_ON" value="1" enum="PhysicsInterpolationMode">
Enables physics interpolation for this node and for children set to [constant PHYSICS_INTERPOLATION_MODE_INHERIT]. This is the default for the root node.
</constant>
<constant name="PHYSICS_INTERPOLATION_MODE_OFF" value="2" enum="PhysicsInterpolationMode">
Disables physics interpolation for this node and for children set to [constant PHYSICS_INTERPOLATION_MODE_INHERIT].
</constant>
<constant name="DUPLICATE_SIGNALS" value="1" enum="DuplicateFlags">
Duplicate the node's signal connections.
</constant>

View File

@ -25,6 +25,7 @@
<member name="limit_end" type="Vector2" setter="set_limit_end" getter="get_limit_end" default="Vector2(1e+07, 1e+07)">
Bottom-right limits for scrolling to end. If the camera is outside of this limit, the [Parallax2D] will stop scrolling. Must be higher than [member limit_begin] and the viewport size combined to work.
</member>
<member name="physics_interpolation_mode" type="int" setter="set_physics_interpolation_mode" getter="get_physics_interpolation_mode" overrides="Node" enum="Node.PhysicsInterpolationMode" default="2" />
<member name="repeat_size" type="Vector2" setter="set_repeat_size" getter="get_repeat_size" default="Vector2(0, 0)">
Repeats the [Texture2D] of each of this node's children and offsets them by this value. When scrolling, the node's position loops, giving the illusion of an infinite scrolling background if the values are larger than the screen size. If an axis is set to [code]0[/code], the [Texture2D] will not be repeated.
</member>

View File

@ -23,5 +23,6 @@
<member name="motion_scale" type="Vector2" setter="set_motion_scale" getter="get_motion_scale" default="Vector2(1, 1)">
Multiplies the ParallaxLayer's motion. If an axis is set to [code]0[/code], it will not scroll.
</member>
<member name="physics_interpolation_mode" type="int" setter="set_physics_interpolation_mode" getter="get_physics_interpolation_mode" overrides="Node" enum="Node.PhysicsInterpolationMode" default="2" />
</members>
</class>

View File

@ -2266,9 +2266,15 @@
Controls the maximum number of physics steps that can be simulated each rendered frame. The default value is tuned to avoid "spiral of death" situations where expensive physics simulations trigger more expensive simulations indefinitely. However, the game will appear to slow down if the rendering FPS is less than [code]1 / max_physics_steps_per_frame[/code] of [member physics/common/physics_ticks_per_second]. This occurs even if [code]delta[/code] is consistently used in physics calculations. To avoid this, increase [member physics/common/max_physics_steps_per_frame] if you have increased [member physics/common/physics_ticks_per_second] significantly above its default value.
[b]Note:[/b] This property is only read when the project starts. To change the maximum number of simulated physics steps per frame at runtime, set [member Engine.max_physics_steps_per_frame] instead.
</member>
<member name="physics/common/physics_interpolation" type="bool" setter="" getter="" default="false">
If [code]true[/code], the renderer will interpolate the transforms of physics objects between the last two transforms, so that smooth motion is seen even when physics ticks do not coincide with rendered frames. See also [member Node.physics_interpolation_mode] and [method Node.reset_physics_interpolation].
[b]Note:[/b] If [code]true[/code], the physics jitter fix should be disabled by setting [member physics/common/physics_jitter_fix] to [code]0.0[/code].
[b]Note:[/b] This property is only read when the project starts. To toggle physics interpolation at runtime, set [member SceneTree.physics_interpolation] instead.
[b]Note:[/b] This feature is currently only implemented in the 2D renderer.
</member>
<member name="physics/common/physics_jitter_fix" type="float" setter="" getter="" default="0.5">
Controls how much physics ticks are synchronized with real time. For 0 or less, the ticks are synchronized. Such values are recommended for network games, where clock synchronization matters. Higher values cause higher deviation of in-game clock and real clock, but allows smoothing out framerate jitters. The default value of 0.5 should be good enough for most; values above 2 could cause the game to react to dropped frames with a noticeable delay and are not recommended.
[b]Note:[/b] For best results, when using a custom physics interpolation solution, the physics jitter fix should be disabled by setting [member physics/common/physics_jitter_fix] to [code]0[/code].
[b]Note:[/b] When using a physics interpolation solution (such as enabling [member physics/common/physics_interpolation] or using a custom solution), the physics jitter fix should be disabled by setting [member physics/common/physics_jitter_fix] to [code]0.0[/code].
[b]Note:[/b] This property is only read when the project starts. To change the physics jitter fix at runtime, set [member Engine.physics_jitter_fix] instead.
</member>
<member name="physics/common/physics_ticks_per_second" type="int" setter="" getter="" default="60">

View File

@ -424,6 +424,14 @@
[b]Note:[/b] The equivalent node is [CanvasItem].
</description>
</method>
<method name="canvas_item_reset_physics_interpolation">
<return type="void" />
<param index="0" name="item" type="RID" />
<description>
Prevents physics interpolation for the current physics tick.
This is useful when moving a canvas item to a new location, to give an instantaneous change rather than interpolation from the previous location.
</description>
</method>
<method name="canvas_item_set_canvas_group_mode">
<return type="void" />
<param index="0" name="item" type="RID" />
@ -504,6 +512,14 @@
Sets the index for the [CanvasItem].
</description>
</method>
<method name="canvas_item_set_interpolated">
<return type="void" />
<param index="0" name="item" type="RID" />
<param index="1" name="interpolated" type="bool" />
<description>
If [param interpolated] is [code]true[/code], turns on physics interpolation for the canvas item.
</description>
</method>
<method name="canvas_item_set_light_mask">
<return type="void" />
<param index="0" name="item" type="RID" />
@ -612,6 +628,15 @@
Sets the [CanvasItem]'s Z index, i.e. its draw order (lower indexes are drawn first).
</description>
</method>
<method name="canvas_item_transform_physics_interpolation">
<return type="void" />
<param index="0" name="item" type="RID" />
<param index="1" name="transform" type="Transform2D" />
<description>
Transforms both the current and previous stored transform for a canvas item.
This allows transforming a canvas item without creating a "glitch" in the interpolation, which is particularly useful for large worlds utilising a shifting origin.
</description>
</method>
<method name="canvas_light_attach_to_canvas">
<return type="void" />
<param index="0" name="light" type="RID" />
@ -644,6 +669,14 @@
[b]Note:[/b] The equivalent node is [LightOccluder2D].
</description>
</method>
<method name="canvas_light_occluder_reset_physics_interpolation">
<return type="void" />
<param index="0" name="occluder" type="RID" />
<description>
Prevents physics interpolation for the current physics tick.
This is useful when moving an occluder to a new location, to give an instantaneous change rather than interpolation from the previous location.
</description>
</method>
<method name="canvas_light_occluder_set_as_sdf_collision">
<return type="void" />
<param index="0" name="occluder" type="RID" />
@ -659,6 +692,14 @@
Enables or disables light occluder.
</description>
</method>
<method name="canvas_light_occluder_set_interpolated">
<return type="void" />
<param index="0" name="occluder" type="RID" />
<param index="1" name="interpolated" type="bool" />
<description>
If [param interpolated] is [code]true[/code], turns on physics interpolation for the light occluder.
</description>
</method>
<method name="canvas_light_occluder_set_light_mask">
<return type="void" />
<param index="0" name="occluder" type="RID" />
@ -683,6 +724,23 @@
Sets a light occluder's [Transform2D].
</description>
</method>
<method name="canvas_light_occluder_transform_physics_interpolation">
<return type="void" />
<param index="0" name="occluder" type="RID" />
<param index="1" name="transform" type="Transform2D" />
<description>
Transforms both the current and previous stored transform for a light occluder.
This allows transforming an occluder without creating a "glitch" in the interpolation, which is particularly useful for large worlds utilising a shifting origin.
</description>
</method>
<method name="canvas_light_reset_physics_interpolation">
<return type="void" />
<param index="0" name="light" type="RID" />
<description>
Prevents physics interpolation for the current physics tick.
This is useful when moving a canvas item to a new location, to give an instantaneous change rather than interpolation from the previous location.
</description>
</method>
<method name="canvas_light_set_blend_mode">
<return type="void" />
<param index="0" name="light" type="RID" />
@ -723,6 +781,14 @@
Sets a canvas light's height.
</description>
</method>
<method name="canvas_light_set_interpolated">
<return type="void" />
<param index="0" name="light" type="RID" />
<param index="1" name="interpolated" type="bool" />
<description>
If [param interpolated] is [code]true[/code], turns on physics interpolation for the canvas light.
</description>
</method>
<method name="canvas_light_set_item_cull_mask">
<return type="void" />
<param index="0" name="light" type="RID" />
@ -829,6 +895,15 @@
Sets the Z range of objects that will be affected by this light. Equivalent to [member Light2D.range_z_min] and [member Light2D.range_z_max].
</description>
</method>
<method name="canvas_light_transform_physics_interpolation">
<return type="void" />
<param index="0" name="light" type="RID" />
<param index="1" name="transform" type="Transform2D" />
<description>
Transforms both the current and previous stored transform for a canvas light.
This allows transforming a light without creating a "glitch" in the interpolation, which is is particularly useful for large worlds utilising a shifting origin.
</description>
</method>
<method name="canvas_occluder_polygon_create">
<return type="RID" />
<description>

View File

@ -264,6 +264,10 @@
- 2D and 3D physics will be stopped, as well as collision detection and related signals.
- Depending on each node's [member Node.process_mode], their [method Node._process], [method Node._physics_process] and [method Node._input] callback methods may not called anymore.
</member>
<member name="physics_interpolation" type="bool" setter="set_physics_interpolation_enabled" getter="is_physics_interpolation_enabled" default="false">
If [code]true[/code], the renderer will interpolate the transforms of physics objects between the last two transforms, so that smooth motion is seen even when physics ticks do not coincide with rendered frames.
The default value of this property is controlled by [member ProjectSettings.physics/common/physics_interpolation].
</member>
<member name="quit_on_go_back" type="bool" setter="set_quit_on_go_back" getter="is_quit_on_go_back" default="true">
If [code]true[/code], the application quits automatically when navigating back (e.g. using the system "Back" button on Android).
To handle 'Go Back' button when this option is disabled, use [constant DisplayServer.WINDOW_EVENT_GO_BACK_REQUEST].

View File

@ -38,6 +38,7 @@
#include "core/config/project_settings.h"
#include "core/math/geometry_2d.h"
#include "core/math/transform_interpolator.h"
#include "servers/rendering/rendering_server_default.h"
#include "storage/config.h"
#include "storage/material_storage.h"
@ -226,7 +227,15 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_
ERR_CONTINUE(!clight);
}
Vector2 canvas_light_pos = p_canvas_transform.xform(l->xform.get_origin()); //convert light position to canvas coordinates, as all computation is done in canvas coords to avoid precision loss
Transform2D final_xform;
if (!RSG::canvas->_interpolation_data.interpolation_enabled || !l->interpolated) {
final_xform = l->xform_curr;
} else {
real_t f = Engine::get_singleton()->get_physics_interpolation_fraction();
TransformInterpolator::interpolate_transform_2d(l->xform_prev, l->xform_curr, final_xform, f);
}
// Convert light position to canvas coordinates, as all computation is done in canvas coordinates to avoid precision loss.
Vector2 canvas_light_pos = p_canvas_transform.xform(final_xform.get_origin());
state.light_uniforms[index].position[0] = canvas_light_pos.x;
state.light_uniforms[index].position[1] = canvas_light_pos.y;
@ -820,7 +829,7 @@ void RasterizerCanvasGLES3::_record_item_commands(const Item *p_item, RID p_rend
Transform2D base_transform = p_canvas_transform_inverse * p_item->final_transform;
if (p_offset.x || p_offset.y) {
base_transform *= Transform2D(0, p_offset / p_item->xform.get_scale());
base_transform *= Transform2D(0, p_offset / p_item->xform_curr.get_scale()); // TODO: Interpolate or explain why not needed.
}
Transform2D draw_transform; // Used by transform command

View File

@ -3967,6 +3967,11 @@ bool Main::iteration() {
PhysicsServer3D::get_singleton()->flush_queries();
#endif // _3D_DISABLED
// Prepare the fixed timestep interpolated nodes BEFORE they are updated
// by the physics server, otherwise the current and previous transforms
// may be the same, and no interpolation takes place.
OS::get_singleton()->get_main_loop()->iteration_prepare();
PhysicsServer2D::get_singleton()->sync();
PhysicsServer2D::get_singleton()->flush_queries();

View File

@ -299,6 +299,17 @@ int64_t MainTimerSync::DeltaSmoother::smooth_delta(int64_t p_delta) {
// before advance_core considers changing the physics_steps return from
// the typical values as defined by typical_physics_steps
double MainTimerSync::get_physics_jitter_fix() {
// Turn off jitter fix when using fixed timestep interpolation.
// Note this shouldn't be on UNTIL 3d interpolation is implemented,
// otherwise we will get people making 3d games with the physics_interpolation
// set to on getting jitter fix disabled unexpectedly.
#if 0
if (Engine::get_singleton()->is_physics_interpolation_enabled()) {
// Would be better to write a simple bypass for jitter fix but this will do to get started.
return 0.0;
}
#endif
return Engine::get_singleton()->get_physics_jitter_fix();
}

View File

@ -54,7 +54,12 @@ void Camera2D::_update_scroll() {
if (is_current()) {
ERR_FAIL_COND(custom_viewport && !ObjectDB::get_instance(custom_viewport_id));
Transform2D xform = get_camera_transform();
Transform2D xform;
if (is_physics_interpolated_and_enabled()) {
xform = _interpolation_data.xform_prev.interpolate_with(_interpolation_data.xform_curr, Engine::get_singleton()->get_physics_interpolation_fraction());
} else {
xform = get_camera_transform();
}
viewport->set_canvas_transform(xform);
@ -68,10 +73,20 @@ void Camera2D::_update_scroll() {
}
void Camera2D::_update_process_callback() {
if (_is_editing_in_editor()) {
if (is_physics_interpolated_and_enabled()) {
set_process_internal(is_current());
set_physics_process_internal(is_current());
#ifdef TOOLS_ENABLED
if (process_callback == CAMERA2D_PROCESS_IDLE) {
WARN_PRINT_ONCE("Camera2D overridden to physics process mode due to use of physics interpolation.");
}
#endif
} else if (_is_editing_in_editor()) {
set_process_internal(false);
set_physics_process_internal(false);
} else if (process_callback == CAMERA2D_PROCESS_IDLE) {
} else {
if (process_callback == CAMERA2D_PROCESS_IDLE) {
set_process_internal(true);
set_physics_process_internal(false);
} else {
@ -79,6 +94,7 @@ void Camera2D::_update_process_callback() {
set_physics_process_internal(true);
}
}
}
void Camera2D::set_zoom(const Vector2 &p_zoom) {
// Setting zoom to zero causes 'affine_invert' issues
@ -161,8 +177,15 @@ Transform2D Camera2D::get_camera_transform() {
}
}
// FIXME: There is a bug here, introduced before physics interpolation.
// Smoothing occurs rather confusingly during the call to get_camera_transform().
// It may be called MULTIPLE TIMES on certain frames,
// therefore smoothing is not currently applied only once per frame / tick,
// which will result in some haphazard results.
if (position_smoothing_enabled && !_is_editing_in_editor()) {
real_t c = position_smoothing_speed * (process_callback == CAMERA2D_PROCESS_PHYSICS ? get_physics_process_delta_time() : get_process_delta_time());
bool physics_process = (process_callback == CAMERA2D_PROCESS_PHYSICS) || is_physics_interpolated_and_enabled();
real_t delta = physics_process ? get_physics_process_delta_time() : get_process_delta_time();
real_t c = position_smoothing_speed * delta;
smoothed_camera_pos = ((camera_pos - smoothed_camera_pos) * c) + smoothed_camera_pos;
ret_camera_pos = smoothed_camera_pos;
//camera_pos=camera_pos*(1.0-position_smoothing_speed)+new_camera_pos*position_smoothing_speed;
@ -223,19 +246,54 @@ Transform2D Camera2D::get_camera_transform() {
return xform.affine_inverse();
}
void Camera2D::_ensure_update_interpolation_data() {
// The "curr -> previous" update can either occur
// on NOTIFICATION_INTERNAL_PHYSICS_PROCESS, OR
// on NOTIFICATION_TRANSFORM_CHANGED,
// if NOTIFICATION_TRANSFORM_CHANGED takes place earlier than
// NOTIFICATION_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;
}
}
void Camera2D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_INTERNAL_PROCESS:
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
case NOTIFICATION_INTERNAL_PROCESS: {
_update_scroll();
} break;
case NOTIFICATION_TRANSFORM_CHANGED: {
if (!position_smoothing_enabled || _is_editing_in_editor()) {
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
if (is_physics_interpolated_and_enabled()) {
_ensure_update_interpolation_data();
_interpolation_data.xform_curr = get_camera_transform();
} else {
_update_scroll();
}
} break;
case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: {
// Force the limits etc. to update.
_interpolation_data.xform_curr = get_camera_transform();
_interpolation_data.xform_prev = _interpolation_data.xform_curr;
} break;
case NOTIFICATION_TRANSFORM_CHANGED: {
if ((!position_smoothing_enabled && !is_physics_interpolated_and_enabled()) || _is_editing_in_editor()) {
_update_scroll();
}
if (is_physics_interpolated_and_enabled()) {
_ensure_update_interpolation_data();
_interpolation_data.xform_curr = get_camera_transform();
}
} break;
case NOTIFICATION_ENTER_TREE: {
ERR_FAIL_COND(!is_inside_tree());
if (custom_viewport && ObjectDB::get_instance(custom_viewport_id)) {
@ -260,6 +318,15 @@ void Camera2D::_notification(int p_what) {
_update_process_callback();
first = true;
_update_scroll();
// Note that NOTIFICATION_RESET_PHYSICS_INTERPOLATION
// is automatically called before this because Camera2D is inherited
// from CanvasItem. However, the camera transform is not up to date
// until this point, so we do an extra manual reset.
if (is_physics_interpolated_and_enabled()) {
_interpolation_data.xform_curr = get_camera_transform();
_interpolation_data.xform_prev = _interpolation_data.xform_curr;
}
} break;
case NOTIFICATION_EXIT_TREE: {
@ -431,12 +498,17 @@ void Camera2D::_make_current(Object *p_which) {
queue_redraw();
if (p_which == this) {
bool was_current = viewport->get_camera_2d() == this;
bool is_current = p_which == this;
if (is_current) {
viewport->_camera_2d_set(this);
} else {
if (viewport->get_camera_2d() == this) {
} else if (was_current) {
viewport->_camera_2d_set(nullptr);
}
if (is_current != was_current) {
_update_process_callback();
}
}
@ -456,6 +528,7 @@ void Camera2D::make_current() {
_make_current(this);
}
_update_scroll();
_update_process_callback();
}
void Camera2D::clear_current() {
@ -468,6 +541,8 @@ void Camera2D::clear_current() {
if (!custom_viewport || ObjectDB::get_instance(custom_viewport_id)) {
viewport->assign_next_enabled_camera_2d(group_name);
}
_update_process_callback();
}
bool Camera2D::is_current() const {

View File

@ -102,6 +102,14 @@ protected:
Camera2DProcessCallback process_callback = CAMERA2D_PROCESS_IDLE;
struct InterpolationData {
Transform2D xform_curr;
Transform2D xform_prev;
uint32_t last_update_physics_tick = 0;
} _interpolation_data;
void _ensure_update_interpolation_data();
Size2 _get_camera_screen_size() const;
protected:

View File

@ -197,6 +197,10 @@ Light2D::BlendMode Light2D::get_blend_mode() const {
return blend_mode;
}
void Light2D::_physics_interpolated_changed() {
RenderingServer::get_singleton()->canvas_light_set_interpolated(canvas_light, is_physics_interpolated());
}
void Light2D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
@ -212,6 +216,17 @@ void Light2D::_notification(int p_what) {
_update_light_visibility();
} break;
case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: {
if (is_visible_in_tree() && is_physics_interpolated()) {
// Explicitly make sure the transform is up to date in RenderingServer before
// resetting. This is necessary because NOTIFICATION_TRANSFORM_CHANGED
// is normally deferred, and a client change to transform will not always be sent
// before the reset, so we need to guarantee this.
RS::get_singleton()->canvas_light_set_transform(canvas_light, get_global_transform());
RS::get_singleton()->canvas_light_reset_physics_interpolation(canvas_light);
}
} break;
case NOTIFICATION_EXIT_TREE: {
RS::get_singleton()->canvas_light_attach_to_canvas(canvas_light, RID());
_update_light_visibility();

View File

@ -74,6 +74,7 @@ private:
void _update_light_visibility();
virtual void owner_changed_notify() override;
virtual void _physics_interpolated_changed() override;
protected:
_FORCE_INLINE_ RID _get_light() const { return canvas_light; }

View File

@ -158,6 +158,10 @@ void LightOccluder2D::_poly_changed() {
#endif
}
void LightOccluder2D::_physics_interpolated_changed() {
RenderingServer::get_singleton()->canvas_light_occluder_set_interpolated(occluder, is_physics_interpolated());
}
void LightOccluder2D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_CANVAS: {
@ -199,6 +203,17 @@ void LightOccluder2D::_notification(int p_what) {
case NOTIFICATION_EXIT_CANVAS: {
RS::get_singleton()->canvas_light_occluder_attach_to_canvas(occluder, RID());
} break;
case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: {
if (is_visible_in_tree() && is_physics_interpolated()) {
// Explicitly make sure the transform is up to date in RenderingServer before
// resetting. This is necessary because NOTIFICATION_TRANSFORM_CHANGED
// is normally deferred, and a client change to transform will not always be sent
// before the reset, so we need to guarantee this.
RS::get_singleton()->canvas_light_occluder_set_transform(occluder, get_global_transform());
RS::get_singleton()->canvas_light_occluder_reset_physics_interpolation(occluder);
}
} break;
}
}

View File

@ -86,6 +86,8 @@ class LightOccluder2D : public Node2D {
bool sdf_collision = false;
void _poly_changed();
virtual void _physics_interpolated_changed() override;
protected:
void _notification(int p_what);
static void _bind_methods();

View File

@ -129,6 +129,7 @@ void Parallax2D::_update_repeat() {
Point2 repeat_scale = repeat_size * get_scale();
RenderingServer::get_singleton()->canvas_set_item_repeat(get_canvas_item(), repeat_scale, repeat_times);
RenderingServer::get_singleton()->canvas_item_set_interpolated(get_canvas_item(), false);
}
void Parallax2D::set_scroll_scale(const Size2 &p_scale) {
@ -287,4 +288,6 @@ void Parallax2D::_bind_methods() {
}
Parallax2D::Parallax2D() {
// Parallax2D is always updated every frame so there is no need to interpolate.
set_physics_interpolation_mode(Node::PHYSICS_INTERPOLATION_MODE_OFF);
}

View File

@ -73,6 +73,7 @@ void ParallaxLayer::_update_mirroring() {
RID ci = get_canvas_item();
Point2 mirrorScale = mirroring * get_scale();
RenderingServer::get_singleton()->canvas_set_item_mirroring(c, ci, mirrorScale);
RenderingServer::get_singleton()->canvas_item_set_interpolated(ci, false);
}
}
@ -162,4 +163,6 @@ void ParallaxLayer::_bind_methods() {
}
ParallaxLayer::ParallaxLayer() {
// ParallaxLayer is always updated every frame so there is no need to interpolate.
set_physics_interpolation_mode(Node::PHYSICS_INTERPOLATION_MODE_OFF);
}

View File

@ -356,6 +356,14 @@ void TileMapLayer::_rendering_update() {
// Drawing the tile in the canvas item.
TileMap::draw_tile(ci, local_tile_pos - rendering_quadrant->canvas_items_position, tile_set, cell_data.cell.source_id, cell_data.cell.get_atlas_coords(), cell_data.cell.alternative_tile, -1, get_self_modulate(), tile_data, random_animation_offset);
}
// Reset physics interpolation for any recreated canvas items.
if (is_physics_interpolated_and_enabled() && is_visible_in_tree()) {
for (const RID &ci : rendering_quadrant->canvas_items) {
rs->canvas_item_reset_physics_interpolation(ci);
}
}
} else {
// Free the quadrant.
for (int i = 0; i < rendering_quadrant->canvas_items.size(); i++) {
@ -453,6 +461,15 @@ void TileMapLayer::_rendering_notification(int p_what) {
}
}
}
} else if (p_what == NOTIFICATION_RESET_PHYSICS_INTERPOLATION) {
for (const KeyValue<Vector2i, Ref<RenderingQuadrant>> &kv : rendering_quadrant_map) {
for (const RID &ci : kv.value->canvas_items) {
if (ci.is_null()) {
continue;
}
rs->canvas_item_reset_physics_interpolation(ci);
}
}
}
}

View File

@ -3690,6 +3690,8 @@ void Control::_bind_methods() {
Control::Control() {
data.theme_owner = memnew(ThemeOwner(this));
set_physics_interpolation_mode(Node::PHYSICS_INTERPOLATION_MODE_OFF);
}
Control::~Control() {

View File

@ -336,6 +336,19 @@ void CanvasItem::_notification(int p_what) {
get_parent()->connect(SNAME("child_order_changed"), callable_mp(get_viewport(), &Viewport::canvas_parent_mark_dirty).bind(get_parent()), CONNECT_REFERENCE_COUNTED);
}
// If using physics interpolation, reset for this node only,
// as a helper, as in most cases, users will want items reset when
// adding to the tree.
// In cases where they move immediately after adding,
// there will be little cost in having two resets as these are cheap,
// and it is worth it for convenience.
// Do not propagate to children, as each child of an added branch
// receives its own NOTIFICATION_ENTER_TREE, and this would
// cause unnecessary duplicate resets.
if (is_physics_interpolated_and_enabled()) {
notification(NOTIFICATION_RESET_PHYSICS_INTERPOLATION);
}
} break;
case NOTIFICATION_EXIT_TREE: {
ERR_MAIN_THREAD_GUARD;
@ -360,6 +373,12 @@ void CanvasItem::_notification(int p_what) {
}
} break;
case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: {
if (is_visible_in_tree() && is_physics_interpolated()) {
RenderingServer::get_singleton()->canvas_item_reset_physics_interpolation(canvas_item);
}
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
ERR_MAIN_THREAD_GUARD;
@ -921,6 +940,10 @@ void CanvasItem::_notify_transform(CanvasItem *p_node) {
}
}
void CanvasItem::_physics_interpolated_changed() {
RenderingServer::get_singleton()->canvas_item_set_interpolated(canvas_item, is_physics_interpolated());
}
Rect2 CanvasItem::get_viewport_rect() const {
ERR_READ_THREAD_GUARD_V(Rect2());
ERR_FAIL_COND_V(!is_inside_tree(), Rect2());

View File

@ -140,6 +140,8 @@ private:
void _notify_transform(CanvasItem *p_node);
virtual void _physics_interpolated_changed() override;
static CanvasItem *current_item_drawn;
friend class Viewport;
void _refresh_texture_repeat_cache() const;

View File

@ -99,6 +99,14 @@ void Node::_notification(int p_notification) {
}
}
if (data.physics_interpolation_mode == PHYSICS_INTERPOLATION_MODE_INHERIT) {
bool interpolate = true; // Root node default is for interpolation to be on.
if (data.parent) {
interpolate = data.parent->is_physics_interpolated();
}
_propagate_physics_interpolated(interpolate);
}
// Update auto translate mode.
if (data.auto_translate_mode == AUTO_TRANSLATE_MODE_INHERIT && !data.parent) {
ERR_PRINT("The root node can't be set to Inherit auto translate mode, reverting to Always instead.");
@ -395,6 +403,36 @@ void Node::_propagate_exit_tree() {
data.depth = -1;
}
void Node::_propagate_physics_interpolated(bool p_interpolated) {
switch (data.physics_interpolation_mode) {
case PHYSICS_INTERPOLATION_MODE_INHERIT:
// Keep the parent p_interpolated.
break;
case PHYSICS_INTERPOLATION_MODE_OFF: {
p_interpolated = false;
} break;
case PHYSICS_INTERPOLATION_MODE_ON: {
p_interpolated = true;
} break;
}
// No change? No need to propagate further.
if (data.physics_interpolated == p_interpolated) {
return;
}
data.physics_interpolated = p_interpolated;
// Allow a call to the RenderingServer etc. in derived classes.
_physics_interpolated_changed();
data.blocked++;
for (KeyValue<StringName, Node *> &K : data.children) {
K.value->_propagate_physics_interpolated(p_interpolated);
}
data.blocked--;
}
void Node::move_child(Node *p_child, int p_index) {
ERR_FAIL_COND_MSG(data.inside_tree && !Thread::is_main_thread(), "Moving child node positions inside the SceneTree is only allowed from the main thread. Use call_deferred(\"move_child\",child,index).");
ERR_FAIL_NULL(p_child);
@ -507,6 +545,8 @@ void Node::move_child_notify(Node *p_child) {
void Node::owner_changed_notify() {
}
void Node::_physics_interpolated_changed() {}
void Node::set_physics_process(bool p_process) {
ERR_THREAD_GUARD
if (data.physics_process == p_process) {
@ -821,6 +861,42 @@ bool Node::_can_process(bool p_paused) const {
}
}
void Node::set_physics_interpolation_mode(PhysicsInterpolationMode p_mode) {
ERR_THREAD_GUARD
if (data.physics_interpolation_mode == p_mode) {
return;
}
data.physics_interpolation_mode = p_mode;
bool interpolate = true; // Default for root node.
switch (p_mode) {
case PHYSICS_INTERPOLATION_MODE_INHERIT: {
if (is_inside_tree() && data.parent) {
interpolate = data.parent->is_physics_interpolated();
}
} break;
case PHYSICS_INTERPOLATION_MODE_OFF: {
interpolate = false;
} break;
case PHYSICS_INTERPOLATION_MODE_ON: {
interpolate = true;
} break;
}
// If swapping from interpolated to non-interpolated, use this as an extra means to cause a reset.
if (is_physics_interpolated() && !interpolate) {
reset_physics_interpolation();
}
_propagate_physics_interpolated(interpolate);
}
void Node::reset_physics_interpolation() {
propagate_notification(NOTIFICATION_RESET_PHYSICS_INTERPOLATION);
}
bool Node::_is_enabled() const {
ProcessMode process_mode;
@ -3489,6 +3565,12 @@ void Node::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_physics_process_internal", "enable"), &Node::set_physics_process_internal);
ClassDB::bind_method(D_METHOD("is_physics_processing_internal"), &Node::is_physics_processing_internal);
ClassDB::bind_method(D_METHOD("set_physics_interpolation_mode", "mode"), &Node::set_physics_interpolation_mode);
ClassDB::bind_method(D_METHOD("get_physics_interpolation_mode"), &Node::get_physics_interpolation_mode);
ClassDB::bind_method(D_METHOD("is_physics_interpolated"), &Node::is_physics_interpolated);
ClassDB::bind_method(D_METHOD("is_physics_interpolated_and_enabled"), &Node::is_physics_interpolated_and_enabled);
ClassDB::bind_method(D_METHOD("reset_physics_interpolation"), &Node::reset_physics_interpolation);
ClassDB::bind_method(D_METHOD("set_auto_translate_mode", "mode"), &Node::set_auto_translate_mode);
ClassDB::bind_method(D_METHOD("get_auto_translate_mode"), &Node::get_auto_translate_mode);
@ -3594,6 +3676,7 @@ void Node::_bind_methods() {
BIND_CONSTANT(NOTIFICATION_POST_ENTER_TREE);
BIND_CONSTANT(NOTIFICATION_DISABLED);
BIND_CONSTANT(NOTIFICATION_ENABLED);
BIND_CONSTANT(NOTIFICATION_RESET_PHYSICS_INTERPOLATION);
BIND_CONSTANT(NOTIFICATION_EDITOR_PRE_SAVE);
BIND_CONSTANT(NOTIFICATION_EDITOR_POST_SAVE);
@ -3633,6 +3716,10 @@ void Node::_bind_methods() {
BIND_BITFIELD_FLAG(FLAG_PROCESS_THREAD_MESSAGES_PHYSICS);
BIND_BITFIELD_FLAG(FLAG_PROCESS_THREAD_MESSAGES_ALL);
BIND_ENUM_CONSTANT(PHYSICS_INTERPOLATION_MODE_INHERIT);
BIND_ENUM_CONSTANT(PHYSICS_INTERPOLATION_MODE_ON);
BIND_ENUM_CONSTANT(PHYSICS_INTERPOLATION_MODE_OFF);
BIND_ENUM_CONSTANT(DUPLICATE_SIGNALS);
BIND_ENUM_CONSTANT(DUPLICATE_GROUPS);
BIND_ENUM_CONSTANT(DUPLICATE_SCRIPTS);
@ -3674,6 +3761,9 @@ void Node::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "process_thread_group_order"), "set_process_thread_group_order", "get_process_thread_group_order");
ADD_PROPERTY(PropertyInfo(Variant::INT, "process_thread_messages", PROPERTY_HINT_FLAGS, "Process,Physics Process"), "set_process_thread_messages", "get_process_thread_messages");
ADD_GROUP("Physics Interpolation", "physics_interpolation_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "physics_interpolation_mode", PROPERTY_HINT_ENUM, "Inherit,Off,On"), "set_physics_interpolation_mode", "get_physics_interpolation_mode");
ADD_GROUP("Auto Translate", "auto_translate_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "auto_translate_mode", PROPERTY_HINT_ENUM, "Inherit,Always,Disabled"), "set_auto_translate_mode", "get_auto_translate_mode");
@ -3708,6 +3798,35 @@ String Node::_get_name_num_separator() {
Node::Node() {
orphan_node_count++;
// Default member initializer for bitfield is a C++20 extension, so:
data.process_mode = PROCESS_MODE_INHERIT;
data.physics_interpolation_mode = PHYSICS_INTERPOLATION_MODE_INHERIT;
data.physics_process = false;
data.process = false;
data.physics_process_internal = false;
data.process_internal = false;
data.input = false;
data.shortcut_input = false;
data.unhandled_input = false;
data.unhandled_key_input = false;
data.physics_interpolated = false;
data.parent_owned = false;
data.in_constructor = true;
data.use_placeholder = false;
data.display_folded = false;
data.editable_instance = false;
data.inside_tree = false;
data.ready_notified = false; // This is a small hack, so if a node is added during _ready() to the tree, it correctly gets the _ready() notification.
data.ready_first = true;
}
Node::~Node() {

View File

@ -66,7 +66,9 @@ protected:
};
public:
enum ProcessMode {
// N.B. Any enum stored as a bitfield should be specified as UNSIGNED to work around
// some compilers trying to store it as signed, and requiring 1 more bit than necessary.
enum ProcessMode : unsigned int {
PROCESS_MODE_INHERIT, // same as parent node
PROCESS_MODE_PAUSABLE, // process only if not paused
PROCESS_MODE_WHEN_PAUSED, // process only if paused
@ -86,6 +88,12 @@ public:
FLAG_PROCESS_THREAD_MESSAGES_ALL = 3,
};
enum PhysicsInterpolationMode : unsigned int {
PHYSICS_INTERPOLATION_MODE_INHERIT,
PHYSICS_INTERPOLATION_MODE_ON,
PHYSICS_INTERPOLATION_MODE_OFF,
};
enum DuplicateFlags {
DUPLICATE_SIGNALS = 1,
DUPLICATE_GROUPS = 2,
@ -170,9 +178,7 @@ private:
int blocked = 0; // Safeguard that throws an error when attempting to modify the tree in a harmful way while being traversed.
StringName name;
SceneTree *tree = nullptr;
bool inside_tree = false;
bool ready_notified = false; // This is a small hack, so if a node is added during _ready() to the tree, it correctly gets the _ready() notification.
bool ready_first = true;
#ifdef TOOLS_ENABLED
NodePath import_path; // Path used when imported, used by scene editors to keep tracking.
#endif
@ -184,7 +190,6 @@ private:
List<Node *>::Element *OW = nullptr; // Owned element.
List<Node *> owned;
ProcessMode process_mode = PROCESS_MODE_INHERIT;
Node *process_owner = nullptr;
ProcessThreadGroup process_thread_group = PROCESS_THREAD_GROUP_INHERIT;
Node *process_thread_group_owner = nullptr;
@ -196,26 +201,39 @@ private:
Variant rpc_config;
// Variables used to properly sort the node when processing, ignored otherwise.
// TODO: Should move all the stuff below to bits.
bool physics_process = false;
bool process = false;
int process_priority = 0;
int physics_process_priority = 0;
bool physics_process_internal = false;
bool process_internal = false;
// Keep bitpacked values together to get better packing.
ProcessMode process_mode : 3;
PhysicsInterpolationMode physics_interpolation_mode : 2;
bool input = false;
bool shortcut_input = false;
bool unhandled_input = false;
bool unhandled_key_input = false;
bool physics_process : 1;
bool process : 1;
bool parent_owned = false;
bool in_constructor = true;
bool use_placeholder = false;
bool physics_process_internal : 1;
bool process_internal : 1;
bool display_folded = false;
bool editable_instance = false;
bool input : 1;
bool shortcut_input : 1;
bool unhandled_input : 1;
bool unhandled_key_input : 1;
// Physics interpolation can be turned on and off on a per node basis.
// This only takes effect when the SceneTree (or project setting) physics interpolation
// is switched on.
bool physics_interpolated : 1;
bool parent_owned : 1;
bool in_constructor : 1;
bool use_placeholder : 1;
bool display_folded : 1;
bool editable_instance : 1;
bool inside_tree : 1;
bool ready_notified : 1;
bool ready_first : 1;
AutoTranslateMode auto_translate_mode = AUTO_TRANSLATE_MODE_INHERIT;
mutable bool is_auto_translating = true;
@ -243,6 +261,7 @@ private:
void _propagate_ready();
void _propagate_exit_tree();
void _propagate_after_exit_tree();
void _propagate_physics_interpolated(bool p_interpolated);
void _propagate_process_owner(Node *p_owner, int p_pause_notification, int p_enabled_notification);
void _propagate_groups_dirty();
Array _get_node_and_resource(const NodePath &p_path);
@ -295,6 +314,8 @@ protected:
void _notification(int p_notification);
virtual void _physics_interpolated_changed();
virtual void add_child_notify(Node *p_child);
virtual void remove_child_notify(Node *p_child);
virtual void move_child_notify(Node *p_child);
@ -339,7 +360,7 @@ protected:
public:
enum {
// you can make your own, but don't use the same numbers as other notifications in other nodes
// You can make your own, but don't use the same numbers as other notifications in other nodes.
NOTIFICATION_ENTER_TREE = 10,
NOTIFICATION_EXIT_TREE = 11,
NOTIFICATION_MOVED_IN_PARENT = 12,
@ -360,8 +381,8 @@ public:
NOTIFICATION_POST_ENTER_TREE = 27,
NOTIFICATION_DISABLED = 28,
NOTIFICATION_ENABLED = 29,
//keep these linked to node
NOTIFICATION_RESET_PHYSICS_INTERPOLATION = 2001, // A GodotSpace Odyssey.
// Keep these linked to Node.
NOTIFICATION_WM_MOUSE_ENTER = 1002,
NOTIFICATION_WM_MOUSE_EXIT = 1003,
NOTIFICATION_WM_WINDOW_FOCUS_IN = 1004,
@ -613,6 +634,13 @@ public:
ProcessMode get_process_mode() const;
bool can_process() const;
bool can_process_notification(int p_what) const;
void set_physics_interpolation_mode(PhysicsInterpolationMode p_mode);
PhysicsInterpolationMode get_physics_interpolation_mode() const { return data.physics_interpolation_mode; }
_FORCE_INLINE_ bool is_physics_interpolated() const { return data.physics_interpolated; }
_FORCE_INLINE_ bool is_physics_interpolated_and_enabled() const { return is_inside_tree() && get_tree()->is_physics_interpolation_enabled() && is_physics_interpolated(); }
void reset_physics_interpolation();
bool is_enabled() const;
bool is_ready() const;
@ -742,6 +770,7 @@ VARIANT_ENUM_CAST(Node::ProcessMode);
VARIANT_ENUM_CAST(Node::ProcessThreadGroup);
VARIANT_BITFIELD_CAST(Node::ProcessThreadMessages);
VARIANT_ENUM_CAST(Node::InternalMode);
VARIANT_ENUM_CAST(Node::PhysicsInterpolationMode);
VARIANT_ENUM_CAST(Node::AutoTranslateMode);
typedef HashSet<Node *, Node::Comparator> NodeSet;

View File

@ -451,6 +451,30 @@ void SceneTree::initialize() {
root->_set_tree(this);
}
void SceneTree::set_physics_interpolation_enabled(bool p_enabled) {
// We never want interpolation in the editor.
if (Engine::get_singleton()->is_editor_hint()) {
p_enabled = false;
}
if (p_enabled == _physics_interpolation_enabled) {
return;
}
_physics_interpolation_enabled = p_enabled;
RenderingServer::get_singleton()->set_physics_interpolation_enabled(p_enabled);
}
bool SceneTree::is_physics_interpolation_enabled() const {
return _physics_interpolation_enabled;
}
void SceneTree::iteration_prepare() {
if (_physics_interpolation_enabled) {
RenderingServer::get_singleton()->tick();
}
}
bool SceneTree::physics_process(double p_time) {
root_lock++;
@ -1606,6 +1630,9 @@ void SceneTree::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_frame"), &SceneTree::get_frame);
ClassDB::bind_method(D_METHOD("quit", "exit_code"), &SceneTree::quit, DEFVAL(EXIT_SUCCESS));
ClassDB::bind_method(D_METHOD("set_physics_interpolation_enabled", "enabled"), &SceneTree::set_physics_interpolation_enabled);
ClassDB::bind_method(D_METHOD("is_physics_interpolation_enabled"), &SceneTree::is_physics_interpolation_enabled);
ClassDB::bind_method(D_METHOD("queue_delete", "obj"), &SceneTree::queue_delete);
MethodInfo mi;
@ -1657,6 +1684,7 @@ void SceneTree::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "current_scene", PROPERTY_HINT_RESOURCE_TYPE, "Node", PROPERTY_USAGE_NONE), "set_current_scene", "get_current_scene");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "root", PROPERTY_HINT_RESOURCE_TYPE, "Node", PROPERTY_USAGE_NONE), "", "get_root");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "multiplayer_poll"), "set_multiplayer_poll_enabled", "is_multiplayer_poll_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "physics_interpolation"), "set_physics_interpolation_enabled", "is_physics_interpolation_enabled");
ADD_SIGNAL(MethodInfo("tree_changed"));
ADD_SIGNAL(MethodInfo("tree_process_mode_changed")); //editor only signal, but due to API hash it can't be removed in run-time
@ -1748,6 +1776,8 @@ SceneTree::SceneTree() {
root->set_as_audio_listener_3d(true);
#endif // _3D_DISABLED
set_physics_interpolation_enabled(GLOBAL_DEF("physics/common/physics_interpolation", false));
// Initialize network state.
set_multiplayer(MultiplayerAPI::create_default_interface());

View File

@ -139,6 +139,8 @@ private:
HashMap<StringName, Group> group_map;
bool _quit = false;
bool _physics_interpolation_enabled = false;
StringName tree_changed_name = "tree_changed";
StringName node_added_name = "node_added";
StringName node_removed_name = "node_removed";
@ -313,6 +315,8 @@ public:
virtual void initialize() override;
virtual void iteration_prepare() override;
virtual bool physics_process(double p_time) override;
virtual bool process(double p_time) override;
@ -425,6 +429,9 @@ public:
void set_disable_node_threading(bool p_disable);
//default texture settings
void set_physics_interpolation_enabled(bool p_enabled);
bool is_physics_interpolation_enabled() const;
SceneTree();
~SceneTree();
};

View File

@ -32,6 +32,7 @@
#include "core/config/project_settings.h"
#include "core/math/geometry_2d.h"
#include "core/math/transform_interpolator.h"
#include "renderer_viewport.h"
#include "rendering_server_default.h"
#include "rendering_server_globals.h"
@ -81,7 +82,7 @@ void _collect_ysort_children(RendererCanvasCull::Item *p_canvas_item, Transform2
if (r_items) {
r_items[r_index] = child_items[i];
child_items[i]->ysort_xform = p_transform;
child_items[i]->ysort_pos = p_transform.xform(child_items[i]->xform.columns[2]);
child_items[i]->ysort_pos = p_transform.xform(child_items[i]->xform_curr.columns[2]);
child_items[i]->material_owner = child_items[i]->use_parent_material ? p_material_owner : nullptr;
child_items[i]->ysort_modulate = p_modulate;
child_items[i]->ysort_index = r_index;
@ -98,7 +99,7 @@ void _collect_ysort_children(RendererCanvasCull::Item *p_canvas_item, Transform2
r_index++;
if (child_items[i]->sort_y) {
_collect_ysort_children(child_items[i], p_transform * child_items[i]->xform, child_items[i]->use_parent_material ? p_material_owner : child_items[i], p_modulate * child_items[i]->modulate, r_items, r_index, abs_z);
_collect_ysort_children(child_items[i], p_transform * child_items[i]->xform_curr, child_items[i]->use_parent_material ? p_material_owner : child_items[i], p_modulate * child_items[i]->modulate, r_items, r_index, abs_z);
}
}
}
@ -244,7 +245,14 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
}
}
Transform2D xform = ci->xform;
Transform2D final_xform;
if (!_interpolation_data.interpolation_enabled || !ci->interpolated) {
final_xform = ci->xform_curr;
} else {
real_t f = Engine::get_singleton()->get_physics_interpolation_fraction();
TransformInterpolator::interpolate_transform_2d(ci->xform_prev, ci->xform_curr, final_xform, f);
}
Transform2D parent_xform = p_parent_xform;
Point2 repeat_size = p_repeat_size;
@ -258,19 +266,19 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
ci->repeat_times = repeat_times;
if (repeat_size.x || repeat_size.y) {
rect.size += repeat_size * repeat_times / ci->xform.get_scale();
rect.size += repeat_size * repeat_times / final_xform.get_scale();
rect.position -= repeat_size * (repeat_times / 2);
}
}
if (snapping_2d_transforms_to_pixel) {
xform.columns[2] = xform.columns[2].round();
final_xform.columns[2] = final_xform.columns[2].round();
parent_xform.columns[2] = parent_xform.columns[2].round();
}
xform = parent_xform * xform;
final_xform = parent_xform * final_xform;
Rect2 global_rect = xform.xform(rect);
Rect2 global_rect = final_xform.xform(rect);
global_rect.position += p_clip_rect.position;
if (ci->use_parent_material && p_material_owner) {
@ -324,7 +332,7 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
child_item_count = ci->ysort_children_count + 1;
child_items = (Item **)alloca(child_item_count * sizeof(Item *));
ci->ysort_xform = ci->xform.affine_inverse();
ci->ysort_xform = final_xform.affine_inverse();
ci->ysort_pos = Vector2();
ci->ysort_modulate = Color(1, 1, 1, 1);
ci->ysort_index = 0;
@ -337,7 +345,7 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
sorter.sort(child_items, child_item_count);
for (i = 0; i < child_item_count; i++) {
_cull_canvas_item(child_items[i], xform * child_items[i]->ysort_xform, p_clip_rect, modulate * child_items[i]->ysort_modulate, child_items[i]->ysort_parent_abs_z_index, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, (Item *)child_items[i]->material_owner, false, p_canvas_cull_mask, repeat_size, repeat_times);
_cull_canvas_item(child_items[i], final_xform * child_items[i]->ysort_xform, p_clip_rect, modulate * child_items[i]->ysort_modulate, child_items[i]->ysort_parent_abs_z_index, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, (Item *)child_items[i]->material_owner, false, p_canvas_cull_mask, repeat_size, repeat_times);
}
} else {
RendererCanvasRender::Item *canvas_group_from = nullptr;
@ -347,7 +355,7 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
canvas_group_from = r_z_last_list[zidx];
}
_attach_canvas_item_for_draw(ci, p_canvas_clip, r_z_list, r_z_last_list, xform, p_clip_rect, global_rect, modulate, p_z, p_material_owner, use_canvas_group, canvas_group_from);
_attach_canvas_item_for_draw(ci, p_canvas_clip, r_z_list, r_z_last_list, final_xform, p_clip_rect, global_rect, modulate, p_z, p_material_owner, use_canvas_group, canvas_group_from);
}
} else {
RendererCanvasRender::Item *canvas_group_from = nullptr;
@ -361,14 +369,14 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
if (!child_items[i]->behind && !use_canvas_group) {
continue;
}
_cull_canvas_item(child_items[i], xform, p_clip_rect, modulate, p_z, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, p_material_owner, true, p_canvas_cull_mask, repeat_size, repeat_times);
_cull_canvas_item(child_items[i], final_xform, p_clip_rect, modulate, p_z, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, p_material_owner, true, p_canvas_cull_mask, repeat_size, repeat_times);
}
_attach_canvas_item_for_draw(ci, p_canvas_clip, r_z_list, r_z_last_list, xform, p_clip_rect, global_rect, modulate, p_z, p_material_owner, use_canvas_group, canvas_group_from);
_attach_canvas_item_for_draw(ci, p_canvas_clip, r_z_list, r_z_last_list, final_xform, p_clip_rect, global_rect, modulate, p_z, p_material_owner, use_canvas_group, canvas_group_from);
for (int i = 0; i < child_item_count; i++) {
if (child_items[i]->behind || use_canvas_group) {
continue;
}
_cull_canvas_item(child_items[i], xform, p_clip_rect, modulate, p_z, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, p_material_owner, true, p_canvas_cull_mask, repeat_size, repeat_times);
_cull_canvas_item(child_items[i], final_xform, p_clip_rect, modulate, p_z, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, p_material_owner, true, p_canvas_cull_mask, repeat_size, repeat_times);
}
}
}
@ -512,7 +520,16 @@ void RendererCanvasCull::canvas_item_set_transform(RID p_item, const Transform2D
Item *canvas_item = canvas_item_owner.get_or_null(p_item);
ERR_FAIL_NULL(canvas_item);
canvas_item->xform = p_transform;
if (_interpolation_data.interpolation_enabled && canvas_item->interpolated) {
if (!canvas_item->on_interpolate_transform_list) {
_interpolation_data.canvas_item_transform_update_list_curr->push_back(p_item);
canvas_item->on_interpolate_transform_list = true;
} else {
DEV_ASSERT(_interpolation_data.canvas_item_transform_update_list_curr->size() > 0);
}
}
canvas_item->xform_curr = p_transform;
}
void RendererCanvasCull::canvas_item_set_visibility_layer(RID p_item, uint32_t p_visibility_layer) {
@ -1622,6 +1639,26 @@ bool RendererCanvasCull::canvas_item_get_debug_redraw() const {
return debug_redraw;
}
void RendererCanvasCull::canvas_item_set_interpolated(RID p_item, bool p_interpolated) {
Item *canvas_item = canvas_item_owner.get_or_null(p_item);
ERR_FAIL_NULL(canvas_item);
canvas_item->interpolated = p_interpolated;
}
void RendererCanvasCull::canvas_item_reset_physics_interpolation(RID p_item) {
Item *canvas_item = canvas_item_owner.get_or_null(p_item);
ERR_FAIL_NULL(canvas_item);
canvas_item->xform_prev = canvas_item->xform_curr;
}
// Useful especially for origin shifting.
void RendererCanvasCull::canvas_item_transform_physics_interpolation(RID p_item, const Transform2D &p_transform) {
Item *canvas_item = canvas_item_owner.get_or_null(p_item);
ERR_FAIL_NULL(canvas_item);
canvas_item->xform_prev = p_transform * canvas_item->xform_prev;
canvas_item->xform_curr = p_transform * canvas_item->xform_curr;
}
void RendererCanvasCull::canvas_item_set_canvas_group_mode(RID p_item, RS::CanvasGroupMode p_mode, float p_clear_margin, bool p_fit_empty, float p_fit_margin, bool p_blur_mipmaps) {
Item *canvas_item = canvas_item_owner.get_or_null(p_item);
ERR_FAIL_NULL(canvas_item);
@ -1720,7 +1757,16 @@ void RendererCanvasCull::canvas_light_set_transform(RID p_light, const Transform
RendererCanvasRender::Light *clight = canvas_light_owner.get_or_null(p_light);
ERR_FAIL_NULL(clight);
clight->xform = p_transform;
if (_interpolation_data.interpolation_enabled && clight->interpolated) {
if (!clight->on_interpolate_transform_list) {
_interpolation_data.canvas_light_transform_update_list_curr->push_back(p_light);
clight->on_interpolate_transform_list = true;
} else {
DEV_ASSERT(_interpolation_data.canvas_light_transform_update_list_curr->size() > 0);
}
}
clight->xform_curr = p_transform;
}
void RendererCanvasCull::canvas_light_set_texture(RID p_light, RID p_texture) {
@ -1839,6 +1885,25 @@ void RendererCanvasCull::canvas_light_set_shadow_smooth(RID p_light, float p_smo
clight->shadow_smooth = p_smooth;
}
void RendererCanvasCull::canvas_light_set_interpolated(RID p_light, bool p_interpolated) {
RendererCanvasRender::Light *clight = canvas_light_owner.get_or_null(p_light);
ERR_FAIL_NULL(clight);
clight->interpolated = p_interpolated;
}
void RendererCanvasCull::canvas_light_reset_physics_interpolation(RID p_light) {
RendererCanvasRender::Light *clight = canvas_light_owner.get_or_null(p_light);
ERR_FAIL_NULL(clight);
clight->xform_prev = clight->xform_curr;
}
void RendererCanvasCull::canvas_light_transform_physics_interpolation(RID p_light, const Transform2D &p_transform) {
RendererCanvasRender::Light *clight = canvas_light_owner.get_or_null(p_light);
ERR_FAIL_NULL(clight);
clight->xform_prev = p_transform * clight->xform_prev;
clight->xform_curr = p_transform * clight->xform_curr;
}
RID RendererCanvasCull::canvas_light_occluder_allocate() {
return canvas_light_occluder_owner.allocate_rid();
}
@ -1911,7 +1976,16 @@ void RendererCanvasCull::canvas_light_occluder_set_transform(RID p_occluder, con
RendererCanvasRender::LightOccluderInstance *occluder = canvas_light_occluder_owner.get_or_null(p_occluder);
ERR_FAIL_NULL(occluder);
occluder->xform = p_xform;
if (_interpolation_data.interpolation_enabled && occluder->interpolated) {
if (!occluder->on_interpolate_transform_list) {
_interpolation_data.canvas_light_occluder_transform_update_list_curr->push_back(p_occluder);
occluder->on_interpolate_transform_list = true;
} else {
DEV_ASSERT(_interpolation_data.canvas_light_occluder_transform_update_list_curr->size() > 0);
}
}
occluder->xform_curr = p_xform;
}
void RendererCanvasCull::canvas_light_occluder_set_light_mask(RID p_occluder, int p_mask) {
@ -1921,6 +1995,25 @@ void RendererCanvasCull::canvas_light_occluder_set_light_mask(RID p_occluder, in
occluder->light_mask = p_mask;
}
void RendererCanvasCull::canvas_light_occluder_set_interpolated(RID p_occluder, bool p_interpolated) {
RendererCanvasRender::LightOccluderInstance *occluder = canvas_light_occluder_owner.get_or_null(p_occluder);
ERR_FAIL_NULL(occluder);
occluder->interpolated = p_interpolated;
}
void RendererCanvasCull::canvas_light_occluder_reset_physics_interpolation(RID p_occluder) {
RendererCanvasRender::LightOccluderInstance *occluder = canvas_light_occluder_owner.get_or_null(p_occluder);
ERR_FAIL_NULL(occluder);
occluder->xform_prev = occluder->xform_curr;
}
void RendererCanvasCull::canvas_light_occluder_transform_physics_interpolation(RID p_occluder, const Transform2D &p_transform) {
RendererCanvasRender::LightOccluderInstance *occluder = canvas_light_occluder_owner.get_or_null(p_occluder);
ERR_FAIL_NULL(occluder);
occluder->xform_prev = p_transform * occluder->xform_prev;
occluder->xform_curr = p_transform * occluder->xform_curr;
}
RID RendererCanvasCull::canvas_occluder_polygon_allocate() {
return canvas_light_occluder_polygon_owner.allocate_rid();
}
@ -2075,6 +2168,7 @@ bool RendererCanvasCull::free(RID p_rid) {
} else if (canvas_item_owner.owns(p_rid)) {
Item *canvas_item = canvas_item_owner.get_or_null(p_rid);
ERR_FAIL_NULL_V(canvas_item, true);
_interpolation_data.notify_free_canvas_item(p_rid, *canvas_item);
if (canvas_item->parent.is_valid()) {
if (canvas_owner.owns(canvas_item->parent)) {
@ -2114,6 +2208,7 @@ bool RendererCanvasCull::free(RID p_rid) {
} else if (canvas_light_owner.owns(p_rid)) {
RendererCanvasRender::Light *canvas_light = canvas_light_owner.get_or_null(p_rid);
ERR_FAIL_NULL_V(canvas_light, true);
_interpolation_data.notify_free_canvas_light(p_rid, *canvas_light);
if (canvas_light->canvas.is_valid()) {
Canvas *canvas = canvas_owner.get_or_null(canvas_light->canvas);
@ -2129,6 +2224,7 @@ bool RendererCanvasCull::free(RID p_rid) {
} else if (canvas_light_occluder_owner.owns(p_rid)) {
RendererCanvasRender::LightOccluderInstance *occluder = canvas_light_occluder_owner.get_or_null(p_rid);
ERR_FAIL_NULL_V(occluder, true);
_interpolation_data.notify_free_canvas_light_occluder(p_rid, *occluder);
if (occluder->polygon.is_valid()) {
LightOccluderPolygon *occluder_poly = canvas_light_occluder_polygon_owner.get_or_null(occluder->polygon);
@ -2186,6 +2282,81 @@ void RendererCanvasCull::finalize() {
_free_rids(canvas_light_occluder_polygon_owner, "CanvasLightOccluderPolygon");
}
void RendererCanvasCull::tick() {
if (_interpolation_data.interpolation_enabled) {
update_interpolation_tick(true);
}
}
void RendererCanvasCull::update_interpolation_tick(bool p_process) {
#define GODOT_UPDATE_INTERPOLATION_TICK(m_list_prev, m_list_curr, m_type, m_owner_list) \
/* Detect any that were on the previous transform list that are no longer active. */ \
for (unsigned int n = 0; n < _interpolation_data.m_list_prev->size(); n++) { \
const RID &rid = (*_interpolation_data.m_list_prev)[n]; \
m_type *item = m_owner_list.get_or_null(rid); \
/* no longer active? (either the instance deleted or no longer being transformed) */ \
if (item && !item->on_interpolate_transform_list) { \
item->xform_prev = item->xform_curr; \
} \
} \
/* and now for any in the transform list (being actively interpolated), */ \
/* keep the previous transform value up to date and ready for next tick */ \
if (p_process) { \
for (unsigned int n = 0; n < _interpolation_data.m_list_curr->size(); n++) { \
const RID &rid = (*_interpolation_data.m_list_curr)[n]; \
m_type *item = m_owner_list.get_or_null(rid); \
if (item) { \
item->xform_prev = item->xform_curr; \
item->on_interpolate_transform_list = false; \
} \
} \
} \
SWAP(_interpolation_data.m_list_curr, _interpolation_data.m_list_prev); \
_interpolation_data.m_list_curr->clear();
GODOT_UPDATE_INTERPOLATION_TICK(canvas_item_transform_update_list_prev, canvas_item_transform_update_list_curr, Item, canvas_item_owner);
GODOT_UPDATE_INTERPOLATION_TICK(canvas_light_transform_update_list_prev, canvas_light_transform_update_list_curr, RendererCanvasRender::Light, canvas_light_owner);
GODOT_UPDATE_INTERPOLATION_TICK(canvas_light_occluder_transform_update_list_prev, canvas_light_occluder_transform_update_list_curr, RendererCanvasRender::LightOccluderInstance, canvas_light_occluder_owner);
#undef GODOT_UPDATE_INTERPOLATION_TICK
}
void RendererCanvasCull::InterpolationData::notify_free_canvas_item(RID p_rid, RendererCanvasCull::Item &r_canvas_item) {
r_canvas_item.on_interpolate_transform_list = false;
if (!interpolation_enabled) {
return;
}
// If the instance was on any of the lists, remove.
canvas_item_transform_update_list_curr->erase_multiple_unordered(p_rid);
canvas_item_transform_update_list_prev->erase_multiple_unordered(p_rid);
}
void RendererCanvasCull::InterpolationData::notify_free_canvas_light(RID p_rid, RendererCanvasRender::Light &r_canvas_light) {
r_canvas_light.on_interpolate_transform_list = false;
if (!interpolation_enabled) {
return;
}
// If the instance was on any of the lists, remove.
canvas_light_transform_update_list_curr->erase_multiple_unordered(p_rid);
canvas_light_transform_update_list_prev->erase_multiple_unordered(p_rid);
}
void RendererCanvasCull::InterpolationData::notify_free_canvas_light_occluder(RID p_rid, RendererCanvasRender::LightOccluderInstance &r_canvas_light_occluder) {
r_canvas_light_occluder.on_interpolate_transform_list = false;
if (!interpolation_enabled) {
return;
}
// If the instance was on any of the lists, remove.
canvas_light_occluder_transform_update_list_curr->erase_multiple_unordered(p_rid);
canvas_light_occluder_transform_update_list_prev->erase_multiple_unordered(p_rid);
}
RendererCanvasCull::RendererCanvasCull() {
z_list = (RendererCanvasRender::Item **)memalloc(z_range * sizeof(RendererCanvasRender::Item *));
z_last_list = (RendererCanvasRender::Item **)memalloc(z_range * sizeof(RendererCanvasRender::Item *));

View File

@ -271,6 +271,10 @@ public:
void canvas_item_set_debug_redraw(bool p_enabled);
bool canvas_item_get_debug_redraw() const;
void canvas_item_set_interpolated(RID p_item, bool p_interpolated);
void canvas_item_reset_physics_interpolation(RID p_item);
void canvas_item_transform_physics_interpolation(RID p_item, const Transform2D &p_transform);
RID canvas_light_allocate();
void canvas_light_initialize(RID p_rid);
@ -297,6 +301,10 @@ public:
void canvas_light_set_shadow_color(RID p_light, const Color &p_color);
void canvas_light_set_shadow_smooth(RID p_light, float p_smooth);
void canvas_light_set_interpolated(RID p_light, bool p_interpolated);
void canvas_light_reset_physics_interpolation(RID p_light);
void canvas_light_transform_physics_interpolation(RID p_light, const Transform2D &p_transform);
RID canvas_light_occluder_allocate();
void canvas_light_occluder_initialize(RID p_rid);
@ -307,6 +315,10 @@ public:
void canvas_light_occluder_set_transform(RID p_occluder, const Transform2D &p_xform);
void canvas_light_occluder_set_light_mask(RID p_occluder, int p_mask);
void canvas_light_occluder_set_interpolated(RID p_occluder, bool p_interpolated);
void canvas_light_occluder_reset_physics_interpolation(RID p_occluder);
void canvas_light_occluder_transform_physics_interpolation(RID p_occluder, const Transform2D &p_transform);
RID canvas_occluder_polygon_allocate();
void canvas_occluder_polygon_initialize(RID p_rid);
@ -336,6 +348,32 @@ public:
void finalize();
/* INTERPOLATION */
void tick();
void update_interpolation_tick(bool p_process = true);
void set_physics_interpolation_enabled(bool p_enabled) { _interpolation_data.interpolation_enabled = p_enabled; }
struct InterpolationData {
void notify_free_canvas_item(RID p_rid, RendererCanvasCull::Item &r_canvas_item);
void notify_free_canvas_light(RID p_rid, RendererCanvasRender::Light &r_canvas_light);
void notify_free_canvas_light_occluder(RID p_rid, RendererCanvasRender::LightOccluderInstance &r_canvas_light_occluder);
LocalVector<RID> canvas_item_transform_update_lists[2];
LocalVector<RID> *canvas_item_transform_update_list_curr = &canvas_item_transform_update_lists[0];
LocalVector<RID> *canvas_item_transform_update_list_prev = &canvas_item_transform_update_lists[1];
LocalVector<RID> canvas_light_transform_update_lists[2];
LocalVector<RID> *canvas_light_transform_update_list_curr = &canvas_light_transform_update_lists[0];
LocalVector<RID> *canvas_light_transform_update_list_prev = &canvas_light_transform_update_lists[1];
LocalVector<RID> canvas_light_occluder_transform_update_lists[2];
LocalVector<RID> *canvas_light_occluder_transform_update_list_curr = &canvas_light_occluder_transform_update_lists[0];
LocalVector<RID> *canvas_light_occluder_transform_update_list_prev = &canvas_light_occluder_transform_update_lists[1];
bool interpolation_enabled = false;
} _interpolation_data;
RendererCanvasCull();
~RendererCanvasCull();
};

View File

@ -51,9 +51,12 @@ public:
};
struct Light {
bool enabled;
bool enabled : 1;
bool on_interpolate_transform_list : 1;
bool interpolated : 1;
Color color;
Transform2D xform;
Transform2D xform_curr;
Transform2D xform_prev;
float height;
float energy;
float scale;
@ -97,6 +100,8 @@ public:
Light() {
version = 0;
enabled = true;
on_interpolate_transform_list = false;
interpolated = true;
color = Color(1, 1, 1);
shadow_color = Color(0, 0, 0, 0);
height = 0;
@ -307,11 +312,17 @@ public:
Rect2 rect;
};
Transform2D xform;
bool clip;
bool visible;
bool behind;
bool update_when_visible;
// For interpolation we store the current local xform,
// and the previous xform from the previous tick.
Transform2D xform_curr;
Transform2D xform_prev;
bool clip : 1;
bool visible : 1;
bool behind : 1;
bool update_when_visible : 1;
bool on_interpolate_transform_list : 1;
bool interpolated : 1;
struct CanvasGroup {
RS::CanvasGroupMode mode;
@ -472,6 +483,8 @@ public:
texture_filter = RS::CANVAS_ITEM_TEXTURE_FILTER_DEFAULT;
texture_repeat = RS::CANVAS_ITEM_TEXTURE_REPEAT_DEFAULT;
repeat_source = false;
on_interpolate_transform_list = false;
interpolated = true;
}
virtual ~Item() {
clear();
@ -487,12 +500,15 @@ public:
virtual void canvas_render_items(RID p_to_render_target, Item *p_item_list, const Color &p_modulate, Light *p_light_list, Light *p_directional_list, const Transform2D &p_canvas_transform, RS::CanvasItemTextureFilter p_default_filter, RS::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_vertices_to_pixel, bool &r_sdf_used, RenderingMethod::RenderInfo *r_render_info = nullptr) = 0;
struct LightOccluderInstance {
bool enabled;
bool enabled : 1;
bool on_interpolate_transform_list : 1;
bool interpolated : 1;
RID canvas;
RID polygon;
RID occluder;
Rect2 aabb_cache;
Transform2D xform;
Transform2D xform_curr;
Transform2D xform_prev;
Transform2D xform_cache;
int light_mask;
bool sdf_collision;
@ -502,6 +518,8 @@ public:
LightOccluderInstance() {
enabled = true;
on_interpolate_transform_list = false;
interpolated = false;
sdf_collision = false;
next = nullptr;
light_mask = 1;

View File

@ -34,6 +34,7 @@
#include "core/math/geometry_2d.h"
#include "core/math/math_defs.h"
#include "core/math/math_funcs.h"
#include "core/math/transform_interpolator.h"
#include "renderer_compositor_rd.h"
#include "servers/rendering/renderer_rd/storage_rd/material_storage.h"
#include "servers/rendering/renderer_rd/storage_rd/particles_storage.h"
@ -427,7 +428,7 @@ void RendererCanvasRenderRD::_render_item(RD::DrawListID p_draw_list, RID p_rend
Transform2D base_transform = p_canvas_transform_inverse * p_item->final_transform;
if (p_offset.x || p_offset.y) {
base_transform *= Transform2D(0, p_offset / p_item->xform.get_scale());
base_transform *= Transform2D(0, p_offset / p_item->xform_curr.get_scale()); // TODO: Interpolate or explain why not needed.
}
Transform2D draw_transform;
@ -1366,7 +1367,15 @@ void RendererCanvasRenderRD::canvas_render_items(RID p_to_render_target, Item *p
ERR_CONTINUE(!clight);
}
Vector2 canvas_light_pos = p_canvas_transform.xform(l->xform.get_origin()); //convert light position to canvas coordinates, as all computation is done in canvas coords to avoid precision loss
Transform2D final_xform;
if (!RSG::canvas->_interpolation_data.interpolation_enabled || !l->interpolated) {
final_xform = l->xform_curr;
} else {
real_t f = Engine::get_singleton()->get_physics_interpolation_fraction();
TransformInterpolator::interpolate_transform_2d(l->xform_prev, l->xform_curr, final_xform, f);
}
// Convert light position to canvas coordinates, as all computation is done in canvas coordinates to avoid precision loss.
Vector2 canvas_light_pos = p_canvas_transform.xform(final_xform.get_origin());
state.light_uniforms[index].position[0] = canvas_light_pos.x;
state.light_uniforms[index].position[1] = canvas_light_pos.y;

View File

@ -31,6 +31,7 @@
#include "renderer_viewport.h"
#include "core/config/project_settings.h"
#include "core/math/transform_interpolator.h"
#include "core/object/worker_thread_pool.h"
#include "renderer_canvas_cull.h"
#include "renderer_scene_cull.h"
@ -339,7 +340,14 @@ void RendererViewport::_draw_viewport(Viewport *p_viewport) {
if (!F->enabled) {
continue;
}
F->xform_cache = xf * F->xform;
if (!RSG::canvas->_interpolation_data.interpolation_enabled || !F->interpolated) {
F->xform_cache = xf * F->xform_curr;
} else {
real_t f = Engine::get_singleton()->get_physics_interpolation_fraction();
TransformInterpolator::interpolate_transform_2d(F->xform_prev, F->xform_curr, F->xform_cache, f);
F->xform_cache = xf * F->xform_cache;
}
if (sdf_rect.intersects_transformed(F->xform_cache, F->aabb_cache)) {
F->next = occluders;
@ -378,7 +386,14 @@ void RendererViewport::_draw_viewport(Viewport *p_viewport) {
Vector2 offset = tsize / 2.0;
cl->rect_cache = Rect2(-offset + cl->texture_offset, tsize);
cl->xform_cache = xf * cl->xform;
if (!RSG::canvas->_interpolation_data.interpolation_enabled || !cl->interpolated) {
cl->xform_cache = xf * cl->xform_curr;
} else {
real_t f = Engine::get_singleton()->get_physics_interpolation_fraction();
TransformInterpolator::interpolate_transform_2d(cl->xform_prev, cl->xform_curr, cl->xform_cache, f);
cl->xform_cache = xf * cl->xform_cache;
}
if (clip_rect.intersects_transformed(cl->xform_cache, cl->rect_cache)) {
cl->filter_next_ptr = lights;
@ -386,7 +401,7 @@ void RendererViewport::_draw_viewport(Viewport *p_viewport) {
Transform2D scale;
scale.scale(cl->rect_cache.size);
scale.columns[2] = cl->rect_cache.position;
cl->light_shader_xform = xf * cl->xform * scale;
cl->light_shader_xform = cl->xform_cache * scale;
if (cl->use_shadow) {
cl->shadows_next_ptr = lights_with_shadow;
if (lights_with_shadow == nullptr) {
@ -406,7 +421,13 @@ void RendererViewport::_draw_viewport(Viewport *p_viewport) {
if (cl->enabled) {
cl->filter_next_ptr = directional_lights;
directional_lights = cl;
cl->xform_cache = xf * cl->xform;
if (!RSG::canvas->_interpolation_data.interpolation_enabled || !cl->interpolated) {
cl->xform_cache = xf * cl->xform_curr;
} else {
real_t f = Engine::get_singleton()->get_physics_interpolation_fraction();
TransformInterpolator::interpolate_transform_2d(cl->xform_prev, cl->xform_curr, cl->xform_cache, f);
cl->xform_cache = xf * cl->xform_cache;
}
cl->xform_cache.columns[2] = Vector2(); //translation is pointless
if (cl->use_shadow) {
cl->shadows_next_ptr = directional_lights_with_shadow;
@ -441,7 +462,13 @@ void RendererViewport::_draw_viewport(Viewport *p_viewport) {
if (!F->enabled) {
continue;
}
F->xform_cache = xf * F->xform;
if (!RSG::canvas->_interpolation_data.interpolation_enabled || !F->interpolated) {
F->xform_cache = xf * F->xform_curr;
} else {
real_t f = Engine::get_singleton()->get_physics_interpolation_fraction();
TransformInterpolator::interpolate_transform_2d(F->xform_prev, F->xform_curr, F->xform_cache, f);
F->xform_cache = xf * F->xform_cache;
}
if (shadow_rect.intersects_transformed(F->xform_cache, F->aabb_cache)) {
F->next = occluders;
occluders = F;
@ -521,7 +548,13 @@ void RendererViewport::_draw_viewport(Viewport *p_viewport) {
if (!F->enabled) {
continue;
}
F->xform_cache = xf * F->xform;
if (!RSG::canvas->_interpolation_data.interpolation_enabled || !F->interpolated) {
F->xform_cache = xf * F->xform_curr;
} else {
real_t f = Engine::get_singleton()->get_physics_interpolation_fraction();
TransformInterpolator::interpolate_transform_2d(F->xform_prev, F->xform_curr, F->xform_cache, f);
F->xform_cache = xf * F->xform_cache;
}
Transform2D localizer = F->xform_cache.affine_inverse();
for (int j = 0; j < point_count; j++) {

View File

@ -370,6 +370,16 @@ void RenderingServerDefault::_thread_loop() {
_finish();
}
/* INTERPOLATION */
void RenderingServerDefault::tick() {
RSG::canvas->tick();
}
void RenderingServerDefault::set_physics_interpolation_enabled(bool p_enabled) {
RSG::canvas->set_physics_interpolation_enabled(p_enabled);
}
/* EVENT QUEUING */
void RenderingServerDefault::sync() {

View File

@ -926,6 +926,10 @@ public:
FUNC1(canvas_item_set_debug_redraw, bool)
FUNC0RC(bool, canvas_item_get_debug_redraw)
FUNC2(canvas_item_set_interpolated, RID, bool)
FUNC1(canvas_item_reset_physics_interpolation, RID)
FUNC2(canvas_item_transform_physics_interpolation, RID, const Transform2D &)
FUNCRIDSPLIT(canvas_light)
FUNC2(canvas_light_set_mode, RID, CanvasLightMode)
@ -952,6 +956,10 @@ public:
FUNC2(canvas_light_set_shadow_color, RID, const Color &)
FUNC2(canvas_light_set_shadow_smooth, RID, float)
FUNC2(canvas_light_set_interpolated, RID, bool)
FUNC1(canvas_light_reset_physics_interpolation, RID)
FUNC2(canvas_light_transform_physics_interpolation, RID, const Transform2D &)
FUNCRIDSPLIT(canvas_light_occluder)
FUNC2(canvas_light_occluder_attach_to_canvas, RID, RID)
FUNC2(canvas_light_occluder_set_enabled, RID, bool)
@ -960,6 +968,10 @@ public:
FUNC2(canvas_light_occluder_set_transform, RID, const Transform2D &)
FUNC2(canvas_light_occluder_set_light_mask, RID, int)
FUNC2(canvas_light_occluder_set_interpolated, RID, bool)
FUNC1(canvas_light_occluder_reset_physics_interpolation, RID)
FUNC2(canvas_light_occluder_transform_physics_interpolation, RID, const Transform2D &)
FUNCRIDSPLIT(canvas_occluder_polygon)
FUNC3(canvas_occluder_polygon_set_shape, RID, const Vector<Vector2> &, bool)
@ -1021,6 +1033,11 @@ public:
}
}
/* INTERPOLATION */
virtual void tick() override;
virtual void set_physics_interpolation_enabled(bool p_enabled) override;
/* EVENT QUEUING */
virtual void request_frame_drawn_callback(const Callable &p_callable) override;

View File

@ -3219,6 +3219,9 @@ void RenderingServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("canvas_item_set_modulate", "item", "color"), &RenderingServer::canvas_item_set_modulate);
ClassDB::bind_method(D_METHOD("canvas_item_set_self_modulate", "item", "color"), &RenderingServer::canvas_item_set_self_modulate);
ClassDB::bind_method(D_METHOD("canvas_item_set_draw_behind_parent", "item", "enabled"), &RenderingServer::canvas_item_set_draw_behind_parent);
ClassDB::bind_method(D_METHOD("canvas_item_set_interpolated", "item", "interpolated"), &RenderingServer::canvas_item_set_interpolated);
ClassDB::bind_method(D_METHOD("canvas_item_reset_physics_interpolation", "item"), &RenderingServer::canvas_item_reset_physics_interpolation);
ClassDB::bind_method(D_METHOD("canvas_item_transform_physics_interpolation", "item", "transform"), &RenderingServer::canvas_item_transform_physics_interpolation);
/* Primitives */
@ -3302,6 +3305,9 @@ void RenderingServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("canvas_light_set_shadow_color", "light", "color"), &RenderingServer::canvas_light_set_shadow_color);
ClassDB::bind_method(D_METHOD("canvas_light_set_shadow_smooth", "light", "smooth"), &RenderingServer::canvas_light_set_shadow_smooth);
ClassDB::bind_method(D_METHOD("canvas_light_set_blend_mode", "light", "mode"), &RenderingServer::canvas_light_set_blend_mode);
ClassDB::bind_method(D_METHOD("canvas_light_set_interpolated", "light", "interpolated"), &RenderingServer::canvas_light_set_interpolated);
ClassDB::bind_method(D_METHOD("canvas_light_reset_physics_interpolation", "light"), &RenderingServer::canvas_light_reset_physics_interpolation);
ClassDB::bind_method(D_METHOD("canvas_light_transform_physics_interpolation", "light", "transform"), &RenderingServer::canvas_light_transform_physics_interpolation);
BIND_ENUM_CONSTANT(CANVAS_LIGHT_MODE_POINT);
BIND_ENUM_CONSTANT(CANVAS_LIGHT_MODE_DIRECTIONAL);
@ -3324,6 +3330,9 @@ void RenderingServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("canvas_light_occluder_set_as_sdf_collision", "occluder", "enable"), &RenderingServer::canvas_light_occluder_set_as_sdf_collision);
ClassDB::bind_method(D_METHOD("canvas_light_occluder_set_transform", "occluder", "transform"), &RenderingServer::canvas_light_occluder_set_transform);
ClassDB::bind_method(D_METHOD("canvas_light_occluder_set_light_mask", "occluder", "mask"), &RenderingServer::canvas_light_occluder_set_light_mask);
ClassDB::bind_method(D_METHOD("canvas_light_occluder_set_interpolated", "occluder", "interpolated"), &RenderingServer::canvas_light_occluder_set_interpolated);
ClassDB::bind_method(D_METHOD("canvas_light_occluder_reset_physics_interpolation", "occluder"), &RenderingServer::canvas_light_occluder_reset_physics_interpolation);
ClassDB::bind_method(D_METHOD("canvas_light_occluder_transform_physics_interpolation", "occluder", "transform"), &RenderingServer::canvas_light_occluder_transform_physics_interpolation);
/* CANVAS LIGHT OCCLUDER POLYGON */

View File

@ -1476,6 +1476,10 @@ public:
virtual void canvas_item_set_debug_redraw(bool p_enabled) = 0;
virtual bool canvas_item_get_debug_redraw() const = 0;
virtual void canvas_item_set_interpolated(RID p_item, bool p_interpolated) = 0;
virtual void canvas_item_reset_physics_interpolation(RID p_item) = 0;
virtual void canvas_item_transform_physics_interpolation(RID p_item, const Transform2D &p_transform) = 0;
/* CANVAS LIGHT */
virtual RID canvas_light_create() = 0;
@ -1523,6 +1527,10 @@ public:
virtual void canvas_light_set_shadow_color(RID p_light, const Color &p_color) = 0;
virtual void canvas_light_set_shadow_smooth(RID p_light, float p_smooth) = 0;
virtual void canvas_light_set_interpolated(RID p_light, bool p_interpolated) = 0;
virtual void canvas_light_reset_physics_interpolation(RID p_light) = 0;
virtual void canvas_light_transform_physics_interpolation(RID p_light, const Transform2D &p_transform) = 0;
/* CANVAS LIGHT OCCLUDER */
virtual RID canvas_light_occluder_create() = 0;
@ -1533,6 +1541,10 @@ public:
virtual void canvas_light_occluder_set_transform(RID p_occluder, const Transform2D &p_xform) = 0;
virtual void canvas_light_occluder_set_light_mask(RID p_occluder, int p_mask) = 0;
virtual void canvas_light_occluder_set_interpolated(RID p_occluder, bool p_interpolated) = 0;
virtual void canvas_light_occluder_reset_physics_interpolation(RID p_occluder) = 0;
virtual void canvas_light_occluder_transform_physics_interpolation(RID p_occluder, const Transform2D &p_transform) = 0;
/* CANVAS LIGHT OCCLUDER POLYGON */
virtual RID canvas_occluder_polygon_create() = 0;
@ -1604,6 +1616,11 @@ public:
virtual void free(RID p_rid) = 0; // Free RIDs associated with the rendering server.
/* INTERPOLATION */
virtual void tick() = 0;
virtual void set_physics_interpolation_enabled(bool p_enabled) = 0;
/* EVENT QUEUING */
virtual void request_frame_drawn_callback(const Callable &p_callable) = 0;