From 761f3b84eaf6fb00a0da459082ce2a182a6e8eed Mon Sep 17 00:00:00 2001 From: Mateusz Dukat Date: Mon, 11 Mar 2024 21:29:56 +0100 Subject: [PATCH] [GPU] Change FPS limit strategy Framerate is now unlimited, when cvars::framerate_limit is set to 0. Framerate locks at 60, when VSYNC is enabled, and framerate_limit is set to 0. Removed any previous hard_fps reference. --- src/xenia/gpu/gpu_flags.cc | 12 ++--- src/xenia/gpu/gpu_flags.h | 5 +- src/xenia/gpu/graphics_system.cc | 85 +++++++++++++++++++------------- src/xenia/gpu/graphics_system.h | 4 +- 4 files changed, 59 insertions(+), 47 deletions(-) diff --git a/src/xenia/gpu/gpu_flags.cc b/src/xenia/gpu/gpu_flags.cc index 810d9f5ee..8699aa7aa 100644 --- a/src/xenia/gpu/gpu_flags.cc +++ b/src/xenia/gpu/gpu_flags.cc @@ -20,14 +20,10 @@ DEFINE_path( DEFINE_bool(vsync, true, "Enable VSYNC.", "GPU"); -DEFINE_uint64(vsync_fps, 60, "VSYNC frames per second", "GPU"); - -DEFINE_uint64(hard_fps, 60, - "Hard FPS lock, ignored when vsync is used. " - "Game will never exceed this value", "GPU"); - -DEFINE_bool(hard_fps_enable, false, - "Enable hard FPS lock, ignored if vsync is enabled", "GPU"); +DEFINE_uint64(framerate_limit, 60, + "Maximum frames per second. 0 = Unlimited frames.\n" + "Defaults to 60, when set to 0, and VSYNC is enabled.", + "GPU"); DEFINE_bool( gpu_allow_invalid_fetch_constants, true, diff --git a/src/xenia/gpu/gpu_flags.h b/src/xenia/gpu/gpu_flags.h index e0f97a348..1ff240035 100644 --- a/src/xenia/gpu/gpu_flags.h +++ b/src/xenia/gpu/gpu_flags.h @@ -18,10 +18,7 @@ DECLARE_path(dump_shaders); DECLARE_bool(vsync); -DECLARE_uint64(vsync_fps); - -DECLARE_uint64(hard_fps); -DECLARE_bool(hard_fps_enable); +DECLARE_uint64(framerate_limit); DECLARE_bool(gpu_allow_invalid_fetch_constants); diff --git a/src/xenia/gpu/graphics_system.cc b/src/xenia/gpu/graphics_system.cc index 91c3a3139..e374a911e 100644 --- a/src/xenia/gpu/graphics_system.cc +++ b/src/xenia/gpu/graphics_system.cc @@ -50,7 +50,7 @@ __declspec(dllexport) uint32_t AmdPowerXpressRequestHighPerformance = 1; } // extern "C" #endif // XE_PLATFORM_WIN32 -GraphicsSystem::GraphicsSystem() : vsync_worker_running_(false) { +GraphicsSystem::GraphicsSystem() : frame_limiter_worker_running_(false) { register_file_ = reinterpret_cast(memory::AllocFixed( nullptr, sizeof(RegisterFile), memory::AllocationType::kReserveCommit, memory::PageAccess::kReadWrite)); @@ -100,45 +100,64 @@ X_STATUS GraphicsSystem::Setup(cpu::Processor* processor, reinterpret_cast(ReadRegisterThunk), reinterpret_cast(WriteRegisterThunk)); - // 60hz vsync timer. - vsync_worker_running_ = true; - vsync_worker_thread_ = kernel::object_ref( + // Frame limiter thread. + frame_limiter_worker_running_ = true; + frame_limiter_worker_thread_ = kernel::object_ref( new kernel::XHostThread(kernel_state_, 128 * 1024, 0, [this]() { + uint64_t normalized_framerate_limit = + std::max(0, cvars::framerate_limit); + + // If VSYNC is enabled, but frames are not limited, + // lock framerate at default value of 60 + if (normalized_framerate_limit == 0 && cvars::vsync) + normalized_framerate_limit = 60; + const double vsync_duration_d = cvars::vsync ? std::max( - 5.0, 1000.0 / static_cast(cvars::vsync_fps)) + 5.0, 1000.0 / static_cast( + normalized_framerate_limit + )) : 1.0; uint64_t last_frame_time = Clock::QueryGuestTickCount(); // Sleep for 90% of the vblank duration, spin for 10% const double duration_scalar = 0.90; - while (vsync_worker_running_) { - const uint64_t current_time = Clock::QueryGuestTickCount(); - const uint64_t tick_freq = Clock::guest_tick_frequency(); - const uint64_t time_delta = current_time - last_frame_time; - const double elapsed_d = static_cast(time_delta) / - (static_cast(tick_freq) / 1000.0); - if (elapsed_d >= vsync_duration_d) { - last_frame_time = current_time; + while (frame_limiter_worker_running_) { + if (cvars::vsync) { + const uint64_t current_time = Clock::QueryGuestTickCount(); + const uint64_t tick_freq = Clock::guest_tick_frequency(); + const uint64_t time_delta = current_time - last_frame_time; + const double elapsed_d = static_cast(time_delta) / + (static_cast(tick_freq) / 1000.0); + if (elapsed_d >= vsync_duration_d) { + last_frame_time = current_time; - // TODO(disjtqz): should recalculate the remaining time to a vblank - // after MarkVblank, no idea how long the guest code normally takes - MarkVblank(); - if (cvars::vsync) { - const uint64_t estimated_nanoseconds = static_cast( - (vsync_duration_d * 1000000.0) * - duration_scalar); // 1000 microseconds = 1 ms + // TODO(disjtqz): should recalculate the remaining time to a + // vblank after MarkVblank, no idea how long the guest code + // normally takes + MarkVblank(); + if (cvars::vsync) { + const uint64_t estimated_nanoseconds = static_cast( + (vsync_duration_d * 1000000.0) * + duration_scalar); // 1000 microseconds = 1 ms - threading::NanoSleep(estimated_nanoseconds); + threading::NanoSleep(estimated_nanoseconds); + } } } - + if (!cvars::vsync) { - if (cvars::hard_fps_enable) { - uint64_t hard_frame_sleep_time = 1000000000 / cvars::hard_fps; - xe::threading::NanoSleep(hard_frame_sleep_time); + MarkVblank(); + if (normalized_framerate_limit > 0) { + // framerate_limit is over 0, vsync disabled + // - No VSYNC + limited frames defined by user + uint64_t framerate_limited_sleep_time = + 1000000000 / normalized_framerate_limit; + xe::threading::NanoSleep(framerate_limited_sleep_time); } else { + // framerate_limit is 0, vsync disabled + // - No VSYNC + unlimited frames xe::threading::Sleep(std::chrono::milliseconds(1)); } } @@ -146,10 +165,10 @@ X_STATUS GraphicsSystem::Setup(cpu::Processor* processor, return 0; }, kernel_state->GetIdleProcess())); // As we run vblank interrupts the debugger must be able to suspend us. - vsync_worker_thread_->set_can_debugger_suspend(true); - vsync_worker_thread_->set_name("GPU VSync"); - vsync_worker_thread_->Create(); - vsync_worker_thread_->thread()->set_priority( + frame_limiter_worker_thread_->set_can_debugger_suspend(true); + frame_limiter_worker_thread_->set_name("GPU Frame limiter"); + frame_limiter_worker_thread_->Create(); + frame_limiter_worker_thread_->thread()->set_priority( threading::ThreadPriority::kLowest); if (cvars::trace_gpu_stream) { BeginTracing(); @@ -165,10 +184,10 @@ void GraphicsSystem::Shutdown() { command_processor_.reset(); } - if (vsync_worker_thread_) { - vsync_worker_running_ = false; - vsync_worker_thread_->Wait(0, 0, 0, nullptr); - vsync_worker_thread_.reset(); + if (frame_limiter_worker_thread_) { + frame_limiter_worker_running_ = false; + frame_limiter_worker_thread_->Wait(0, 0, 0, nullptr); + frame_limiter_worker_thread_.reset(); } if (presenter_) { diff --git a/src/xenia/gpu/graphics_system.h b/src/xenia/gpu/graphics_system.h index ef58d4569..aa3662edc 100644 --- a/src/xenia/gpu/graphics_system.h +++ b/src/xenia/gpu/graphics_system.h @@ -109,8 +109,8 @@ class GraphicsSystem { uint32_t interrupt_callback_ = 0; uint32_t interrupt_callback_data_ = 0; - std::atomic vsync_worker_running_; - kernel::object_ref vsync_worker_thread_; + std::atomic frame_limiter_worker_running_; + kernel::object_ref frame_limiter_worker_thread_; RegisterFile* register_file_; std::unique_ptr command_processor_;