renderer_vulkan: Disable FIFO when refresh rate is lower than ~60hz or Apple low power mode is enabled (#1193)

* renderer_vulkan: Disable FIFO when refresh rate is lower than ~60hz

Also disables FIFO when Apple low power mode is enabled, as it can limit the application framerate to 30fps

* renderer_vulkan.cpp: Put `IsLowRefreshRate` into anon namespace + make static
This commit is contained in:
OpenSauce 2025-06-29 21:06:44 +01:00 committed by GitHub
parent a15af9b550
commit df3c0c18e4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 113 additions and 24 deletions

View File

@ -17,7 +17,7 @@ puts 'done'
print 'Checking files...' print 'Checking files...'
issue_files = [] issue_files = []
branch_changed_files.each do |file_name| branch_changed_files.each do |file_name|
if file_name.end_with?('.cpp', '.h', '.kt', '.kts') and File.file?(file_name) if file_name.end_with?('.cpp', '.h', '.kt', '.kts', '.m', '.mm') and File.file?(file_name)
file_content = File.read(file_name, mode: 'r:bom|utf-8') file_content = File.read(file_name, mode: 'r:bom|utf-8')
if not file_content.start_with?(license_header) if not file_content.start_with?(license_header)
issue_files.push(file_name) issue_files.push(file_name)

View File

@ -18,7 +18,11 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modul
include(DownloadExternals) include(DownloadExternals)
include(CMakeDependentOption) include(CMakeDependentOption)
project(citra LANGUAGES C CXX ASM) if (APPLE)
project(citra LANGUAGES C CXX OBJC OBJCXX ASM)
else()
project(citra LANGUAGES C CXX ASM)
endif()
# Some submodules like to pick their own default build type if not specified. # Some submodules like to pick their own default build type if not specified.
# Make sure we default to Release build type always, unless the generator has custom types. # Make sure we default to Release build type always, unless the generator has custom types.
@ -125,6 +129,9 @@ endif()
if (ENABLE_ROOM) if (ENABLE_ROOM)
add_definitions(-DENABLE_ROOM) add_definitions(-DENABLE_ROOM)
endif() endif()
if (ENABLE_SDL2)
add_definitions(-DENABLE_SDL2)
endif()
if (ENABLE_SDL2_FRONTEND) if (ENABLE_SDL2_FRONTEND)
add_definitions(-DENABLE_SDL2_FRONTEND) add_definitions(-DENABLE_SDL2_FRONTEND)
endif() endif()

View File

@ -165,10 +165,12 @@ if (UNIX AND NOT APPLE)
endif() endif()
if (APPLE) if (APPLE)
target_sources(citra_common PUBLIC target_sources(citra_common PUBLIC
apple_authorization.h apple_authorization.h
apple_authorization.cpp apple_authorization.cpp
) apple_utils.h
apple_utils.mm
)
endif() endif()
if (MSVC) if (MSVC)
@ -211,4 +213,4 @@ endif()
if (SSE42_COMPILE_OPTION) if (SSE42_COMPILE_OPTION)
target_compile_definitions(citra_common PRIVATE CITRA_HAS_SSE42) target_compile_definitions(citra_common PRIVATE CITRA_HAS_SSE42)
target_compile_options(citra_common PRIVATE ${SSE42_COMPILE_OPTION}) target_compile_options(citra_common PRIVATE ${SSE42_COMPILE_OPTION})
endif() endif()

9
src/common/apple_utils.h Normal file
View File

@ -0,0 +1,9 @@
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
namespace AppleUtils {
int IsLowPowerModeEnabled();
}

13
src/common/apple_utils.mm Normal file
View File

@ -0,0 +1,13 @@
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#import <Foundation/Foundation.h>
namespace AppleUtils {
int IsLowPowerModeEnabled() {
return (int)[NSProcessInfo processInfo].lowPowerModeEnabled;
}
} // namespace AppleUtils

View File

@ -203,6 +203,10 @@ if (ENABLE_VULKAN)
target_link_libraries(video_core PRIVATE vulkan-headers vma sirit SPIRV glslang) target_link_libraries(video_core PRIVATE vulkan-headers vma sirit SPIRV glslang)
endif() endif()
if(ENABLE_SDL2)
target_link_libraries(video_core PRIVATE SDL2::SDL2)
endif()
add_dependencies(video_core host_shaders) add_dependencies(video_core host_shaders)
target_include_directories(video_core PRIVATE ${HOST_SHADERS_INCLUDE}) target_include_directories(video_core PRIVATE ${HOST_SHADERS_INCLUDE})

View File

@ -22,6 +22,14 @@
#include <vk_mem_alloc.h> #include <vk_mem_alloc.h>
#ifdef __APPLE__
#include "common/apple_utils.h"
#endif
#ifdef ENABLE_SDL2
#include <SDL.h>
#endif
MICROPROFILE_DEFINE(Vulkan_RenderFrame, "Vulkan", "Render Frame", MP_RGB(128, 128, 64)); MICROPROFILE_DEFINE(Vulkan_RenderFrame, "Vulkan", "Render Frame", MP_RGB(128, 128, 64));
namespace Vulkan { namespace Vulkan {
@ -50,11 +58,48 @@ constexpr static std::array<vk::DescriptorSetLayoutBinding, 1> PRESENT_BINDINGS
{0, vk::DescriptorType::eCombinedImageSampler, 3, vk::ShaderStageFlagBits::eFragment}, {0, vk::DescriptorType::eCombinedImageSampler, 3, vk::ShaderStageFlagBits::eFragment},
}}; }};
namespace {
static bool IsLowRefreshRate() {
#ifdef ENABLE_SDL2
const auto sdl_init_status = SDL_Init(SDL_INIT_VIDEO);
if (sdl_init_status < 0) {
LOG_ERROR(Render_Vulkan, "SDL failed to initialize, unable to check refresh rate");
} else {
SDL_DisplayMode cur_display_mode;
SDL_GetCurrentDisplayMode(0, &cur_display_mode); // TODO: Multimonitor handling. -OS
const auto cur_refresh_rate = cur_display_mode.refresh_rate;
SDL_Quit();
if (cur_refresh_rate < SCREEN_REFRESH_RATE) {
LOG_WARNING(Render_Vulkan,
"Detected refresh rate lower than the emulated 3DS screen: {}hz. FIFO will "
"be disabled",
cur_refresh_rate);
return true;
}
}
#endif
#ifdef __APPLE__
// Apple's low power mode sometimes limits applications to 30fps without changing the refresh
// rate, meaning the above code doesn't catch it.
if (AppleUtils::IsLowPowerModeEnabled()) {
LOG_WARNING(Render_Vulkan, "Apple's low power mode is enabled, assuming low application "
"framerate. FIFO will be disabled");
return true;
}
#endif
return false;
}
} // Anonymous namespace
RendererVulkan::RendererVulkan(Core::System& system, Pica::PicaCore& pica_, RendererVulkan::RendererVulkan(Core::System& system, Pica::PicaCore& pica_,
Frontend::EmuWindow& window, Frontend::EmuWindow* secondary_window) Frontend::EmuWindow& window, Frontend::EmuWindow* secondary_window)
: RendererBase{system, window, secondary_window}, memory{system.Memory()}, pica{pica_}, : RendererBase{system, window, secondary_window}, memory{system.Memory()}, pica{pica_},
instance{window, Settings::values.physical_device.GetValue()}, scheduler{instance}, instance{window, Settings::values.physical_device.GetValue()}, scheduler{instance},
renderpass_cache{instance, scheduler}, main_window{window, instance, scheduler}, renderpass_cache{instance, scheduler},
main_window{window, instance, scheduler, IsLowRefreshRate()},
vertex_buffer{instance, scheduler, vk::BufferUsageFlagBits::eVertexBuffer, vertex_buffer{instance, scheduler, vk::BufferUsageFlagBits::eVertexBuffer,
VERTEX_BUFFER_SIZE}, VERTEX_BUFFER_SIZE},
update_queue{instance}, update_queue{instance},
@ -66,7 +111,8 @@ RendererVulkan::RendererVulkan(Core::System& system, Pica::PicaCore& pica_,
BuildLayouts(); BuildLayouts();
BuildPipelines(); BuildPipelines();
if (secondary_window) { if (secondary_window) {
second_window = std::make_unique<PresentWindow>(*secondary_window, instance, scheduler); second_window = std::make_unique<PresentWindow>(*secondary_window, instance, scheduler,
IsLowRefreshRate());
} }
} }
@ -841,7 +887,8 @@ void RendererVulkan::SwapBuffers() {
ASSERT(secondary_window); ASSERT(secondary_window);
const auto& secondary_layout = secondary_window->GetFramebufferLayout(); const auto& secondary_layout = secondary_window->GetFramebufferLayout();
if (!second_window) { if (!second_window) {
second_window = std::make_unique<PresentWindow>(*secondary_window, instance, scheduler); second_window = std::make_unique<PresentWindow>(*secondary_window, instance, scheduler,
IsLowRefreshRate());
} }
RenderToWindow(*second_window, secondary_layout, false); RenderToWindow(*second_window, secondary_layout, false);
secondary_window->PollEvents(); secondary_window->PollEvents();

View File

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -98,11 +98,12 @@ bool CanBlitToSwapchain(const vk::PhysicalDevice& physical_device, vk::Format fo
} // Anonymous namespace } // Anonymous namespace
PresentWindow::PresentWindow(Frontend::EmuWindow& emu_window_, const Instance& instance_, PresentWindow::PresentWindow(Frontend::EmuWindow& emu_window_, const Instance& instance_,
Scheduler& scheduler_) Scheduler& scheduler_, bool low_refresh_rate_)
: emu_window{emu_window_}, instance{instance_}, scheduler{scheduler_}, : emu_window{emu_window_}, instance{instance_}, scheduler{scheduler_},
low_refresh_rate{low_refresh_rate_},
surface{CreateSurface(instance.GetInstance(), emu_window)}, next_surface{surface}, surface{CreateSurface(instance.GetInstance(), emu_window)}, next_surface{surface},
swapchain{instance, emu_window.GetFramebufferLayout().width, swapchain{instance, emu_window.GetFramebufferLayout().width,
emu_window.GetFramebufferLayout().height, surface}, emu_window.GetFramebufferLayout().height, surface, low_refresh_rate_},
graphics_queue{instance.GetGraphicsQueue()}, present_renderpass{CreateRenderpass()}, graphics_queue{instance.GetGraphicsQueue()}, present_renderpass{CreateRenderpass()},
vsync_enabled{Settings::values.use_vsync_new.GetValue()}, vsync_enabled{Settings::values.use_vsync_new.GetValue()},
blit_supported{ blit_supported{
@ -355,7 +356,7 @@ void PresentWindow::CopyToSwapchain(Frame* frame) {
#endif #endif
std::scoped_lock submit_lock{scheduler.submit_mutex}; std::scoped_lock submit_lock{scheduler.submit_mutex};
graphics_queue.waitIdle(); graphics_queue.waitIdle();
swapchain.Create(frame->width, frame->height, surface); swapchain.Create(frame->width, frame->height, surface, low_refresh_rate);
}; };
#ifndef ANDROID #ifndef ANDROID

View File

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -37,7 +37,7 @@ struct Frame {
class PresentWindow final { class PresentWindow final {
public: public:
explicit PresentWindow(Frontend::EmuWindow& emu_window, const Instance& instance, explicit PresentWindow(Frontend::EmuWindow& emu_window, const Instance& instance,
Scheduler& scheduler); Scheduler& scheduler, bool low_refresh_rate);
~PresentWindow(); ~PresentWindow();
/// Waits for all queued frames to finish presenting. /// Waits for all queued frames to finish presenting.
@ -74,6 +74,7 @@ private:
Frontend::EmuWindow& emu_window; Frontend::EmuWindow& emu_window;
const Instance& instance; const Instance& instance;
Scheduler& scheduler; Scheduler& scheduler;
bool low_refresh_rate;
vk::SurfaceKHR surface; vk::SurfaceKHR surface;
vk::SurfaceKHR next_surface{}; vk::SurfaceKHR next_surface{};
Swapchain swapchain; Swapchain swapchain;

View File

@ -15,11 +15,12 @@ MICROPROFILE_DEFINE(Vulkan_Present, "Vulkan", "Swapchain Present", MP_RGB(66, 18
namespace Vulkan { namespace Vulkan {
Swapchain::Swapchain(const Instance& instance_, u32 width, u32 height, vk::SurfaceKHR surface_) Swapchain::Swapchain(const Instance& instance_, u32 width, u32 height, vk::SurfaceKHR surface_,
bool low_refresh_rate)
: instance{instance_}, surface{surface_} { : instance{instance_}, surface{surface_} {
FindPresentFormat(); FindPresentFormat();
SetPresentMode(); SetPresentMode();
Create(width, height, surface); Create(width, height, surface, low_refresh_rate);
} }
Swapchain::~Swapchain() { Swapchain::~Swapchain() {
@ -27,10 +28,11 @@ Swapchain::~Swapchain() {
instance.GetInstance().destroySurfaceKHR(surface); instance.GetInstance().destroySurfaceKHR(surface);
} }
void Swapchain::Create(u32 width_, u32 height_, vk::SurfaceKHR surface_) { void Swapchain::Create(u32 width_, u32 height_, vk::SurfaceKHR surface_, bool low_refresh_rate_) {
width = width_; width = width_;
height = height_; height = height_;
surface = surface_; surface = surface_;
low_refresh_rate = low_refresh_rate_;
needs_recreation = false; needs_recreation = false;
Destroy(); Destroy();
@ -188,7 +190,8 @@ void Swapchain::SetPresentMode() {
// If vsync is enabled attempt to use mailbox mode in case the user wants to speedup/slowdown // If vsync is enabled attempt to use mailbox mode in case the user wants to speedup/slowdown
// the game. If mailbox is not available use immediate and warn about it. // the game. If mailbox is not available use immediate and warn about it.
if (use_vsync && (frame_limit > 100 || frame_limit == 0)) { // 0 = unthrottled if (use_vsync &&
(frame_limit > 100 || frame_limit == 0 || low_refresh_rate)) { // 0 = unthrottled
present_mode = has_mailbox ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eImmediate; present_mode = has_mailbox ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eImmediate;
if (!has_mailbox) { if (!has_mailbox) {
LOG_WARNING( LOG_WARNING(
@ -275,4 +278,4 @@ void Swapchain::SetupImages() {
} }
} }
} // namespace Vulkan } // namespace Vulkan

View File

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -16,11 +16,12 @@ class Scheduler;
class Swapchain { class Swapchain {
public: public:
explicit Swapchain(const Instance& instance, u32 width, u32 height, vk::SurfaceKHR surface); explicit Swapchain(const Instance& instance, u32 width, u32 height, vk::SurfaceKHR surface,
bool low_refresh_rate);
~Swapchain(); ~Swapchain();
/// Creates (or recreates) the swapchain with a given size. /// Creates (or recreates) the swapchain with a given size.
void Create(u32 width, u32 height, vk::SurfaceKHR surface); void Create(u32 width, u32 height, vk::SurfaceKHR surface, bool low_refresh_rate);
/// Acquires the next image in the swapchain. /// Acquires the next image in the swapchain.
bool AcquireNextImage(); bool AcquireNextImage();
@ -105,6 +106,7 @@ private:
u32 image_index = 0; u32 image_index = 0;
u32 frame_index = 0; u32 frame_index = 0;
bool needs_recreation = true; bool needs_recreation = true;
bool low_refresh_rate;
}; };
} // namespace Vulkan } // namespace Vulkan