From ebb3cda782353f147613b520e2b9aaa752a5475c Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 8 Mar 2026 03:56:58 -0400 Subject: [PATCH] [android] Quantization for frames + hints Surface --- .../src/main/jni/emu_window/emu_window.cpp | 105 +++++++++++++++++- .../app/src/main/jni/emu_window/emu_window.h | 13 +++ 2 files changed, 117 insertions(+), 1 deletion(-) diff --git a/src/android/app/src/main/jni/emu_window/emu_window.cpp b/src/android/app/src/main/jni/emu_window/emu_window.cpp index 36615f9d96..2bd9f060d6 100644 --- a/src/android/app/src/main/jni/emu_window/emu_window.cpp +++ b/src/android/app/src/main/jni/emu_window/emu_window.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -29,6 +30,10 @@ void EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) { m_window_height = 0; window_info.render_surface = nullptr; m_last_frame_rate_hint = -1.0f; + m_pending_frame_rate_hint = -1.0f; + m_pending_frame_rate_hint_votes = 0; + m_smoothed_present_rate = 0.0f; + m_last_frame_display_time = {}; return; } @@ -59,6 +64,7 @@ void EmuWindow_Android::OnTouchReleased(int id) { } void EmuWindow_Android::OnFrameDisplayed() { + UpdateObservedFrameRate(); UpdateFrameRateHint(); if (!m_first_frame) { @@ -68,6 +74,44 @@ void EmuWindow_Android::OnFrameDisplayed() { } } +void EmuWindow_Android::UpdateObservedFrameRate() { + const auto now = Clock::now(); + if (m_last_frame_display_time.time_since_epoch().count() != 0) { + const auto frame_time = std::chrono::duration(now - m_last_frame_display_time); + const float seconds = frame_time.count(); + if (seconds > 0.0f) { + const float instantaneous_rate = 1.0f / seconds; + if (std::isfinite(instantaneous_rate) && instantaneous_rate >= 10.0f && + instantaneous_rate <= 240.0f) { + constexpr float SmoothingFactor = 0.15f; + if (m_smoothed_present_rate <= 0.0f) { + m_smoothed_present_rate = instantaneous_rate; + } else { + m_smoothed_present_rate += + (instantaneous_rate - m_smoothed_present_rate) * SmoothingFactor; + } + } + } + } + m_last_frame_display_time = now; +} + +float EmuWindow_Android::QuantizeFrameRateHint(float frame_rate) { + if (!std::isfinite(frame_rate) || frame_rate < 20.0f) { + return 0.0f; + } + + constexpr std::array CandidateRates{30.0f, 45.0f, 60.0f, 90.0f, 120.0f}; + const auto best = std::min_element(CandidateRates.begin(), CandidateRates.end(), + [frame_rate](float lhs, float rhs) { + return std::fabs(frame_rate - lhs) < + std::fabs(frame_rate - rhs); + }); + const float best_rate = *best; + const float tolerance = std::max(best_rate * 0.18f, 5.0f); + return std::fabs(frame_rate - best_rate) <= tolerance ? best_rate : 0.0f; +} + float EmuWindow_Android::GetFrameRateHint() const { if (!Settings::values.use_speed_limit.GetValue()) { return 0.0f; @@ -89,7 +133,45 @@ float EmuWindow_Android::GetFrameRateHint() const { return 0.0f; } - return desired_rate; + if (m_last_frame_rate_hint > 0.0f && m_smoothed_present_rate > 0.0f) { + const float observed_rate = m_smoothed_present_rate; + switch (static_cast(m_last_frame_rate_hint)) { + case 30: + if (observed_rate < 42.0f) { + return 30.0f; + } + break; + case 45: + if (observed_rate >= 35.0f && observed_rate < 54.0f) { + return 45.0f; + } + break; + case 60: + if (observed_rate >= 48.0f && observed_rate < 75.0f) { + return 60.0f; + } + break; + case 90: + if (observed_rate >= 74.0f && observed_rate < 105.0f) { + return 90.0f; + } + break; + case 120: + if (observed_rate >= 100.0f) { + return 120.0f; + } + break; + default: + break; + } + } + + const float observed_hint = QuantizeFrameRateHint(m_smoothed_present_rate); + if (observed_hint > 0.0f) { + return observed_hint; + } + + return QuantizeFrameRateHint(desired_rate); } void EmuWindow_Android::UpdateFrameRateHint() { @@ -100,9 +182,28 @@ void EmuWindow_Android::UpdateFrameRateHint() { const float frame_rate_hint = GetFrameRateHint(); if (std::fabs(frame_rate_hint - m_last_frame_rate_hint) < 0.01f) { + m_pending_frame_rate_hint = frame_rate_hint; + m_pending_frame_rate_hint_votes = 0; return; } + if (frame_rate_hint == 0.0f) { + m_pending_frame_rate_hint = frame_rate_hint; + m_pending_frame_rate_hint_votes = 0; + } else if (m_last_frame_rate_hint >= 0.0f) { + if (std::fabs(frame_rate_hint - m_pending_frame_rate_hint) >= 0.01f) { + m_pending_frame_rate_hint = frame_rate_hint; + m_pending_frame_rate_hint_votes = 1; + return; + } + + ++m_pending_frame_rate_hint_votes; + constexpr std::uint32_t StableVoteThreshold = 12; + if (m_pending_frame_rate_hint_votes < StableVoteThreshold) { + return; + } + } + using SetFrameRateWithChangeStrategyFn = int32_t (*)(ANativeWindow*, float, int8_t, int8_t); static const auto set_frame_rate_with_change_strategy = @@ -123,6 +224,8 @@ void EmuWindow_Android::UpdateFrameRateHint() { } m_last_frame_rate_hint = frame_rate_hint; + m_pending_frame_rate_hint = frame_rate_hint; + m_pending_frame_rate_hint_votes = 0; } EmuWindow_Android::EmuWindow_Android(ANativeWindow* surface, diff --git a/src/android/app/src/main/jni/emu_window/emu_window.h b/src/android/app/src/main/jni/emu_window/emu_window.h index b2210e0b20..3d0f74a481 100644 --- a/src/android/app/src/main/jni/emu_window/emu_window.h +++ b/src/android/app/src/main/jni/emu_window/emu_window.h @@ -1,8 +1,13 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #pragma once +#include +#include #include #include @@ -50,8 +55,12 @@ public: }; private: + using Clock = std::chrono::steady_clock; + void UpdateFrameRateHint(); + void UpdateObservedFrameRate(); [[nodiscard]] float GetFrameRateHint() const; + [[nodiscard]] static float QuantizeFrameRateHint(float frame_rate); float m_window_width{}; float m_window_height{}; @@ -60,4 +69,8 @@ private: bool m_first_frame = false; float m_last_frame_rate_hint = -1.0f; + float m_pending_frame_rate_hint = -1.0f; + float m_smoothed_present_rate = 0.0f; + Clock::time_point m_last_frame_display_time{}; + std::uint32_t m_pending_frame_rate_hint_votes = 0; };