From b2c83b42f6502d03457e4276417b5ab84b5c805b Mon Sep 17 00:00:00 2001 From: Arseny Kapoulkine Date: Sat, 17 Aug 2024 09:28:40 -0700 Subject: [PATCH] Relax motion vector updates to allow skipped frames for skeletons Before this change, a skeleton that was not updated every frame would result in a difference of 2+ between last_change and frame index every frame, which would disable the buffer rotation and set motion vectors to zero. This results in significant visual artifacts for FSR2 that are especially prominent on the characters that move together with the view such as the main character in third person mode. This is a significant problem for high refresh rate displays: at 120 Hz, we are effectively guaranteed to skip skeleton updates every other frame with skeleton update happening during physics processing, and the lack of physics interpolation for skeletons. This happens by default in TPS demo when FSR2 is enabled. In other places where motion vectors are disabled, such as multi-mesh and mesh rendering (where previous transform is updated), the logic effectively allows for a single-frame gap in updates, because it compares the frame where the update happened (which is the current frame if updates are consistent) with the current frame, so the latency of 0 means "update just happened", but both multi-mesh and mesh transform updates permit a latency of 1 as well. Here, however, last_change is updated *after* the frame processing has concluded, so a zero-latency update has a distance of 1. Allowing a distance of 2 (latency 1) reduces the severity of the problem and aligns the logic with transform updates. Note that the problem will still happen when refresh rate is noticeably higher than physics rate times 2. For example, it still happens at 240 Hz. However, a longer latency allowance is inconsistent with other transforms and could lead to issues, so ideally long term physical interpolation of skeleton transforms would completely solve this. (cherry picked from commit 92f2bc70dd734e0de3b6c7f18c57680c63715343) --- servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp index 99622996d45..057c0450813 100644 --- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp +++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp @@ -1056,8 +1056,9 @@ void MeshStorage::update_mesh_instances() { mi->surfaces[i].previous_buffer = mi->surfaces[i].current_buffer; - if (uses_motion_vectors && (frame - mi->surfaces[i].last_change) == 1) { - // Previous buffer's data can only be one frame old to be able to use motion vectors. + if (uses_motion_vectors && mi->surfaces[i].last_change && (frame - mi->surfaces[i].last_change) <= 2) { + // Use a 2-frame tolerance so that stepped skeletal animations have correct motion vectors + // (stepped animation is common for distant NPCs). uint32_t new_buffer_index = mi->surfaces[i].current_buffer ^ 1; if (mi->surfaces[i].uniform_set[new_buffer_index].is_null()) {