From f856c2dfa028814fee2ef838b8b0b782200dd23a Mon Sep 17 00:00:00 2001 From: xbzk Date: Sat, 14 Mar 2026 20:18:02 -0300 Subject: [PATCH] [nvmflinger] fixed 30fps cap for games with swap_interval=2, and prefer newest queued frames to reduce input latency --- .../nvnflinger/buffer_queue_consumer.cpp | 11 ++++++ .../service/nvnflinger/hardware_composer.cpp | 34 ++++--------------- 2 files changed, 18 insertions(+), 27 deletions(-) diff --git a/src/core/hle/service/nvnflinger/buffer_queue_consumer.cpp b/src/core/hle/service/nvnflinger/buffer_queue_consumer.cpp index 1052dfa0ef..e434debf0f 100644 --- a/src/core/hle/service/nvnflinger/buffer_queue_consumer.cpp +++ b/src/core/hle/service/nvnflinger/buffer_queue_consumer.cpp @@ -45,6 +45,17 @@ Status BufferQueueConsumer::AcquireBuffer(BufferItem* out_buffer, auto front(core->queue.begin()); + // prefer newest queued frame for lower input latency (if no present deadline is requested). + if (expected_present.count() == 0 && core->queue.size() > 1) { + while (core->queue.size() > 1) { + const auto stale = core->queue.begin(); + if (core->StillTracking(*stale)) slots[stale->slot].buffer_state = BufferState::Free; + core->queue.erase(stale); + } + core->SignalDequeueCondition(); + front = core->queue.begin(); + } + // If expected_present is specified, we may not want to return a buffer yet. if (expected_present.count() != 0) { constexpr auto MAX_REASONABLE_NSEC = 1000000000LL; // 1 second diff --git a/src/core/hle/service/nvnflinger/hardware_composer.cpp b/src/core/hle/service/nvnflinger/hardware_composer.cpp index cfd999497b..68a111c905 100644 --- a/src/core/hle/service/nvnflinger/hardware_composer.cpp +++ b/src/core/hle/service/nvnflinger/hardware_composer.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -77,26 +77,7 @@ u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, Display& display, // Acquire all necessary framebuffers. for (auto& layer : display.stack.layers) { auto consumer_id = layer->consumer_id; - - bool should_try_acquire = true; - if (!layer->is_overlay) { - auto fb_it = m_framebuffers.find(consumer_id); - if (fb_it != m_framebuffers.end() && fb_it->second.is_acquired) { - const u64 frames_since_last_acquire = m_frame_number - fb_it->second.last_acquire_frame; - const s32 expected_interval = NormalizeSwapInterval(nullptr, fb_it->second.item.swap_interval); - - if (frames_since_last_acquire < static_cast(expected_interval)) { - should_try_acquire = false; - } - } - } - - // Try to fetch the framebuffer (either new or stale). - const auto result = should_try_acquire - ? this->CacheFramebufferLocked(*layer, consumer_id) - : (m_framebuffers.find(consumer_id) != m_framebuffers.end() && m_framebuffers[consumer_id].is_acquired - ? CacheStatus::CachedBufferReused - : CacheStatus::NoBufferAvailable); + const auto result = this->CacheFramebufferLocked(*layer, consumer_id); // If we failed, skip this layer. if (result == CacheStatus::NoBufferAvailable) { @@ -134,9 +115,9 @@ u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, Display& display, continue; } - // We need to compose again either before this frame is supposed to - // be released, or exactly on the vsync period it should be released. - const s32 item_swap_interval = NormalizeSwapInterval(out_speed_scale, item.swap_interval); + // hard-throttle non-overlay presentation cadence on 1..4 removed!! + NormalizeSwapInterval(out_speed_scale, item.swap_interval); + const s32 item_swap_interval = 1; // TODO: handle cases where swap intervals are relatively prime. So far, // only swap intervals of 0, 1 and 2 have been observed, but if 3 were @@ -233,9 +214,8 @@ bool HardwareComposer::TryAcquireFramebufferLocked(Layer& layer, Framebuffer& fr return false; } - // We succeeded, so set the new release frame info. - const s32 swap_interval = layer.is_overlay ? 1 : NormalizeSwapInterval(nullptr, framebuffer.item.swap_interval); - framebuffer.release_frame_number = m_frame_number + swap_interval; + // Keep 60Hz consumer cadence and let producer queue timing decide effective FPS. + framebuffer.release_frame_number = m_frame_number + 1; framebuffer.last_acquire_frame = m_frame_number; framebuffer.is_acquired = true;