From 34ed823bfc6822f3bd4cc0cbcaabd100f2a73c44 Mon Sep 17 00:00:00 2001 From: Mateusz Dukat Date: Thu, 14 Mar 2024 20:26:39 +0100 Subject: [PATCH] [GPU] Change FPS limit strategy --- src/xenia/gpu/gpu_flags.cc | 5 +- src/xenia/gpu/gpu_flags.h | 2 +- src/xenia/gpu/graphics_system.cc | 90 ++++++++++++++++++++------------ src/xenia/gpu/graphics_system.h | 4 +- 4 files changed, 64 insertions(+), 37 deletions(-) diff --git a/src/xenia/gpu/gpu_flags.cc b/src/xenia/gpu/gpu_flags.cc index e6ae0ad69..4e1506deb 100644 --- a/src/xenia/gpu/gpu_flags.cc +++ b/src/xenia/gpu/gpu_flags.cc @@ -20,7 +20,10 @@ DEFINE_path( DEFINE_bool(vsync, true, "Enable VSYNC.", "GPU"); -DEFINE_uint64(vsync_fps, 60, "VSYNC frames per second", "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 c5ba7fcdd..1ff240035 100644 --- a/src/xenia/gpu/gpu_flags.h +++ b/src/xenia/gpu/gpu_flags.h @@ -18,7 +18,7 @@ DECLARE_path(dump_shaders); DECLARE_bool(vsync); -DECLARE_uint64(vsync_fps); +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 1bb183fb1..5a9d8ac64 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,55 +100,79 @@ X_STATUS GraphicsSystem::Setup(cpu::Processor* processor, reinterpret_cast(ReadRegisterThunk), reinterpret_cast(WriteRegisterThunk)); - // 60hz vsync timer. - vsync_worker_running_ = true; - vsync_worker_thread_ = + // 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)) + ? std::max(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) { - xe::threading::Sleep(std::chrono::milliseconds(1)); + 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)); + } } } 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(); @@ -164,10 +188,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_;