mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-08-14 04:24:31 +00:00
Queues and refresh rate adjustments
This commit is contained in:
@@ -222,6 +222,12 @@ object NativeLibrary {
|
||||
|
||||
external fun getDebugKnobAt(index: Int): Boolean
|
||||
|
||||
/**
|
||||
* Publishes the refresh rate of the display the emulation surface lives on, in hertz.
|
||||
* Frame generation uses it to cap the multiplier to what the display can actually present.
|
||||
*/
|
||||
external fun setDisplayRefreshRate(rate: Float)
|
||||
|
||||
/**
|
||||
* Set the current speed limit to the configured turbo speed.
|
||||
*/
|
||||
|
||||
@@ -245,6 +245,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
publishDisplayRefreshRate()
|
||||
nfcReader.startScanning()
|
||||
startMotionSensorListener()
|
||||
InputHandler.updateControllerData()
|
||||
@@ -253,6 +254,11 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
|
||||
buildPictureInPictureParams()
|
||||
}
|
||||
|
||||
private fun publishDisplayRefreshRate() {
|
||||
val rate = display?.mode?.refreshRate ?: display?.refreshRate ?: 0f
|
||||
NativeLibrary.setDisplayRefreshRate(rate)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
nfcReader.stopScanning()
|
||||
stopMotionSensorListener()
|
||||
|
||||
@@ -20,6 +20,7 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
|
||||
RENDERER_ACCURACY("gpu_accuracy"),
|
||||
RENDERER_RESOLUTION("resolution_setup"),
|
||||
RENDERER_FRAME_GEN_MULTIPLIER("frame_gen_multiplier"),
|
||||
RENDERER_FRAME_GEN_QUEUE_TARGET("frame_gen_queue_target"),
|
||||
RENDERER_FRAME_GEN_FLOW_SCALE("frame_gen_flow_scale"),
|
||||
RENDERER_VSYNC("use_vsync"),
|
||||
RENDERER_SCALING_FILTER("scaling_filter"),
|
||||
|
||||
+10
@@ -99,6 +99,7 @@ abstract class SettingsItem(
|
||||
private val frameGenKeys = setOf(
|
||||
BooleanSetting.RENDERER_FRAME_GEN.key,
|
||||
IntSetting.RENDERER_FRAME_GEN_MULTIPLIER.key,
|
||||
IntSetting.RENDERER_FRAME_GEN_QUEUE_TARGET.key,
|
||||
IntSetting.RENDERER_FRAME_GEN_FLOW_SCALE.key,
|
||||
BooleanSetting.RENDERER_FRAME_GEN_FP16.key,
|
||||
BooleanSetting.RENDERER_FRAME_GEN_DUMP_FLOW.key
|
||||
@@ -638,6 +639,15 @@ abstract class SettingsItem(
|
||||
valuesId = R.array.frameGenMultiplierValues
|
||||
)
|
||||
)
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.RENDERER_FRAME_GEN_QUEUE_TARGET,
|
||||
titleId = R.string.frame_gen_queue_target,
|
||||
descriptionId = R.string.frame_gen_queue_target_description,
|
||||
choicesId = R.array.frameGenQueueTargetNames,
|
||||
valuesId = R.array.frameGenQueueTargetValues
|
||||
)
|
||||
)
|
||||
put(
|
||||
SliderSetting(
|
||||
IntSetting.RENDERER_FRAME_GEN_FLOW_SCALE,
|
||||
|
||||
+1
@@ -126,6 +126,7 @@ class SettingsFragmentPresenter(
|
||||
|
||||
add(BooleanSetting.RENDERER_FRAME_GEN.key)
|
||||
add(IntSetting.RENDERER_FRAME_GEN_MULTIPLIER.key)
|
||||
add(IntSetting.RENDERER_FRAME_GEN_QUEUE_TARGET.key)
|
||||
add(IntSetting.RENDERER_FRAME_GEN_FLOW_SCALE.key)
|
||||
add(BooleanSetting.RENDERER_FRAME_GEN_FP16.key)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <dlfcn.h>
|
||||
|
||||
#include "common/android/id_cache.h"
|
||||
@@ -33,8 +34,12 @@ void EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) {
|
||||
m_pending_frame_rate_hint = -1.0f;
|
||||
m_pending_frame_rate_hint_votes = 0;
|
||||
m_smoothed_present_rate = 0.0f;
|
||||
m_peak_guest_frame_rate = 0.0f;
|
||||
m_published_guest_frame_rate = 0.0f;
|
||||
Settings::values.guest_frame_rate = 0.0f;
|
||||
m_last_frame_display_time = {};
|
||||
m_pending_frame_rate_since = {};
|
||||
m_guest_frame_rate_since = {};
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -64,8 +69,28 @@ void EmuWindow_Android::OnTouchReleased(int id) {
|
||||
EmulationSession::GetInstance().GetInputSubsystem().GetTouchScreen()->TouchReleased(id);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::array GUEST_FRAME_RATE_LADDER{20.0f, 30.0f, 60.0f};
|
||||
|
||||
[[nodiscard]] float SnapToGuestFrameRateLadder(float rate) {
|
||||
float snapped = GUEST_FRAME_RATE_LADDER.front();
|
||||
float best_distance = std::numeric_limits<float>::max();
|
||||
for (const float step : GUEST_FRAME_RATE_LADDER) {
|
||||
const float distance = std::fabs(step - rate);
|
||||
if (distance <= best_distance) {
|
||||
best_distance = distance;
|
||||
snapped = step;
|
||||
}
|
||||
}
|
||||
return snapped;
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
void EmuWindow_Android::OnFrameDisplayed() {
|
||||
UpdateObservedFrameRate();
|
||||
UpdateGuestFrameRate();
|
||||
UpdateFrameRateHint();
|
||||
|
||||
if (!m_first_frame) {
|
||||
@@ -97,6 +122,40 @@ void EmuWindow_Android::UpdateObservedFrameRate() {
|
||||
m_last_frame_display_time = now;
|
||||
}
|
||||
|
||||
void EmuWindow_Android::UpdateGuestFrameRate() {
|
||||
const float measured = GetFrameTimeVerifiedHint();
|
||||
if (measured <= 0.0f) {
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr float PeakDecayFactor = 0.0015f;
|
||||
m_peak_guest_frame_rate =
|
||||
measured > m_peak_guest_frame_rate
|
||||
? measured
|
||||
: m_peak_guest_frame_rate + (measured - m_peak_guest_frame_rate) * PeakDecayFactor;
|
||||
|
||||
const float candidate = SnapToGuestFrameRateLadder(m_peak_guest_frame_rate);
|
||||
if (candidate == m_published_guest_frame_rate) {
|
||||
m_guest_frame_rate_since = {};
|
||||
return;
|
||||
}
|
||||
|
||||
const auto now = Clock::now();
|
||||
if (m_guest_frame_rate_since.time_since_epoch().count() == 0) {
|
||||
m_guest_frame_rate_since = now;
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr auto SettleDuration = std::chrono::seconds(3);
|
||||
if (now - m_guest_frame_rate_since < SettleDuration) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_published_guest_frame_rate = candidate;
|
||||
Settings::values.guest_frame_rate = candidate;
|
||||
m_guest_frame_rate_since = {};
|
||||
}
|
||||
|
||||
float EmuWindow_Android::QuantizeFrameRateHint(float frame_rate) {
|
||||
if (!std::isfinite(frame_rate) || frame_rate <= 0.0f) {
|
||||
return 0.0f;
|
||||
@@ -128,7 +187,7 @@ float EmuWindow_Android::GetPresentedFrameMultiplier() {
|
||||
if (!Settings::values.frame_gen.GetValue()) {
|
||||
return 1.0f;
|
||||
}
|
||||
return static_cast<float>(std::clamp<u32>(Settings::values.frame_gen_multiplier.GetValue(), 2, 4));
|
||||
return static_cast<float>(Settings::FrameGenMultiplier());
|
||||
}
|
||||
|
||||
float EmuWindow_Android::GetFrameRateHint() const {
|
||||
|
||||
@@ -59,6 +59,7 @@ private:
|
||||
|
||||
void UpdateFrameRateHint();
|
||||
void UpdateObservedFrameRate();
|
||||
void UpdateGuestFrameRate();
|
||||
[[nodiscard]] float GetFrameRateHint() const;
|
||||
[[nodiscard]] float GetFrameTimeVerifiedHint() const;
|
||||
[[nodiscard]] static float GetPresentedFrameMultiplier();
|
||||
@@ -73,7 +74,10 @@ private:
|
||||
float m_last_frame_rate_hint = -1.0f;
|
||||
float m_pending_frame_rate_hint = -1.0f;
|
||||
float m_smoothed_present_rate = 0.0f;
|
||||
float m_peak_guest_frame_rate = 0.0f;
|
||||
float m_published_guest_frame_rate = 0.0f;
|
||||
Clock::time_point m_last_frame_display_time{};
|
||||
Clock::time_point m_pending_frame_rate_since{};
|
||||
Clock::time_point m_guest_frame_rate_since{};
|
||||
std::uint32_t m_pending_frame_rate_hint_votes = 0;
|
||||
};
|
||||
|
||||
@@ -1229,6 +1229,11 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_getDebugKnobAt(JNIEnv* env, jobje
|
||||
return static_cast<jboolean>(Settings::getDebugKnobAt(static_cast<u8>(index)));
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setDisplayRefreshRate(JNIEnv* env, jobject jobj,
|
||||
jfloat rate) {
|
||||
Settings::values.display_refresh_rate = static_cast<float>(rate);
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setTurboSpeedLimit(JNIEnv *env, jobject jobj, jboolean enabled) {
|
||||
if (enabled) {
|
||||
Settings::values.use_speed_limit.SetValue(true);
|
||||
|
||||
@@ -163,17 +163,31 @@
|
||||
</string-array>
|
||||
|
||||
<string-array name="frameGenMultiplierNames">
|
||||
<item>@string/frame_gen_multiplier_auto</item>
|
||||
<item>@string/frame_gen_multiplier_2x</item>
|
||||
<item>@string/frame_gen_multiplier_3x</item>
|
||||
<item>@string/frame_gen_multiplier_4x</item>
|
||||
</string-array>
|
||||
|
||||
<integer-array name="frameGenMultiplierValues">
|
||||
<item>0</item>
|
||||
<item>2</item>
|
||||
<item>3</item>
|
||||
<item>4</item>
|
||||
</integer-array>
|
||||
|
||||
<string-array name="frameGenQueueTargetNames">
|
||||
<item>@string/frame_gen_queue_target_0</item>
|
||||
<item>@string/frame_gen_queue_target_1</item>
|
||||
<item>@string/frame_gen_queue_target_2</item>
|
||||
</string-array>
|
||||
|
||||
<integer-array name="frameGenQueueTargetValues">
|
||||
<item>0</item>
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
</integer-array>
|
||||
|
||||
<string-array name="rendererVSyncNames">
|
||||
<item>@string/renderer_vsync_immediate</item>
|
||||
<item>@string/renderer_vsync_mailbox</item>
|
||||
|
||||
@@ -302,10 +302,16 @@
|
||||
<string name="frame_gen_submenu_description">Manage and configure frame generation</string>
|
||||
<string name="frame_gen_description">Insert interpolated frames between rendered ones using Lossless Scaling. Forces FIFO presentation while enabled.</string>
|
||||
<string name="frame_gen_multiplier">Frame multiplier</string>
|
||||
<string name="frame_gen_multiplier_description">How many frames to display for each rendered frame. Higher values cost proportionally more GPU time.</string>
|
||||
<string name="frame_gen_multiplier_description">How many frames to display for each rendered frame. Higher values cost proportionally more GPU time. Capped to what your display can present at the game\'s frame rate.</string>
|
||||
<string name="frame_gen_multiplier_auto">Auto</string>
|
||||
<string name="frame_gen_multiplier_2x">2x</string>
|
||||
<string name="frame_gen_multiplier_3x">3x</string>
|
||||
<string name="frame_gen_multiplier_4x">4x</string>
|
||||
<string name="frame_gen_queue_target">Frame queue target</string>
|
||||
<string name="frame_gen_queue_target_description">How many finished frames may wait ahead of the display. Larger queues absorb GPU spikes at the cost of input latency.</string>
|
||||
<string name="frame_gen_queue_target_0">Unbuffered (lowest latency)</string>
|
||||
<string name="frame_gen_queue_target_1">One frame (balanced)</string>
|
||||
<string name="frame_gen_queue_target_2">Two frames (smoothest)</string>
|
||||
<string name="frame_gen_flow_scale">Motion estimation resolution</string>
|
||||
<string name="frame_gen_flow_scale_description">Resolution of the optical flow pass, as a fraction of the output. Lowering it is the cheapest way to reclaim performance.</string>
|
||||
<string name="frame_gen_fp16">Half precision shaders</string>
|
||||
|
||||
@@ -380,6 +380,43 @@ void UpdateRescalingInfo() {
|
||||
TranslateResolutionInfo(setup, info);
|
||||
}
|
||||
|
||||
u32 FrameGenMultiplierCeiling() {
|
||||
const u32 configured = values.frame_gen_multiplier.GetValue();
|
||||
if (configured == AUTO_FRAME_GEN_MULTIPLIER) {
|
||||
return MAX_FRAME_GEN_MULTIPLIER;
|
||||
}
|
||||
return std::clamp(configured, MIN_FRAME_GEN_MULTIPLIER, MAX_FRAME_GEN_MULTIPLIER);
|
||||
}
|
||||
|
||||
u32 FrameGenMultiplier() {
|
||||
const u32 ceiling = FrameGenMultiplierCeiling();
|
||||
|
||||
const float refresh = values.display_refresh_rate;
|
||||
if (refresh <= 1.0f) {
|
||||
return ceiling;
|
||||
}
|
||||
|
||||
constexpr float UNMEASURED_GUEST_FRAME_RATE = 60.0f;
|
||||
float base = values.guest_frame_rate > 1.0f ? values.guest_frame_rate
|
||||
: UNMEASURED_GUEST_FRAME_RATE;
|
||||
if (values.use_speed_limit.GetValue()) {
|
||||
const u16 limit = std::min<u16>(SpeedLimit(), 100);
|
||||
if (limit > 0) {
|
||||
base *= static_cast<float>(limit) / 100.0f;
|
||||
}
|
||||
}
|
||||
|
||||
const u32 presentable = static_cast<u32>(refresh / std::max(base, 1.0f));
|
||||
return std::min(ceiling, std::max(presentable, 1u));
|
||||
}
|
||||
|
||||
size_t FrameGenGenerations() {
|
||||
if (!values.frame_gen.GetValue()) {
|
||||
return 0;
|
||||
}
|
||||
return FrameGenMultiplier() - 1;
|
||||
}
|
||||
|
||||
void RestoreGlobalState(bool is_powered_on) {
|
||||
// If a game is running, DO NOT restore the global settings state
|
||||
if (is_powered_on) {
|
||||
|
||||
+27
-2
@@ -392,8 +392,8 @@ struct Values {
|
||||
Specialization::Default, true, false};
|
||||
|
||||
SwitchableSetting<u32, true> frame_gen_multiplier{linkage,
|
||||
2,
|
||||
2,
|
||||
0,
|
||||
0,
|
||||
4,
|
||||
"frame_gen_multiplier",
|
||||
Category::Renderer,
|
||||
@@ -414,6 +414,17 @@ struct Values {
|
||||
true,
|
||||
&frame_gen};
|
||||
|
||||
SwitchableSetting<u32, true> frame_gen_queue_target{linkage,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
"frame_gen_queue_target",
|
||||
Category::Renderer,
|
||||
Specialization::Countable,
|
||||
true,
|
||||
false,
|
||||
&frame_gen};
|
||||
|
||||
SwitchableSetting<bool> frame_gen_fp16{linkage, true, "frame_gen_fp16", Category::Renderer,
|
||||
Specialization::Default, true, false, &frame_gen};
|
||||
|
||||
@@ -902,10 +913,24 @@ struct Values {
|
||||
|
||||
// Per-game overrides
|
||||
bool use_squashed_iterated_blend;
|
||||
|
||||
// Rates published by the frontend, in hertz. Zero means not measured yet.
|
||||
float display_refresh_rate;
|
||||
float guest_frame_rate;
|
||||
};
|
||||
|
||||
extern Values values;
|
||||
|
||||
constexpr u32 AUTO_FRAME_GEN_MULTIPLIER = 0;
|
||||
constexpr u32 MIN_FRAME_GEN_MULTIPLIER = 2;
|
||||
constexpr u32 MAX_FRAME_GEN_MULTIPLIER = 4;
|
||||
|
||||
[[nodiscard]] u32 FrameGenMultiplierCeiling();
|
||||
|
||||
[[nodiscard]] u32 FrameGenMultiplier();
|
||||
|
||||
[[nodiscard]] size_t FrameGenGenerations();
|
||||
|
||||
bool getDebugKnobAt(u8 i);
|
||||
|
||||
void UpdateGPUAccuracy();
|
||||
|
||||
@@ -27,9 +27,7 @@ constexpr u64 LSFG_REQUIRED_FRAMES = 2;
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t ConfiguredGenerations() {
|
||||
const u32 multiplier = std::clamp<u32>(Settings::values.frame_gen_multiplier.GetValue(),
|
||||
LSFG_MIN_MULTIPLIER, LSFG_MAX_MULTIPLIER);
|
||||
return multiplier - 1;
|
||||
return Settings::FrameGenGenerations();
|
||||
}
|
||||
|
||||
bool IsBlueFirst(VkFormat format) {
|
||||
@@ -232,7 +230,7 @@ void FrameGen::Process(const Device& device, Frame* frame, VkFormat format, bool
|
||||
}
|
||||
|
||||
size_t FrameGen::WantedGenerations() const {
|
||||
if (unavailable || !Settings::values.frame_gen.GetValue()) {
|
||||
if (unavailable) {
|
||||
return 0;
|
||||
}
|
||||
return ConfiguredGenerations();
|
||||
|
||||
@@ -27,9 +27,6 @@ constexpr VkFormat LSFG_FLOW_FORMAT = VK_FORMAT_R8_UNORM;
|
||||
constexpr VkFormat LSFG_MOTION_FORMAT = VK_FORMAT_R16G16B16A16_SFLOAT;
|
||||
|
||||
constexpr size_t LSFG_HISTORY_SLOTS = 3;
|
||||
constexpr size_t LSFG_MIN_MULTIPLIER = 2;
|
||||
constexpr size_t LSFG_MAX_MULTIPLIER = 4;
|
||||
constexpr size_t LSFG_MAX_GENERATIONS = LSFG_MAX_MULTIPLIER - 1;
|
||||
constexpr size_t LSFG_MAX_TARGETS = 7;
|
||||
|
||||
[[nodiscard]] constexpr f32 LsfgTimestamp(size_t generation, size_t generation_count) {
|
||||
|
||||
@@ -351,14 +351,13 @@ void PresentManager::SetImageCount() {
|
||||
// We cannot have more than 7 images in flight at any given time.
|
||||
// FRAMES_IN_FLIGHT is 8, and the cache TICKS_TO_DESTROY is 8.
|
||||
// Mali drivers will give us 6.
|
||||
const size_t generations =
|
||||
Settings::values.frame_gen.GetValue()
|
||||
? static_cast<size_t>(Settings::values.frame_gen_multiplier.GetValue()) - 1
|
||||
: 0;
|
||||
const size_t frames_per_composite = generations + 1;
|
||||
image_count = std::min<size_t>(
|
||||
std::max<size_t>(swapchain.GetImageCount() + generations, frames_per_composite * 2),
|
||||
MAX_FRAMES_IN_FLIGHT);
|
||||
const size_t generations = Settings::values.frame_gen.GetValue()
|
||||
? Settings::FrameGenMultiplierCeiling() - 1
|
||||
: 0;
|
||||
const size_t queued_composites = Settings::values.frame_gen_queue_target.GetValue() + 1;
|
||||
image_count =
|
||||
std::clamp<size_t>((generations + 1) * queued_composites, swapchain.GetImageCount(),
|
||||
MAX_FRAMES_IN_FLIGHT);
|
||||
}
|
||||
|
||||
void PresentManager::CopyToSwapchain(Frame* frame) {
|
||||
|
||||
Reference in New Issue
Block a user