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
11 changed files with 113 additions and 24 deletions

View File

@@ -17,7 +17,7 @@ puts 'done'
print 'Checking files...'
issue_files = []
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')
if not file_content.start_with?(license_header)
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(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.
# Make sure we default to Release build type always, unless the generator has custom types.
@@ -125,6 +129,9 @@ endif()
if (ENABLE_ROOM)
add_definitions(-DENABLE_ROOM)
endif()
if (ENABLE_SDL2)
add_definitions(-DENABLE_SDL2)
endif()
if (ENABLE_SDL2_FRONTEND)
add_definitions(-DENABLE_SDL2_FRONTEND)
endif()

View File

@@ -165,10 +165,12 @@ if (UNIX AND NOT APPLE)
endif()
if (APPLE)
target_sources(citra_common PUBLIC
apple_authorization.h
apple_authorization.cpp
)
target_sources(citra_common PUBLIC
apple_authorization.h
apple_authorization.cpp
apple_utils.h
apple_utils.mm
)
endif()
if (MSVC)
@@ -211,4 +213,4 @@ endif()
if (SSE42_COMPILE_OPTION)
target_compile_definitions(citra_common PRIVATE CITRA_HAS_SSE42)
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)
endif()
if(ENABLE_SDL2)
target_link_libraries(video_core PRIVATE SDL2::SDL2)
endif()
add_dependencies(video_core host_shaders)
target_include_directories(video_core PRIVATE ${HOST_SHADERS_INCLUDE})

View File

@@ -22,6 +22,14 @@
#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));
namespace Vulkan {
@@ -50,11 +58,48 @@ constexpr static std::array<vk::DescriptorSetLayoutBinding, 1> PRESENT_BINDINGS
{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_,
Frontend::EmuWindow& window, Frontend::EmuWindow* secondary_window)
: RendererBase{system, window, secondary_window}, memory{system.Memory()}, pica{pica_},
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_SIZE},
update_queue{instance},
@@ -66,7 +111,8 @@ RendererVulkan::RendererVulkan(Core::System& system, Pica::PicaCore& pica_,
BuildLayouts();
BuildPipelines();
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);
const auto& secondary_layout = secondary_window->GetFramebufferLayout();
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);
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
// Refer to the license.txt file included.
@@ -98,11 +98,12 @@ bool CanBlitToSwapchain(const vk::PhysicalDevice& physical_device, vk::Format fo
} // Anonymous namespace
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_},
low_refresh_rate{low_refresh_rate_},
surface{CreateSurface(instance.GetInstance(), emu_window)}, next_surface{surface},
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()},
vsync_enabled{Settings::values.use_vsync_new.GetValue()},
blit_supported{
@@ -355,7 +356,7 @@ void PresentWindow::CopyToSwapchain(Frame* frame) {
#endif
std::scoped_lock submit_lock{scheduler.submit_mutex};
graphics_queue.waitIdle();
swapchain.Create(frame->width, frame->height, surface);
swapchain.Create(frame->width, frame->height, surface, low_refresh_rate);
};
#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
// Refer to the license.txt file included.
@@ -37,7 +37,7 @@ struct Frame {
class PresentWindow final {
public:
explicit PresentWindow(Frontend::EmuWindow& emu_window, const Instance& instance,
Scheduler& scheduler);
Scheduler& scheduler, bool low_refresh_rate);
~PresentWindow();
/// Waits for all queued frames to finish presenting.
@@ -74,6 +74,7 @@ private:
Frontend::EmuWindow& emu_window;
const Instance& instance;
Scheduler& scheduler;
bool low_refresh_rate;
vk::SurfaceKHR surface;
vk::SurfaceKHR next_surface{};
Swapchain swapchain;

View File

@@ -15,11 +15,12 @@ MICROPROFILE_DEFINE(Vulkan_Present, "Vulkan", "Swapchain Present", MP_RGB(66, 18
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_} {
FindPresentFormat();
SetPresentMode();
Create(width, height, surface);
Create(width, height, surface, low_refresh_rate);
}
Swapchain::~Swapchain() {
@@ -27,10 +28,11 @@ Swapchain::~Swapchain() {
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_;
height = height_;
surface = surface_;
low_refresh_rate = low_refresh_rate_;
needs_recreation = false;
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
// 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;
if (!has_mailbox) {
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
// Refer to the license.txt file included.
@@ -16,11 +16,12 @@ class Scheduler;
class Swapchain {
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();
/// 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.
bool AcquireNextImage();
@@ -105,6 +106,7 @@ private:
u32 image_index = 0;
u32 frame_index = 0;
bool needs_recreation = true;
bool low_refresh_rate;
};
} // namespace Vulkan