diff --git a/.gitignore b/.gitignore index 37b419d07..45783cdf2 100644 --- a/.gitignore +++ b/.gitignore @@ -173,3 +173,6 @@ PublishProfiles/ # Glade backup files *.glade~ + +# Log files +/logs/ diff --git a/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/GameHost.kt b/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/GameHost.kt index 291375d53..8aa165ea1 100644 --- a/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/GameHost.kt +++ b/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/GameHost.kt @@ -169,6 +169,7 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su override fun surfaceDestroyed(holder: SurfaceHolder) { ghLog("surfaceDestroyed → shutdownBinding()") + try { KenjinxNative.detachWindow() } catch (_: Throwable) {} // Always bind and unbind (prevents leaks when swiping away tasks) shutdownBinding() // 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) if (visibility != android.view.View.VISIBLE) { ghLog("window not visible → shutdownBinding()") + try { KenjinxNative.detachWindow() } catch (_: Throwable) {} shutdownBinding() } } @@ -290,7 +292,12 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su } private fun runGame() { - KenjinxNative.graphicsRendererRunLoop() + mainViewModel.onEmulationStarted() + try { + KenjinxNative.graphicsRendererRunLoop() + } finally { + mainViewModel.onEmulationStopped() + } game?.close() } @@ -307,7 +314,7 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su try { if (emuBound && _startedViaService) { emuBinder?.stopEmulation { - try { KenjinxNative.deviceCloseEmulation() } catch (_: Throwable) {} + // Close is handled in the service hard-close path. } } } catch (_: Throwable) { } @@ -315,6 +322,12 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su // Fallback: Terminate local thread try { _updateThread?.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 shutdownBinding() @@ -488,11 +501,13 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su emuBinder?.startEmulation { try { + mainViewModel.onEmulationStarted() KenjinxNative.graphicsRendererRunLoop() } catch (t: Throwable) { Log.e("GameHost", "RunLoop crash in service", t) } finally { _startedViaService = false + mainViewModel.onEmulationStopped() } } ghLog("RunLoop started in EmulationService") diff --git a/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/KenjinxNative.kt b/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/KenjinxNative.kt index 43459ec7a..e9f0e7a2c 100644 --- a/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/KenjinxNative.kt +++ b/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/KenjinxNative.kt @@ -71,6 +71,7 @@ interface KenjinxNativeJna : Library { fun deviceReinitEmulation() fun deviceSignalEmulationClose() + fun detachWindow() // >>> Rendering-related additions for the toggle: fun deviceWaitForGpuDone(timeoutMs: Int) fun deviceRecreateSwapchain() @@ -279,10 +280,10 @@ object KenjinxNative : KenjinxNativeJna by jnaInstance { } catch (_: Throwable) {} } - @JvmStatic - fun detachWindow() { + override fun detachWindow() { try { graphicsSetPresentEnabled(false) } catch (_: Throwable) {} try { deviceWaitForGpuDone(100) } catch (_: Throwable) {} + try { jnaInstance.detachWindow() } catch (_: Throwable) {} try { deviceSetWindowHandle(0) } catch (_: Throwable) {} } diff --git a/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/MainActivity.kt b/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/MainActivity.kt index 3ad425c40..2066b717c 100644 --- a/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/MainActivity.kt +++ b/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/MainActivity.kt @@ -409,8 +409,8 @@ class MainActivity : BaseActivity() { setAudioForegroundState(false) if (MainActivity.mainViewModel?.rendererReady == true) { try { - KenjinxNative.graphicsSetPresentEnabled(false) - Log.d(TAG_FG, "present=DISABLED (onTrimMemory:$level)") + setPresentEnabled(false, "onTrimMemory:$level") + KenjinxNative.detachWindow() } catch (_: Throwable) {} } else { Log.d(TAG_FG, "skip disable present (onTrimMemory) — rendererReady=${MainActivity.mainViewModel?.rendererReady}") diff --git a/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/viewmodels/MainViewModel.kt b/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/viewmodels/MainViewModel.kt index d96b9726c..e8d725b3e 100644 --- a/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/viewmodels/MainViewModel.kt +++ b/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/viewmodels/MainViewModel.kt @@ -2,6 +2,7 @@ package org.kenjinx.android.viewmodels import android.annotation.SuppressLint import android.net.Uri +import android.os.SystemClock import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.navigation.NavHostController @@ -23,6 +24,7 @@ import org.kenjinx.android.KenjinxNative import org.kenjinx.android.PerformanceMonitor import org.kenjinx.android.SystemLanguage import org.kenjinx.android.UiHandler +import android.util.Log import java.io.File import java.util.TimeZone @@ -52,6 +54,8 @@ class MainViewModel(val activity: MainActivity) { private var showLoading: MutableState? = null private var refreshUser: MutableState? = null @Volatile var rendererReady: Boolean = false + @Volatile var emulationRunning: Boolean = false + @Volatile var emulationClosing: Boolean = false // Default Game Folder var defaultGameFolderUri: Uri? = null @@ -84,15 +88,47 @@ class MainViewModel(val activity: MainActivity) { } fun closeGame() { + emulationClosing = true KenjinxNative.deviceSignalEmulationClose() gameHost?.close() - KenjinxNative.deviceCloseEmulation() motionSensorManager?.unregister() physicalControllerManager?.disconnect() motionSensorManager?.setControllerId(-1) 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) ---- private fun loadSystemLanguage(): SystemLanguage { 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 { + if (emulationClosing || emulationRunning) { + if (!waitForEmulationStop(5000)) { + return 0 + } + } + KenjinxNative.deviceReinitEmulation() MainActivity.mainViewModel?.activity?.uiHandler = UiHandler() diff --git a/src/LibKenjinx/Android/JniExportedMethods.cs b/src/LibKenjinx/Android/JniExportedMethods.cs index f09fbf5e5..aafa65725 100644 --- a/src/LibKenjinx/Android/JniExportedMethods.cs +++ b/src/LibKenjinx/Android/JniExportedMethods.cs @@ -321,7 +321,7 @@ namespace LibKenjinx [UnmanagedCallersOnly(EntryPoint = "deviceReloadFilesystem")] public static void JnaReloadFileSystem() { - Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + Logger.Trace?.Print(LogClass.Application, "Jni Function Call: deviceReloadFilesystem"); SwitchDevice?.ReloadFileSystem(); } @@ -423,21 +423,21 @@ namespace LibKenjinx [UnmanagedCallersOnly(EntryPoint = "deviceSignalEmulationClose")] public static void JniSignalEmulationCloseNative() { - Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + Logger.Trace?.Print(LogClass.Application, "Jni Function Call: deviceSignalEmulationClose"); SignalEmulationClose(); } [UnmanagedCallersOnly(EntryPoint = "deviceCloseEmulation")] public static void JniCloseEmulationNative() { - Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + Logger.Trace?.Print(LogClass.Application, "Jni Function Call: deviceCloseEmulation"); CloseEmulation(); } [UnmanagedCallersOnly(EntryPoint = "deviceReinitEmulation")] public static void JniReinitEmulationNative() { - Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + Logger.Trace?.Print(LogClass.Application, "Jni Function Call: deviceReinitEmulation"); ReinitEmulation(); } @@ -923,6 +923,7 @@ namespace LibKenjinx { try { + Logger.Trace?.Print(LogClass.Application, "[JNI] deviceRecreateSwapchain"); if (Renderer?.Window == null) { Logger.Warning?.Print(LogClass.Application, "[JNI] deviceRecreateSwapchain: Renderer.Window == null"); @@ -995,6 +996,7 @@ namespace LibKenjinx { try { + Logger.Trace?.Print(LogClass.Application, "[JNI] graphicsRendererRecreateSurface"); _ = (Renderer as VulkanRenderer)?.RecreateSurface(); } 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 [UnmanagedCallersOnly(EntryPoint = "reattachWindowIfReady")] public static bool JniReattachWindowIfReady() diff --git a/src/LibKenjinx/LibKenjinx.Device.cs b/src/LibKenjinx/LibKenjinx.Device.cs index 2b9facca4..4ccdc242b 100644 --- a/src/LibKenjinx/LibKenjinx.Device.cs +++ b/src/LibKenjinx/LibKenjinx.Device.cs @@ -212,12 +212,12 @@ namespace LibKenjinx _touchScreenManager?.Dispose(); _touchScreenManager = null; - _gpuDoneEvent.WaitOne(3000); - _gpuDoneEvent.Dispose(); + _gpuDoneEvent?.WaitOne(3000); + _gpuDoneEvent?.Dispose(); _gpuDoneEvent = null; - _gpuCancellationTokenSource.Cancel(); - _gpuCancellationTokenSource.Dispose(); + _gpuCancellationTokenSource?.Cancel(); + _gpuCancellationTokenSource?.Dispose(); _gpuCancellationTokenSource = null; SwitchDevice.Dispose(); diff --git a/src/LibKenjinx/LibKenjinx.Native.cs b/src/LibKenjinx/LibKenjinx.Native.cs index 356dd2e04..037b1da25 100644 --- a/src/LibKenjinx/LibKenjinx.Native.cs +++ b/src/LibKenjinx/LibKenjinx.Native.cs @@ -13,24 +13,11 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +#if !ANDROID namespace LibKenjinx { public static partial class LibKenjinx { - private unsafe static IntPtr CreateStringArray(List strings) - { - uint size = (uint)(Marshal.SizeOf() * (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")] public static bool InitializeDeviceNative(MemoryManagerMode memoryManagerMode, bool useHypervisor, @@ -469,28 +456,6 @@ namespace LibKenjinx 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) [UnmanagedCallersOnly(EntryPoint = "graphicsSetFullscreenStretch")] public static void GraphicsSetFullscreenStretchNativeAlias(bool enable) @@ -499,3 +464,4 @@ namespace LibKenjinx } } } +#endif diff --git a/src/LibKenjinx/LibKenjinx.NativeHelpers.cs b/src/LibKenjinx/LibKenjinx.NativeHelpers.cs new file mode 100644 index 000000000..52ff4e5a7 --- /dev/null +++ b/src/LibKenjinx/LibKenjinx.NativeHelpers.cs @@ -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 strings) + { + uint size = (uint)(Marshal.SizeOf() * (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 { } + } + } + } +} diff --git a/src/LibKenjinx/LibKenjinx.cs b/src/LibKenjinx/LibKenjinx.cs index 4933cae50..4fde167e8 100644 --- a/src/LibKenjinx/LibKenjinx.cs +++ b/src/LibKenjinx/LibKenjinx.cs @@ -75,6 +75,11 @@ namespace LibKenjinx Logger.Notice.Print(LogClass.Application, "Initializing..."); 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(); SwitchDevice = new SwitchDevice(AndroidFileSystem); } @@ -751,6 +756,7 @@ namespace LibKenjinx { private readonly SystemVersion _firmwareVersion; + private int _contextDisposeState; public VirtualFileSystem VirtualFileSystem { get; set; } public ContentManager ContentManager { get; set; } public AccountManager AccountManager { get; set; } @@ -764,10 +770,17 @@ namespace LibKenjinx public bool EnableFsIntegrityChecks { get; set; } - internal void DisposeContext() + private void DisposeEmulationContext() { - if (EmulationContext == null) + if (System.Threading.Interlocked.Exchange(ref _contextDisposeState, 1) != 0) + { return; + } + + if (EmulationContext == null) + { + return; + } Logger.Info?.Print(LogClass.Application, "Disposing EmulationContext"); @@ -779,25 +792,21 @@ namespace LibKenjinx } 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() { GC.SuppressFinalize(this); Logger.Info?.Print(LogClass.Application, "Disposing SwitchDevice"); - try - { - EmulationContext?.Dispose(); - EmulationContext?.DisposeGpu(); - EmulationContext = null; - } - catch (Exception ex) - { - Logger.Error?.Print(LogClass.Application, $"Error disposing EmulationContext: {ex.Message}"); - } + DisposeEmulationContext(); try { diff --git a/src/LibKenjinx/LibKenjinx.csproj b/src/LibKenjinx/LibKenjinx.csproj index d57bc7d10..006625cb4 100644 --- a/src/LibKenjinx/LibKenjinx.csproj +++ b/src/LibKenjinx/LibKenjinx.csproj @@ -5,6 +5,12 @@ lld $(DefineConstants);FORCE_EXTERNAL_BASE_DIR + + $(DefineConstants);ANDROID + + + + true true diff --git a/src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs b/src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs index 3a694db34..7341ccd39 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs @@ -47,7 +47,7 @@ namespace Ryujinx.Graphics.Gpu.Image class AutoDeleteCache : IEnumerable { private const int MinCountForDeletion = 32; - private const int MaxCapacity = 2048; + private const int MaxCapacity = 1024; private const ulong MiB = 1024 * 1024; private const ulong GiB = 1024 * 1024 * 1024; private ulong MaxTextureSizeCapacity = 4 * GiB; @@ -59,7 +59,7 @@ namespace Ryujinx.Graphics.Gpu.Image private const ulong TextureSizeCapacity10GiB = 10 * GiB; private const ulong TextureSizeCapacity12GiB = 12 * GiB; - private const float MemoryScaleFactor = 0.50f; + private const float MemoryScaleFactor = 0.30f; private ulong _maxCacheMemoryUsage = DefaultTextureSizeCapacity; private readonly LinkedList _textures; @@ -90,10 +90,10 @@ namespace Ryujinx.Graphics.Gpu.Image < 6 when MaximumGpuMemoryGiB < 6 || context.Capabilities.MaximumGpuMemory == 0 => DefaultTextureSizeCapacity, < 6 => TextureSizeCapacity4GiB, - 6 => TextureSizeCapacity6GiB, - 8 => TextureSizeCapacity8GiB, - 10 => TextureSizeCapacity10GiB, - _ => TextureSizeCapacity12GiB + 6 => TextureSizeCapacity4GiB, + 8 => TextureSizeCapacity6GiB, + 10 => TextureSizeCapacity6GiB, + _ => TextureSizeCapacity8GiB }; var cacheMemory = (ulong)(context.Capabilities.MaximumGpuMemory * MemoryScaleFactor); diff --git a/src/Ryujinx.Graphics.Gpu/Synchronization/SynchronizationManager.cs b/src/Ryujinx.Graphics.Gpu/Synchronization/SynchronizationManager.cs index f36e74107..fe3dbfa73 100644 --- a/src/Ryujinx.Graphics.Gpu/Synchronization/SynchronizationManager.cs +++ b/src/Ryujinx.Graphics.Gpu/Synchronization/SynchronizationManager.cs @@ -83,7 +83,7 @@ namespace Ryujinx.Graphics.Gpu.Synchronization // TODO: Remove this when GPU channel scheduling will be implemented. if (timeout == Timeout.InfiniteTimeSpan) { - timeout = TimeSpan.FromSeconds(1); + timeout = TimeSpan.FromSeconds(30); } using ManualResetEvent waitEvent = new(false); @@ -98,9 +98,13 @@ namespace Ryujinx.Graphics.Gpu.Synchronization 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); + + // Give the GPU some time to recover if it's struggling. + Thread.Sleep(100); } return !signaled; diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineState.cs b/src/Ryujinx.Graphics.Vulkan/PipelineState.cs index eed783930..a4e571b4f 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineState.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineState.cs @@ -2,6 +2,7 @@ using Ryujinx.Common.Memory; using Silk.NET.Vulkan; using System; using System.Numerics; +using System.Threading; namespace Ryujinx.Graphics.Vulkan { @@ -9,6 +10,8 @@ namespace Ryujinx.Graphics.Vulkan { private const int MaxDynamicStatesCount = 9; + private static readonly Lock _pipelineCreateLock = new(); + public PipelineUid Internal; public float LineWidth @@ -367,7 +370,10 @@ namespace Ryujinx.Graphics.Vulkan 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(new DisposablePipeline(gd.Api, device, pipelineHandle)); @@ -635,7 +641,12 @@ namespace Ryujinx.Graphics.Vulkan 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) { diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs b/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs index 541a703bf..e9899d4d6 100644 --- a/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs +++ b/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs @@ -4,9 +4,11 @@ using Ryujinx.Graphics.GAL; using Silk.NET.Vulkan; using Silk.NET.Vulkan.Extensions.EXT; using Silk.NET.Vulkan.Extensions.KHR; +using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; +using System.Text.RegularExpressions; namespace Ryujinx.Graphics.Vulkan { @@ -291,6 +293,61 @@ namespace Ryujinx.Graphics.Vulkan 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() { SType = StructureType.PhysicalDeviceFeatures2, @@ -382,6 +439,28 @@ namespace Ryujinx.Graphics.Vulkan 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() { SType = StructureType.PhysicalDeviceVulkan12Features, @@ -422,49 +501,47 @@ namespace Ryujinx.Graphics.Vulkan 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)) { - featuresTransformFeedback = new PhysicalDeviceTransformFeedbackFeaturesEXT - { - SType = StructureType.PhysicalDeviceTransformFeedbackFeaturesExt, - PNext = pExtendedFeatures, - TransformFeedback = supportedFeaturesTransformFeedback.TransformFeedback, - }; + featuresTransformFeedback.PNext = pExtendedFeatures; + featuresTransformFeedback.TransformFeedback = supportedFeaturesTransformFeedback.TransformFeedback; pExtendedFeatures = &featuresTransformFeedback; } - PhysicalDevicePrimitiveTopologyListRestartFeaturesEXT featuresPrimitiveTopologyListRestart; - if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_primitive_topology_list_restart")) { - featuresPrimitiveTopologyListRestart = new PhysicalDevicePrimitiveTopologyListRestartFeaturesEXT - { - SType = StructureType.PhysicalDevicePrimitiveTopologyListRestartFeaturesExt, - PNext = pExtendedFeatures, - PrimitiveTopologyListRestart = supportedFeaturesPrimitiveTopologyListRestart.PrimitiveTopologyListRestart, - PrimitiveTopologyPatchListRestart = supportedFeaturesPrimitiveTopologyListRestart.PrimitiveTopologyPatchListRestart, - }; + featuresPrimitiveTopologyListRestart.PNext = pExtendedFeatures; + featuresPrimitiveTopologyListRestart.PrimitiveTopologyListRestart = supportedFeaturesPrimitiveTopologyListRestart.PrimitiveTopologyListRestart; + featuresPrimitiveTopologyListRestart.PrimitiveTopologyPatchListRestart = supportedFeaturesPrimitiveTopologyListRestart.PrimitiveTopologyPatchListRestart; pExtendedFeatures = &featuresPrimitiveTopologyListRestart; } - PhysicalDeviceRobustness2FeaturesEXT featuresRobustness2; - if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_robustness2")) { - featuresRobustness2 = new PhysicalDeviceRobustness2FeaturesEXT - { - SType = StructureType.PhysicalDeviceRobustness2FeaturesExt, - PNext = pExtendedFeatures, - NullDescriptor = supportedFeaturesRobustness2.NullDescriptor, - }; + featuresRobustness2.PNext = pExtendedFeatures; + featuresRobustness2.NullDescriptor = supportedFeaturesRobustness2.NullDescriptor; pExtendedFeatures = &featuresRobustness2; } + if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_vertex_input_dynamic_state")) + { + featuresVertexInputDynamicState.PNext = pExtendedFeatures; + featuresVertexInputDynamicState.VertexInputDynamicState = supportedFeaturesVertexInputDynamicState.VertexInputDynamicState; + + pExtendedFeatures = &featuresVertexInputDynamicState; + } + var featuresExtendedDynamicState = new PhysicalDeviceExtendedDynamicStateFeaturesEXT { SType = StructureType.PhysicalDeviceExtendedDynamicStateFeaturesExt, @@ -479,6 +556,10 @@ namespace Ryujinx.Graphics.Vulkan SType = StructureType.PhysicalDeviceVulkan11Features, PNext = pExtendedFeatures, ShaderDrawParameters = supportedFeaturesVk11.ShaderDrawParameters, + StorageBuffer16BitAccess = supportedFeaturesVk11.StorageBuffer16BitAccess, + UniformAndStorageBuffer16BitAccess = supportedFeaturesVk11.UniformAndStorageBuffer16BitAccess, + StoragePushConstant16 = supportedFeaturesVk11.StoragePushConstant16, + StorageInputOutput16 = supportedFeaturesVk11.StorageInputOutput16, }; pExtendedFeatures = &featuresVk11; @@ -492,107 +573,132 @@ namespace Ryujinx.Graphics.Vulkan UniformBufferStandardLayout = supportedPhysicalDeviceVulkan12Features.UniformBufferStandardLayout, UniformAndStorageBuffer8BitAccess = supportedPhysicalDeviceVulkan12Features.UniformAndStorageBuffer8BitAccess, StorageBuffer8BitAccess = supportedPhysicalDeviceVulkan12Features.StorageBuffer8BitAccess, + StoragePushConstant8 = supportedPhysicalDeviceVulkan12Features.StoragePushConstant8, }; pExtendedFeatures = &featuresVk12; - PhysicalDeviceIndexTypeUint8FeaturesEXT featuresIndexU8; - if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_index_type_uint8")) { - featuresIndexU8 = new PhysicalDeviceIndexTypeUint8FeaturesEXT - { - SType = StructureType.PhysicalDeviceIndexTypeUint8FeaturesExt, - PNext = pExtendedFeatures, - IndexTypeUint8 = true, - }; + featuresIndexU8.PNext = pExtendedFeatures; + featuresIndexU8.IndexTypeUint8 = true; pExtendedFeatures = &featuresIndexU8; } - PhysicalDeviceFragmentShaderInterlockFeaturesEXT featuresFragmentShaderInterlock; - if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_fragment_shader_interlock")) { - featuresFragmentShaderInterlock = new PhysicalDeviceFragmentShaderInterlockFeaturesEXT - { - SType = StructureType.PhysicalDeviceFragmentShaderInterlockFeaturesExt, - PNext = pExtendedFeatures, - FragmentShaderPixelInterlock = true, - }; + featuresFragmentShaderInterlock.PNext = pExtendedFeatures; + featuresFragmentShaderInterlock.FragmentShaderPixelInterlock = true; pExtendedFeatures = &featuresFragmentShaderInterlock; } - PhysicalDeviceCustomBorderColorFeaturesEXT featuresCustomBorderColor; - if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_custom_border_color") && supportedFeaturesCustomBorderColor.CustomBorderColors && supportedFeaturesCustomBorderColor.CustomBorderColorWithoutFormat) { - featuresCustomBorderColor = new PhysicalDeviceCustomBorderColorFeaturesEXT - { - SType = StructureType.PhysicalDeviceCustomBorderColorFeaturesExt, - PNext = pExtendedFeatures, - CustomBorderColors = true, - CustomBorderColorWithoutFormat = true, - }; + featuresCustomBorderColor.PNext = pExtendedFeatures; + featuresCustomBorderColor.CustomBorderColors = true; + featuresCustomBorderColor.CustomBorderColorWithoutFormat = true; pExtendedFeatures = &featuresCustomBorderColor; } - PhysicalDeviceDepthClipControlFeaturesEXT featuresDepthClipControl; - if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_depth_clip_control") && supportedFeaturesDepthClipControl.DepthClipControl) { - featuresDepthClipControl = new PhysicalDeviceDepthClipControlFeaturesEXT - { - SType = StructureType.PhysicalDeviceDepthClipControlFeaturesExt, - PNext = pExtendedFeatures, - DepthClipControl = true, - }; + featuresDepthClipControl.PNext = pExtendedFeatures; + featuresDepthClipControl.DepthClipControl = true; pExtendedFeatures = &featuresDepthClipControl; } - PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT featuresAttachmentFeedbackLoopLayout; - if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_layout") && supportedFeaturesAttachmentFeedbackLoopLayout.AttachmentFeedbackLoopLayout) { - featuresAttachmentFeedbackLoopLayout = new() - { - SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesExt, - PNext = pExtendedFeatures, - AttachmentFeedbackLoopLayout = true, - }; + featuresAttachmentFeedbackLoop.PNext = pExtendedFeatures; + featuresAttachmentFeedbackLoop.AttachmentFeedbackLoopLayout = true; - pExtendedFeatures = &featuresAttachmentFeedbackLoopLayout; + pExtendedFeatures = &featuresAttachmentFeedbackLoop; } - PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT featuresDynamicAttachmentFeedbackLoopLayout; - if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_dynamic_state") && supportedFeaturesDynamicAttachmentFeedbackLoopLayout.AttachmentFeedbackLoopDynamicState) { - featuresDynamicAttachmentFeedbackLoopLayout = new() - { - SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesExt, - PNext = pExtendedFeatures, - AttachmentFeedbackLoopDynamicState = true, - }; + featuresDynamicAttachmentFeedbackLoop.PNext = pExtendedFeatures; + featuresDynamicAttachmentFeedbackLoop.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]; - - for (int i = 0; i < enabledExtensions.Length; i++) + if (VendorUtils.FromId(physicalDevice.PhysicalDeviceProperties.VendorID) == Vendor.Qualcomm && + (physicalDevice.DeviceName.Contains("Adreno") && Regex.IsMatch(physicalDevice.DeviceName, @"8[3-9]\d"))) { - 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 @@ -602,13 +708,13 @@ namespace Ryujinx.Graphics.Vulkan QueueCreateInfoCount = 1, PQueueCreateInfos = &queueCreateInfo, PpEnabledExtensionNames = (byte**)ppEnabledExtensions, - EnabledExtensionCount = (uint)enabledExtensions.Length, + EnabledExtensionCount = (uint)finalEnabledExtensions.Length, PEnabledFeatures = &features, }; 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]); } diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs index bff8b6ac8..9419b0825 100644 --- a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs +++ b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs @@ -11,6 +11,7 @@ using Silk.NET.Vulkan.Extensions.KHR; using System; using System.Collections.Generic; using System.Runtime.InteropServices; +using System.Text.RegularExpressions; using System.Threading; using Format = Ryujinx.Graphics.GAL.Format; using PrimitiveTopology = Ryujinx.Graphics.GAL.PrimitiveTopology; @@ -414,39 +415,41 @@ namespace Ryujinx.Graphics.Vulkan properties.Limits.FramebufferDepthSampleCounts & properties.Limits.FramebufferStencilSampleCounts; + bool isAdreno8xx = Vendor == Vendor.Qualcomm && (GpuRenderer.Contains("Adreno") && Regex.IsMatch(GpuRenderer, @"8[3-9]\d")); + Capabilities = new HardwareCapabilities( - _physicalDevice.IsDeviceExtensionPresent("VK_EXT_index_type_uint8"), - supportsCustomBorderColor, + _physicalDevice.IsDeviceExtensionPresent("VK_EXT_index_type_uint8") && !isAdreno8xx, + supportsCustomBorderColor && !isAdreno8xx, supportsBlendOperationAdvanced, propertiesBlendOperationAdvanced.AdvancedBlendCorrelatedOverlap, propertiesBlendOperationAdvanced.AdvancedBlendNonPremultipliedSrcColor, propertiesBlendOperationAdvanced.AdvancedBlendNonPremultipliedDstColor, _physicalDevice.IsDeviceExtensionPresent(KhrDrawIndirectCount.ExtensionName), - _physicalDevice.IsDeviceExtensionPresent("VK_EXT_fragment_shader_interlock"), - _physicalDevice.IsDeviceExtensionPresent("VK_NV_geometry_shader_passthrough"), + _physicalDevice.IsDeviceExtensionPresent("VK_EXT_fragment_shader_interlock") && !isAdreno8xx, + _physicalDevice.IsDeviceExtensionPresent("VK_NV_geometry_shader_passthrough") && !isAdreno8xx, features2.Features.ShaderFloat64, - featuresShaderInt8.ShaderInt8, - _physicalDevice.IsDeviceExtensionPresent("VK_EXT_shader_stencil_export"), + featuresShaderInt8.ShaderInt8 && !isAdreno8xx, + _physicalDevice.IsDeviceExtensionPresent("VK_EXT_shader_stencil_export") && !isAdreno8xx, features2.Features.ShaderStorageImageMultisample, _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 - featuresRobustness2.NullDescriptor || IsMoltenVk, + (featuresRobustness2.NullDescriptor || IsMoltenVk) && !isAdreno8xx, supportsPushDescriptors && !IsMoltenVk, propertiesPushDescriptor.MaxPushDescriptors, - featuresPrimitiveTopologyListRestart.PrimitiveTopologyListRestart, - featuresPrimitiveTopologyListRestart.PrimitiveTopologyPatchListRestart, - supportsTransformFeedback, + featuresPrimitiveTopologyListRestart.PrimitiveTopologyListRestart && !isAdreno8xx, + featuresPrimitiveTopologyListRestart.PrimitiveTopologyPatchListRestart && !isAdreno8xx, + supportsTransformFeedback && !isAdreno8xx, propertiesTransformFeedback.TransformFeedbackQueries, features2.Features.OcclusionQueryPrecise, _physicalDevice.PhysicalDeviceFeatures.PipelineStatisticsQuery, - _physicalDevice.PhysicalDeviceFeatures.GeometryShader, - _physicalDevice.PhysicalDeviceFeatures.TessellationShader, + _physicalDevice.PhysicalDeviceFeatures.GeometryShader && !isAdreno8xx, + _physicalDevice.PhysicalDeviceFeatures.TessellationShader && !isAdreno8xx, _physicalDevice.IsDeviceExtensionPresent("VK_NV_viewport_array2"), - _physicalDevice.IsDeviceExtensionPresent(ExtExternalMemoryHost.ExtensionName), + _physicalDevice.IsDeviceExtensionPresent(ExtExternalMemoryHost.ExtensionName) && !isAdreno8xx, supportsDepthClipControl && featuresDepthClipControl.DepthClipControl, - supportsAttachmentFeedbackLoop && featuresAttachmentFeedbackLoop.AttachmentFeedbackLoopLayout, - supportsDynamicAttachmentFeedbackLoop && featuresDynamicAttachmentFeedbackLoop.AttachmentFeedbackLoopDynamicState, + supportsAttachmentFeedbackLoop && featuresAttachmentFeedbackLoop.AttachmentFeedbackLoopLayout && !isAdreno8xx, + supportsDynamicAttachmentFeedbackLoop && featuresDynamicAttachmentFeedbackLoop.AttachmentFeedbackLoopDynamicState && !isAdreno8xx, propertiesSubgroup.SubgroupSize, supportedSampleCounts, portabilityFlags, @@ -744,6 +747,8 @@ namespace Ryujinx.Graphics.Vulkan SystemMemoryType.DedicatedMemory; } + bool isAdreno8xx = Vendor == Vendor.Qualcomm && (GpuRenderer.Contains("Adreno") && Regex.IsMatch(GpuRenderer, @"8[3-9]\d")); + return new Capabilities( api: TargetApi.Vulkan, GpuVendor, @@ -766,13 +771,13 @@ namespace Ryujinx.Graphics.Vulkan supports5BitComponentFormat: supports5BitComponentFormat, supportsSparseBuffer: features2.Features.SparseBinding && mainQueueProperties.QueueFlags.HasFlag(QueueFlags.SparseBindingBit), supportsBlendEquationAdvanced: Capabilities.SupportsBlendEquationAdvanced, - supportsFragmentShaderInterlock: Capabilities.SupportsFragmentShaderInterlock, + supportsFragmentShaderInterlock: Capabilities.SupportsFragmentShaderInterlock && !isAdreno8xx, supportsFragmentShaderOrderingIntel: false, - supportsGeometryShader: Capabilities.SupportsGeometryShader, - supportsGeometryShaderPassthrough: Capabilities.SupportsGeometryShaderPassthrough, - supportsTransformFeedback: Capabilities.SupportsTransformFeedback, + supportsGeometryShader: Capabilities.SupportsGeometryShader && !isAdreno8xx, + supportsGeometryShaderPassthrough: Capabilities.SupportsGeometryShaderPassthrough && !isAdreno8xx, + supportsTransformFeedback: Capabilities.SupportsTransformFeedback && !isAdreno8xx, supportsImageLoadFormatted: features2.Features.ShaderStorageImageReadWithoutFormat, - supportsLayerVertexTessellation: featuresVk12.ShaderOutputLayer, + supportsLayerVertexTessellation: featuresVk12.ShaderOutputLayer && !isAdreno8xx, supportsMismatchingViewFormat: true, supportsCubemapView: !IsAmdGcn, supportsNonConstantTextureOffset: false, @@ -784,7 +789,7 @@ namespace Ryujinx.Graphics.Vulkan supportsTextureGatherOffsets: features2.Features.ShaderImageGatherExtended && !IsMoltenVk, supportsTextureShadowLod: false, supportsVertexStoreAndAtomics: features2.Features.VertexPipelineStoresAndAtomics, - supportsViewportIndexVertexTessellation: featuresVk12.ShaderOutputViewportIndex, + supportsViewportIndexVertexTessellation: featuresVk12.ShaderOutputViewportIndex && !isAdreno8xx, supportsViewportMask: Capabilities.SupportsViewportArray2, supportsViewportSwizzle: false, supportsIndirectParameters: true, @@ -1062,11 +1067,16 @@ namespace Ryujinx.Graphics.Vulkan { lock (SurfaceLock) { + if (!_initialized) + { + return; + } + try { ( _window as Window )?.SetSurfaceQueryAllowed(false); - if (_surface.Handle != 0) + if (_surface.Handle != 0 && SurfaceApi != null && _instance != null) { SurfaceApi.DestroySurface(_instance.Instance, _surface, null); _surface = new SurfaceKHR(0); @@ -1077,7 +1087,14 @@ namespace Ryujinx.Graphics.Vulkan // 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; + if (!_initialized) + { + return; + } + if (!enabled) { - ( _window as Window )?.SetSurfaceQueryAllowed(false); - ReleaseSurface(); + try + { + ( _window as Window )?.SetSurfaceQueryAllowed(false); + ReleaseSurface(); + } + catch + { + // ignore shutdown path exceptions + } } else { - _ = RecreateSurface(); + try + { + _ = RecreateSurface(); + } + catch + { + // ignore resume path exceptions + } } } diff --git a/src/Ryujinx.HLE/HOS/Kernel/Common/KResourceLimit.cs b/src/Ryujinx.HLE/HOS/Kernel/Common/KResourceLimit.cs index 46882a46d..dc574a533 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Common/KResourceLimit.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Common/KResourceLimit.cs @@ -1,4 +1,5 @@ using Ryujinx.Common; +using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.Horizon.Common; using System.Collections.Generic; @@ -52,8 +53,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Common 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; } @@ -87,6 +89,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Common 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; diff --git a/src/Ryujinx.HLE/HOS/Kernel/Common/KernelInit.cs b/src/Ryujinx.HLE/HOS/Kernel/Common/KernelInit.cs index 53ceb5b91..b7058935d 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Common/KernelInit.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Common/KernelInit.cs @@ -1,3 +1,4 @@ +using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Kernel.Memory; using Ryujinx.Horizon.Common; using System; @@ -31,8 +32,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Common } 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.Event, 700)); EnsureSuccess(resourceLimit.SetLimitValue(LimitableResource.TransferMemory, 200)); @@ -47,8 +51,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Common public static KMemoryRegionManager[] GetMemoryRegions(MemorySize size, MemoryArrange arrange) { - ulong poolEnd = KSystemControl.GetDramEndAddress(size); - ulong applicationPoolSize = KSystemControl.GetApplicationPoolSize(arrange); + ulong dramSize = KSystemControl.GetDramSize(size); + ulong slack = 12288 * 1024 * 1024UL; + ulong poolEnd = DramMemoryMap.DramBase + dramSize + slack; + + ulong applicationPoolSize = KSystemControl.GetApplicationPoolSize(arrange) + slack; ulong appletPoolSize = KSystemControl.GetAppletPoolSize(arrange); MemoryRegion servicePool; diff --git a/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs index 987e8cadb..a7af9dae6 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs @@ -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, + $"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(); // TODO: Debug events. currentThread.Owner.TerminateCurrentProcess(); - - throw new GuestBrokeExecutionException(); + currentThread.Exit(); + return; } else { diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs index 8896e5e79..e044b9a21 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs @@ -1263,17 +1263,30 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading _schedulerWaitEvent.Wait(); KernelStatic.SetKernelContext(KernelContext, this); - if (_customThreadStart != null) + try { - _customThreadStart(); + if (_customThreadStart != null) + { + _customThreadStart(); - // Ensure that anything trying to join the HLE thread is unblocked. - Exit(); - HandlePostSyscall(); + // Ensure that anything trying to join the HLE thread is unblocked. + Exit(); + 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(); diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/NvHostAsGpuDeviceFile.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/NvHostAsGpuDeviceFile.cs index d2068290f..3b0b47964 100644 --- a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/NvHostAsGpuDeviceFile.cs +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/NvHostAsGpuDeviceFile.cs @@ -398,6 +398,9 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu return NvInternalResult.Success; } - public override void Close() { } + public override void Close() + { + _asContext.Close(); + } } } diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceContext.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceContext.cs index 9dd52e6da..36530f89e 100644 --- a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceContext.cs +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceContext.cs @@ -123,6 +123,17 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types 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 list, ulong address) { int left = 0; diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostEvent.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostEvent.cs index 48622a224..26a6ff679 100644 --- a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostEvent.cs +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostEvent.cs @@ -32,7 +32,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl /// Max failing count until waiting on CPU. /// FIXME: This seems enough for most of the cases, reduce if needed. /// - private const uint FailingCountMax = 2; + private const uint FailingCountMax = 10; public NvHostEvent(NvHostSyncpt syncpointManager, uint eventId, Horizon system) { diff --git a/src/Ryujinx.HLE/HOS/Services/ServerBase.cs b/src/Ryujinx.HLE/HOS/Services/ServerBase.cs index 838612507..2af1ef20a 100644 --- a/src/Ryujinx.HLE/HOS/Services/ServerBase.cs +++ b/src/Ryujinx.HLE/HOS/Services/ServerBase.cs @@ -368,7 +368,16 @@ namespace Ryujinx.HLE.HOS.Services _requestDataReader, _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(); } diff --git a/src/Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs b/src/Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs index 61a05be3d..f58edcabf 100644 --- a/src/Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs +++ b/src/Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs @@ -334,8 +334,11 @@ namespace Ryujinx.HLE.Loaders.Processes KResourceLimit resourceLimit = new(context); 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) { diff --git a/src/Ryujinx.HLE/MemoryConfiguration.cs b/src/Ryujinx.HLE/MemoryConfiguration.cs index 35b9b3663..0ee92346b 100644 --- a/src/Ryujinx.HLE/MemoryConfiguration.cs +++ b/src/Ryujinx.HLE/MemoryConfiguration.cs @@ -18,6 +18,7 @@ namespace Ryujinx.HLE static class MemoryConfigurationExtensions { private const ulong GiB = 1024 * 1024 * 1024; + private const ulong DRAMSlackSize = 12288 * 1024 * 1024UL; #pragma warning disable IDE0055 // Disable formatting public static MemoryArrange ToKernelMemoryArrange(this MemoryConfiguration configuration) @@ -54,7 +55,7 @@ namespace Ryujinx.HLE public static ulong ToDramSize(this MemoryConfiguration configuration) { - return configuration switch + ulong size = configuration switch { MemoryConfiguration.MemoryConfiguration4GiB or MemoryConfiguration.MemoryConfiguration4GiBAppletDev or @@ -66,6 +67,8 @@ namespace Ryujinx.HLE MemoryConfiguration.MemoryConfiguration12GiB => 12 * GiB, _ => throw new AggregateException($"Invalid memory configuration \"{configuration}\"."), }; + + return size + DRAMSlackSize; } #pragma warning restore IDE0055 }