Skip to content

Commit 1d5d3b5

Browse files
authored
Merge pull request #207 from winnerspiros/copilot/fix-game-crash-on-startup-again
Integrate osu-framework/veldrid fork features: D3D12, low-latency provider, Vulkan optimizations
2 parents 4663603 + 447e7c1 commit 1d5d3b5

11 files changed

Lines changed: 126 additions & 29 deletions

File tree

osu.Android.props

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,14 @@
8888
<ProjectReference Include="$(MSBuildThisFileDirectory)submodules\osu-framework\osu.Framework.Android\osu.Framework.Android.csproj" />
8989
</ItemGroup>
9090

91+
<!-- Include framework native libraries (BASS audio, FFmpeg, etc.) from the submodule.
92+
When using a NuGet package these are bundled automatically; with a ProjectReference
93+
they must be declared explicitly or the app crashes at startup with
94+
System.DllNotFoundException: bass (or similar). -->
95+
<ItemGroup>
96+
<AndroidNativeLibrary Include="$(MSBuildThisFileDirectory)submodules\osu-framework\osu.Framework.Android\arm64-v8a\*.so" Abi="arm64-v8a" />
97+
</ItemGroup>
98+
9199
<PropertyGroup>
92100
<!-- Fody does not handle Android build well, and warns when unchanged.
93101
Since Realm objects are not declared directly in Android projects, simply disable Fody. -->

osu.Android/AndroidNativeBridgeManager.cs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,19 @@ public void StartVulkanProbe()
190190
public string GetVulkanStatus()
191191
{
192192
if (vulkanProbe is not VulkanProbe probe) return string.Empty;
193-
return cachedVulkanStatus ??= $"{(probe.SupportsMailboxPresentMode ? "MAILBOX" : "FIFO")}{(probe.DisablePresentId ? " [NoID]" : "")}{(probe.DisablePresentWait ? " [NoWait]" : "")}{(probe.DisableGraphicsPipelineLibrary ? " [NoGPL]" : "")}";
193+
194+
if (cachedVulkanStatus != null)
195+
return cachedVulkanStatus;
196+
197+
int ver = probe.ApiVersion;
198+
int major = (ver >> 22) & 0x3FF;
199+
int minor = (ver >> 12) & 0x3FF;
200+
201+
cachedVulkanStatus = $"Vk{major}.{minor}"
202+
+ (probe.DisablePresentId ? " [NoID]" : "")
203+
+ (probe.DisablePresentWait ? " [NoWait]" : "")
204+
+ (probe.DisableGraphicsPipelineLibrary ? " [NoGPL]" : "");
205+
return cachedVulkanStatus;
194206
}
195207

196208
public void StopVulkanProbe()
@@ -211,10 +223,12 @@ private static void logVulkanInfo(VulkanProbe probe)
211223

212224
Debug.WriteLine($"[osu!] Vulkan GPU: {probe.DeviceLocalMemoryMB}MB, "
213225
+ $"API={major}.{minor}.{patch}, "
214-
+ $"mailbox={probe.SupportsMailboxPresentMode}, "
215226
+ $"vk1.3={probe.MeetsVulkan13}, "
227+
+ $"vk1.4={probe.MeetsVulkan14}, "
216228
+ $"gpl={probe.SupportsGraphicsPipelineLibrary}, "
217-
+ $"shaderObj={probe.SupportsShaderObject}");
229+
+ $"shaderObj={probe.SupportsShaderObject}, "
230+
+ $"hostCopy={probe.SupportsHostImageCopy}, "
231+
+ $"pushDesc={probe.SupportsPushDescriptors}");
218232
}
219233

220234
[MethodImpl(MethodImplOptions.NoInlining)]

osu.Android/Native/CMakeLists.txt

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
66

