1
0
mirror of https://github.com/godotengine/godot.git synced 2025-11-23 15:16:17 +00:00

2D Fixed Timestep Interpolation

Adds support to canvas items and Camera2D.
This commit is contained in:
lawnjelly
2022-11-09 15:53:23 +00:00
parent ac5d7dc821
commit 5162efbfe9
30 changed files with 738 additions and 139 deletions

View File

@@ -29,6 +29,7 @@
/**************************************************************************/
#include "visual_server_canvas.h"
#include "core/math/transform_interpolator.h"
#include "visual_server_globals.h"
#include "visual_server_raster.h"
#include "visual_server_viewport.h"
@@ -65,7 +66,7 @@ void _collect_ysort_children(VisualServerCanvas::Item *p_canvas_item, Transform2
r_items[r_index] = child_items[i];
child_items[i]->ysort_modulate = p_modulate;
child_items[i]->ysort_xform = p_transform;
child_items[i]->ysort_pos = p_transform.xform(child_items[i]->xform.elements[2]);
child_items[i]->ysort_pos = p_transform.xform(child_items[i]->xform_curr.elements[2]);
child_items[i]->material_owner = child_items[i]->use_parent_material ? p_material_owner : nullptr;
child_items[i]->ysort_index = r_index;
}
@@ -74,7 +75,7 @@ void _collect_ysort_children(VisualServerCanvas::Item *p_canvas_item, Transform2
if (child_items[i]->sort_y) {
_collect_ysort_children(child_items[i],
p_transform * child_items[i]->xform,
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);
@@ -241,6 +242,37 @@ void VisualServerCanvas::_calculate_canvas_item_bound(Item *p_canvas_item, Rect2
}
_finalize_and_merge_local_bound_to_branch(ci, r_branch_bound);
// If we are interpolating, we want to modify the local_bound (combined)
// to include both the previous AND current bounds.
if (local_bound && _interpolation_data.interpolation_enabled && ci->interpolated) {
Rect2 bound_prev = ci->local_bound_prev;
// Keep track of the previously assigned exact bound for the next tick.
ci->local_bound_prev = ci->local_bound;
// The combined bound is the exact current bound merged with the previous exact bound.
ci->local_bound = ci->local_bound.merge(bound_prev);
// This can overflow, it's no problem, it is just rough to detect when items stop
// having local bounds updated, so we can set prev to curr.
ci->local_bound_last_update_tick = Engine::get_singleton()->get_physics_frames();
// Detect special case of overflow.
// This is omitted but included for reference.
// It is such a rare possibility, and even if it did occur
// so it should just result in slightly larger culling bounds
// probably for one tick (and no visual errors).
// Would occur once every 828.5 days at 60 ticks per second
// with uint32_t counter.
#if 0
if (!ci->local_bound_last_update_tick) {
// Prevents it being treated as non-dirty.
// Just has an increased delay of one tick in this very rare occurrence.
ci->local_bound_last_update_tick = 1;
}
#endif
}
}
void VisualServerCanvas::_finalize_and_merge_local_bound_to_branch(Item *p_canvas_item, Rect2 *r_branch_bound) {
@@ -275,7 +307,7 @@ void VisualServerCanvas::_merge_local_bound_to_branch(Item *p_canvas_item, Rect2
return;
}
Rect2 this_item_total_local_bound = p_canvas_item->xform.xform(p_canvas_item->local_bound);
Rect2 this_item_total_local_bound = p_canvas_item->xform_curr.xform(p_canvas_item->local_bound);
if (!r_branch_bound->has_no_area()) {
*r_branch_bound = r_branch_bound->merge(this_item_total_local_bound);
@@ -297,10 +329,17 @@ void VisualServerCanvas::_render_canvas_item_cull_by_item(Item *p_canvas_item, c
}
Rect2 rect = ci->get_rect();
Transform2D xform = ci->xform;
xform = p_transform * xform;
Rect2 global_rect = xform.xform(rect);
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);
}
final_xform = p_transform * final_xform;
Rect2 global_rect = final_xform.xform(rect);
global_rect.position += p_clip_rect.position;
if (ci->use_parent_material && p_material_owner) {
@@ -360,14 +399,14 @@ void VisualServerCanvas::_render_canvas_item_cull_by_item(Item *p_canvas_item, c
continue;
}
if (ci->sort_y) {
_render_canvas_item_cull_by_item(child_items[i], xform * child_items[i]->ysort_xform, p_clip_rect, modulate * child_items[i]->ysort_modulate, p_z, z_list, z_last_list, (Item *)ci->final_clip_owner, (Item *)child_items[i]->material_owner);
_render_canvas_item_cull_by_item(child_items[i], final_xform * child_items[i]->ysort_xform, p_clip_rect, modulate * child_items[i]->ysort_modulate, p_z, z_list, z_last_list, (Item *)ci->final_clip_owner, (Item *)child_items[i]->material_owner);
} else {
_render_canvas_item_cull_by_item(child_items[i], xform, p_clip_rect, modulate, p_z, z_list, z_last_list, (Item *)ci->final_clip_owner, p_material_owner);
_render_canvas_item_cull_by_item(child_items[i], final_xform, p_clip_rect, modulate, p_z, z_list, z_last_list, (Item *)ci->final_clip_owner, p_material_owner);
}
}
if (ci->copy_back_buffer) {
ci->copy_back_buffer->screen_rect = xform.xform(ci->copy_back_buffer->rect).clip(p_clip_rect);
ci->copy_back_buffer->screen_rect = final_xform.xform(ci->copy_back_buffer->rect).clip(p_clip_rect);
}
if (ci->update_when_visible) {
@@ -376,7 +415,7 @@ void VisualServerCanvas::_render_canvas_item_cull_by_item(Item *p_canvas_item, c
if ((!ci->commands.empty() && p_clip_rect.intersects(global_rect, true)) || ci->vp_render || ci->copy_back_buffer) {
//something to draw?
ci->final_transform = xform;
ci->final_transform = final_xform;
ci->final_modulate = Color(modulate.r * ci->self_modulate.r, modulate.g * ci->self_modulate.g, modulate.b * ci->self_modulate.b, modulate.a * ci->self_modulate.a);
ci->global_rect_cache = global_rect;
ci->global_rect_cache.position -= p_clip_rect.position;
@@ -401,9 +440,9 @@ void VisualServerCanvas::_render_canvas_item_cull_by_item(Item *p_canvas_item, c
continue;
}
if (ci->sort_y) {
_render_canvas_item_cull_by_item(child_items[i], xform * child_items[i]->ysort_xform, p_clip_rect, modulate * child_items[i]->ysort_modulate, p_z, z_list, z_last_list, (Item *)ci->final_clip_owner, (Item *)child_items[i]->material_owner);
_render_canvas_item_cull_by_item(child_items[i], final_xform * child_items[i]->ysort_xform, p_clip_rect, modulate * child_items[i]->ysort_modulate, p_z, z_list, z_last_list, (Item *)ci->final_clip_owner, (Item *)child_items[i]->material_owner);
} else {
_render_canvas_item_cull_by_item(child_items[i], xform, p_clip_rect, modulate, p_z, z_list, z_last_list, (Item *)ci->final_clip_owner, p_material_owner);
_render_canvas_item_cull_by_item(child_items[i], final_xform, p_clip_rect, modulate, p_z, z_list, z_last_list, (Item *)ci->final_clip_owner, p_material_owner);
}
}
}
@@ -418,9 +457,22 @@ void VisualServerCanvas::_render_canvas_item_cull_by_node(Item *p_canvas_item, c
// This should have been calculated as a pre-process.
DEV_ASSERT(!ci->bound_dirty);
// If we are interpolating, and the updates have stopped, we can reduce the local bound.
if (ci->local_bound_last_update_tick && (ci->local_bound_last_update_tick != Engine::get_singleton()->get_physics_frames())) {
// The combined bound is reduced to the last calculated exact bound.
ci->local_bound = ci->local_bound_prev;
ci->local_bound_last_update_tick = 0;
}
Rect2 rect = ci->get_rect();
Transform2D final_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);
}
final_xform = p_transform * final_xform;
Rect2 global_rect = final_xform.xform(rect);
@@ -825,7 +877,16 @@ void VisualServerCanvas::canvas_item_set_transform(RID p_item, const Transform2D
Item *canvas_item = canvas_item_owner.getornull(p_item);
ERR_FAIL_COND(!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());
}
}
canvas_item->xform_curr = p_transform;
// Special case!
// Modifying the transform DOES NOT affect the local bound.
@@ -1435,6 +1496,64 @@ void VisualServerCanvas::canvas_item_set_skeleton_relative_xform(RID p_item, Tra
}
}
// Useful especially for origin shifting.
void VisualServerCanvas::canvas_item_transform_physics_interpolation(RID p_item, Transform2D p_transform) {
Item *canvas_item = canvas_item_owner.getornull(p_item);
ERR_FAIL_COND(!canvas_item);
canvas_item->xform_prev = p_transform * canvas_item->xform_prev;
canvas_item->xform_curr = p_transform * canvas_item->xform_curr;
}
void VisualServerCanvas::canvas_item_reset_physics_interpolation(RID p_item) {
Item *canvas_item = canvas_item_owner.getornull(p_item);
ERR_FAIL_COND(!canvas_item);
canvas_item->xform_prev = canvas_item->xform_curr;
}
void VisualServerCanvas::canvas_item_set_interpolated(RID p_item, bool p_interpolated) {
Item *canvas_item = canvas_item_owner.getornull(p_item);
ERR_FAIL_COND(!canvas_item);
canvas_item->interpolated = p_interpolated;
}
void VisualServerCanvas::canvas_light_set_interpolated(RID p_light, bool p_interpolated) {
RasterizerCanvas::Light *clight = canvas_light_owner.get(p_light);
ERR_FAIL_COND(!clight);
clight->interpolated = p_interpolated;
}
void VisualServerCanvas::canvas_light_reset_physics_interpolation(RID p_light) {
RasterizerCanvas::Light *clight = canvas_light_owner.get(p_light);
ERR_FAIL_COND(!clight);
clight->xform_prev = clight->xform_curr;
}
void VisualServerCanvas::canvas_light_transform_physics_interpolation(RID p_light, Transform2D p_transform) {
RasterizerCanvas::Light *clight = canvas_light_owner.get(p_light);
ERR_FAIL_COND(!clight);
clight->xform_prev = p_transform * clight->xform_prev;
clight->xform_curr = p_transform * clight->xform_curr;
}
void VisualServerCanvas::canvas_light_occluder_set_interpolated(RID p_occluder, bool p_interpolated) {
RasterizerCanvas::LightOccluderInstance *occluder = canvas_light_occluder_owner.get(p_occluder);
ERR_FAIL_COND(!occluder);
occluder->interpolated = p_interpolated;
}
void VisualServerCanvas::canvas_light_occluder_reset_physics_interpolation(RID p_occluder) {
RasterizerCanvas::LightOccluderInstance *occluder = canvas_light_occluder_owner.get(p_occluder);
ERR_FAIL_COND(!occluder);
occluder->xform_prev = occluder->xform_curr;
}
void VisualServerCanvas::canvas_light_occluder_transform_physics_interpolation(RID p_occluder, Transform2D p_transform) {
RasterizerCanvas::LightOccluderInstance *occluder = canvas_light_occluder_owner.get(p_occluder);
ERR_FAIL_COND(!occluder);
occluder->xform_prev = p_transform * occluder->xform_prev;
occluder->xform_curr = p_transform * occluder->xform_curr;
}
void VisualServerCanvas::canvas_item_attach_skeleton(RID p_item, RID p_skeleton) {
Item *canvas_item = canvas_item_owner.getornull(p_item);
ERR_FAIL_COND(!canvas_item);
@@ -1573,7 +1692,16 @@ void VisualServerCanvas::canvas_light_set_transform(RID p_light, const Transform
RasterizerCanvas::Light *clight = canvas_light_owner.get(p_light);
ERR_FAIL_COND(!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());
}
}
clight->xform_curr = p_transform;
}
void VisualServerCanvas::canvas_light_set_texture(RID p_light, RID p_texture) {
RasterizerCanvas::Light *clight = canvas_light_owner.get(p_light);
@@ -1760,7 +1888,16 @@ void VisualServerCanvas::canvas_light_occluder_set_transform(RID p_occluder, con
RasterizerCanvas::LightOccluderInstance *occluder = canvas_light_occluder_owner.get(p_occluder);
ERR_FAIL_COND(!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());
}
}
occluder->xform_curr = p_xform;
}
void VisualServerCanvas::canvas_light_occluder_set_light_mask(RID p_occluder, int p_mask) {
RasterizerCanvas::LightOccluderInstance *occluder = canvas_light_occluder_owner.get(p_occluder);
@@ -1871,6 +2008,7 @@ bool VisualServerCanvas::free(RID p_rid) {
Item *canvas_item = canvas_item_owner.get(p_rid);
ERR_FAIL_COND_V(!canvas_item, true);
_make_bound_dirty(canvas_item);
_interpolation_data.notify_free_canvas_item(p_rid, *canvas_item);
if (canvas_item->parent.is_valid()) {
if (canvas_owner.owns(canvas_item->parent)) {
@@ -1904,6 +2042,7 @@ bool VisualServerCanvas::free(RID p_rid) {
} else if (canvas_light_owner.owns(p_rid)) {
RasterizerCanvas::Light *canvas_light = canvas_light_owner.get(p_rid);
ERR_FAIL_COND_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(canvas_light->canvas);
@@ -1924,6 +2063,7 @@ bool VisualServerCanvas::free(RID p_rid) {
} else if (canvas_light_occluder_owner.owns(p_rid)) {
RasterizerCanvas::LightOccluderInstance *occluder = canvas_light_occluder_owner.get(p_rid);
ERR_FAIL_COND_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(occluder->polygon);
@@ -2056,6 +2196,81 @@ void VisualServerCanvas::_print_tree_down(int p_child_id, int p_depth, const Ite
#endif
void VisualServerCanvas::tick() {
if (_interpolation_data.interpolation_enabled) {
update_interpolation_tick(true);
}
}
void VisualServerCanvas::update_interpolation_tick(bool p_process) {
#define GODOT_UPDATE_INTERPOLATION_TICK(LIST_PREV, LIST_CURR, TYPE, OWNER_LIST) \
/* Detect any that were on the previous transform list that are no longer active. */ \
for (unsigned int n = 0; n < _interpolation_data.LIST_PREV->size(); n++) { \
const RID &rid = (*_interpolation_data.LIST_PREV)[n]; \
TYPE *item = OWNER_LIST.getornull(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.LIST_CURR->size(); n++) { \
const RID &rid = (*_interpolation_data.LIST_CURR)[n]; \
TYPE *item = OWNER_LIST.getornull(rid); \
if (item) { \
item->xform_prev = item->xform_curr; \
item->on_interpolate_transform_list = false; \
} \
} \
} \
SWAP(_interpolation_data.LIST_CURR, _interpolation_data.LIST_PREV); \
_interpolation_data.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, RasterizerCanvas::Light, canvas_light_owner);
GODOT_UPDATE_INTERPOLATION_TICK(canvas_light_occluder_transform_update_list_prev, canvas_light_occluder_transform_update_list_curr, RasterizerCanvas::LightOccluderInstance, canvas_light_occluder_owner);
#undef GODOT_UPDATE_INTERPOLATION_TICK
}
void VisualServerCanvas::InterpolationData::notify_free_canvas_item(RID p_rid, VisualServerCanvas::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 VisualServerCanvas::InterpolationData::notify_free_canvas_light(RID p_rid, RasterizerCanvas::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 VisualServerCanvas::InterpolationData::notify_free_canvas_light_occluder(RID p_rid, RasterizerCanvas::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);
}
VisualServerCanvas::VisualServerCanvas() {
z_list = (RasterizerCanvas::Item **)memalloc(z_range * sizeof(RasterizerCanvas::Item *));
z_last_list = (RasterizerCanvas::Item **)memalloc(z_range * sizeof(RasterizerCanvas::Item *));