mirror of
https://git.ryujinx.app/projects/Kenji-NX.git
synced 2026-10-07 18:51:04 +02:00
Snapdragon 8 Elite (& Adreno 8xx) Fixes
See merge request kenji-nx/ryujinx!20
This commit is contained in:
@@ -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}")
|
||||||
|
|||||||
+43
-1
@@ -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()
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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 { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user