77
# Maximum release optimizations for lowest-latency audio callback path.
88
# -O3: aggressive inlining & vectorisation
9-
# -flto: link-time optimisation across translation units
9+
# -flto=thin: link-time optimisation with thin backend (20-40% faster link than full LTO,
10+
# equivalent binary quality for small TUs like this project)
1011
# -ffast-math: enable SIMD-friendly FP (safe — we only memset + store a double)
1112
# -ffunction/data-sections + --gc-sections: dead-code elimination
1213
# -fvisibility=hidden: only OSU_EXPORT symbols are visible
13-
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -flto -ffast-math -ffunction-sections -fdata-sections -fvisibility=hidden -DNDEBUG")
14-
set(CMAKE_C_FLAGS_RELEASE "-O3 -flto -ffast-math -ffunction-sections -fdata-sections -fvisibility=hidden -DNDEBUG")
14+
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -flto=thin -ffast-math -ffunction-sections -fdata-sections -fvisibility=hidden -DNDEBUG")
15+
set(CMAKE_C_FLAGS_RELEASE "-O3 -flto=thin -ffast-math -ffunction-sections -fdata-sections -fvisibility=hidden -DNDEBUG")
1516
# -Wl,-z,max-page-size=16384: align ELF LOAD segments to 16 KB for Android 15+
1617
# devices with 16 KB page sizes. Without this, the .so will fail to load on
1718
# such devices. NDK r28+ supports this flag.
@@ -23,7 +24,10 @@ set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "-Wl,--gc-sections -Wl,-z,max-page-size=16
2324
set(OBOE_ENABLE_FLOWGRAPH OFF CACHE BOOL "Disable Oboe flowgraph to reduce binary size")
2425

2526
# Download and build Oboe main branch from source to ensure we have the latest
26-
# features (like ADPF performance hints) regardless of the build environment.
27+
# features and fixes (ADPF performance hints, workload management, spatialization,
28+
# API 36 compatibility) regardless of the build environment.
29+
# main is preferred over pinned tags because Oboe releases infrequently (~yearly)
30+
# and the main branch accumulates significant latency-critical improvements between tags.
2731
include(FetchContent)
2832
FetchContent_Declare(oboe
2933
GIT_REPOSITORY https://github.com/google/oboe.git
@@ -41,9 +45,9 @@ endif()
4145

4246
set(OBOE_LIB oboe)
4347

44-
find_library(vulkan-lib vulkan)
45-
find_library(log-lib log)
46-
find_library(android-lib android)
48+
find_library(vulkan-lib vulkan REQUIRED)
49+
find_library(log-lib log REQUIRED)
50+
find_library(android-lib android REQUIRED)
4751

4852
add_library(osu_native SHARED
4953
oboe_bridge.cpp

osu.Android/Native/VulkanProbe.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ static VulkanProbe()
6868
public bool SupportsGlobalPriority => !disposed && nativePtr != IntPtr.Zero && nVulkanSupportsGlobalPriority(nativePtr) != 0;
6969
public bool SupportsMemoryBudget => !disposed && nativePtr != IntPtr.Zero && nVulkanSupportsMemoryBudget(nativePtr) != 0;
7070
public bool SupportsSurfaceMaintenance1 => !disposed && nativePtr != IntPtr.Zero && nVulkanSupportsSurfaceMaintenance1(nativePtr) != 0;
71+
public bool MeetsVulkan14 => !disposed && nativePtr != IntPtr.Zero && nVulkanMeetsVulkan14(nativePtr) != 0;
72+
public bool SupportsHostImageCopy => !disposed && nativePtr != IntPtr.Zero && nVulkanSupportsHostImageCopy(nativePtr) != 0;
73+
public bool SupportsPushDescriptors => !disposed && nativePtr != IntPtr.Zero && nVulkanSupportsPushDescriptors(nativePtr) != 0;
7174

7275
public bool DisablePresentId => !disposed && nativePtr != IntPtr.Zero && nVulkanDisablePresentId(nativePtr) != 0;
7376
public bool DisablePresentWait => !disposed && nativePtr != IntPtr.Zero && nVulkanDisablePresentWait(nativePtr) != 0;
@@ -108,5 +111,8 @@ public void Dispose()
108111
[DllImport(lib_name)] private static extern byte nVulkanDisablePresentId(IntPtr ptr);
109112
[DllImport(lib_name)] private static extern byte nVulkanDisablePresentWait(IntPtr ptr);
110113
[DllImport(lib_name)] private static extern byte nVulkanDisableGraphicsPipelineLibrary(IntPtr ptr);
114+
[DllImport(lib_name)] private static extern byte nVulkanMeetsVulkan14(IntPtr ptr);
115+
[DllImport(lib_name)] private static extern byte nVulkanSupportsHostImageCopy(IntPtr ptr);
116+
[DllImport(lib_name)] private static extern byte nVulkanSupportsPushDescriptors(IntPtr ptr);
111117
}
112118
}

