mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-10-06 14:09:58 +00:00
Framerate display corrections
This commit is contained in:
@@ -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<float>((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<float>(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<float>(std::clamp<u32>(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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#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_t>(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_t>(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<u64> pages;
|
||||
std::vector<Page> pages;
|
||||
};
|
||||
|
||||
} // namespace VideoCommon
|
||||
|
||||
@@ -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<const Tegra::FramebufferConfig> framebuff
|
||||
}
|
||||
|
||||
m_gpu.RendererFrameEndNotify();
|
||||
render_window.OnFrameDisplayed();
|
||||
render_window.OnFrameDisplayed(1);
|
||||
}
|
||||
|
||||
std::vector<u8> RendererNull::GetAppletCaptureBuffer() {
|
||||
|
||||
@@ -151,7 +151,7 @@ void RendererOpenGL::Composite(std::span<const Tegra::FramebufferConfig> framebu
|
||||
rasterizer.TickFrame();
|
||||
|
||||
context->SwapBuffers();
|
||||
render_window.OnFrameDisplayed();
|
||||
render_window.OnFrameDisplayed(1);
|
||||
}
|
||||
|
||||
void RendererOpenGL::AddTelemetryFields() {
|
||||
|
||||
@@ -185,8 +185,9 @@ RendererVulkan::~RendererVulkan() {
|
||||
}
|
||||
|
||||
void RendererVulkan::Composite(std::span<const Tegra::FramebufferConfig> 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<const Tegra::FramebufferConfig> 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<const Tegra::FramebufferConfig> framebu
|
||||
output.View());
|
||||
scheduler.Flush(*generated->render_ready);
|
||||
present_manager.Present(generated, static_cast<u32>(generation), generated_step);
|
||||
pace_index = static_cast<u32>(generated_frames);
|
||||
pace_index = static_cast<u32>(generation + 1);
|
||||
pace_step = generated_step;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -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<const VideoCommon::BufferCopy> 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<u8> Buffer::CoherentMapping() noexcept {
|
||||
@@ -462,13 +475,8 @@ u32 BufferCacheRuntime::GetStorageBufferAlignment() const {
|
||||
return static_cast<u32>(device.GetStorageBufferAlignment());
|
||||
}
|
||||
|
||||
void BufferCacheRuntime::TickFrame(Common::SlotVector<Buffer>& slot_buffers) noexcept {
|
||||
void BufferCacheRuntime::TickFrame(Common::SlotVector<Buffer>&) 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<const u8> BufferCacheRuntime::DirectDownloadSpan(Buffer& buffer) {
|
||||
std::span<u8> BufferCacheRuntime::DirectUploadSpan(
|
||||
Buffer& buffer, std::span<const VideoCommon::BufferCopy> copies) {
|
||||
const std::span<u8> 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;
|
||||
|
||||
@@ -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<const VideoCommon::BufferCopy> copies) noexcept;
|
||||
|
||||
[[nodiscard]] u64 LastUploadTick() const noexcept {
|
||||
return last_upload_tick;
|
||||
@@ -94,8 +86,8 @@ private:
|
||||
vk::Buffer buffer;
|
||||
std::vector<BufferView> 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<Buffer>& slot_buffers) noexcept;
|
||||
void TickFrame(Common::SlotVector<Buffer>&) noexcept;
|
||||
|
||||
u64 CurrentTick();
|
||||
|
||||
@@ -165,7 +157,7 @@ public:
|
||||
void CopyBuffer(Buffer& dst_buffer, VkBuffer src_buffer,
|
||||
std::span<const VideoCommon::BufferCopy> 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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<Core::Frontend::GraphicsContext> CreateSharedContext() const override;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user