Native renderer performance overhaul + high-fps motion smoothing
This commit is contained in:
parent
77d22e22e5
commit
c613ffacc8
4 changed files with 1503 additions and 230 deletions
|
|
@ -24,6 +24,10 @@ REXCVAR_DECLARE(bool, skate3_native_render_scene_tex_revalidate);
|
|||
REXCVAR_DECLARE(bool, skate3_native_render_scene_mesh_revalidate);
|
||||
REXCVAR_DECLARE(bool, skate3_native_render_scene_tex_mips);
|
||||
REXCVAR_DECLARE(int32_t, skate3_native_render_scene_debug);
|
||||
// Smoothness / pacing.
|
||||
REXCVAR_DECLARE(bool, skate3_native_render_scene_smooth_camera);
|
||||
REXCVAR_DECLARE(bool, skate3_native_render_scene_sort_opaque);
|
||||
REXCVAR_DECLARE(double, skate3_guest_fps_cap);
|
||||
// SDK: emulated-draw suppression while the native output is active.
|
||||
REXCVAR_DECLARE(bool, native_render_suppress_emulated_draws);
|
||||
|
||||
|
|
@ -149,6 +153,34 @@ void NativeDebugDialog::OnDraw(ImGuiIO& io) {
|
|||
CvarCheckbox("Neon splines", REXCVAR_GET(skate3_native_render_scene_splines),
|
||||
"Waypoint arrows / marker beams"));
|
||||
|
||||
ImGui::SeparatorText("Smoothness / pacing");
|
||||
REXCVAR_SET(skate3_native_render_scene_smooth_camera,
|
||||
CvarCheckbox("Smooth camera + entity poses",
|
||||
REXCVAR_GET(skate3_native_render_scene_smooth_camera),
|
||||
"The guest updates its camera/entities on its own ~200 Hz "
|
||||
"sim tick; raw poses judder at high render rates. Re-times "
|
||||
"them on the host clock (1 kHz camera sampler + pose "
|
||||
"interpolation, a few ms of camera latency)."));
|
||||
REXCVAR_SET(skate3_native_render_scene_sort_opaque,
|
||||
CvarCheckbox("Front-to-back opaque sort",
|
||||
REXCVAR_GET(skate3_native_render_scene_sort_opaque),
|
||||
"Early-z rejects occluded pixels before the material shading"));
|
||||
{
|
||||
int cap = int(REXCVAR_GET(skate3_guest_fps_cap));
|
||||
if (ImGui::InputInt("Guest fps cap (0 = off)", &cap, 10, 30)) {
|
||||
if (cap < 0) cap = 0;
|
||||
if (cap > 1000) cap = 1000;
|
||||
REXCVAR_SET(skate3_guest_fps_cap, double(cap));
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip(
|
||||
"Pace guest frames on an even beat. Set a few fps below the display "
|
||||
"refresh (with G-Sync/VRR this is what makes motion read as smooth; "
|
||||
"uncapped, the guest's irregular frame times drive the refresh "
|
||||
"directly).");
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SeparatorText("Caches (lightmap-era plumbing)");
|
||||
REXCVAR_SET(
|
||||
skate3_native_render_scene_tex_revalidate,
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
#include <fstream>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#if defined(_WIN32)
|
||||
|
|
@ -49,6 +50,17 @@ REXCVAR_DEFINE_INT32(skate3_native_render_snapshot_stride, 1, "Skate 3",
|
|||
REXCVAR_DEFINE_STRING(skate3_native_render_snapshot_dir, "native_render_snapshots", "Skate 3",
|
||||
"Directory for native-render guest memory snapshots and metadata")
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||
REXCVAR_DEFINE_DOUBLE(skate3_guest_fps_cap, 0.0, "Skate 3",
|
||||
"Pace the guest render loop to this frame rate (0 = uncapped). The "
|
||||
"guest produces frames at irregular 2-9 ms intervals; the display "
|
||||
"(especially with G-Sync/VRR, which follows present times directly) "
|
||||
"turns that variance into visible irregular judder that no content "
|
||||
"smoothing can fix. An even cap a few fps below the display refresh "
|
||||
"(e.g. 140 on a 144 Hz panel) is the standard VRR recipe: every "
|
||||
"frame arrives on a steady beat. Precise pacing: coarse sleep to "
|
||||
"~1.5 ms before the target, then spin.")
|
||||
.range(0.0, 1000.0)
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||
|
||||
namespace skate3::native_render {
|
||||
namespace {
|
||||
|
|
@ -263,7 +275,42 @@ void OnClothDraw(uint8_t* base, uint32_t r4, uint32_t r5, uint32_t r6, uint32_t
|
|||
g_current_frame.push_back({3, key, 0, dyn});
|
||||
}
|
||||
|
||||
// Precise guest frame pacing (see skate3_guest_fps_cap): called on the guest
|
||||
// render thread at the swap boundary. Absolute-schedule pacing (target +=
|
||||
// interval) so sleep jitter never accumulates; resyncs when the guest falls
|
||||
// more than one interval behind (loads, hitches).
|
||||
void PaceGuestFrame() {
|
||||
const double cap = REXCVAR_GET(skate3_guest_fps_cap);
|
||||
static std::chrono::steady_clock::time_point s_next{};
|
||||
if (cap < 1.0) {
|
||||
s_next = {};
|
||||
return;
|
||||
}
|
||||
const auto interval =
|
||||
std::chrono::duration_cast<std::chrono::steady_clock::duration>(
|
||||
std::chrono::duration<double>(1.0 / cap));
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
if (s_next.time_since_epoch().count() == 0 || now > s_next + interval) {
|
||||
s_next = now + interval;
|
||||
return;
|
||||
}
|
||||
// Coarse sleep to ~1.5 ms before the target, then spin for precision.
|
||||
while (true) {
|
||||
const auto remaining = s_next - std::chrono::steady_clock::now();
|
||||
if (remaining <= std::chrono::steady_clock::duration::zero()) {
|
||||
break;
|
||||
}
|
||||
if (remaining > std::chrono::milliseconds(2)) {
|
||||
std::this_thread::sleep_for(remaining - std::chrono::milliseconds(2));
|
||||
} else if (remaining > std::chrono::microseconds(50)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
||||
s_next += interval;
|
||||
}
|
||||
|
||||
void OnFrameEnd(uint8_t* base) {
|
||||
PaceGuestFrame();
|
||||
std::lock_guard<std::mutex> lock(g_mutex);
|
||||
++g_frame_index;
|
||||
const size_t mesh_count = g_current_frame.size();
|
||||
|
|
@ -306,6 +353,18 @@ void OnFrameEnd(uint8_t* base) {
|
|||
g_frame_index, mesh_count);
|
||||
}
|
||||
f9_was_down = f9_down;
|
||||
// F8: flush the native texture + mesh caches. Debug/bisect aid, and the
|
||||
// reproducible worst-case decode burst for perf work (everything visible
|
||||
// re-decodes at once; the decode workers should absorb it with a brief
|
||||
// white/pop-in instead of a render-thread freeze).
|
||||
static bool f8_was_down = false;
|
||||
const bool f8_down = (GetAsyncKeyState(VK_F8) & 0x8000) != 0;
|
||||
if (f8_down && !f8_was_down) {
|
||||
skate3::native_scene::FlushTextureCache();
|
||||
skate3::native_scene::FlushMeshCache();
|
||||
REXLOG_INFO("native-render: F8, texture + mesh caches flushed");
|
||||
}
|
||||
f8_was_down = f8_down;
|
||||
static bool f10_was_down = false;
|
||||
const bool f10_down = (GetAsyncKeyState(VK_F10) & 0x8000) != 0;
|
||||
if (f10_down && !f10_was_down) {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
2
third_party/rexglue-sdk
vendored
2
third_party/rexglue-sdk
vendored
|
|
@ -1 +1 @@
|
|||
Subproject commit 0fcbad33e66829aa61ca95c166c64f387e56fcd0
|
||||
Subproject commit 96de5455f2eb87f183dfc9459780008780ee245c
|
||||
Loading…
Add table
Reference in a new issue