Revive onion skinning

This also reverts commit 6bbc3cbf04.
This commit is contained in:
Pedro J. Estébanez 2023-09-27 16:37:26 +02:00
parent 77d8372285
commit 071d8e5c9c
2 changed files with 154 additions and 120 deletions

View file

@ -112,6 +112,7 @@ void AnimationPlayerEditor::_notification(int p_what) {
}
last_active = player->is_playing();
updating = false;
} break;
@ -942,11 +943,6 @@ void AnimationPlayerEditor::_update_player() {
onion_toggle->set_disabled(no_anims_found);
onion_skinning->set_disabled(no_anims_found);
if (hack_disable_onion_skinning) {
onion_toggle->set_disabled(true);
onion_skinning->set_disabled(true);
}
_update_animation_list_icons();
updating = false;
@ -1150,33 +1146,33 @@ void AnimationPlayerEditor::forward_force_draw_over_viewport(Control *p_overlay)
float alpha_step = 1.0 / (onion.steps + 1);
int cidx = 0;
uint32_t capture_idx = 0;
if (onion.past) {
float alpha = 0;
float alpha = 0.0f;
do {
alpha += alpha_step;
if (onion.captures_valid[cidx]) {
if (onion.captures_valid[capture_idx]) {
RS::get_singleton()->canvas_item_add_texture_rect_region(
ci, dst_rect, RS::get_singleton()->viewport_get_texture(onion.captures[cidx]), src_rect, Color(1, 1, 1, alpha));
ci, dst_rect, RS::get_singleton()->viewport_get_texture(onion.captures[capture_idx]), src_rect, Color(1, 1, 1, alpha));
}
cidx++;
} while (cidx < onion.steps);
capture_idx++;
} while (capture_idx < onion.steps);
}
if (onion.future) {
float alpha = 1;
int base_cidx = cidx;
float alpha = 1.0f;
uint32_t base_cidx = capture_idx;
do {
alpha -= alpha_step;
if (onion.captures_valid[cidx]) {
if (onion.captures_valid[capture_idx]) {
RS::get_singleton()->canvas_item_add_texture_rect_region(
ci, dst_rect, RS::get_singleton()->viewport_get_texture(onion.captures[cidx]), src_rect, Color(1, 1, 1, alpha));
ci, dst_rect, RS::get_singleton()->viewport_get_texture(onion.captures[capture_idx]), src_rect, Color(1, 1, 1, alpha));
}
cidx++;
} while (cidx < base_cidx + onion.steps); // In case there's the present capture at the end, skip it.
capture_idx++;
} while (capture_idx < base_cidx + onion.steps); // In case there's the present capture at the end, skip it.
}
}
@ -1266,7 +1262,7 @@ void AnimationPlayerEditor::_seek_value_changed(float p_value, bool p_set, bool
if (!p_timeline_only) {
if (player->is_valid() && !p_set) {
double delta = pos - player->get_current_animation_position();
double delta = player->get_current_animation_position();
player->seek(pos, true, true);
player->seek(pos + delta, true, true);
} else {
@ -1394,7 +1390,10 @@ void AnimationPlayerEditor::_onion_skinning_menu(int p_option) {
onion.enabled = !onion.enabled;
if (onion.enabled) {
_start_onion_skinning();
if (get_player() && !get_player()->has_animation(SceneStringNames::get_singleton()->RESET)) {
EditorNode::get_singleton()->show_warning(TTR("Onion skinning requires a RESET animation."));
}
_start_onion_skinning(); // It will check for RESET animation anyway.
} else {
_stop_onion_skinning();
}
@ -1416,7 +1415,7 @@ void AnimationPlayerEditor::_onion_skinning_menu(int p_option) {
onion.steps = (p_option - ONION_SKINNING_1_STEP) + 1;
int one_frame_idx = menu->get_item_index(ONION_SKINNING_1_STEP);
for (int i = 0; i <= ONION_SKINNING_LAST_STEPS_OPTION - ONION_SKINNING_1_STEP; i++) {
menu->set_item_checked(one_frame_idx + i, onion.steps == i + 1);
menu->set_item_checked(one_frame_idx + i, (int)onion.steps == i + 1);
}
} break;
case ONION_SKINNING_DIFFERENCES_ONLY: {
@ -1475,15 +1474,15 @@ void AnimationPlayerEditor::_editor_visibility_changed() {
bool AnimationPlayerEditor::_are_onion_layers_valid() {
ERR_FAIL_COND_V(!onion.past && !onion.future, false);
Point2 capture_size = get_tree()->get_root()->get_size();
return onion.captures.size() == onion.get_needed_capture_count() && onion.capture_size == capture_size;
Size2 capture_size = DisplayServer::get_singleton()->window_get_size(DisplayServer::MAIN_WINDOW_ID);
return onion.captures.size() == onion.get_capture_count() && onion.capture_size == capture_size;
}
void AnimationPlayerEditor::_allocate_onion_layers() {
_free_onion_layers();
int captures = onion.get_needed_capture_count();
Point2 capture_size = get_tree()->get_root()->get_size();
int captures = onion.get_capture_count();
Size2 capture_size = DisplayServer::get_singleton()->window_get_size(DisplayServer::MAIN_WINDOW_ID);
onion.captures.resize(captures);
onion.captures_valid.resize(captures);
@ -1492,7 +1491,7 @@ void AnimationPlayerEditor::_allocate_onion_layers() {
bool is_present = onion.differences_only && i == captures - 1;
// Each capture is a viewport with a canvas item attached that renders a full-size rect with the contents of the main viewport.
onion.captures.write[i] = RS::get_singleton()->viewport_create();
onion.captures[i] = RS::get_singleton()->viewport_create();
RS::get_singleton()->viewport_set_size(onion.captures[i], capture_size.width, capture_size.height);
RS::get_singleton()->viewport_set_update_mode(onion.captures[i], RS::VIEWPORT_UPDATE_ALWAYS);
@ -1502,13 +1501,13 @@ void AnimationPlayerEditor::_allocate_onion_layers() {
// Reset the capture canvas item to the current root viewport texture (defensive).
RS::get_singleton()->canvas_item_clear(onion.capture.canvas_item);
RS::get_singleton()->canvas_item_add_texture_rect(onion.capture.canvas_item, Rect2(Point2(), capture_size), get_tree()->get_root()->get_texture()->get_rid());
RS::get_singleton()->canvas_item_add_texture_rect(onion.capture.canvas_item, Rect2(Point2(), Point2(capture_size.x, -capture_size.y)), get_tree()->get_root()->get_texture()->get_rid());
onion.capture_size = capture_size;
}
void AnimationPlayerEditor::_free_onion_layers() {
for (int i = 0; i < onion.captures.size(); i++) {
for (uint32_t i = 0; i < onion.captures.size(); i++) {
if (onion.captures[i].is_valid()) {
RS::get_singleton()->free(onion.captures[i]);
}
@ -1524,7 +1523,7 @@ void AnimationPlayerEditor::_prepare_onion_layers_1() {
return;
}
if (!onion.enabled || !is_processing() || !is_visible() || !get_player()) {
if (!onion.enabled || !is_visible() || !get_player() || !get_player()->has_animation(SceneStringNames::get_singleton()->RESET)) {
_stop_onion_skinning();
return;
}
@ -1540,14 +1539,10 @@ void AnimationPlayerEditor::_prepare_onion_layers_1() {
}
// And go to next step afterwards.
call_deferred(SNAME("_prepare_onion_layers_2"));
callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_2_prolog).call_deferred();
}
void AnimationPlayerEditor::_prepare_onion_layers_1_deferred() {
call_deferred(SNAME("_prepare_onion_layers_1"));
}
void AnimationPlayerEditor::_prepare_onion_layers_2() {
void AnimationPlayerEditor::_prepare_onion_layers_2_prolog() {
Ref<Animation> anim = player->get_animation(player->get_assigned_animation());
if (!anim.is_valid()) {
return;
@ -1558,21 +1553,20 @@ void AnimationPlayerEditor::_prepare_onion_layers_2() {
}
// Hide superfluous elements that would make the overlay unnecessary cluttered.
Dictionary canvas_edit_state;
Dictionary spatial_edit_state;
if (Node3DEditor::get_singleton()->is_visible()) {
// 3D
spatial_edit_state = Node3DEditor::get_singleton()->get_state();
Dictionary new_state = spatial_edit_state.duplicate();
onion.temp.spatial_edit_state = Node3DEditor::get_singleton()->get_state();
Dictionary new_state = onion.temp.spatial_edit_state.duplicate();
new_state["show_grid"] = false;
new_state["show_origin"] = false;
Array orig_vp = spatial_edit_state["viewports"];
Array orig_vp = onion.temp.spatial_edit_state["viewports"];
Array vp;
vp.resize(4);
for (int i = 0; i < vp.size(); i++) {
Dictionary d = ((Dictionary)orig_vp[i]).duplicate();
d["use_environment"] = false;
d["doppler"] = false;
d["listener"] = false;
d["gizmos"] = onion.include_gizmos ? d["gizmos"] : Variant(false);
d["information"] = false;
vp[i] = d;
@ -1580,23 +1574,27 @@ void AnimationPlayerEditor::_prepare_onion_layers_2() {
new_state["viewports"] = vp;
// TODO: Save/restore only affected entries.
Node3DEditor::get_singleton()->set_state(new_state);
} else { // CanvasItemEditor
// 2D
canvas_edit_state = CanvasItemEditor::get_singleton()->get_state();
Dictionary new_state = canvas_edit_state.duplicate();
} else {
// CanvasItemEditor.
onion.temp.canvas_edit_state = CanvasItemEditor::get_singleton()->get_state();
Dictionary new_state = onion.temp.canvas_edit_state.duplicate();
new_state["show_origin"] = false;
new_state["show_grid"] = false;
new_state["show_rulers"] = false;
new_state["show_guides"] = false;
new_state["show_helpers"] = false;
new_state["show_zoom_control"] = false;
new_state["show_edit_locks"] = false;
new_state["grid_visibility"] = 2; // TODO: Expose CanvasItemEditor::GRID_VISIBILITY_HIDE somehow and use it.
new_state["show_transformation_gizmos"] = onion.include_gizmos ? new_state["gizmos"] : Variant(false);
// TODO: Save/restore only affected entries.
CanvasItemEditor::get_singleton()->set_state(new_state);
}
// Tweak the root viewport to ensure it's rendered before our target.
RID root_vp = get_tree()->get_root()->get_viewport_rid();
Rect2 root_vp_screen_rect = Rect2(Vector2(), get_tree()->get_root()->get_size());
RS::get_singleton()->viewport_attach_to_screen(root_vp, Rect2());
onion.temp.screen_rect = Rect2(Vector2(), DisplayServer::get_singleton()->window_get_size(DisplayServer::MAIN_WINDOW_ID));
RS::get_singleton()->viewport_attach_to_screen(root_vp, Rect2(), DisplayServer::INVALID_WINDOW_ID);
RS::get_singleton()->viewport_set_update_mode(root_vp, RS::VIEWPORT_UPDATE_ALWAYS);
RID present_rid;
@ -1611,8 +1609,8 @@ void AnimationPlayerEditor::_prepare_onion_layers_2() {
}
// Backup current animation state.
Ref<AnimatedValuesBackup> backup_current = player->make_backup();
float cpos = player->get_current_animation_position();
onion.temp.anim_values_backup = player->make_backup();
onion.temp.anim_player_position = player->get_current_animation_position();
// Render every past/future step with the capture shader.
@ -1620,55 +1618,94 @@ void AnimationPlayerEditor::_prepare_onion_layers_2() {
onion.capture.material->set_shader_parameter("bkg_color", GLOBAL_GET("rendering/environment/defaults/default_clear_color"));
onion.capture.material->set_shader_parameter("differences_only", onion.differences_only);
onion.capture.material->set_shader_parameter("present", onion.differences_only ? RS::get_singleton()->viewport_get_texture(present_rid) : RID());
int step_off_a = onion.past ? -onion.steps : 0;
int step_off_b = onion.future ? onion.steps : 0;
int cidx = 0;
onion.capture.material->set_shader_parameter("dir_color", onion.force_white_modulate ? Color(1, 1, 1) : Color(EDITOR_GET("editors/animation/onion_layers_past_color")));
for (int step_off = step_off_a; step_off <= step_off_b; step_off++) {
if (step_off == 0) {
// Skip present step and switch to the color of future.
if (!onion.force_white_modulate) {
onion.capture.material->set_shader_parameter("dir_color", EDITOR_GET("editors/animation/onion_layers_future_color"));
}
continue;
}
float pos = cpos + step_off * anim->get_step();
uint32_t p_capture_idx = 0;
int first_step_offset = onion.past ? -(int)onion.steps : 0;
_prepare_onion_layers_2_step_prepare(first_step_offset, p_capture_idx);
}
void AnimationPlayerEditor::_prepare_onion_layers_2_step_prepare(int p_step_offset, uint32_t p_capture_idx) {
uint32_t next_capture_idx = p_capture_idx;
if (p_step_offset == 0) {
// Skip present step and switch to the color of future.
if (!onion.force_white_modulate) {
onion.capture.material->set_shader_parameter("dir_color", EDITOR_GET("editors/animation/onion_layers_future_color"));
}
} else {
Ref<Animation> anim = player->get_animation(player->get_assigned_animation());
double pos = onion.temp.anim_player_position + p_step_offset * anim->get_step();
bool valid = anim->get_loop_mode() != Animation::LOOP_NONE || (pos >= 0 && pos <= anim->get_length());
onion.captures_valid.write[cidx] = valid;
onion.captures_valid[p_capture_idx] = valid;
if (valid) {
player->seek(pos, true);
get_tree()->flush_transform_notifications(); // Needed for transforms of Node3Ds.
RS::get_singleton()->viewport_set_active(onion.captures[cidx], true);
RS::get_singleton()->viewport_set_parent_viewport(root_vp, onion.captures[cidx]);
RS::get_singleton()->draw(false);
RS::get_singleton()->viewport_set_active(onion.captures[cidx], false);
OS::get_singleton()->get_main_loop()->process(0);
// This is the key: process the frame and let all callbacks/updates/notifications happen
// so everything (transforms, skeletons, etc.) is up-to-date visually.
callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_2_step_capture).bind(p_step_offset, p_capture_idx).call_deferred();
return;
} else {
next_capture_idx++;
}
cidx++;
}
int last_step_offset = onion.future ? onion.steps : 0;
if (p_step_offset < last_step_offset) {
_prepare_onion_layers_2_step_prepare(p_step_offset + 1, next_capture_idx);
} else {
_prepare_onion_layers_2_epilog();
}
}
void AnimationPlayerEditor::_prepare_onion_layers_2_step_capture(int p_step_offset, uint32_t p_capture_idx) {
DEV_ASSERT(p_step_offset != 0);
DEV_ASSERT(onion.captures_valid[p_capture_idx]);
RID root_vp = get_tree()->get_root()->get_viewport_rid();
RS::get_singleton()->viewport_set_active(onion.captures[p_capture_idx], true);
RS::get_singleton()->viewport_set_parent_viewport(root_vp, onion.captures[p_capture_idx]);
RS::get_singleton()->draw(false);
RS::get_singleton()->viewport_set_active(onion.captures[p_capture_idx], false);
int last_step_offset = onion.future ? onion.steps : 0;
if (p_step_offset < last_step_offset) {
_prepare_onion_layers_2_step_prepare(p_step_offset + 1, p_capture_idx + 1);
} else {
_prepare_onion_layers_2_epilog();
}
}
void AnimationPlayerEditor::_prepare_onion_layers_2_epilog() {
// Restore root viewport.
RID root_vp = get_tree()->get_root()->get_viewport_rid();
RS::get_singleton()->viewport_set_parent_viewport(root_vp, RID());
RS::get_singleton()->viewport_attach_to_screen(root_vp, root_vp_screen_rect);
RS::get_singleton()->viewport_attach_to_screen(root_vp, onion.temp.screen_rect, DisplayServer::MAIN_WINDOW_ID);
RS::get_singleton()->viewport_set_update_mode(root_vp, RS::VIEWPORT_UPDATE_WHEN_VISIBLE);
// Restore animation state
// (Seeking with update=true wouldn't do the trick because the current value of the properties
// may not match their value for the current point in the animation).
player->seek(cpos, false);
player->restore(backup_current);
// Restore animation state.
// Here we're combine the power of seeking back to the original position and
// restoring the values backup. In most cases they will bring the same value back,
// but there are cases handled by one that the other can't.
// Namely:
// - Seeking won't restore any values that may have been modified by the user
// in the node after the last time the AnimationPlayer updated it.
// - Restoring the backup won't account for values that are not directly involved
// in the animation but a consequence of them (e.g., SkeletonModification2DLookAt).
// FIXME: Since backup of values is based on the reset animation, only values
// backed by a proper reset animation will work correctly with onion
// skinning and the possibility to restore the values mentioned in the
// first point above is gone. Still good enough.
player->seek(onion.temp.anim_player_position, true, true);
player->restore(onion.temp.anim_values_backup);
// Restore state of main editors.
if (Node3DEditor::get_singleton()->is_visible()) {
// 3D
Node3DEditor::get_singleton()->set_state(spatial_edit_state);
Node3DEditor::get_singleton()->set_state(onion.temp.spatial_edit_state);
} else { // CanvasItemEditor
// 2D
CanvasItemEditor::get_singleton()->set_state(canvas_edit_state);
CanvasItemEditor::get_singleton()->set_state(onion.temp.canvas_edit_state);
}
// Update viewports with skin layers overlaid for the actual engine loop render.
@ -1677,21 +1714,26 @@ void AnimationPlayerEditor::_prepare_onion_layers_2() {
}
void AnimationPlayerEditor::_start_onion_skinning() {
// FIXME: Using "process_frame" makes onion layers update one frame behind the current.
if (!get_tree()->is_connected(SNAME("process_frame"), callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred))) {
get_tree()->connect(SNAME("process_frame"), callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred));
if (get_player() && !get_player()->has_animation(SceneStringNames::get_singleton()->RESET)) {
onion.enabled = false;
onion_toggle->set_pressed_no_signal(false);
return;
}
if (!get_tree()->is_connected(SNAME("process_frame"), callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1))) {
get_tree()->connect(SNAME("process_frame"), callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1));
}
}
void AnimationPlayerEditor::_stop_onion_skinning() {
if (get_tree()->is_connected(SNAME("process_frame"), callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred))) {
get_tree()->disconnect(SNAME("process_frame"), callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred));
if (get_tree()->is_connected(SNAME("process_frame"), callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1))) {
get_tree()->disconnect(SNAME("process_frame"), callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1));
_free_onion_layers();
// Clean up the overlay.
// Clean up.
onion.can_overlay = false;
plugin->update_overlays();
onion.temp = {};
}
}
@ -1773,8 +1815,6 @@ void AnimationPlayerEditor::_bind_methods() {
ClassDB::bind_method(D_METHOD("_list_changed"), &AnimationPlayerEditor::_list_changed);
ClassDB::bind_method(D_METHOD("_animation_duplicate"), &AnimationPlayerEditor::_animation_duplicate);
ClassDB::bind_method(D_METHOD("_prepare_onion_layers_1"), &AnimationPlayerEditor::_prepare_onion_layers_1);
ClassDB::bind_method(D_METHOD("_prepare_onion_layers_2"), &AnimationPlayerEditor::_prepare_onion_layers_2);
ClassDB::bind_method(D_METHOD("_start_onion_skinning"), &AnimationPlayerEditor::_start_onion_skinning);
ClassDB::bind_method(D_METHOD("_stop_onion_skinning"), &AnimationPlayerEditor::_stop_onion_skinning);
@ -1914,16 +1954,6 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug
onion_skinning->get_popup()->add_check_item(TTR("Include Gizmos (3D)"), ONION_SKINNING_INCLUDE_GIZMOS);
hb->add_child(onion_skinning);
// FIXME: Onion skinning disabled for now as it's broken and triggers fast
// flickering red/blue modulation (GH-53870).
if (hack_disable_onion_skinning) {
onion_toggle->set_disabled(true);
onion_toggle->set_tooltip_text(TTR("Onion Skinning temporarily disabled due to rendering bug."));
onion_skinning->set_disabled(true);
onion_skinning->set_tooltip_text(TTR("Onion Skinning temporarily disabled due to rendering bug."));
}
hb->add_child(memnew(VSeparator));
pin = memnew(Button);
@ -2013,24 +2043,13 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug
track_editor->connect(SNAME("visibility_changed"), callable_mp(this, &AnimationPlayerEditor::_editor_visibility_changed));
onion.enabled = false;
onion.past = true;
onion.future = false;
onion.steps = 1;
onion.differences_only = false;
onion.force_white_modulate = false;
onion.include_gizmos = false;
onion.last_frame = 0;
onion.can_overlay = false;
onion.capture_size = Size2();
onion.capture.canvas = RS::get_singleton()->canvas_create();
onion.capture.canvas_item = RS::get_singleton()->canvas_item_create();
RS::get_singleton()->canvas_item_set_parent(onion.capture.canvas_item, onion.capture.canvas);
onion.capture.material = Ref<ShaderMaterial>(memnew(ShaderMaterial));
onion.capture.material.instantiate();
onion.capture.shader = Ref<Shader>(memnew(Shader));
onion.capture.shader.instantiate();
onion.capture.shader->set_code(R"(
// Animation editor onion skinning shader.
@ -2047,10 +2066,15 @@ float zero_if_equal(vec4 a, vec4 b) {
void fragment() {
vec4 capture_samp = texture(TEXTURE, UV);
vec4 present_samp = texture(present, UV);
float bkg_mask = zero_if_equal(capture_samp, bkg_color);
float diff_mask = 1.0 - zero_if_equal(present_samp, bkg_color);
diff_mask = min(1.0, diff_mask + float(!differences_only));
float diff_mask = 1.0;
if (differences_only) {
// FIXME: If Y-flips across render target, canvas item, etc. was handled correctly,
// this would not be as convoluted in the shader.
vec4 capture_samp2 = texture(TEXTURE, vec2(UV.x, 1.0 - UV.y));
vec4 present_samp = texture(present, vec2(UV.x, 1.0 - UV.y));
diff_mask = 1.0 - zero_if_equal(present_samp, bkg_color);
}
COLOR = vec4(capture_samp.rgb * dir_color.rgb, bkg_mask * diff_mask);
}
)");
@ -2061,6 +2085,7 @@ AnimationPlayerEditor::~AnimationPlayerEditor() {
_free_onion_layers();
RS::get_singleton()->free(onion.capture.canvas);
RS::get_singleton()->free(onion.capture.canvas_item);
onion.capture = {};
}
void AnimationPlayerEditorPlugin::_notification(int p_what) {

View file

@ -136,20 +136,18 @@ class AnimationPlayerEditor : public VBoxContainer {
AnimationTrackEditor *track_editor = nullptr;
static AnimationPlayerEditor *singleton;
bool hack_disable_onion_skinning = true; // Temporary hack for GH-53870.
// Onion skinning.
struct {
// Settings.
bool enabled = false;
bool past = false;
bool past = true;
bool future = false;
int steps = 0;
uint32_t steps = 1;
bool differences_only = false;
bool force_white_modulate = false;
bool include_gizmos = false;
int get_needed_capture_count() const {
uint32_t get_capture_count() const {
// 'Differences only' needs a capture of the present.
return (past && future ? 2 * steps : steps) + (differences_only ? 1 : 0);
}
@ -158,14 +156,23 @@ class AnimationPlayerEditor : public VBoxContainer {
int64_t last_frame = 0;
int can_overlay = 0;
Size2 capture_size;
Vector<RID> captures;
Vector<bool> captures_valid;
LocalVector<RID> captures;
LocalVector<bool> captures_valid;
struct {
RID canvas;
RID canvas_item;
Ref<ShaderMaterial> material;
Ref<Shader> shader;
} capture;
// Cross-call state.
struct {
double anim_player_position = 0.0;
Ref<AnimatedValuesBackup> anim_values_backup;
Rect2 screen_rect;
Dictionary canvas_edit_state;
Dictionary spatial_edit_state;
} temp;
} onion;
void _select_anim_by_name(const String &p_anim);
@ -215,8 +222,10 @@ class AnimationPlayerEditor : public VBoxContainer {
void _allocate_onion_layers();
void _free_onion_layers();
void _prepare_onion_layers_1();
void _prepare_onion_layers_1_deferred();
void _prepare_onion_layers_2();
void _prepare_onion_layers_2_prolog();
void _prepare_onion_layers_2_step_prepare(int p_step_offset, uint32_t p_capture_idx);
void _prepare_onion_layers_2_step_capture(int p_step_offset, uint32_t p_capture_idx);
void _prepare_onion_layers_2_epilog();
void _start_onion_skinning();
void _stop_onion_skinning();