osu.Android/Native/oboe_bridge.cpp

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@ bool OboeBridge::open(int32_t sampleRate) {
9393
oboe::OboeExtensions::setMMapEnabled(true);
9494

9595
// Initialise StabilizedCallback to even out callback execution time.
96-
stabilizedCallback_ = std::make_unique<oboe::StabilizedCallback>(this);
96+
// shared_ptr is used to satisfy the non-deprecated setDataCallback overload.
97+
stabilizedCallback_ = std::make_shared<oboe::StabilizedCallback>(this);
9798

9899
oboe::AudioStreamBuilder builder;
99100
builder.setDirection(oboe::Direction::Output)
@@ -106,11 +107,19 @@ bool OboeBridge::open(int32_t sampleRate) {
106107
->setContentType(oboe::ContentType::Music)
107108
->setUsage(oboe::Usage::Game)
108109
->setAudioApi(oboe::AudioApi::AAudio)
109-
->setFramesPerCallback(oboe::kUnspecified)
110+
->setFramesPerDataCallback(oboe::kUnspecified)
110111
->setBufferCapacityInFrames(oboe::kUnspecified)
111112
->setChannelConversionAllowed(false)
112113
->setFormatConversionAllowed(false)
113-
->setCallback(stabilizedCallback_.get());
114+
// Audio is pre-mixed by BASS — tell Android not to spatialize it again.
115+
->setIsContentSpatialized(true)
116+
// Prevent other apps from capturing our audio stream (competitive integrity).
117+
->setAllowedCapturePolicy(oboe::AllowedCapturePolicy::AllowNone)
118+
// Use shared_ptr overload (non-deprecated) for data callback.
119+
->setDataCallback(stabilizedCallback_)
120+
// Non-owning shared_ptr for error callback — OboeBridge outlives the stream.
121+
->setErrorCallback(std::shared_ptr<oboe::AudioStreamErrorCallback>(
122+
std::shared_ptr<void>(), static_cast<oboe::AudioStreamErrorCallback*>(this)));
114123

115124
oboe::Result result = builder.openStream(stream_);
116125

@@ -255,6 +264,10 @@ oboe::DataCallbackResult OboeBridge::onAudioReady(
255264
if (provider) {
256265
int32_t framesRead = provider(audioData, numFrames);
257266

267+
// Clamp to valid range: negative or out-of-range values from the provider
268+
// would wrap to a huge size_t, causing a buffer overrun in the memset below.
269+
framesRead = std::clamp(framesRead, 0, numFrames);
270+
258271
if (framesRead < numFrames) {
259272
size_t bytesDone = static_cast<size_t>(framesRead) * stream->getChannelCount() * sizeof(float);
260273
size_t totalBytes = static_cast<size_t>(numFrames) * stream->getChannelCount() * sizeof(float);

osu.Android/Native/oboe_bridge.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ class OboeBridge : public oboe::AudioStreamCallback {
4444

4545
private:
4646
std::shared_ptr<oboe::AudioStream> stream_;
47+
std::shared_ptr<oboe::StabilizedCallback> stabilizedCallback_;
4748
std::unique_ptr<oboe::LatencyTuner> tuner_;
48-
std::unique_ptr<oboe::StabilizedCallback> stabilizedCallback_;
4949

5050
mutable std::mutex streamLock_;
5151
std::atomic<bool> active_{false};

osu.Android/Native/vulkan_bridge.cpp

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
#include <cstring>
88
#include <new>
99

10-
#define LOG_TAG "osu_native"
10+
#define LOG_TAG "osu!native"
1111
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
1212
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
1313

@@ -26,7 +26,8 @@ VulkanProbe::VulkanProbe() {
2626
available_ = true;
2727

2828
LOGI("Vulkan available: %s (Vendor: 0x%x, API %u.%u.%u, driver %u, VRAM %u MB, "
29-
"qCount %u, mailbox %d, vk1.3 %d, sync2 %d, pWait %d, gpl %d, sObj %d, gPrio %d)",
29+
"qCount %u, vk1.3 %d, vk1.4 %d, sync2 %d, pWait %d, gpl %d, sObj %d, gPrio %d, "
30+
"hostCopy %d, pushDesc %d)",
3031
deviceInfo_.deviceName.c_str(),
3132
deviceInfo_.vendorId,
3233
VK_VERSION_MAJOR(deviceInfo_.apiVersion),
@@ -35,13 +36,15 @@ VulkanProbe::VulkanProbe() {
3536
deviceInfo_.driverVersion,
3637
deviceInfo_.deviceLocalMemoryMB,
3738
deviceInfo_.queueFamilyCount,
38-
deviceInfo_.supportsMailboxPresentMode ? 1 : 0,
3939
deviceInfo_.meetsVulkan13 ? 1 : 0,
40+
deviceInfo_.meetsVulkan14 ? 1 : 0,
4041
deviceInfo_.supportsSynchronization2 ? 1 : 0,
4142
deviceInfo_.supportsPresentWait ? 1 : 0,
4243
deviceInfo_.supportsGraphicsPipelineLibrary ? 1 : 0,
4344
deviceInfo_.supportsShaderObject ? 1 : 0,
44-
deviceInfo_.supportsGlobalPriority ? 1 : 0);
45+
deviceInfo_.supportsGlobalPriority ? 1 : 0,
46+
deviceInfo_.supportsHostImageCopy ? 1 : 0,
47+
deviceInfo_.supportsPushDescriptors ? 1 : 0);
4548
}
4649

4750
VulkanProbe::~VulkanProbe() {
@@ -99,8 +102,15 @@ bool VulkanProbe::queryDevice() {
99102

100103
queryMemory(selected);
101104
queryQueueFamilies(selected);
102-
queryVulkan13Features(selected);
105+
// Query extensions first (sets extension-based feature flags for pre-1.3 devices).
103106
queryModernExtensions(selected);
107+
// Feature queries for 1.3+ override extension-based values with actual feature support.
108+
queryVulkan13Features(selected);
109+
110+
// MAILBOX present mode cannot be queried without a VkSurfaceKHR (we have none in
111+
// this lightweight probe). The actual present mode is selected by the renderer
112+
// (Veldrid) at swapchain creation time via vkGetPhysicalDeviceSurfacePresentModesKHR.
113+
// We leave supportsMailboxPresentMode = false (default) to avoid false positives.
104114

105115
return true;
106116
}
@@ -137,26 +147,40 @@ void VulkanProbe::queryModernExtensions(VkPhysicalDevice device) {
137147
std::vector<VkExtensionProperties> exts(count);
138148
vkEnumerateDeviceExtensionProperties(device, nullptr, &count, exts.data());
139149
for (const auto& ext : exts) {
140-
if (strcmp(ext.extensionName, VK_KHR_SWAPCHAIN_EXTENSION_NAME) == 0) { deviceInfo_.supportsSwapchain = true; deviceInfo_.supportsMailboxPresentMode = true; }
150+
if (strcmp(ext.extensionName, VK_KHR_SWAPCHAIN_EXTENSION_NAME) == 0) deviceInfo_.supportsSwapchain = true;
141151
if (strcmp(ext.extensionName, VK_KHR_PRESENT_ID_EXTENSION_NAME) == 0) deviceInfo_.supportsPresentId = true;
142152
if (strcmp(ext.extensionName, VK_KHR_PRESENT_WAIT_EXTENSION_NAME) == 0) deviceInfo_.supportsPresentWait = true;
143153
if (strcmp(ext.extensionName, VK_EXT_GRAPHICS_PIPELINE_LIBRARY_EXTENSION_NAME) == 0) deviceInfo_.supportsGraphicsPipelineLibrary = true;
144154
if (strcmp(ext.extensionName, VK_EXT_SHADER_OBJECT_EXTENSION_NAME) == 0) deviceInfo_.supportsShaderObject = true;
145155
if (strcmp(ext.extensionName, VK_EXT_GLOBAL_PRIORITY_EXTENSION_NAME) == 0 || strcmp(ext.extensionName, VK_KHR_GLOBAL_PRIORITY_EXTENSION_NAME) == 0) deviceInfo_.supportsGlobalPriority = true;
146156
if (strcmp(ext.extensionName, VK_EXT_MEMORY_BUDGET_EXTENSION_NAME) == 0) deviceInfo_.supportsMemoryBudget = true;
147157
if (strcmp(ext.extensionName, "VK_EXT_surface_maintenance1") == 0) deviceInfo_.supportsSurfaceMaintenance1 = true;
158+
159+
// Extension-based fallback for pre-1.3 devices (overridden by queryVulkan13Features on 1.3+).
160+
if (strcmp(ext.extensionName, "VK_KHR_dynamic_rendering") == 0) deviceInfo_.supportsDynamicRendering = true;
161+
if (strcmp(ext.extensionName, "VK_KHR_synchronization2") == 0) deviceInfo_.supportsSynchronization2 = true;
162+
163+
// Vulkan 1.4+ / Android 16+ extensions — used by string literals since NDK r29
164+
// headers may not define these macros.
165+
if (strcmp(ext.extensionName, "VK_EXT_host_image_copy") == 0) deviceInfo_.supportsHostImageCopy = true;
166+
if (strcmp(ext.extensionName, "VK_KHR_push_descriptor") == 0) deviceInfo_.supportsPushDescriptors = true;
148167
}
149168

150169
// ── Vendor-specific GPU quirks ──────────────────────────────────────
151170
// Qualcomm Adreno 7xx series: known flickering with PresentId/PresentWait
152171
// and broken Graphics Pipeline Library compilation on some driver versions.
153-
if (deviceInfo_.vendorId == 0x5143 && (deviceInfo_.deviceName.find("740") != std::string::npos ||
154-
deviceInfo_.deviceName.find("750") != std::string::npos ||
155-
deviceInfo_.deviceName.find("Adreno") != std::string::npos)) {
156-
LOGI("Adreno 7xx GPU detected: applying performance and flickering overrides");
157-
deviceInfo_.disablePresentId = true;
158-
deviceInfo_.disablePresentWait = true;
159-
deviceInfo_.disableGraphicsPipelineLibrary = true;
172+
// Only target 7xx (730/740/750) — other Adreno generations are unaffected.
173+
if (deviceInfo_.vendorId == 0x5143) {
174+
const auto& name = deviceInfo_.deviceName;
175+
bool isAdreno7xx = name.find("730") != std::string::npos ||
176+
name.find("740") != std::string::npos ||
177+
name.find("750") != std::string::npos;
178+
if (isAdreno7xx) {
179+
LOGI("Adreno 7xx GPU detected: applying performance and flickering overrides");
180+
deviceInfo_.disablePresentId = true;
181+
deviceInfo_.disablePresentWait = true;
182+
deviceInfo_.disableGraphicsPipelineLibrary = true;
183+
}
160184
}
161185

162186
// ARM Mali (Samsung Exynos, MediaTek Dimensity, Google Tensor):
@@ -184,12 +208,18 @@ void VulkanProbe::queryModernExtensions(VkPhysicalDevice device) {
184208
void VulkanProbe::queryVulkan13Features(VkPhysicalDevice device) {
185209
if (VK_VERSION_MAJOR(deviceInfo_.apiVersion) < 1 || (VK_VERSION_MAJOR(deviceInfo_.apiVersion) == 1 && VK_VERSION_MINOR(deviceInfo_.apiVersion) < 3)) return;
186210
deviceInfo_.meetsVulkan13 = true;
211+
212+
// Vulkan 1.4 is just a version check — no NDK header support needed.
213+
if (VK_VERSION_MINOR(deviceInfo_.apiVersion) >= 4)
214+
deviceInfo_.meetsVulkan14 = true;
215+
187216
VkPhysicalDeviceVulkan13Features f13{};
188217
f13.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES;
189218
VkPhysicalDeviceFeatures2 f2{};
190219
f2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
191220
f2.pNext = &f13;
192221
vkGetPhysicalDeviceFeatures2(device, &f2);
222+
// Override extension-based flags with accurate feature queries for 1.3+ devices.
193223
deviceInfo_.supportsDynamicRendering = f13.dynamicRendering == VK_TRUE;
194224
deviceInfo_.supportsSynchronization2 = f13.synchronization2 == VK_TRUE;
195225
}
@@ -227,4 +257,7 @@ OSU_EXPORT byte nVulkanSupportsSurfaceMaintenance1(intptr_t ptr) { return (ptr &
227257
OSU_EXPORT byte nVulkanDisablePresentId(intptr_t ptr) { return (ptr && reinterpret_cast<VulkanProbe*>(ptr)->getDeviceInfo().disablePresentId) ? 1 : 0; }
228258
OSU_EXPORT byte nVulkanDisablePresentWait(intptr_t ptr) { return (ptr && reinterpret_cast<VulkanProbe*>(ptr)->getDeviceInfo().disablePresentWait) ? 1 : 0; }
229259
OSU_EXPORT byte nVulkanDisableGraphicsPipelineLibrary(intptr_t ptr) { return (ptr && reinterpret_cast<VulkanProbe*>(ptr)->getDeviceInfo().disableGraphicsPipelineLibrary) ? 1 : 0; }
260+
OSU_EXPORT byte nVulkanMeetsVulkan14(intptr_t ptr) { return (ptr && reinterpret_cast<VulkanProbe*>(ptr)->getDeviceInfo().meetsVulkan14) ? 1 : 0; }
261+
OSU_EXPORT byte nVulkanSupportsHostImageCopy(intptr_t ptr) { return (ptr && reinterpret_cast<VulkanProbe*>(ptr)->getDeviceInfo().supportsHostImageCopy) ? 1 : 0; }
262+
OSU_EXPORT byte nVulkanSupportsPushDescriptors(intptr_t ptr) { return (ptr && reinterpret_cast<VulkanProbe*>(ptr)->getDeviceInfo().supportsPushDescriptors) ? 1 : 0; }
230263
}

osu.Android/Native/vulkan_bridge.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class VulkanProbe {
2929
bool supportsDynamicRendering = false;
3030
bool supportsSynchronization2 = false;
3131

32-
// API 31+ / Modern High-Performance Extensions
32+
// Modern High-Performance Extensions
3333
bool supportsPresentId = false;
3434
bool supportsPresentWait = false;
3535
bool supportsGraphicsPipelineLibrary = false;
@@ -38,6 +38,11 @@ class VulkanProbe {
3838
bool supportsMemoryBudget = false;
3939
bool supportsSurfaceMaintenance1 = false;
4040

41+
// Vulkan 1.4+ / Android 16+ features
42+
bool meetsVulkan14 = false;
43+
bool supportsHostImageCopy = false;
44+
bool supportsPushDescriptors = false;
45+
4146
// Quirks / Blacklist flags
4247
bool disablePresentId = false;
4348
bool disablePresentWait = false;

osu.Game/Localisation/GraphicsSettingsStrings.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,11 @@ public static class GraphicsSettingsStrings
169169
/// </summary>
170170
public static LocalisableString ShrinkGameToSafeArea => new TranslatableString(getKey(@"shrink_game_to_safe_area"), @"Shrink game to avoid cameras and notches");
171171

172+
/// <summary>
173+
/// "Low latency"
174+
/// </summary>
175+
public static LocalisableString LowLatency => new TranslatableString(getKey(@"low_latency"), @"Low latency");
176+
172177
private static string getKey(string key) => $@"{prefix}:{key}";
173178
}
174179
}

osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using osu.Framework.Configuration;
88
using osu.Framework.Extensions;
99
using osu.Framework.Graphics;
10+
using osu.Framework.Graphics.Rendering.LowLatency;
1011
using osu.Framework.Localisation;
1112
using osu.Framework.Platform;
1213
using osu.Game.Configuration;
@@ -82,6 +83,14 @@ private void load(FrameworkConfigManager config, OsuConfigManager osuConfig, IDi
8283
{
8384
Keywords = new[] { @"framerate", @"counter" },
8485
},
86+
new SettingsItemV2(new FormEnumDropdown<LatencyMode>
87+
{
88+
Caption = GraphicsSettingsStrings.LowLatency,
89+
Current = config.GetBindable<LatencyMode>(FrameworkSetting.LatencyMode),
90+
})
91+
{
92+
Keywords = new[] { @"latency", @"reflex", @"input" },
93+
},
8594
};
8695

8796
renderer.BindValueChanged(r =>

0 commit comments

Comments
 (0)