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 8dfea40693..614fd2569c 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 @@ -64,8 +64,8 @@ void EmuWindow_Android::OnTouchReleased(int id) { EmulationSession::GetInstance().GetInputSubsystem().GetTouchScreen()->TouchReleased(id); } -void EmuWindow_Android::OnFrameDisplayed() { - UpdateObservedFrameRate(); +void EmuWindow_Android::OnFrameDisplayed(u32 presented_frames) { + UpdateObservedFrameRate(presented_frames); UpdateFrameRateHint(); if (!m_first_frame) { @@ -75,15 +75,16 @@ void EmuWindow_Android::OnFrameDisplayed() { } } -void EmuWindow_Android::UpdateObservedFrameRate() { +void EmuWindow_Android::UpdateObservedFrameRate(u32 presented_frames) { + m_presented_frames = static_cast((std::max)(presented_frames, 1u)); 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; + const float instantaneous_rate = m_presented_frames / seconds; if (std::isfinite(instantaneous_rate) && instantaneous_rate >= 1.0f && - instantaneous_rate <= 240.0f) { + instantaneous_rate <= 240.0f * m_presented_frames) { constexpr float SmoothingFactor = 0.15f; if (m_smoothed_present_rate <= 0.0f) { m_smoothed_present_rate = instantaneous_rate; @@ -124,18 +125,9 @@ float EmuWindow_Android::GetFrameTimeVerifiedHint() const { return QuantizeFrameRateHint(verified_rate); } -float EmuWindow_Android::GetPresentedFrameMultiplier() { - if (!Settings::values.frame_gen.GetValue()) { - return 1.0f; - } - return static_cast(std::clamp(Settings::values.frame_gen_multiplier.GetValue(), 2, 4)); -} - float EmuWindow_Android::GetFrameRateHint() const { - const float presented_multiplier = GetPresentedFrameMultiplier(); - const float observed_rate = - std::clamp(m_smoothed_present_rate * presented_multiplier, 0.0f, 240.0f); - const float frame_time_verified_hint = GetFrameTimeVerifiedHint() * presented_multiplier; + const float observed_rate = std::clamp(m_smoothed_present_rate, 0.0f, 240.0f); + const float frame_time_verified_hint = GetFrameTimeVerifiedHint() * m_presented_frames; if (m_last_frame_rate_hint > 0.0f && observed_rate > 0.0f) { const float tolerance = std::max(m_last_frame_rate_hint * 0.12f, 4.0f); @@ -159,7 +151,7 @@ float EmuWindow_Android::GetFrameRateHint() const { return frame_time_verified_hint; } - const float nominal_rate = 60.0f * presented_multiplier; + const float nominal_rate = 60.0f * m_presented_frames; if (!Settings::values.use_speed_limit.GetValue()) { return QuantizeFrameRateHint(nominal_rate); } 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 fca83b91fa..67883946c9 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 @@ -41,7 +41,7 @@ public: ~EmuWindow_Android() = default; void OnSurfaceChanged(ANativeWindow* surface); - void OnFrameDisplayed() override; + void OnFrameDisplayed(u32 presented_frames) override; void OnTouchPressed(int id, float x, float y); void OnTouchMoved(int id, float x, float y); @@ -58,10 +58,9 @@ private: using Clock = std::chrono::steady_clock; void UpdateFrameRateHint(); - void UpdateObservedFrameRate(); + void UpdateObservedFrameRate(u32 presented_frames); [[nodiscard]] float GetFrameRateHint() const; [[nodiscard]] float GetFrameTimeVerifiedHint() const; - [[nodiscard]] static float GetPresentedFrameMultiplier(); [[nodiscard]] static float QuantizeFrameRateHint(float frame_rate); float m_window_width{}; @@ -73,6 +72,7 @@ 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_presented_frames = 1.0f; Clock::time_point m_last_frame_display_time{}; Clock::time_point m_pending_frame_rate_since{}; std::uint32_t m_pending_frame_rate_hint_votes = 0; diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h index ee7a7693b2..e495e8978f 100644 --- a/src/core/frontend/emu_window.h +++ b/src/core/frontend/emu_window.h @@ -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: 2014 Citra Emulator Project @@ -74,7 +74,7 @@ public: }; /// Called from GPU thread when a frame is displayed. - virtual void OnFrameDisplayed() {} + virtual void OnFrameDisplayed([[maybe_unused]] u32 presented_frames) {} /** * Returns a GraphicsContext that the frontend provides to be used for rendering. diff --git a/src/video_core/buffer_cache/usage_tracker.h b/src/video_core/buffer_cache/usage_tracker.h index 6f84e463e3..563bcc0e3b 100644 --- a/src/video_core/buffer_cache/usage_tracker.h +++ b/src/video_core/buffer_cache/usage_tracker.h @@ -6,7 +6,9 @@ #pragma once -#include "common/alignment.h" +#include +#include + #include "common/common_types.h" namespace VideoCommon { @@ -15,74 +17,58 @@ class UsageTracker { // PAGE_SHIFT is a macro on FreeBSD static constexpr size_t BUFFER_BYTES_PER_BITSHIFT = 6; static constexpr size_t BUFFER_PAGE_SHIFT = 6 + BUFFER_BYTES_PER_BITSHIFT; - static constexpr size_t BUFFER_PAGE_BYTES = 1 << BUFFER_PAGE_SHIFT; + static constexpr u64 BUFFER_PAGE_BYTES = u64{1} << BUFFER_PAGE_SHIFT; + + struct Page { + u64 bits; + u64 tick; + }; + public: - explicit UsageTracker(size_t size) { - const size_t num_pages = (size >> BUFFER_PAGE_SHIFT) + 1; - pages.resize(num_pages, 0ULL); - } + explicit UsageTracker(size_t size) : pages((size >> BUFFER_PAGE_SHIFT) + 1) {} - void Reset() noexcept { - std::ranges::fill(pages, 0ULL); - } - - void Track(u64 offset, u64 size) noexcept { - const size_t page = offset >> BUFFER_PAGE_SHIFT; - const size_t page_end = (offset + size) >> BUFFER_PAGE_SHIFT; - if (page_end < page || page_end >= pages.size()) { + void Track(u64 offset, u64 size, u64 tick, u64 gpu_tick) noexcept { + const u64 end = offset + size; + if (size == 0 || ((end - 1) >> BUFFER_PAGE_SHIFT) >= pages.size()) { return; } - TrackPage(page, offset, size); - if (page == page_end) { - return; + for (u64 page = offset >> BUFFER_PAGE_SHIFT; page <= (end - 1) >> BUFFER_PAGE_SHIFT; + ++page) { + Page& entry = pages[page]; + if (entry.tick <= gpu_tick) { + entry.bits = 0; + } + entry.bits |= PageMask(page, offset, end); + entry.tick = (std::max)(entry.tick, tick); } - for (size_t i = page + 1; i < page_end; i++) { - pages[i] = ~u64{0}; - } - const size_t offset_end = offset + size; - const size_t offset_end_page_aligned = Common::AlignDown(offset_end, BUFFER_PAGE_BYTES); - TrackPage(page_end, offset_end_page_aligned, offset_end - offset_end_page_aligned); } - [[nodiscard]] bool IsUsed(u64 offset, u64 size) const noexcept { - const size_t page = offset >> BUFFER_PAGE_SHIFT; - const size_t page_end = (offset + size) >> BUFFER_PAGE_SHIFT; - if (page_end < page || page_end >= pages.size()) { + [[nodiscard]] bool IsUsed(u64 offset, u64 size, u64 gpu_tick) const noexcept { + const u64 end = offset + size; + if (size == 0 || ((end - 1) >> BUFFER_PAGE_SHIFT) >= pages.size()) { return false; } - if (IsPageUsed(page, offset, size)) { - return true; - } - for (size_t i = page + 1; i < page_end; i++) { - if (pages[i] != 0) { + for (u64 page = offset >> BUFFER_PAGE_SHIFT; page <= (end - 1) >> BUFFER_PAGE_SHIFT; + ++page) { + const Page& entry = pages[page]; + if (entry.tick > gpu_tick && (entry.bits & PageMask(page, offset, end)) != 0) { return true; } } - const size_t offset_end = offset + size; - const size_t offset_end_page_aligned = Common::AlignDown(offset_end, BUFFER_PAGE_BYTES); - return IsPageUsed(page_end, offset_end_page_aligned, offset_end - offset_end_page_aligned); + return false; } private: - void TrackPage(u64 page, u64 offset, u64 size) noexcept { - const size_t offset_in_page = offset % BUFFER_PAGE_BYTES; - const size_t first_bit = offset_in_page >> BUFFER_BYTES_PER_BITSHIFT; - const size_t num_bits = std::min(size, BUFFER_PAGE_BYTES) >> BUFFER_BYTES_PER_BITSHIFT; - const size_t mask = ~u64{0} >> (64 - num_bits); - pages[page] |= (~u64{0} & mask) << first_bit; + [[nodiscard]] static u64 PageMask(u64 page, u64 offset, u64 end) noexcept { + const u64 page_begin = page << BUFFER_PAGE_SHIFT; + const u64 first = + ((std::max)(offset, page_begin) - page_begin) >> BUFFER_BYTES_PER_BITSHIFT; + const u64 last = ((std::min)(end, page_begin + BUFFER_PAGE_BYTES) - 1 - page_begin) >> + BUFFER_BYTES_PER_BITSHIFT; + return (~u64{0} >> (63 - last)) & (~u64{0} << first); } - bool IsPageUsed(u64 page, u64 offset, u64 size) const noexcept { - const size_t offset_in_page = offset % BUFFER_PAGE_BYTES; - const size_t first_bit = offset_in_page >> BUFFER_BYTES_PER_BITSHIFT; - const size_t num_bits = std::min(size, BUFFER_PAGE_BYTES) >> BUFFER_BYTES_PER_BITSHIFT; - const size_t mask = ~u64{0} >> (64 - num_bits); - const size_t mask2 = (~u64{0} & mask) << first_bit; - return (pages[page] & mask2) != 0; - } - -private: - std::vector pages; + std::vector pages; }; } // namespace VideoCommon diff --git a/src/video_core/renderer_null/renderer_null.cpp b/src/video_core/renderer_null/renderer_null.cpp index e6147d66cc..434fc172e3 100644 --- a/src/video_core/renderer_null/renderer_null.cpp +++ b/src/video_core/renderer_null/renderer_null.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -20,7 +23,7 @@ void RendererNull::Composite(std::span framebuff } m_gpu.RendererFrameEndNotify(); - render_window.OnFrameDisplayed(); + render_window.OnFrameDisplayed(1); } std::vector RendererNull::GetAppletCaptureBuffer() { diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index f9ec32e652..caf15294b2 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -151,7 +151,7 @@ void RendererOpenGL::Composite(std::span framebu rasterizer.TickFrame(); context->SwapBuffers(); - render_window.OnFrameDisplayed(); + render_window.OnFrameDisplayed(1); } void RendererOpenGL::AddTelemetryFields() { diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index f963ef4aef..b207997b76 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -185,8 +185,9 @@ RendererVulkan::~RendererVulkan() { } void RendererVulkan::Composite(std::span framebuffers) { + u32 pace_index = 0; SCOPE_EXIT { - render_window.OnFrameDisplayed(); + render_window.OnFrameDisplayed(pace_index + 1); }; RenderAppletCaptureLayer(framebuffers); @@ -204,7 +205,6 @@ void RendererVulkan::Composite(std::span framebu present_manager.SwapchainImageCount(), swapchain.GetImageViewFormat()); - u32 pace_index = 0; std::chrono::nanoseconds pace_step{}; #ifdef HAS_LSFG void(frame_gen.WantedGenerations(present_manager.MaxExtraFrames())); @@ -229,7 +229,7 @@ void RendererVulkan::Composite(std::span framebu output.View()); scheduler.Flush(*generated->render_ready); present_manager.Present(generated, static_cast(generation), generated_step); - pace_index = static_cast(generated_frames); + pace_index = static_cast(generation + 1); pace_step = generated_step; } #endif diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp index 0547e7ed71..52c3ad8804 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp @@ -109,7 +109,8 @@ vk::Buffer CreateBuffer(const Device& device, const MemoryAllocator& memory_allo } // Anonymous namespace Buffer::Buffer(BufferCacheRuntime& runtime, VideoCommon::NullBufferParams null_params) - : VideoCommon::BufferBase(null_params), scheduler{&runtime.scheduler}, tracker{4096} { + : VideoCommon::BufferBase(null_params), scheduler{&runtime.scheduler}, tracker{4096}, + uploads{4096} { if (runtime.device.HasNullDescriptor()) { return; } @@ -127,7 +128,7 @@ Buffer::Buffer(BufferCacheRuntime& runtime, DAddr cpu_addr_, u64 size_bytes_, scheduler{&runtime.scheduler}, buffer{CreateBuffer(*device, runtime.memory_allocator, SizeBytes(), runtime.SparseAlignmentFor(sparse_compatible_))}, - tracker{SizeBytes()} { + tracker{SizeBytes()}, uploads{SizeBytes()} { sparse_compatible = sparse_compatible_; if (runtime.device.HasDebuggingToolAttached()) { buffer.SetObjectNameEXT(fmt::format("Buffer {:#x}", CpuAddr()).c_str()); @@ -137,13 +138,25 @@ Buffer::Buffer(BufferCacheRuntime& runtime, DAddr cpu_addr_, u64 size_bytes_, } } -void Buffer::MarkUsage(u64 offset, u64 size) noexcept { - tracker.Track(offset, size); - last_usage_tick = scheduler->CurrentTick(); +bool Buffer::IsRegionUsed(u64 offset, u64 size) const noexcept { + return tracker.IsUsed(offset, size, scheduler->GetMasterSemaphore().KnownGpuTick()); } -void Buffer::MarkUpload() noexcept { +bool Buffer::IsRegionUploading(u64 offset, u64 size) const noexcept { + return uploads.IsUsed(offset, size, scheduler->GetMasterSemaphore().KnownGpuTick()); +} + +void Buffer::MarkUsage(u64 offset, u64 size) noexcept { + tracker.Track(offset, size, scheduler->CurrentTick(), + scheduler->GetMasterSemaphore().KnownGpuTick()); +} + +void Buffer::MarkUpload(std::span copies) noexcept { last_upload_tick = scheduler->CurrentTick(); + const u64 gpu_tick = scheduler->GetMasterSemaphore().KnownGpuTick(); + for (const VideoCommon::BufferCopy& copy : copies) { + uploads.Track(copy.dst_offset, copy.size, last_upload_tick, gpu_tick); + } } std::span Buffer::CoherentMapping() noexcept { @@ -462,13 +475,8 @@ u32 BufferCacheRuntime::GetStorageBufferAlignment() const { return static_cast(device.GetStorageBufferAlignment()); } -void BufferCacheRuntime::TickFrame(Common::SlotVector& slot_buffers) noexcept { +void BufferCacheRuntime::TickFrame(Common::SlotVector&) noexcept { multi_range_buffers.DrainRetired(scheduler); - for (auto it = slot_buffers.begin(); it != slot_buffers.end(); it++) { - if (scheduler.IsFree(it->LastUsageTick())) { - it->ResetUsageTracking(); - } - } } u64 BufferCacheRuntime::CurrentTick() { @@ -522,8 +530,11 @@ std::span BufferCacheRuntime::DirectDownloadSpan(Buffer& buffer) { std::span BufferCacheRuntime::DirectUploadSpan( Buffer& buffer, std::span copies) { const std::span mapping = buffer.CoherentMapping(); - if (mapping.empty() || !scheduler.IsFree(buffer.LastUploadTick()) || - !CanReorderUpload(buffer, copies)) { + const bool uploading = + std::ranges::any_of(copies, [&buffer](const VideoCommon::BufferCopy& copy) { + return buffer.IsRegionUploading(copy.dst_offset, copy.size); + }); + if (mapping.empty() || uploading || !CanReorderUpload(buffer, copies)) { return {}; } return mapping; diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h index fa6829e623..51f153a4d1 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.h +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h @@ -55,21 +55,13 @@ public: return buffer.Location(); } - [[nodiscard]] bool IsRegionUsed(u64 offset, u64 size) const noexcept { - return tracker.IsUsed(offset, size); - } + [[nodiscard]] bool IsRegionUsed(u64 offset, u64 size) const noexcept; + + [[nodiscard]] bool IsRegionUploading(u64 offset, u64 size) const noexcept; void MarkUsage(u64 offset, u64 size) noexcept; - void ResetUsageTracking() noexcept { - tracker.Reset(); - } - - [[nodiscard]] u64 LastUsageTick() const noexcept { - return last_usage_tick; - } - - void MarkUpload() noexcept; + void MarkUpload(std::span copies) noexcept; [[nodiscard]] u64 LastUploadTick() const noexcept { return last_upload_tick; @@ -94,8 +86,8 @@ private: vk::Buffer buffer; std::vector views; VideoCommon::UsageTracker tracker; + VideoCommon::UsageTracker uploads; VkDeviceAddress device_address{}; - u64 last_usage_tick{}; u64 last_upload_tick{}; bool is_null{}; bool sparse_compatible{}; @@ -117,7 +109,7 @@ public: ComputePassDescriptorQueue& compute_pass_descriptor_queue, DescriptorPool& descriptor_pool); - void TickFrame(Common::SlotVector& slot_buffers) noexcept; + void TickFrame(Common::SlotVector&) noexcept; u64 CurrentTick(); @@ -165,7 +157,7 @@ public: void CopyBuffer(Buffer& dst_buffer, VkBuffer src_buffer, std::span copies, bool barrier, bool can_reorder_upload = false) { - dst_buffer.MarkUpload(); + dst_buffer.MarkUpload(copies); CopyBuffer(dst_buffer.Handle(), src_buffer, copies, barrier, can_reorder_upload); } diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 308db3621b..7affda9c56 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -157,7 +157,7 @@ GRenderWindow::~GRenderWindow() { input_subsystem->Shutdown(); } -void GRenderWindow::OnFrameDisplayed() { +void GRenderWindow::OnFrameDisplayed([[maybe_unused]] u32 presented_frames) { input_subsystem->GetTas()->UpdateThread(); const InputCommon::TasInput::TasState new_tas_state = std::get<0>(input_subsystem->GetTas()->GetStatus()); diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h index bb18bbb3bb..85218f4cb4 100644 --- a/src/yuzu/bootmanager.h +++ b/src/yuzu/bootmanager.h @@ -67,7 +67,7 @@ public: ~GRenderWindow() override; // EmuWindow implementation. - void OnFrameDisplayed() override; + void OnFrameDisplayed(u32 presented_frames) override; bool IsShown() const override; std::unique_ptr CreateSharedContext() const override;