Snapdragon 8 Elite (& Adreno 8xx) Fixes

See merge request kenji-nx/ryujinx!20
This commit is contained in:
BeZide
2026-01-22 03:33:26 -06:00
committed by KeatonTheBot
parent 6f5b351fa0
commit 6840ae0c5c
26 changed files with 511 additions and 201 deletions
+3
View File
@@ -173,3 +173,6 @@ PublishProfiles/
# Glade backup files # Glade backup files
*.glade~ *.glade~
# Log files
/logs/
@@ -169,6 +169,7 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
override fun surfaceDestroyed(holder: SurfaceHolder) { override fun surfaceDestroyed(holder: SurfaceHolder) {
ghLog("surfaceDestroyed → shutdownBinding()") ghLog("surfaceDestroyed → shutdownBinding()")
try { KenjinxNative.detachWindow() } catch (_: Throwable) {}
// Always bind and unbind (prevents leaks when swiping away tasks) // Always bind and unbind (prevents leaks when swiping away tasks)
shutdownBinding() shutdownBinding()
// The actual emulator termination happens via close() / Exit Game // The actual emulator termination happens via close() / Exit Game
@@ -178,6 +179,7 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
super.onWindowVisibilityChanged(visibility) super.onWindowVisibilityChanged(visibility)
if (visibility != android.view.View.VISIBLE) { if (visibility != android.view.View.VISIBLE) {
ghLog("window not visible → shutdownBinding()") ghLog("window not visible → shutdownBinding()")
try { KenjinxNative.detachWindow() } catch (_: Throwable) {}
shutdownBinding() shutdownBinding()
} }
} }
@@ -290,7 +292,12 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
} }
private fun runGame() { private fun runGame() {
KenjinxNative.graphicsRendererRunLoop() mainViewModel.onEmulationStarted()
try {
KenjinxNative.graphicsRendererRunLoop()
} finally {
mainViewModel.onEmulationStopped()
}
game?.close() game?.close()
} }
@@ -307,7 +314,7 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
try { try {
if (emuBound && _startedViaService) { if (emuBound && _startedViaService) {
emuBinder?.stopEmulation { emuBinder?.stopEmulation {
try { KenjinxNative.deviceCloseEmulation() } catch (_: Throwable) {} // Close is handled in the service hard-close path.
} }
} }
} catch (_: Throwable) { } } catch (_: Throwable) { }
@@ -315,6 +322,12 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
// Fallback: Terminate local thread // Fallback: Terminate local thread
try { _updateThread?.join(200) } catch (_: Throwable) {} try { _updateThread?.join(200) } catch (_: Throwable) {}
try { _renderingThreadWatcher?.join(200) } catch (_: Throwable) {} try { _renderingThreadWatcher?.join(200) } catch (_: Throwable) {}
try { _guestThread?.join(1000) } catch (_: Throwable) {}
if (!(emuBound && _startedViaService)) {
try { KenjinxNative.deviceCloseEmulation() } catch (_: Throwable) {}
mainViewModel.onEmulationStopped()
}
// Release the bond // Release the bond
shutdownBinding() shutdownBinding()
@@ -488,11 +501,13 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
emuBinder?.startEmulation { emuBinder?.startEmulation {
try { try {
mainViewModel.onEmulationStarted()
KenjinxNative.graphicsRendererRunLoop() KenjinxNative.graphicsRendererRunLoop()
} catch (t: Throwable) { } catch (t: Throwable) {
Log.e("GameHost", "RunLoop crash in service", t) Log.e("GameHost", "RunLoop crash in service", t)
} finally { } finally {
_startedViaService = false _startedViaService = false
mainViewModel.onEmulationStopped()
} }
} }
ghLog("RunLoop started in EmulationService") ghLog("RunLoop started in EmulationService")
@@ -71,6 +71,7 @@ interface KenjinxNativeJna : Library {
fun deviceReinitEmulation() fun deviceReinitEmulation()
fun deviceSignalEmulationClose() fun deviceSignalEmulationClose()
fun detachWindow()
// >>> Rendering-related additions for the toggle: // >>> Rendering-related additions for the toggle:
fun deviceWaitForGpuDone(timeoutMs: Int) fun deviceWaitForGpuDone(timeoutMs: Int)
fun deviceRecreateSwapchain() fun deviceRecreateSwapchain()
@@ -279,10 +280,10 @@ object KenjinxNative : KenjinxNativeJna by jnaInstance {
} catch (_: Throwable) {} } catch (_: Throwable) {}
} }
@JvmStatic override fun detachWindow() {
fun detachWindow() {
try { graphicsSetPresentEnabled(false) } catch (_: Throwable) {} try { graphicsSetPresentEnabled(false) } catch (_: Throwable) {}
try { deviceWaitForGpuDone(100) } catch (_: Throwable) {} try { deviceWaitForGpuDone(100) } catch (_: Throwable) {}
try { jnaInstance.detachWindow() } catch (_: Throwable) {}
try { deviceSetWindowHandle(0) } catch (_: Throwable) {} try { deviceSetWindowHandle(0) } catch (_: Throwable) {}
} }
@@ -409,8 +409,8 @@ class MainActivity : BaseActivity() {
setAudioForegroundState(false) setAudioForegroundState(false)
if (MainActivity.mainViewModel?.rendererReady == true) { if (MainActivity.mainViewModel?.rendererReady == true) {
try { try {
KenjinxNative.graphicsSetPresentEnabled(false) setPresentEnabled(false, "onTrimMemory:$level")
Log.d(TAG_FG, "present=DISABLED (onTrimMemory:$level)") KenjinxNative.detachWindow()
} catch (_: Throwable) {} } catch (_: Throwable) {}
} else { } else {
Log.d(TAG_FG, "skip disable present (onTrimMemory) — rendererReady=${MainActivity.mainViewModel?.rendererReady}") Log.d(TAG_FG, "skip disable present (onTrimMemory) — rendererReady=${MainActivity.mainViewModel?.rendererReady}")
@@ -2,6 +2,7 @@ package org.kenjinx.android.viewmodels
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.net.Uri import android.net.Uri
import android.os.SystemClock
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
@@ -23,6 +24,7 @@ import org.kenjinx.android.KenjinxNative
import org.kenjinx.android.PerformanceMonitor import org.kenjinx.android.PerformanceMonitor
import org.kenjinx.android.SystemLanguage import org.kenjinx.android.SystemLanguage
import org.kenjinx.android.UiHandler import org.kenjinx.android.UiHandler
import android.util.Log
import java.io.File import java.io.File
import java.util.TimeZone import java.util.TimeZone
@@ -52,6 +54,8 @@ class MainViewModel(val activity: MainActivity) {
private var showLoading: MutableState<Boolean>? = null private var showLoading: MutableState<Boolean>? = null
private var refreshUser: MutableState<Boolean>? = null private var refreshUser: MutableState<Boolean>? = null
@Volatile var rendererReady: Boolean = false @Volatile var rendererReady: Boolean = false
@Volatile var emulationRunning: Boolean = false
@Volatile var emulationClosing: Boolean = false
// Default Game Folder // Default Game Folder
var defaultGameFolderUri: Uri? = null var defaultGameFolderUri: Uri? = null
@@ -84,15 +88,47 @@ class MainViewModel(val activity: MainActivity) {
} }
fun closeGame() { fun closeGame() {
emulationClosing = true
KenjinxNative.deviceSignalEmulationClose() KenjinxNative.deviceSignalEmulationClose()
gameHost?.close() gameHost?.close()
KenjinxNative.deviceCloseEmulation()
motionSensorManager?.unregister() motionSensorManager?.unregister()
physicalControllerManager?.disconnect() physicalControllerManager?.disconnect()
motionSensorManager?.setControllerId(-1) motionSensorManager?.setControllerId(-1)
rendererReady = false rendererReady = false
} }
internal fun onEmulationStarted() {
emulationRunning = true
}
internal fun onEmulationStopped() {
emulationRunning = false
emulationClosing = false
}
private fun waitForEmulationStop(timeoutMs: Long): Boolean {
if (!emulationRunning) {
emulationClosing = false
return true
}
val deadline = SystemClock.uptimeMillis() + timeoutMs
while (emulationRunning && SystemClock.uptimeMillis() < deadline) {
try { Thread.sleep(50) } catch (_: Throwable) {}
}
if (emulationRunning) {
Log.w("MainViewModel", "Emulation still running after timeout; forcing close.")
try { KenjinxNative.deviceCloseEmulation() } catch (_: Throwable) {}
emulationRunning = false
emulationClosing = false
return true
}
emulationClosing = false
return true
}
// ---- Load language/region from Preferences (Defaults: AmericanEnglish/USA) ---- // ---- Load language/region from Preferences (Defaults: AmericanEnglish/USA) ----
private fun loadSystemLanguage(): SystemLanguage { private fun loadSystemLanguage(): SystemLanguage {
val prefs = PreferenceManager.getDefaultSharedPreferences(activity) val prefs = PreferenceManager.getDefaultSharedPreferences(activity)
@@ -108,6 +144,12 @@ class MainViewModel(val activity: MainActivity) {
// ------------------------------------------------------------------------------- // -------------------------------------------------------------------------------
fun loadGame(game: GameModel, overrideSettings: Boolean? = false, forceNceAndPptc: Boolean? = false): Int { fun loadGame(game: GameModel, overrideSettings: Boolean? = false, forceNceAndPptc: Boolean? = false): Int {
if (emulationClosing || emulationRunning) {
if (!waitForEmulationStop(5000)) {
return 0
}
}
KenjinxNative.deviceReinitEmulation() KenjinxNative.deviceReinitEmulation()
MainActivity.mainViewModel?.activity?.uiHandler = UiHandler() MainActivity.mainViewModel?.activity?.uiHandler = UiHandler()
+15 -4
View File
@@ -321,7 +321,7 @@ namespace LibKenjinx
[UnmanagedCallersOnly(EntryPoint = "deviceReloadFilesystem")] [UnmanagedCallersOnly(EntryPoint = "deviceReloadFilesystem")]
public static void JnaReloadFileSystem() public static void JnaReloadFileSystem()
{ {
Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call: deviceReloadFilesystem");
SwitchDevice?.ReloadFileSystem(); SwitchDevice?.ReloadFileSystem();
} }
@@ -423,21 +423,21 @@ namespace LibKenjinx
[UnmanagedCallersOnly(EntryPoint = "deviceSignalEmulationClose")] [UnmanagedCallersOnly(EntryPoint = "deviceSignalEmulationClose")]
public static void JniSignalEmulationCloseNative() public static void JniSignalEmulationCloseNative()
{ {
Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call: deviceSignalEmulationClose");
SignalEmulationClose(); SignalEmulationClose();
} }
[UnmanagedCallersOnly(EntryPoint = "deviceCloseEmulation")] [UnmanagedCallersOnly(EntryPoint = "deviceCloseEmulation")]
public static void JniCloseEmulationNative() public static void JniCloseEmulationNative()
{ {
Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call: deviceCloseEmulation");
CloseEmulation(); CloseEmulation();
} }
[UnmanagedCallersOnly(EntryPoint = "deviceReinitEmulation")] [UnmanagedCallersOnly(EntryPoint = "deviceReinitEmulation")]
public static void JniReinitEmulationNative() public static void JniReinitEmulationNative()
{ {
Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call: deviceReinitEmulation");
ReinitEmulation(); ReinitEmulation();
} }
@@ -923,6 +923,7 @@ namespace LibKenjinx
{ {
try try
{ {
Logger.Trace?.Print(LogClass.Application, "[JNI] deviceRecreateSwapchain");
if (Renderer?.Window == null) if (Renderer?.Window == null)
{ {
Logger.Warning?.Print(LogClass.Application, "[JNI] deviceRecreateSwapchain: Renderer.Window == null"); Logger.Warning?.Print(LogClass.Application, "[JNI] deviceRecreateSwapchain: Renderer.Window == null");
@@ -995,6 +996,7 @@ namespace LibKenjinx
{ {
try try
{ {
Logger.Trace?.Print(LogClass.Application, "[JNI] graphicsRendererRecreateSurface");
_ = (Renderer as VulkanRenderer)?.RecreateSurface(); _ = (Renderer as VulkanRenderer)?.RecreateSurface();
} }
catch (Exception ex) catch (Exception ex)
@@ -1003,6 +1005,15 @@ namespace LibKenjinx
} }
} }
#if ANDROID
[UnmanagedCallersOnly(EntryPoint = "graphicsSetFullscreenStretch")]
public static void JniGraphicsSetFullscreenStretch(bool enable)
{
Logger.Trace?.Print(LogClass.Application, $"[JNI] graphicsSetFullscreenStretch({enable})");
ApplyFullscreenStretch(enable);
}
#endif
// Used by MainActivity/GameHost // Used by MainActivity/GameHost
[UnmanagedCallersOnly(EntryPoint = "reattachWindowIfReady")] [UnmanagedCallersOnly(EntryPoint = "reattachWindowIfReady")]
public static bool JniReattachWindowIfReady() public static bool JniReattachWindowIfReady()
+4 -4
View File
@@ -212,12 +212,12 @@ namespace LibKenjinx
_touchScreenManager?.Dispose(); _touchScreenManager?.Dispose();
_touchScreenManager = null; _touchScreenManager = null;
_gpuDoneEvent.WaitOne(3000); _gpuDoneEvent?.WaitOne(3000);
_gpuDoneEvent.Dispose(); _gpuDoneEvent?.Dispose();
_gpuDoneEvent = null; _gpuDoneEvent = null;
_gpuCancellationTokenSource.Cancel(); _gpuCancellationTokenSource?.Cancel();
_gpuCancellationTokenSource.Dispose(); _gpuCancellationTokenSource?.Dispose();
_gpuCancellationTokenSource = null; _gpuCancellationTokenSource = null;
SwitchDevice.Dispose(); SwitchDevice.Dispose();
+2 -36
View File
@@ -13,24 +13,11 @@ using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
#if !ANDROID
namespace LibKenjinx namespace LibKenjinx
{ {
public static partial class LibKenjinx public static partial class LibKenjinx
{ {
private unsafe static IntPtr CreateStringArray(List<string> strings)
{
uint size = (uint)(Marshal.SizeOf<IntPtr>() * (strings.Count + 1));
var array = (char**)Marshal.AllocHGlobal((int)size);
Unsafe.InitBlockUnaligned(array, 0, size);
for (int i = 0; i < strings.Count; i++)
{
array[i] = (char*)Marshal.StringToHGlobalAnsi(strings[i]);
}
return (nint)array;
}
[UnmanagedCallersOnly(EntryPoint = "device_initialize")] [UnmanagedCallersOnly(EntryPoint = "device_initialize")]
public static bool InitializeDeviceNative(MemoryManagerMode memoryManagerMode, public static bool InitializeDeviceNative(MemoryManagerMode memoryManagerMode,
bool useHypervisor, bool useHypervisor,
@@ -469,28 +456,6 @@ namespace LibKenjinx
CloseUser(userId); CloseUser(userId);
} }
// ----------------------
// Stretch: Helper + Exports
// ----------------------
// central logic (managed)
private static void ApplyFullscreenStretch(bool enable)
{
var ar = enable ? AspectRatio.Stretched : AspectRatio.Fixed16x9;
// Retrieve, modify, write back struct
var cfg = GraphicsConfiguration;
cfg.AspectRatio = ar;
GraphicsConfiguration = cfg;
// Hot-Apply for running emulation
var dev = SwitchDevice?.EmulationContext;
if (dev != null)
{
try { dev.Configuration.AspectRatio = ar; } catch { }
}
}
// CamelCase Export (JNA calls it here) // CamelCase Export (JNA calls it here)
[UnmanagedCallersOnly(EntryPoint = "graphicsSetFullscreenStretch")] [UnmanagedCallersOnly(EntryPoint = "graphicsSetFullscreenStretch")]
public static void GraphicsSetFullscreenStretchNativeAlias(bool enable) public static void GraphicsSetFullscreenStretchNativeAlias(bool enable)
@@ -499,3 +464,4 @@ namespace LibKenjinx
} }
} }
} }
#endif
@@ -0,0 +1,41 @@
using Ryujinx.Common.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace LibKenjinx
{
public static partial class LibKenjinx
{
private unsafe static IntPtr CreateStringArray(List<string> strings)
{
uint size = (uint)(Marshal.SizeOf<IntPtr>() * (strings.Count + 1));
var array = (char**)Marshal.AllocHGlobal((int)size);
Unsafe.InitBlockUnaligned(array, 0, size);
for (int i = 0; i < strings.Count; i++)
{
array[i] = (char*)Marshal.StringToHGlobalAnsi(strings[i]);
}
return (nint)array;
}
private static void ApplyFullscreenStretch(bool enable)
{
var ar = enable ? AspectRatio.Stretched : AspectRatio.Fixed16x9;
var cfg = GraphicsConfiguration;
cfg.AspectRatio = ar;
GraphicsConfiguration = cfg;
var dev = SwitchDevice?.EmulationContext;
if (dev != null)
{
try { dev.Configuration.AspectRatio = ar; } catch { }
}
}
}
}
+22 -13
View File
@@ -75,6 +75,11 @@ namespace LibKenjinx
Logger.Notice.Print(LogClass.Application, "Initializing..."); Logger.Notice.Print(LogClass.Application, "Initializing...");
Logger.Notice.Print(LogClass.Application, $"Using base path: {AppDataManager.BaseDirPath}"); Logger.Notice.Print(LogClass.Application, $"Using base path: {AppDataManager.BaseDirPath}");
#if ANDROID
Logger.Notice.Print(LogClass.Application, "Build flags: ANDROID enabled");
#else
Logger.Warning?.Print(LogClass.Application, "Build flags: ANDROID disabled (unexpected for Android build)");
#endif
AndroidFileSystem = VirtualFileSystem.CreateInstance(); AndroidFileSystem = VirtualFileSystem.CreateInstance();
SwitchDevice = new SwitchDevice(AndroidFileSystem); SwitchDevice = new SwitchDevice(AndroidFileSystem);
} }
@@ -751,6 +756,7 @@ namespace LibKenjinx
{ {
private readonly SystemVersion _firmwareVersion; private readonly SystemVersion _firmwareVersion;
private int _contextDisposeState;
public VirtualFileSystem VirtualFileSystem { get; set; } public VirtualFileSystem VirtualFileSystem { get; set; }
public ContentManager ContentManager { get; set; } public ContentManager ContentManager { get; set; }
public AccountManager AccountManager { get; set; } public AccountManager AccountManager { get; set; }
@@ -764,10 +770,17 @@ namespace LibKenjinx
public bool EnableFsIntegrityChecks { get; set; } public bool EnableFsIntegrityChecks { get; set; }
internal void DisposeContext() private void DisposeEmulationContext()
{ {
if (EmulationContext == null) if (System.Threading.Interlocked.Exchange(ref _contextDisposeState, 1) != 0)
{
return; return;
}
if (EmulationContext == null)
{
return;
}
Logger.Info?.Print(LogClass.Application, "Disposing EmulationContext"); Logger.Info?.Print(LogClass.Application, "Disposing EmulationContext");
@@ -779,25 +792,21 @@ namespace LibKenjinx
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Error?.Print(LogClass.Application, $"Error disposing EmulationContext: {ex.Message}"); Logger.Error?.Print(LogClass.Application, $"Error disposing EmulationContext: {ex}");
} }
} }
internal void DisposeContext()
{
DisposeEmulationContext();
}
public void Dispose() public void Dispose()
{ {
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
Logger.Info?.Print(LogClass.Application, "Disposing SwitchDevice"); Logger.Info?.Print(LogClass.Application, "Disposing SwitchDevice");
try DisposeEmulationContext();
{
EmulationContext?.Dispose();
EmulationContext?.DisposeGpu();
EmulationContext = null;
}
catch (Exception ex)
{
Logger.Error?.Print(LogClass.Application, $"Error disposing EmulationContext: {ex.Message}");
}
try try
{ {
+6
View File
@@ -5,6 +5,12 @@
<LinkerFlavor Condition="'$(RuntimeIdentifier)'=='linux-bionic-arm64'">lld</LinkerFlavor> <LinkerFlavor Condition="'$(RuntimeIdentifier)'=='linux-bionic-arm64'">lld</LinkerFlavor>
<DefineConstants>$(DefineConstants);FORCE_EXTERNAL_BASE_DIR</DefineConstants> <DefineConstants>$(DefineConstants);FORCE_EXTERNAL_BASE_DIR</DefineConstants>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(RuntimeIdentifier)'=='linux-bionic-arm64'">
<DefineConstants>$(DefineConstants);ANDROID</DefineConstants>
</PropertyGroup>
<ItemGroup Condition="'$(RuntimeIdentifier)'=='linux-bionic-arm64'">
<Compile Remove="LibKenjinx.Native.cs" />
</ItemGroup>
<PropertyGroup> <PropertyGroup>
<PublishAot>true</PublishAot> <PublishAot>true</PublishAot>
<PublishAotUsingRuntimePack>true</PublishAotUsingRuntimePack> <PublishAotUsingRuntimePack>true</PublishAotUsingRuntimePack>
@@ -47,7 +47,7 @@ namespace Ryujinx.Graphics.Gpu.Image
class AutoDeleteCache : IEnumerable<Texture> class AutoDeleteCache : IEnumerable<Texture>
{ {
private const int MinCountForDeletion = 32; private const int MinCountForDeletion = 32;
private const int MaxCapacity = 2048; private const int MaxCapacity = 1024;
private const ulong MiB = 1024 * 1024; private const ulong MiB = 1024 * 1024;
private const ulong GiB = 1024 * 1024 * 1024; private const ulong GiB = 1024 * 1024 * 1024;
private ulong MaxTextureSizeCapacity = 4 * GiB; private ulong MaxTextureSizeCapacity = 4 * GiB;
@@ -59,7 +59,7 @@ namespace Ryujinx.Graphics.Gpu.Image
private const ulong TextureSizeCapacity10GiB = 10 * GiB; private const ulong TextureSizeCapacity10GiB = 10 * GiB;
private const ulong TextureSizeCapacity12GiB = 12 * GiB; private const ulong TextureSizeCapacity12GiB = 12 * GiB;
private const float MemoryScaleFactor = 0.50f; private const float MemoryScaleFactor = 0.30f;
private ulong _maxCacheMemoryUsage = DefaultTextureSizeCapacity; private ulong _maxCacheMemoryUsage = DefaultTextureSizeCapacity;
private readonly LinkedList<Texture> _textures; private readonly LinkedList<Texture> _textures;
@@ -90,10 +90,10 @@ namespace Ryujinx.Graphics.Gpu.Image
< 6 when MaximumGpuMemoryGiB < 6 || context.Capabilities.MaximumGpuMemory == 0 => < 6 when MaximumGpuMemoryGiB < 6 || context.Capabilities.MaximumGpuMemory == 0 =>
DefaultTextureSizeCapacity, DefaultTextureSizeCapacity,
< 6 => TextureSizeCapacity4GiB, < 6 => TextureSizeCapacity4GiB,
6 => TextureSizeCapacity6GiB, 6 => TextureSizeCapacity4GiB,
8 => TextureSizeCapacity8GiB, 8 => TextureSizeCapacity6GiB,
10 => TextureSizeCapacity10GiB, 10 => TextureSizeCapacity6GiB,
_ => TextureSizeCapacity12GiB _ => TextureSizeCapacity8GiB
}; };
var cacheMemory = (ulong)(context.Capabilities.MaximumGpuMemory * MemoryScaleFactor); var cacheMemory = (ulong)(context.Capabilities.MaximumGpuMemory * MemoryScaleFactor);
@@ -83,7 +83,7 @@ namespace Ryujinx.Graphics.Gpu.Synchronization
// TODO: Remove this when GPU channel scheduling will be implemented. // TODO: Remove this when GPU channel scheduling will be implemented.
if (timeout == Timeout.InfiniteTimeSpan) if (timeout == Timeout.InfiniteTimeSpan)
{ {
timeout = TimeSpan.FromSeconds(1); timeout = TimeSpan.FromSeconds(30);
} }
using ManualResetEvent waitEvent = new(false); using ManualResetEvent waitEvent = new(false);
@@ -98,9 +98,13 @@ namespace Ryujinx.Graphics.Gpu.Synchronization
if (!signaled && info != null) if (!signaled && info != null)
{ {
Logger.Error?.Print(LogClass.Gpu, $"Wait on syncpoint {id} for threshold {threshold} took more than {timeout.TotalMilliseconds}ms, resuming execution..."); uint currentValue = _syncpoints[id].Value;
Logger.Error?.Print(LogClass.Gpu, $"Wait on syncpoint {id} for threshold {threshold} took more than {timeout.TotalMilliseconds}ms (current value: {currentValue}), resuming execution...");
_syncpoints[id].UnregisterCallback(info); _syncpoints[id].UnregisterCallback(info);
// Give the GPU some time to recover if it's struggling.
Thread.Sleep(100);
} }
return !signaled; return !signaled;
+13 -2
View File
@@ -2,6 +2,7 @@ using Ryujinx.Common.Memory;
using Silk.NET.Vulkan; using Silk.NET.Vulkan;
using System; using System;
using System.Numerics; using System.Numerics;
using System.Threading;
namespace Ryujinx.Graphics.Vulkan namespace Ryujinx.Graphics.Vulkan
{ {
@@ -9,6 +10,8 @@ namespace Ryujinx.Graphics.Vulkan
{ {
private const int MaxDynamicStatesCount = 9; private const int MaxDynamicStatesCount = 9;
private static readonly Lock _pipelineCreateLock = new();
public PipelineUid Internal; public PipelineUid Internal;
public float LineWidth public float LineWidth
@@ -367,7 +370,10 @@ namespace Ryujinx.Graphics.Vulkan
pipelineCreateInfo.Stage.PSpecializationInfo = info; pipelineCreateInfo.Stage.PSpecializationInfo = info;
} }
gd.Api.CreateComputePipelines(device, cache, 1, &pipelineCreateInfo, null, &pipelineHandle).ThrowOnError(); lock (_pipelineCreateLock)
{
gd.Api.CreateComputePipelines(device, cache, 1, &pipelineCreateInfo, null, &pipelineHandle).ThrowOnError();
}
} }
pipeline = new Auto<DisposablePipeline>(new DisposablePipeline(gd.Api, device, pipelineHandle)); pipeline = new Auto<DisposablePipeline>(new DisposablePipeline(gd.Api, device, pipelineHandle));
@@ -635,7 +641,12 @@ namespace Ryujinx.Graphics.Vulkan
RenderPass = renderPass, RenderPass = renderPass,
}; };
Result result = gd.Api.CreateGraphicsPipelines(device, cache, 1, &pipelineCreateInfo, null, &pipelineHandle); Result result;
lock (_pipelineCreateLock)
{
result = gd.Api.CreateGraphicsPipelines(device, cache, 1, &pipelineCreateInfo, null, &pipelineHandle);
}
if (throwOnError) if (throwOnError)
{ {
@@ -4,9 +4,11 @@ using Ryujinx.Graphics.GAL;
using Silk.NET.Vulkan; using Silk.NET.Vulkan;
using Silk.NET.Vulkan.Extensions.EXT; using Silk.NET.Vulkan.Extensions.EXT;
using Silk.NET.Vulkan.Extensions.KHR; using Silk.NET.Vulkan.Extensions.KHR;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
namespace Ryujinx.Graphics.Vulkan namespace Ryujinx.Graphics.Vulkan
{ {
@@ -291,6 +293,61 @@ namespace Ryujinx.Graphics.Vulkan
bool useRobustBufferAccess = VendorUtils.FromId(physicalDevice.PhysicalDeviceProperties.VendorID) == Vendor.Nvidia; bool useRobustBufferAccess = VendorUtils.FromId(physicalDevice.PhysicalDeviceProperties.VendorID) == Vendor.Nvidia;
PhysicalDeviceVertexInputDynamicStateFeaturesEXT featuresVertexInputDynamicState = new()
{
SType = StructureType.PhysicalDeviceVertexInputDynamicStateFeaturesExt,
};
PhysicalDevicePrimitiveTopologyListRestartFeaturesEXT featuresPrimitiveTopologyListRestart = new()
{
SType = StructureType.PhysicalDevicePrimitiveTopologyListRestartFeaturesExt,
};
PhysicalDeviceRobustness2FeaturesEXT featuresRobustness2 = new()
{
SType = StructureType.PhysicalDeviceRobustness2FeaturesExt,
};
PhysicalDeviceCustomBorderColorFeaturesEXT featuresCustomBorderColor = new()
{
SType = StructureType.PhysicalDeviceCustomBorderColorFeaturesExt,
};
PhysicalDeviceDepthClipControlFeaturesEXT featuresDepthClipControl = new()
{
SType = StructureType.PhysicalDeviceDepthClipControlFeaturesExt,
};
PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT featuresAttachmentFeedbackLoop = new()
{
SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesExt,
};
PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT featuresDynamicAttachmentFeedbackLoop = new()
{
SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesExt,
};
PhysicalDeviceIndexTypeUint8FeaturesEXT featuresIndexU8 = new()
{
SType = StructureType.PhysicalDeviceIndexTypeUint8FeaturesExt,
};
PhysicalDeviceFragmentShaderInterlockFeaturesEXT featuresFragmentShaderInterlock = new()
{
SType = StructureType.PhysicalDeviceFragmentShaderInterlockFeaturesExt,
};
PhysicalDeviceShaderFloat16Int8FeaturesKHR featuresShaderInt8 = new()
{
SType = StructureType.PhysicalDeviceShaderFloat16Int8Features,
};
PhysicalDeviceTransformFeedbackFeaturesEXT featuresTransformFeedback = new()
{
SType = StructureType.PhysicalDeviceTransformFeedbackFeaturesExt,
};
PhysicalDeviceFeatures2 features2 = new() PhysicalDeviceFeatures2 features2 = new()
{ {
SType = StructureType.PhysicalDeviceFeatures2, SType = StructureType.PhysicalDeviceFeatures2,
@@ -382,6 +439,28 @@ namespace Ryujinx.Graphics.Vulkan
features2.PNext = &supportedFeaturesDynamicAttachmentFeedbackLoopLayout; features2.PNext = &supportedFeaturesDynamicAttachmentFeedbackLoopLayout;
} }
PhysicalDeviceVertexInputDynamicStateFeaturesEXT supportedFeaturesVertexInputDynamicState = new()
{
SType = StructureType.PhysicalDeviceVertexInputDynamicStateFeaturesExt,
PNext = features2.PNext,
};
if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_vertex_input_dynamic_state"))
{
features2.PNext = &supportedFeaturesVertexInputDynamicState;
}
PhysicalDeviceShaderFloat16Int8FeaturesKHR supportedFeaturesShaderInt8 = new()
{
SType = StructureType.PhysicalDeviceShaderFloat16Int8Features,
PNext = features2.PNext,
};
if (physicalDevice.IsDeviceExtensionPresent("VK_KHR_shader_float16_int8"))
{
features2.PNext = &supportedFeaturesShaderInt8;
}
PhysicalDeviceVulkan12Features supportedPhysicalDeviceVulkan12Features = new() PhysicalDeviceVulkan12Features supportedPhysicalDeviceVulkan12Features = new()
{ {
SType = StructureType.PhysicalDeviceVulkan12Features, SType = StructureType.PhysicalDeviceVulkan12Features,
@@ -422,49 +501,47 @@ namespace Ryujinx.Graphics.Vulkan
void* pExtendedFeatures = null; void* pExtendedFeatures = null;
PhysicalDeviceTransformFeedbackFeaturesEXT featuresTransformFeedback; if (physicalDevice.IsDeviceExtensionPresent("VK_KHR_shader_float16_int8"))
{
featuresShaderInt8.PNext = pExtendedFeatures;
featuresShaderInt8.ShaderInt8 = supportedFeaturesShaderInt8.ShaderInt8;
pExtendedFeatures = &featuresShaderInt8;
}
if (physicalDevice.IsDeviceExtensionPresent(ExtTransformFeedback.ExtensionName)) if (physicalDevice.IsDeviceExtensionPresent(ExtTransformFeedback.ExtensionName))
{ {
featuresTransformFeedback = new PhysicalDeviceTransformFeedbackFeaturesEXT featuresTransformFeedback.PNext = pExtendedFeatures;
{ featuresTransformFeedback.TransformFeedback = supportedFeaturesTransformFeedback.TransformFeedback;
SType = StructureType.PhysicalDeviceTransformFeedbackFeaturesExt,
PNext = pExtendedFeatures,
TransformFeedback = supportedFeaturesTransformFeedback.TransformFeedback,
};
pExtendedFeatures = &featuresTransformFeedback; pExtendedFeatures = &featuresTransformFeedback;
} }
PhysicalDevicePrimitiveTopologyListRestartFeaturesEXT featuresPrimitiveTopologyListRestart;
if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_primitive_topology_list_restart")) if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_primitive_topology_list_restart"))
{ {
featuresPrimitiveTopologyListRestart = new PhysicalDevicePrimitiveTopologyListRestartFeaturesEXT featuresPrimitiveTopologyListRestart.PNext = pExtendedFeatures;
{ featuresPrimitiveTopologyListRestart.PrimitiveTopologyListRestart = supportedFeaturesPrimitiveTopologyListRestart.PrimitiveTopologyListRestart;
SType = StructureType.PhysicalDevicePrimitiveTopologyListRestartFeaturesExt, featuresPrimitiveTopologyListRestart.PrimitiveTopologyPatchListRestart = supportedFeaturesPrimitiveTopologyListRestart.PrimitiveTopologyPatchListRestart;
PNext = pExtendedFeatures,
PrimitiveTopologyListRestart = supportedFeaturesPrimitiveTopologyListRestart.PrimitiveTopologyListRestart,
PrimitiveTopologyPatchListRestart = supportedFeaturesPrimitiveTopologyListRestart.PrimitiveTopologyPatchListRestart,
};
pExtendedFeatures = &featuresPrimitiveTopologyListRestart; pExtendedFeatures = &featuresPrimitiveTopologyListRestart;
} }
PhysicalDeviceRobustness2FeaturesEXT featuresRobustness2;
if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_robustness2")) if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_robustness2"))
{ {
featuresRobustness2 = new PhysicalDeviceRobustness2FeaturesEXT featuresRobustness2.PNext = pExtendedFeatures;
{ featuresRobustness2.NullDescriptor = supportedFeaturesRobustness2.NullDescriptor;
SType = StructureType.PhysicalDeviceRobustness2FeaturesExt,
PNext = pExtendedFeatures,
NullDescriptor = supportedFeaturesRobustness2.NullDescriptor,
};
pExtendedFeatures = &featuresRobustness2; pExtendedFeatures = &featuresRobustness2;
} }
if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_vertex_input_dynamic_state"))
{
featuresVertexInputDynamicState.PNext = pExtendedFeatures;
featuresVertexInputDynamicState.VertexInputDynamicState = supportedFeaturesVertexInputDynamicState.VertexInputDynamicState;
pExtendedFeatures = &featuresVertexInputDynamicState;
}
var featuresExtendedDynamicState = new PhysicalDeviceExtendedDynamicStateFeaturesEXT var featuresExtendedDynamicState = new PhysicalDeviceExtendedDynamicStateFeaturesEXT
{ {
SType = StructureType.PhysicalDeviceExtendedDynamicStateFeaturesExt, SType = StructureType.PhysicalDeviceExtendedDynamicStateFeaturesExt,
@@ -479,6 +556,10 @@ namespace Ryujinx.Graphics.Vulkan
SType = StructureType.PhysicalDeviceVulkan11Features, SType = StructureType.PhysicalDeviceVulkan11Features,
PNext = pExtendedFeatures, PNext = pExtendedFeatures,
ShaderDrawParameters = supportedFeaturesVk11.ShaderDrawParameters, ShaderDrawParameters = supportedFeaturesVk11.ShaderDrawParameters,
StorageBuffer16BitAccess = supportedFeaturesVk11.StorageBuffer16BitAccess,
UniformAndStorageBuffer16BitAccess = supportedFeaturesVk11.UniformAndStorageBuffer16BitAccess,
StoragePushConstant16 = supportedFeaturesVk11.StoragePushConstant16,
StorageInputOutput16 = supportedFeaturesVk11.StorageInputOutput16,
}; };
pExtendedFeatures = &featuresVk11; pExtendedFeatures = &featuresVk11;
@@ -492,107 +573,132 @@ namespace Ryujinx.Graphics.Vulkan
UniformBufferStandardLayout = supportedPhysicalDeviceVulkan12Features.UniformBufferStandardLayout, UniformBufferStandardLayout = supportedPhysicalDeviceVulkan12Features.UniformBufferStandardLayout,
UniformAndStorageBuffer8BitAccess = supportedPhysicalDeviceVulkan12Features.UniformAndStorageBuffer8BitAccess, UniformAndStorageBuffer8BitAccess = supportedPhysicalDeviceVulkan12Features.UniformAndStorageBuffer8BitAccess,
StorageBuffer8BitAccess = supportedPhysicalDeviceVulkan12Features.StorageBuffer8BitAccess, StorageBuffer8BitAccess = supportedPhysicalDeviceVulkan12Features.StorageBuffer8BitAccess,
StoragePushConstant8 = supportedPhysicalDeviceVulkan12Features.StoragePushConstant8,
}; };
pExtendedFeatures = &featuresVk12; pExtendedFeatures = &featuresVk12;
PhysicalDeviceIndexTypeUint8FeaturesEXT featuresIndexU8;
if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_index_type_uint8")) if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_index_type_uint8"))
{ {
featuresIndexU8 = new PhysicalDeviceIndexTypeUint8FeaturesEXT featuresIndexU8.PNext = pExtendedFeatures;
{ featuresIndexU8.IndexTypeUint8 = true;
SType = StructureType.PhysicalDeviceIndexTypeUint8FeaturesExt,
PNext = pExtendedFeatures,
IndexTypeUint8 = true,
};
pExtendedFeatures = &featuresIndexU8; pExtendedFeatures = &featuresIndexU8;
} }
PhysicalDeviceFragmentShaderInterlockFeaturesEXT featuresFragmentShaderInterlock;
if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_fragment_shader_interlock")) if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_fragment_shader_interlock"))
{ {
featuresFragmentShaderInterlock = new PhysicalDeviceFragmentShaderInterlockFeaturesEXT featuresFragmentShaderInterlock.PNext = pExtendedFeatures;
{ featuresFragmentShaderInterlock.FragmentShaderPixelInterlock = true;
SType = StructureType.PhysicalDeviceFragmentShaderInterlockFeaturesExt,
PNext = pExtendedFeatures,
FragmentShaderPixelInterlock = true,
};
pExtendedFeatures = &featuresFragmentShaderInterlock; pExtendedFeatures = &featuresFragmentShaderInterlock;
} }
PhysicalDeviceCustomBorderColorFeaturesEXT featuresCustomBorderColor;
if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_custom_border_color") && if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_custom_border_color") &&
supportedFeaturesCustomBorderColor.CustomBorderColors && supportedFeaturesCustomBorderColor.CustomBorderColors &&
supportedFeaturesCustomBorderColor.CustomBorderColorWithoutFormat) supportedFeaturesCustomBorderColor.CustomBorderColorWithoutFormat)
{ {
featuresCustomBorderColor = new PhysicalDeviceCustomBorderColorFeaturesEXT featuresCustomBorderColor.PNext = pExtendedFeatures;
{ featuresCustomBorderColor.CustomBorderColors = true;
SType = StructureType.PhysicalDeviceCustomBorderColorFeaturesExt, featuresCustomBorderColor.CustomBorderColorWithoutFormat = true;
PNext = pExtendedFeatures,
CustomBorderColors = true,
CustomBorderColorWithoutFormat = true,
};
pExtendedFeatures = &featuresCustomBorderColor; pExtendedFeatures = &featuresCustomBorderColor;
} }
PhysicalDeviceDepthClipControlFeaturesEXT featuresDepthClipControl;
if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_depth_clip_control") && if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_depth_clip_control") &&
supportedFeaturesDepthClipControl.DepthClipControl) supportedFeaturesDepthClipControl.DepthClipControl)
{ {
featuresDepthClipControl = new PhysicalDeviceDepthClipControlFeaturesEXT featuresDepthClipControl.PNext = pExtendedFeatures;
{ featuresDepthClipControl.DepthClipControl = true;
SType = StructureType.PhysicalDeviceDepthClipControlFeaturesExt,
PNext = pExtendedFeatures,
DepthClipControl = true,
};
pExtendedFeatures = &featuresDepthClipControl; pExtendedFeatures = &featuresDepthClipControl;
} }
PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT featuresAttachmentFeedbackLoopLayout;
if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_layout") && if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_layout") &&
supportedFeaturesAttachmentFeedbackLoopLayout.AttachmentFeedbackLoopLayout) supportedFeaturesAttachmentFeedbackLoopLayout.AttachmentFeedbackLoopLayout)
{ {
featuresAttachmentFeedbackLoopLayout = new() featuresAttachmentFeedbackLoop.PNext = pExtendedFeatures;
{ featuresAttachmentFeedbackLoop.AttachmentFeedbackLoopLayout = true;
SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesExt,
PNext = pExtendedFeatures,
AttachmentFeedbackLoopLayout = true,
};
pExtendedFeatures = &featuresAttachmentFeedbackLoopLayout; pExtendedFeatures = &featuresAttachmentFeedbackLoop;
} }
PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT featuresDynamicAttachmentFeedbackLoopLayout;
if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_dynamic_state") && if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_dynamic_state") &&
supportedFeaturesDynamicAttachmentFeedbackLoopLayout.AttachmentFeedbackLoopDynamicState) supportedFeaturesDynamicAttachmentFeedbackLoopLayout.AttachmentFeedbackLoopDynamicState)
{ {
featuresDynamicAttachmentFeedbackLoopLayout = new() featuresDynamicAttachmentFeedbackLoop.PNext = pExtendedFeatures;
{ featuresDynamicAttachmentFeedbackLoop.AttachmentFeedbackLoopDynamicState = true;
SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesExt,
PNext = pExtendedFeatures,
AttachmentFeedbackLoopDynamicState = true,
};
pExtendedFeatures = &featuresDynamicAttachmentFeedbackLoopLayout; pExtendedFeatures = &featuresDynamicAttachmentFeedbackLoop;
} }
var enabledExtensions = _requiredExtensions.Union(_desirableExtensions.Intersect(physicalDevice.DeviceExtensions)).ToArray(); var enabledExtensions = _requiredExtensions.Union(_desirableExtensions.Intersect(physicalDevice.DeviceExtensions));
nint* ppEnabledExtensions = stackalloc nint[enabledExtensions.Length]; if (VendorUtils.FromId(physicalDevice.PhysicalDeviceProperties.VendorID) == Vendor.Qualcomm &&
(physicalDevice.DeviceName.Contains("Adreno") && Regex.IsMatch(physicalDevice.DeviceName, @"8[3-9]\d")))
for (int i = 0; i < enabledExtensions.Length; i++)
{ {
ppEnabledExtensions[i] = Marshal.StringToHGlobalAnsi(enabledExtensions[i]); enabledExtensions = enabledExtensions.Where(e =>
e != "VK_KHR_shader_float_controls" &&
e != "VK_KHR_shader_atomic_int64" &&
e != "VK_KHR_16bit_storage" &&
e != "VK_KHR_8bit_storage" &&
e != "VK_EXT_vertex_input_dynamic_state" &&
e != "VK_EXT_robustness2" &&
e != "VK_EXT_custom_border_color" &&
e != "VK_EXT_fragment_shader_interlock" &&
e != "VK_EXT_shader_stencil_export" &&
e != "VK_EXT_descriptor_indexing" &&
e != "VK_EXT_external_memory_host" &&
e != "VK_EXT_transform_feedback" &&
e != "VK_EXT_extended_dynamic_state" &&
e != "VK_EXT_attachment_feedback_loop_layout" &&
e != "VK_EXT_attachment_feedback_loop_dynamic_state" &&
e != "VK_EXT_index_type_uint8" &&
e != "VK_EXT_primitive_topology_list_restart" &&
e != "VK_EXT_4444_formats" &&
e != "VK_KHR_maintenance2" &&
e != "VK_KHR_shader_float16_int8");
features.ShaderInt64 = false;
featuresVk12.ShaderBufferInt64Atomics = false;
featuresVk12.ShaderSharedInt64Atomics = false;
featuresVk12.UniformAndStorageBuffer8BitAccess = false;
featuresVk12.StorageBuffer8BitAccess = false;
featuresVk12.StoragePushConstant8 = false;
featuresVk11.StorageBuffer16BitAccess = false;
featuresVk11.UniformAndStorageBuffer16BitAccess = false;
featuresVk11.StoragePushConstant16 = false;
featuresVk11.StorageInputOutput16 = false;
featuresVertexInputDynamicState.VertexInputDynamicState = false;
featuresRobustness2.NullDescriptor = false;
featuresCustomBorderColor.CustomBorderColors = false;
featuresCustomBorderColor.CustomBorderColorWithoutFormat = false;
featuresExtendedDynamicState.ExtendedDynamicState = false;
if (physicalDevice.IsDeviceExtensionPresent(ExtTransformFeedback.ExtensionName))
{
featuresTransformFeedback.TransformFeedback = false;
}
featuresAttachmentFeedbackLoop.AttachmentFeedbackLoopLayout = false;
featuresDynamicAttachmentFeedbackLoop.AttachmentFeedbackLoopDynamicState = false;
featuresIndexU8.IndexTypeUint8 = false;
featuresPrimitiveTopologyListRestart.PrimitiveTopologyListRestart = false;
featuresPrimitiveTopologyListRestart.PrimitiveTopologyPatchListRestart = false;
featuresShaderInt8.ShaderInt8 = false;
Logger.Warning?.Print(LogClass.Gpu, "Disabling broken extensions for Snapdragon 8 Elite / Gen 5");
}
var finalEnabledExtensions = enabledExtensions.ToArray();
nint* ppEnabledExtensions = stackalloc nint[finalEnabledExtensions.Length];
for (int i = 0; i < finalEnabledExtensions.Length; i++)
{
ppEnabledExtensions[i] = Marshal.StringToHGlobalAnsi(finalEnabledExtensions[i]);
} }
var deviceCreateInfo = new DeviceCreateInfo var deviceCreateInfo = new DeviceCreateInfo
@@ -602,13 +708,13 @@ namespace Ryujinx.Graphics.Vulkan
QueueCreateInfoCount = 1, QueueCreateInfoCount = 1,
PQueueCreateInfos = &queueCreateInfo, PQueueCreateInfos = &queueCreateInfo,
PpEnabledExtensionNames = (byte**)ppEnabledExtensions, PpEnabledExtensionNames = (byte**)ppEnabledExtensions,
EnabledExtensionCount = (uint)enabledExtensions.Length, EnabledExtensionCount = (uint)finalEnabledExtensions.Length,
PEnabledFeatures = &features, PEnabledFeatures = &features,
}; };
api.CreateDevice(physicalDevice.PhysicalDevice, in deviceCreateInfo, null, out var device).ThrowOnError(); api.CreateDevice(physicalDevice.PhysicalDevice, in deviceCreateInfo, null, out var device).ThrowOnError();
for (int i = 0; i < enabledExtensions.Length; i++) for (int i = 0; i < finalEnabledExtensions.Length; i++)
{ {
Marshal.FreeHGlobal(ppEnabledExtensions[i]); Marshal.FreeHGlobal(ppEnabledExtensions[i]);
} }
+63 -27
View File
@@ -11,6 +11,7 @@ using Silk.NET.Vulkan.Extensions.KHR;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using Format = Ryujinx.Graphics.GAL.Format; using Format = Ryujinx.Graphics.GAL.Format;
using PrimitiveTopology = Ryujinx.Graphics.GAL.PrimitiveTopology; using PrimitiveTopology = Ryujinx.Graphics.GAL.PrimitiveTopology;
@@ -414,39 +415,41 @@ namespace Ryujinx.Graphics.Vulkan
properties.Limits.FramebufferDepthSampleCounts & properties.Limits.FramebufferDepthSampleCounts &
properties.Limits.FramebufferStencilSampleCounts; properties.Limits.FramebufferStencilSampleCounts;
bool isAdreno8xx = Vendor == Vendor.Qualcomm && (GpuRenderer.Contains("Adreno") && Regex.IsMatch(GpuRenderer, @"8[3-9]\d"));
Capabilities = new HardwareCapabilities( Capabilities = new HardwareCapabilities(
_physicalDevice.IsDeviceExtensionPresent("VK_EXT_index_type_uint8"), _physicalDevice.IsDeviceExtensionPresent("VK_EXT_index_type_uint8") && !isAdreno8xx,
supportsCustomBorderColor, supportsCustomBorderColor && !isAdreno8xx,
supportsBlendOperationAdvanced, supportsBlendOperationAdvanced,
propertiesBlendOperationAdvanced.AdvancedBlendCorrelatedOverlap, propertiesBlendOperationAdvanced.AdvancedBlendCorrelatedOverlap,
propertiesBlendOperationAdvanced.AdvancedBlendNonPremultipliedSrcColor, propertiesBlendOperationAdvanced.AdvancedBlendNonPremultipliedSrcColor,
propertiesBlendOperationAdvanced.AdvancedBlendNonPremultipliedDstColor, propertiesBlendOperationAdvanced.AdvancedBlendNonPremultipliedDstColor,
_physicalDevice.IsDeviceExtensionPresent(KhrDrawIndirectCount.ExtensionName), _physicalDevice.IsDeviceExtensionPresent(KhrDrawIndirectCount.ExtensionName),
_physicalDevice.IsDeviceExtensionPresent("VK_EXT_fragment_shader_interlock"), _physicalDevice.IsDeviceExtensionPresent("VK_EXT_fragment_shader_interlock") && !isAdreno8xx,
_physicalDevice.IsDeviceExtensionPresent("VK_NV_geometry_shader_passthrough"), _physicalDevice.IsDeviceExtensionPresent("VK_NV_geometry_shader_passthrough") && !isAdreno8xx,
features2.Features.ShaderFloat64, features2.Features.ShaderFloat64,
featuresShaderInt8.ShaderInt8, featuresShaderInt8.ShaderInt8 && !isAdreno8xx,
_physicalDevice.IsDeviceExtensionPresent("VK_EXT_shader_stencil_export"), _physicalDevice.IsDeviceExtensionPresent("VK_EXT_shader_stencil_export") && !isAdreno8xx,
features2.Features.ShaderStorageImageMultisample, features2.Features.ShaderStorageImageMultisample,
_physicalDevice.IsDeviceExtensionPresent(ExtConditionalRendering.ExtensionName), _physicalDevice.IsDeviceExtensionPresent(ExtConditionalRendering.ExtensionName),
_physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState.ExtensionName), _physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState.ExtensionName) && !isAdreno8xx,
features2.Features.MultiViewport && !(IsMoltenVk && Vendor == Vendor.Amd), // Workaround for AMD on MoltenVK issue features2.Features.MultiViewport && !(IsMoltenVk && Vendor == Vendor.Amd), // Workaround for AMD on MoltenVK issue
featuresRobustness2.NullDescriptor || IsMoltenVk, (featuresRobustness2.NullDescriptor || IsMoltenVk) && !isAdreno8xx,
supportsPushDescriptors && !IsMoltenVk, supportsPushDescriptors && !IsMoltenVk,
propertiesPushDescriptor.MaxPushDescriptors, propertiesPushDescriptor.MaxPushDescriptors,
featuresPrimitiveTopologyListRestart.PrimitiveTopologyListRestart, featuresPrimitiveTopologyListRestart.PrimitiveTopologyListRestart && !isAdreno8xx,
featuresPrimitiveTopologyListRestart.PrimitiveTopologyPatchListRestart, featuresPrimitiveTopologyListRestart.PrimitiveTopologyPatchListRestart && !isAdreno8xx,
supportsTransformFeedback, supportsTransformFeedback && !isAdreno8xx,
propertiesTransformFeedback.TransformFeedbackQueries, propertiesTransformFeedback.TransformFeedbackQueries,
features2.Features.OcclusionQueryPrecise, features2.Features.OcclusionQueryPrecise,
_physicalDevice.PhysicalDeviceFeatures.PipelineStatisticsQuery, _physicalDevice.PhysicalDeviceFeatures.PipelineStatisticsQuery,
_physicalDevice.PhysicalDeviceFeatures.GeometryShader, _physicalDevice.PhysicalDeviceFeatures.GeometryShader && !isAdreno8xx,
_physicalDevice.PhysicalDeviceFeatures.TessellationShader, _physicalDevice.PhysicalDeviceFeatures.TessellationShader && !isAdreno8xx,
_physicalDevice.IsDeviceExtensionPresent("VK_NV_viewport_array2"), _physicalDevice.IsDeviceExtensionPresent("VK_NV_viewport_array2"),
_physicalDevice.IsDeviceExtensionPresent(ExtExternalMemoryHost.ExtensionName), _physicalDevice.IsDeviceExtensionPresent(ExtExternalMemoryHost.ExtensionName) && !isAdreno8xx,
supportsDepthClipControl && featuresDepthClipControl.DepthClipControl, supportsDepthClipControl && featuresDepthClipControl.DepthClipControl,
supportsAttachmentFeedbackLoop && featuresAttachmentFeedbackLoop.AttachmentFeedbackLoopLayout, supportsAttachmentFeedbackLoop && featuresAttachmentFeedbackLoop.AttachmentFeedbackLoopLayout && !isAdreno8xx,
supportsDynamicAttachmentFeedbackLoop && featuresDynamicAttachmentFeedbackLoop.AttachmentFeedbackLoopDynamicState, supportsDynamicAttachmentFeedbackLoop && featuresDynamicAttachmentFeedbackLoop.AttachmentFeedbackLoopDynamicState && !isAdreno8xx,
propertiesSubgroup.SubgroupSize, propertiesSubgroup.SubgroupSize,
supportedSampleCounts, supportedSampleCounts,
portabilityFlags, portabilityFlags,
@@ -744,6 +747,8 @@ namespace Ryujinx.Graphics.Vulkan
SystemMemoryType.DedicatedMemory; SystemMemoryType.DedicatedMemory;
} }
bool isAdreno8xx = Vendor == Vendor.Qualcomm && (GpuRenderer.Contains("Adreno") && Regex.IsMatch(GpuRenderer, @"8[3-9]\d"));
return new Capabilities( return new Capabilities(
api: TargetApi.Vulkan, api: TargetApi.Vulkan,
GpuVendor, GpuVendor,
@@ -766,13 +771,13 @@ namespace Ryujinx.Graphics.Vulkan
supports5BitComponentFormat: supports5BitComponentFormat, supports5BitComponentFormat: supports5BitComponentFormat,
supportsSparseBuffer: features2.Features.SparseBinding && mainQueueProperties.QueueFlags.HasFlag(QueueFlags.SparseBindingBit), supportsSparseBuffer: features2.Features.SparseBinding && mainQueueProperties.QueueFlags.HasFlag(QueueFlags.SparseBindingBit),
supportsBlendEquationAdvanced: Capabilities.SupportsBlendEquationAdvanced, supportsBlendEquationAdvanced: Capabilities.SupportsBlendEquationAdvanced,
supportsFragmentShaderInterlock: Capabilities.SupportsFragmentShaderInterlock, supportsFragmentShaderInterlock: Capabilities.SupportsFragmentShaderInterlock && !isAdreno8xx,
supportsFragmentShaderOrderingIntel: false, supportsFragmentShaderOrderingIntel: false,
supportsGeometryShader: Capabilities.SupportsGeometryShader, supportsGeometryShader: Capabilities.SupportsGeometryShader && !isAdreno8xx,
supportsGeometryShaderPassthrough: Capabilities.SupportsGeometryShaderPassthrough, supportsGeometryShaderPassthrough: Capabilities.SupportsGeometryShaderPassthrough && !isAdreno8xx,
supportsTransformFeedback: Capabilities.SupportsTransformFeedback, supportsTransformFeedback: Capabilities.SupportsTransformFeedback && !isAdreno8xx,
supportsImageLoadFormatted: features2.Features.ShaderStorageImageReadWithoutFormat, supportsImageLoadFormatted: features2.Features.ShaderStorageImageReadWithoutFormat,
supportsLayerVertexTessellation: featuresVk12.ShaderOutputLayer, supportsLayerVertexTessellation: featuresVk12.ShaderOutputLayer && !isAdreno8xx,
supportsMismatchingViewFormat: true, supportsMismatchingViewFormat: true,
supportsCubemapView: !IsAmdGcn, supportsCubemapView: !IsAmdGcn,
supportsNonConstantTextureOffset: false, supportsNonConstantTextureOffset: false,
@@ -784,7 +789,7 @@ namespace Ryujinx.Graphics.Vulkan
supportsTextureGatherOffsets: features2.Features.ShaderImageGatherExtended && !IsMoltenVk, supportsTextureGatherOffsets: features2.Features.ShaderImageGatherExtended && !IsMoltenVk,
supportsTextureShadowLod: false, supportsTextureShadowLod: false,
supportsVertexStoreAndAtomics: features2.Features.VertexPipelineStoresAndAtomics, supportsVertexStoreAndAtomics: features2.Features.VertexPipelineStoresAndAtomics,
supportsViewportIndexVertexTessellation: featuresVk12.ShaderOutputViewportIndex, supportsViewportIndexVertexTessellation: featuresVk12.ShaderOutputViewportIndex && !isAdreno8xx,
supportsViewportMask: Capabilities.SupportsViewportArray2, supportsViewportMask: Capabilities.SupportsViewportArray2,
supportsViewportSwizzle: false, supportsViewportSwizzle: false,
supportsIndirectParameters: true, supportsIndirectParameters: true,
@@ -1062,11 +1067,16 @@ namespace Ryujinx.Graphics.Vulkan
{ {
lock (SurfaceLock) lock (SurfaceLock)
{ {
if (!_initialized)
{
return;
}
try try
{ {
( _window as Window )?.SetSurfaceQueryAllowed(false); ( _window as Window )?.SetSurfaceQueryAllowed(false);
if (_surface.Handle != 0) if (_surface.Handle != 0 && SurfaceApi != null && _instance != null)
{ {
SurfaceApi.DestroySurface(_instance.Instance, _surface, null); SurfaceApi.DestroySurface(_instance.Instance, _surface, null);
_surface = new SurfaceKHR(0); _surface = new SurfaceKHR(0);
@@ -1077,7 +1087,14 @@ namespace Ryujinx.Graphics.Vulkan
// still // still
} }
( _window as Window )?.OnSurfaceLost(); try
{
( _window as Window )?.OnSurfaceLost();
}
catch
{
// ignore shutdown path exceptions
}
} }
} }
@@ -1085,14 +1102,33 @@ namespace Ryujinx.Graphics.Vulkan
{ {
PresentAllowed = enabled; PresentAllowed = enabled;
if (!_initialized)
{
return;
}
if (!enabled) if (!enabled)
{ {
( _window as Window )?.SetSurfaceQueryAllowed(false); try
ReleaseSurface(); {
( _window as Window )?.SetSurfaceQueryAllowed(false);
ReleaseSurface();
}
catch
{
// ignore shutdown path exceptions
}
} }
else else
{ {
_ = RecreateSurface(); try
{
_ = RecreateSurface();
}
catch
{
// ignore resume path exceptions
}
} }
} }
@@ -1,4 +1,5 @@
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.Horizon.Common; using Ryujinx.Horizon.Common;
using System.Collections.Generic; using System.Collections.Generic;
@@ -52,8 +53,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
lock (_lock) lock (_lock)
{ {
if (_current2[index] >= _limit[index]) if (_current2[index] >= _limit[index] && amount > 0)
{ {
Logger.Warning?.Print(LogClass.Kernel, $"Resource limit {resource} reached! Current: {_current[index]}, Current2: {_current2[index]}, Limit: {_limit[index]}, Requested: {amount}");
return false; return false;
} }
@@ -87,6 +89,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
success = true; success = true;
} }
else
{
Logger.Warning?.Print(LogClass.Kernel, $"Resource limit {resource} exceeded! Current: {_current[index]}, Current2: {_current2[index]}, Limit: {_limit[index]}, Requested: {amount}");
}
} }
return success; return success;
@@ -1,3 +1,4 @@
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Kernel.Memory; using Ryujinx.HLE.HOS.Kernel.Memory;
using Ryujinx.Horizon.Common; using Ryujinx.Horizon.Common;
using System; using System;
@@ -31,8 +32,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
} }
ulong ramSize = KSystemControl.GetDramSize(size); ulong ramSize = KSystemControl.GetDramSize(size);
ulong memoryLimit = ramSize + 12288 * 1024 * 1024UL; // Add 12GB slack for System Resource and MapPhysicalMemory
EnsureSuccess(resourceLimit.SetLimitValue(LimitableResource.Memory, (long)ramSize)); Logger.Info?.Print(LogClass.Kernel, $"Initializing system resource limit: DRAM={ramSize / 1024 / 1024}MB, Total={memoryLimit / 1024 / 1024}MB");
EnsureSuccess(resourceLimit.SetLimitValue(LimitableResource.Memory, (long)memoryLimit));
EnsureSuccess(resourceLimit.SetLimitValue(LimitableResource.Thread, 800)); EnsureSuccess(resourceLimit.SetLimitValue(LimitableResource.Thread, 800));
EnsureSuccess(resourceLimit.SetLimitValue(LimitableResource.Event, 700)); EnsureSuccess(resourceLimit.SetLimitValue(LimitableResource.Event, 700));
EnsureSuccess(resourceLimit.SetLimitValue(LimitableResource.TransferMemory, 200)); EnsureSuccess(resourceLimit.SetLimitValue(LimitableResource.TransferMemory, 200));
@@ -47,8 +51,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
public static KMemoryRegionManager[] GetMemoryRegions(MemorySize size, MemoryArrange arrange) public static KMemoryRegionManager[] GetMemoryRegions(MemorySize size, MemoryArrange arrange)
{ {
ulong poolEnd = KSystemControl.GetDramEndAddress(size); ulong dramSize = KSystemControl.GetDramSize(size);
ulong applicationPoolSize = KSystemControl.GetApplicationPoolSize(arrange); ulong slack = 12288 * 1024 * 1024UL;
ulong poolEnd = DramMemoryMap.DramBase + dramSize + slack;
ulong applicationPoolSize = KSystemControl.GetApplicationPoolSize(arrange) + slack;
ulong appletPoolSize = KSystemControl.GetAppletPoolSize(arrange); ulong appletPoolSize = KSystemControl.GetAppletPoolSize(arrange);
MemoryRegion servicePool; MemoryRegion servicePool;
@@ -1887,12 +1887,16 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
} }
Logger.Error?.Print(LogClass.KernelSvc, "The guest program broke execution!"); Logger.Error?.Print(LogClass.KernelSvc, "The guest program broke execution!");
Logger.Error?.Print(
LogClass.KernelSvc,
$"SvcBreak reason=0x{reason:X16} pid={currentThread.Owner?.Pid} titleId=0x{currentThread.Owner?.TitleId:X16} name={currentThread.Owner?.Name} tid={currentThread.ThreadUid} pc=0x{currentThread.Context.Pc:X}"
);
Logger.Flush(); Logger.Flush();
// TODO: Debug events. // TODO: Debug events.
currentThread.Owner.TerminateCurrentProcess(); currentThread.Owner.TerminateCurrentProcess();
currentThread.Exit();
throw new GuestBrokeExecutionException(); return;
} }
else else
{ {
@@ -1263,17 +1263,30 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
_schedulerWaitEvent.Wait(); _schedulerWaitEvent.Wait();
KernelStatic.SetKernelContext(KernelContext, this); KernelStatic.SetKernelContext(KernelContext, this);
if (_customThreadStart != null) try
{ {
_customThreadStart(); if (_customThreadStart != null)
{
_customThreadStart();
// Ensure that anything trying to join the HLE thread is unblocked. // Ensure that anything trying to join the HLE thread is unblocked.
Exit(); Exit();
HandlePostSyscall(); HandlePostSyscall();
}
else
{
Owner.Context.Execute(Context, _entrypoint);
}
} }
else catch (Ryujinx.HLE.Exceptions.GuestBrokeExecutionException)
{ {
Owner.Context.Execute(Context, _entrypoint); Logger.Warning?.Print(LogClass.Kernel, "Guest broke execution. Thread exiting.");
Exit();
}
catch (Exception ex)
{
Logger.Error?.Print(LogClass.Kernel, $"Unhandled exception in guest thread: {ex}");
Exit();
} }
Context.Dispose(); Context.Dispose();
@@ -398,6 +398,9 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu
return NvInternalResult.Success; return NvInternalResult.Success;
} }
public override void Close() { } public override void Close()
{
_asContext.Close();
}
} }
} }
@@ -123,6 +123,17 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types
return _reservations.Remove(gpuVa); return _reservations.Remove(gpuVa);
} }
public void Close()
{
foreach (var map in _maps.Values)
{
Gmm.Unmap(map.Start, map.End - map.Start);
}
_maps.Clear();
_reservations.Clear();
}
private Range BinarySearch(SortedList<ulong, Range> list, ulong address) private Range BinarySearch(SortedList<ulong, Range> list, ulong address)
{ {
int left = 0; int left = 0;
@@ -32,7 +32,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl
/// Max failing count until waiting on CPU. /// Max failing count until waiting on CPU.
/// FIXME: This seems enough for most of the cases, reduce if needed. /// FIXME: This seems enough for most of the cases, reduce if needed.
/// </summary> /// </summary>
private const uint FailingCountMax = 2; private const uint FailingCountMax = 10;
public NvHostEvent(NvHostSyncpt syncpointManager, uint eventId, Horizon system) public NvHostEvent(NvHostSyncpt syncpointManager, uint eventId, Horizon system)
{ {
+10 -1
View File
@@ -368,7 +368,16 @@ namespace Ryujinx.HLE.HOS.Services
_requestDataReader, _requestDataReader,
_responseDataWriter); _responseDataWriter);
GetSessionObj(serverSessionHandle).CallCmifMethod(context); try
{
GetSessionObj(serverSessionHandle).CallCmifMethod(context);
}
catch (Ryujinx.HLE.Exceptions.GuestBrokeExecutionException)
{
Logger.Warning?.Print(LogClass.Kernel, "Guest broke execution in service call. Thread exiting.");
_selfThread.Exit();
return false;
}
response.RawData = _responseDataStream.ToArray(); response.RawData = _responseDataStream.ToArray();
} }
@@ -334,8 +334,11 @@ namespace Ryujinx.HLE.Loaders.Processes
KResourceLimit resourceLimit = new(context); KResourceLimit resourceLimit = new(context);
long applicationRgSize = (long)context.MemoryManager.MemoryRegions[(int)MemoryRegion.Application].Size; long applicationRgSize = (long)context.MemoryManager.MemoryRegions[(int)MemoryRegion.Application].Size;
long memoryLimit = applicationRgSize + 12288 * 1024 * 1024L; // Add 12GB slack for System Resource and MapPhysicalMemory
result = resourceLimit.SetLimitValue(LimitableResource.Memory, applicationRgSize); Logger.Info?.Print(LogClass.Loader, $"Initializing process resource limit: ApplicationRegion={applicationRgSize / 1024 / 1024}MB, Total={memoryLimit / 1024 / 1024}MB");
result = resourceLimit.SetLimitValue(LimitableResource.Memory, memoryLimit);
if (result.IsSuccess) if (result.IsSuccess)
{ {
+4 -1
View File
@@ -18,6 +18,7 @@ namespace Ryujinx.HLE
static class MemoryConfigurationExtensions static class MemoryConfigurationExtensions
{ {
private const ulong GiB = 1024 * 1024 * 1024; private const ulong GiB = 1024 * 1024 * 1024;
private const ulong DRAMSlackSize = 12288 * 1024 * 1024UL;
#pragma warning disable IDE0055 // Disable formatting #pragma warning disable IDE0055 // Disable formatting
public static MemoryArrange ToKernelMemoryArrange(this MemoryConfiguration configuration) public static MemoryArrange ToKernelMemoryArrange(this MemoryConfiguration configuration)
@@ -54,7 +55,7 @@ namespace Ryujinx.HLE
public static ulong ToDramSize(this MemoryConfiguration configuration) public static ulong ToDramSize(this MemoryConfiguration configuration)
{ {
return configuration switch ulong size = configuration switch
{ {
MemoryConfiguration.MemoryConfiguration4GiB or MemoryConfiguration.MemoryConfiguration4GiB or
MemoryConfiguration.MemoryConfiguration4GiBAppletDev or MemoryConfiguration.MemoryConfiguration4GiBAppletDev or
@@ -66,6 +67,8 @@ namespace Ryujinx.HLE
MemoryConfiguration.MemoryConfiguration12GiB => 12 * GiB, MemoryConfiguration.MemoryConfiguration12GiB => 12 * GiB,
_ => throw new AggregateException($"Invalid memory configuration \"{configuration}\"."), _ => throw new AggregateException($"Invalid memory configuration \"{configuration}\"."),
}; };
return size + DRAMSlackSize;
} }
#pragma warning restore IDE0055 #pragma warning restore IDE0055
} }