mirror of
https://git.743378673.xyz/MeloNX/MeloNX.git
synced 2025-07-16 12:16:29 +02:00
Rewrite Display Code and more
This commit is contained in:
parent
e372f6eb35
commit
8ca88def54
40 changed files with 1804 additions and 582 deletions
173
src/Ryujinx.Headless.SDL2/MoltenVK/MoltenVKWindow.cs
Normal file
173
src/Ryujinx.Headless.SDL2/MoltenVK/MoltenVKWindow.cs
Normal file
|
@ -0,0 +1,173 @@
|
|||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Input.HLE;
|
||||
using Ryujinx.SDL2.Common;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using static SDL2.SDL;
|
||||
using Silk.NET.Vulkan;
|
||||
using Silk.NET.Vulkan.Extensions.EXT;
|
||||
using Silk.NET.Vulkan.Extensions.KHR;
|
||||
|
||||
namespace Ryujinx.Headless.SDL2.Vulkan
|
||||
{
|
||||
class MoltenVKWindow : WindowBase
|
||||
{
|
||||
public IntPtr nativeMetalLayer = IntPtr.Zero;
|
||||
|
||||
private Vk _vk;
|
||||
private ExtMetalSurface _metalSurface;
|
||||
private SurfaceKHR _surface;
|
||||
private bool _surfaceCreated;
|
||||
|
||||
public MoltenVKWindow(
|
||||
InputManager inputManager,
|
||||
GraphicsDebugLevel glLogLevel,
|
||||
AspectRatio aspectRatio,
|
||||
bool enableMouse,
|
||||
HideCursorMode hideCursorMode) : base(inputManager, glLogLevel, aspectRatio, enableMouse, hideCursorMode)
|
||||
{
|
||||
_vk = Vk.GetApi();
|
||||
_surfaceCreated = false;
|
||||
}
|
||||
|
||||
public override SDL_WindowFlags GetWindowFlags() => SDL_WindowFlags.SDL_WINDOW_VULKAN;
|
||||
|
||||
protected override void InitializeWindowRenderer() {}
|
||||
|
||||
protected override void InitializeRenderer()
|
||||
{
|
||||
if (IsExclusiveFullscreen)
|
||||
{
|
||||
Renderer?.Window.SetSize(ExclusiveFullscreenWidth, ExclusiveFullscreenHeight);
|
||||
MouseDriver.SetClientSize(ExclusiveFullscreenWidth, ExclusiveFullscreenHeight);
|
||||
}
|
||||
else
|
||||
{
|
||||
Renderer?.Window.SetSize(DefaultWidth, DefaultHeight);
|
||||
MouseDriver.SetClientSize(DefaultWidth, DefaultHeight);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetNativeWindow(IntPtr metalLayer)
|
||||
{
|
||||
if (metalLayer == IntPtr.Zero)
|
||||
{
|
||||
return;
|
||||
}
|
||||
nativeMetalLayer = IntPtr.Zero;
|
||||
nativeMetalLayer = metalLayer;
|
||||
}
|
||||
|
||||
private static void BasicInvoke(Action action)
|
||||
{
|
||||
action();
|
||||
}
|
||||
|
||||
public unsafe IntPtr CreateWindowSurface(IntPtr instanceHandle)
|
||||
{
|
||||
if (_surfaceCreated)
|
||||
{
|
||||
return (IntPtr)(ulong)_surface.Handle;
|
||||
}
|
||||
|
||||
if (nativeMetalLayer == IntPtr.Zero)
|
||||
{
|
||||
throw new Exception("Cannot create Vulkan surface: No CAMetalLayer set");
|
||||
}
|
||||
|
||||
var instance = new Instance((nint)instanceHandle);
|
||||
if (!_vk.TryGetInstanceExtension(instance, out _metalSurface))
|
||||
{
|
||||
throw new Exception("Failed to get ExtMetalSurface extension");
|
||||
}
|
||||
|
||||
var createInfo = new MetalSurfaceCreateInfoEXT
|
||||
{
|
||||
SType = StructureType.MetalSurfaceCreateInfoExt,
|
||||
PNext = null,
|
||||
PLayer = (nint*)nativeMetalLayer
|
||||
};
|
||||
|
||||
SurfaceKHR* surfacePtr = stackalloc SurfaceKHR[1];
|
||||
Result result = _metalSurface.CreateMetalSurface(instance, &createInfo, null, surfacePtr);
|
||||
if (result != Result.Success)
|
||||
{
|
||||
throw new Exception($"vkCreateMetalSurfaceEXT failed with error code {result}");
|
||||
}
|
||||
|
||||
_surface = *surfacePtr;
|
||||
_surfaceCreated = true;
|
||||
|
||||
return (IntPtr)(ulong)_surface.Handle;
|
||||
}
|
||||
|
||||
public unsafe string[] GetRequiredInstanceExtensions()
|
||||
{
|
||||
List<string> requiredExtensions = new List<string>
|
||||
{
|
||||
"VK_KHR_surface",
|
||||
"VK_EXT_metal_surface"
|
||||
};
|
||||
|
||||
uint extensionCount = 0;
|
||||
_vk.EnumerateInstanceExtensionProperties((byte*)null, &extensionCount, null);
|
||||
|
||||
if (extensionCount == 0)
|
||||
{
|
||||
string errorMessage = "Failed to enumerate Vulkan instance extensions";
|
||||
Logger.Error?.Print(LogClass.Application, errorMessage);
|
||||
throw new Exception(errorMessage);
|
||||
}
|
||||
|
||||
ExtensionProperties* extensions = stackalloc ExtensionProperties[(int)extensionCount];
|
||||
|
||||
Result result = _vk.EnumerateInstanceExtensionProperties((byte*)null, &extensionCount, extensions);
|
||||
|
||||
if (result != Result.Success)
|
||||
{
|
||||
string errorMessage = $"Failed to enumerate Vulkan instance extensions, error: {result}";
|
||||
Logger.Error?.Print(LogClass.Application, errorMessage);
|
||||
throw new Exception(errorMessage);
|
||||
}
|
||||
|
||||
List<string> availableExtensions = new List<string>();
|
||||
|
||||
for (int i = 0; i < extensionCount; i++)
|
||||
{
|
||||
string extName = Marshal.PtrToStringAnsi((IntPtr)extensions[i].ExtensionName);
|
||||
availableExtensions.Add(extName);
|
||||
}
|
||||
|
||||
Logger.Info?.Print(LogClass.Application, $"Available Vulkan extensions: {string.Join(", ", availableExtensions)}");
|
||||
|
||||
foreach (string requiredExt in requiredExtensions)
|
||||
{
|
||||
if (!availableExtensions.Contains(requiredExt))
|
||||
{
|
||||
string errorMessage = $"Required Vulkan extension {requiredExt} is not available";
|
||||
Logger.Error?.Print(LogClass.Application, errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
Logger.Info?.Print(LogClass.Application, $"Using Vulkan extensions: {string.Join(", ", requiredExtensions)}");
|
||||
|
||||
return requiredExtensions.ToArray();
|
||||
}
|
||||
|
||||
protected override void FinalizeWindowRenderer()
|
||||
{
|
||||
if (_surfaceCreated)
|
||||
{
|
||||
_surface = default;
|
||||
_surfaceCreated = false;
|
||||
}
|
||||
|
||||
nativeMetalLayer = IntPtr.Zero;
|
||||
}
|
||||
|
||||
|
||||
protected override void SwapBuffers() {}
|
||||
}
|
||||
}
|
|
@ -113,6 +113,8 @@ namespace Ryujinx.Headless.SDL2
|
|||
private static List<InputConfig> _inputConfiguration;
|
||||
private static bool _enableKeyboard;
|
||||
private static bool _enableMouse;
|
||||
private static IntPtr nativeMetalLayer = IntPtr.Zero;
|
||||
private static readonly object metalLayerLock = new object();
|
||||
|
||||
private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||
private static readonly TitleUpdateMetadataJsonSerializerContext _titleSerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||
|
@ -142,6 +144,19 @@ namespace Ryujinx.Headless.SDL2
|
|||
return 0;
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly(EntryPoint = "set_native_window")]
|
||||
public static unsafe void SetNativeWindow(IntPtr layer) {
|
||||
lock (metalLayerLock) {
|
||||
nativeMetalLayer = layer;
|
||||
Logger.Info?.Print(LogClass.Application, $"SetNativeWindow called with layer: {layer}");
|
||||
}
|
||||
}
|
||||
|
||||
public static IntPtr GetNativeMetalLayer()
|
||||
{
|
||||
return nativeMetalLayer;
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly(EntryPoint = "get_dlc_nca_list")]
|
||||
public static unsafe DlcNcaList GetDlcNcaList(IntPtr titleIdPtr, IntPtr pathPtr)
|
||||
{
|
||||
|
@ -391,12 +406,13 @@ namespace Ryujinx.Headless.SDL2
|
|||
[UnmanagedCallersOnly(EntryPoint = "stop_emulation")]
|
||||
public static void StopEmulation()
|
||||
{
|
||||
|
||||
if (_window != null)
|
||||
{
|
||||
_window.Exit();
|
||||
_emulationContext.Dispose();
|
||||
_emulationContext = null;
|
||||
if (_window._isPaused) {
|
||||
_window._isPaused = false;
|
||||
} else {
|
||||
_window._isPaused = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -977,8 +993,7 @@ namespace Ryujinx.Headless.SDL2
|
|||
}
|
||||
else
|
||||
{
|
||||
bool isAppleController = gamepadName.Contains("Apple") ? option.OnScreenCorrespond : false;
|
||||
bool isNintendoStyle = gamepadName.Contains("Nintendo") || isAppleController;
|
||||
bool isNintendoStyle = gamepadName.Contains("Nintendo") || gamepadName.Contains("Joycons");
|
||||
|
||||
config = new StandardControllerInputConfig
|
||||
{
|
||||
|
@ -1190,7 +1205,7 @@ namespace Ryujinx.Headless.SDL2
|
|||
}
|
||||
|
||||
if (option.InputPath == "MiiMaker") {
|
||||
string contentPath = _contentManager.GetInstalledContentPath(0x0100000000001009, StorageId.BuiltInSystem, NcaContentType.Program);
|
||||
string contentPath = _contentManager.GetInstalledContentPath(0x0100000000001000, StorageId.BuiltInSystem, NcaContentType.Program);
|
||||
|
||||
option.InputPath = contentPath;
|
||||
}
|
||||
|
@ -1292,18 +1307,25 @@ namespace Ryujinx.Headless.SDL2
|
|||
|
||||
private static WindowBase CreateWindow(Options options)
|
||||
{
|
||||
return options.GraphicsBackend == GraphicsBackend.Vulkan
|
||||
? new VulkanWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableMouse, options.HideCursorMode)
|
||||
: new OpenGLWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableMouse, options.HideCursorMode);
|
||||
if (OperatingSystem.IsIOS()) {
|
||||
return new MoltenVKWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableMouse, options.HideCursorMode);
|
||||
}
|
||||
else
|
||||
{
|
||||
return options.GraphicsBackend == GraphicsBackend.Vulkan
|
||||
? new VulkanWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableMouse, options.HideCursorMode)
|
||||
: new OpenGLWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableMouse, options.HideCursorMode);
|
||||
}
|
||||
}
|
||||
|
||||
private static IRenderer CreateRenderer(Options options, WindowBase window)
|
||||
{
|
||||
if (options.GraphicsBackend == GraphicsBackend.Vulkan && window is VulkanWindow vulkanWindow)
|
||||
if (options.GraphicsBackend == GraphicsBackend.Vulkan)
|
||||
{
|
||||
string preferredGpuId = string.Empty;
|
||||
Vk api = Vk.GetApi();
|
||||
|
||||
// Handle GPU preference selection
|
||||
if (!string.IsNullOrEmpty(options.PreferredGPUVendor))
|
||||
{
|
||||
string preferredGpuVendor = options.PreferredGPUVendor.ToLowerInvariant();
|
||||
|
@ -1319,13 +1341,25 @@ namespace Ryujinx.Headless.SDL2
|
|||
}
|
||||
}
|
||||
|
||||
return new VulkanRenderer(
|
||||
api,
|
||||
(instance, vk) => new SurfaceKHR((ulong)(vulkanWindow.CreateWindowSurface(instance.Handle))),
|
||||
vulkanWindow.GetRequiredInstanceExtensions,
|
||||
preferredGpuId);
|
||||
if (window is VulkanWindow vulkanWindow)
|
||||
{
|
||||
return new VulkanRenderer(
|
||||
api,
|
||||
(instance, vk) => new SurfaceKHR((ulong)(vulkanWindow.CreateWindowSurface(instance.Handle))),
|
||||
vulkanWindow.GetRequiredInstanceExtensions,
|
||||
preferredGpuId);
|
||||
}
|
||||
else if (window is MoltenVKWindow mvulkanWindow)
|
||||
{
|
||||
return new VulkanRenderer(
|
||||
api,
|
||||
(instance, vk) => new SurfaceKHR((ulong)(mvulkanWindow.CreateWindowSurface(instance.Handle))),
|
||||
mvulkanWindow.GetRequiredInstanceExtensions,
|
||||
preferredGpuId);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to OpenGL renderer if Vulkan is not used
|
||||
return new OpenGLRenderer();
|
||||
}
|
||||
|
||||
|
@ -1333,12 +1367,7 @@ namespace Ryujinx.Headless.SDL2
|
|||
{
|
||||
BackendThreading threadingMode = options.BackendThreading;
|
||||
|
||||
bool threadedGAL = threadingMode == BackendThreading.On || (threadingMode == BackendThreading.Auto && renderer.PreferThreading);
|
||||
|
||||
if (threadedGAL)
|
||||
{
|
||||
renderer = new ThreadedRenderer(renderer);
|
||||
}
|
||||
renderer = new ThreadedRenderer(renderer);
|
||||
|
||||
bool AppleHV = false;
|
||||
|
||||
|
@ -1413,6 +1442,11 @@ namespace Ryujinx.Headless.SDL2
|
|||
Logger.RestartTime();
|
||||
|
||||
WindowBase window = CreateWindow(options);
|
||||
|
||||
if (window is MoltenVKWindow mvulkanWindow) {
|
||||
mvulkanWindow.SetNativeWindow(nativeMetalLayer);
|
||||
}
|
||||
|
||||
IRenderer renderer = CreateRenderer(options, window);
|
||||
|
||||
_window = window;
|
||||
|
|
|
@ -66,7 +66,7 @@ namespace Ryujinx.Headless.SDL2
|
|||
public ScalingFilter ScalingFilter { get; set; }
|
||||
public int ScalingFilterLevel { get; set; }
|
||||
|
||||
public SDL2MouseDriver MouseDriver;
|
||||
public iOSTouchDriver MouseDriver;
|
||||
private readonly InputManager _inputManager;
|
||||
private readonly IKeyboard _keyboardInterface;
|
||||
private readonly GraphicsDebugLevel _glLogLevel;
|
||||
|
@ -81,6 +81,8 @@ namespace Ryujinx.Headless.SDL2
|
|||
private bool _isStopped;
|
||||
private uint _windowId;
|
||||
|
||||
public bool _isPaused = false;
|
||||
|
||||
private string _gpuVendorName;
|
||||
|
||||
private readonly AspectRatio _aspectRatio;
|
||||
|
@ -93,7 +95,7 @@ namespace Ryujinx.Headless.SDL2
|
|||
bool enableMouse,
|
||||
HideCursorMode hideCursorMode)
|
||||
{
|
||||
MouseDriver = new SDL2MouseDriver(hideCursorMode);
|
||||
MouseDriver = new iOSTouchDriver(hideCursorMode);
|
||||
_inputManager = inputManager;
|
||||
_inputManager.SetMouseDriver(MouseDriver);
|
||||
NpadManager = _inputManager.CreateNpadManager();
|
||||
|
@ -159,48 +161,59 @@ namespace Ryujinx.Headless.SDL2
|
|||
|
||||
private void InitializeWindow()
|
||||
{
|
||||
var activeProcess = Device.Processes.ActiveApplication;
|
||||
var nacp = activeProcess.ApplicationControlProperties;
|
||||
int desiredLanguage = (int)Device.System.State.DesiredTitleLanguage;
|
||||
if (this is Ryujinx.Headless.SDL2.Vulkan.MoltenVKWindow) {
|
||||
string message = $"Not using SDL Windows, Skipping...";
|
||||
|
||||
string titleNameSection = string.IsNullOrWhiteSpace(nacp.Title[desiredLanguage].NameString.ToString()) ? string.Empty : $" - {nacp.Title[desiredLanguage].NameString.ToString()}";
|
||||
string titleVersionSection = string.IsNullOrWhiteSpace(nacp.DisplayVersionString.ToString()) ? string.Empty : $" v{nacp.DisplayVersionString.ToString()}";
|
||||
string titleIdSection = string.IsNullOrWhiteSpace(activeProcess.ProgramIdText) ? string.Empty : $" ({activeProcess.ProgramIdText.ToUpper()})";
|
||||
string titleArchSection = activeProcess.Is64Bit ? " (64-bit)" : " (32-bit)";
|
||||
Logger.Info?.Print(LogClass.Application, message);
|
||||
|
||||
Width = DefaultWidth;
|
||||
Height = DefaultHeight;
|
||||
Width = DefaultWidth;
|
||||
Height = DefaultHeight;
|
||||
|
||||
if (IsExclusiveFullscreen)
|
||||
{
|
||||
Width = ExclusiveFullscreenWidth;
|
||||
Height = ExclusiveFullscreenHeight;
|
||||
MouseDriver.SetClientSize(Width, Height);
|
||||
} else {
|
||||
var activeProcess = Device.Processes.ActiveApplication;
|
||||
var nacp = activeProcess.ApplicationControlProperties;
|
||||
int desiredLanguage = (int)Device.System.State.DesiredTitleLanguage;
|
||||
|
||||
DefaultFlags = SDL_WindowFlags.SDL_WINDOW_ALLOW_HIGHDPI;
|
||||
FullscreenFlag = SDL_WindowFlags.SDL_WINDOW_FULLSCREEN;
|
||||
string titleNameSection = string.IsNullOrWhiteSpace(nacp.Title[desiredLanguage].NameString.ToString()) ? string.Empty : $" - {nacp.Title[desiredLanguage].NameString.ToString()}";
|
||||
string titleVersionSection = string.IsNullOrWhiteSpace(nacp.DisplayVersionString.ToString()) ? string.Empty : $" v{nacp.DisplayVersionString.ToString()}";
|
||||
string titleIdSection = string.IsNullOrWhiteSpace(activeProcess.ProgramIdText) ? string.Empty : $" ({activeProcess.ProgramIdText.ToUpper()})";
|
||||
string titleArchSection = activeProcess.Is64Bit ? " (64-bit)" : " (32-bit)";
|
||||
|
||||
Width = DefaultWidth;
|
||||
Height = DefaultHeight;
|
||||
|
||||
if (IsExclusiveFullscreen)
|
||||
{
|
||||
Width = ExclusiveFullscreenWidth;
|
||||
Height = ExclusiveFullscreenHeight;
|
||||
|
||||
DefaultFlags = SDL_WindowFlags.SDL_WINDOW_ALLOW_HIGHDPI;
|
||||
FullscreenFlag = SDL_WindowFlags.SDL_WINDOW_FULLSCREEN;
|
||||
}
|
||||
else if (IsFullscreen)
|
||||
{
|
||||
DefaultFlags = SDL_WindowFlags.SDL_WINDOW_ALLOW_HIGHDPI;
|
||||
FullscreenFlag = SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP;
|
||||
}
|
||||
|
||||
// WindowHandle = SDL_GetWindowFromID(1);
|
||||
WindowHandle = SDL_CreateWindow($"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}", 0, 0, Width, Height, DefaultFlags | FullscreenFlag | GetWindowFlags());
|
||||
|
||||
if (WindowHandle == IntPtr.Zero)
|
||||
{
|
||||
string errorMessage = $"SDL_CreateWindow failed with error \"{SDL_GetError()}\"";
|
||||
|
||||
Logger.Error?.Print(LogClass.Application, errorMessage);
|
||||
|
||||
throw new Exception(errorMessage);
|
||||
}
|
||||
|
||||
SetWindowIcon();
|
||||
|
||||
_windowId = SDL_GetWindowID(WindowHandle);
|
||||
SDL2Driver.Instance.RegisterWindow(_windowId, HandleWindowEvent);
|
||||
}
|
||||
else if (IsFullscreen)
|
||||
{
|
||||
DefaultFlags = SDL_WindowFlags.SDL_WINDOW_ALLOW_HIGHDPI;
|
||||
FullscreenFlag = SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP;
|
||||
}
|
||||
|
||||
// WindowHandle = SDL_GetWindowFromID(1);
|
||||
WindowHandle = SDL_CreateWindow($"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}", 0, 0, Width, Height, DefaultFlags | FullscreenFlag | GetWindowFlags());
|
||||
|
||||
if (WindowHandle == IntPtr.Zero)
|
||||
{
|
||||
string errorMessage = $"SDL_CreateWindow failed with error \"{SDL_GetError()}\"";
|
||||
|
||||
Logger.Error?.Print(LogClass.Application, errorMessage);
|
||||
|
||||
throw new Exception(errorMessage);
|
||||
}
|
||||
|
||||
SetWindowIcon();
|
||||
|
||||
_windowId = SDL_GetWindowID(WindowHandle);
|
||||
SDL2Driver.Instance.RegisterWindow(_windowId, HandleWindowEvent);
|
||||
}
|
||||
|
||||
private void HandleWindowEvent(SDL_Event evnt)
|
||||
|
@ -232,7 +245,7 @@ namespace Ryujinx.Headless.SDL2
|
|||
}
|
||||
else
|
||||
{
|
||||
MouseDriver.Update(evnt);
|
||||
// MouseDriver.Update(evnt);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -289,6 +302,11 @@ namespace Ryujinx.Headless.SDL2
|
|||
return;
|
||||
}
|
||||
|
||||
if (_isPaused)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_ticks += _chrono.ElapsedTicks;
|
||||
|
||||
_chrono.Restart();
|
||||
|
@ -369,6 +387,13 @@ namespace Ryujinx.Headless.SDL2
|
|||
{
|
||||
while (_isActive)
|
||||
{
|
||||
|
||||
if (_isPaused)
|
||||
{
|
||||
Thread.Sleep(1);
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateFrame();
|
||||
|
||||
SDL_PumpEvents();
|
||||
|
@ -408,6 +433,11 @@ namespace Ryujinx.Headless.SDL2
|
|||
return true;
|
||||
}
|
||||
|
||||
if (_isPaused)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_isStopped)
|
||||
{
|
||||
return false;
|
||||
|
@ -416,13 +446,12 @@ namespace Ryujinx.Headless.SDL2
|
|||
NpadManager.Update();
|
||||
|
||||
// Touchscreen
|
||||
bool hasTouch = false;
|
||||
bool hasTouch = true;
|
||||
|
||||
MouseDriver.SetClientSize(Width, Height);
|
||||
|
||||
hasTouch = TouchScreenManager.Update(true, (_inputManager.MouseDriver as iOSTouchDriver).IsButtonPressed(MouseButton.Button1), _aspectRatio.ToFloat());
|
||||
|
||||
// Get screen touch position
|
||||
if (!_enableMouse)
|
||||
{
|
||||
hasTouch = TouchScreenManager.Update(true, (_inputManager.MouseDriver as SDL2MouseDriver).IsButtonPressed(MouseButton.Button1), _aspectRatio.ToFloat());
|
||||
}
|
||||
|
||||
if (!hasTouch)
|
||||
{
|
||||
|
@ -545,11 +574,14 @@ namespace Ryujinx.Headless.SDL2
|
|||
TouchScreenManager?.Dispose();
|
||||
NpadManager.Dispose();
|
||||
|
||||
SDL2Driver.Instance.UnregisterWindow(_windowId);
|
||||
if (!(this is Ryujinx.Headless.SDL2.Vulkan.MoltenVKWindow))
|
||||
{
|
||||
SDL2Driver.Instance.UnregisterWindow(_windowId);
|
||||
|
||||
SDL_DestroyWindow(WindowHandle);
|
||||
SDL_DestroyWindow(WindowHandle);
|
||||
|
||||
SDL2Driver.Instance.Dispose();
|
||||
SDL2Driver.Instance.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
90
src/Ryujinx.Headless.SDL2/iOSMouse.cs
Normal file
90
src/Ryujinx.Headless.SDL2/iOSMouse.cs
Normal file
|
@ -0,0 +1,90 @@
|
|||
using Ryujinx.Common.Configuration.Hid;
|
||||
using Ryujinx.Input;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Ryujinx.Headless.SDL2
|
||||
{
|
||||
class iOSMouse : IMouse
|
||||
{
|
||||
private iOSTouchDriver _driver;
|
||||
|
||||
public GamepadFeaturesFlag Features => throw new NotImplementedException();
|
||||
|
||||
public string Id => "0";
|
||||
|
||||
public string Name => "iOSMouse";
|
||||
|
||||
public bool IsConnected => true;
|
||||
|
||||
public bool[] Buttons => _driver.PressedButtons;
|
||||
|
||||
Size IMouse.ClientSize => _driver.GetClientSize();
|
||||
|
||||
public iOSMouse(iOSTouchDriver driver)
|
||||
{
|
||||
_driver = driver;
|
||||
}
|
||||
|
||||
public Vector2 GetPosition()
|
||||
{
|
||||
return _driver.CurrentPosition;
|
||||
}
|
||||
|
||||
public Vector2 GetScroll()
|
||||
{
|
||||
return _driver.Scroll;
|
||||
}
|
||||
|
||||
public GamepadStateSnapshot GetMappedStateSnapshot()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Vector3 GetMotionData(MotionInputId inputId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public GamepadStateSnapshot GetStateSnapshot()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public (float, float) GetStick(StickInputId inputId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool IsButtonPressed(MouseButton button)
|
||||
{
|
||||
return _driver.IsButtonPressed(button);
|
||||
}
|
||||
|
||||
public bool IsPressed(GamepadButtonInputId inputId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Rumble(float lowFrequency, float highFrequency, uint durationMs)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void SetConfiguration(InputConfig configuration)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void SetTriggerThreshold(float triggerThreshold)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_driver = null;
|
||||
}
|
||||
}
|
||||
}
|
156
src/Ryujinx.Headless.SDL2/iOSTouchDriver.cs
Normal file
156
src/Ryujinx.Headless.SDL2/iOSTouchDriver.cs
Normal file
|
@ -0,0 +1,156 @@
|
|||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Input;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Headless.SDL2
|
||||
{
|
||||
class iOSTouchDriver : IGamepadDriver
|
||||
{
|
||||
private const int CursorHideIdleTime = 5;
|
||||
|
||||
private bool _isDisposed;
|
||||
private readonly HideCursorMode _hideCursorMode;
|
||||
private bool _isHidden;
|
||||
private long _lastCursorMoveTime;
|
||||
|
||||
public bool[] PressedButtons { get; }
|
||||
public Vector2 CurrentPosition { get; private set; }
|
||||
public Vector2 Scroll { get; private set; }
|
||||
public Size ClientSize;
|
||||
|
||||
private static Dictionary<int, Vector2> _activeTouches = new();
|
||||
|
||||
public iOSTouchDriver(HideCursorMode hideCursorMode)
|
||||
{
|
||||
PressedButtons = new bool[(int)MouseButton.Count];
|
||||
_hideCursorMode = hideCursorMode;
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly(EntryPoint = "touch_began")]
|
||||
public static void TouchBeganAtPoint(float x, float y, int index)
|
||||
{
|
||||
Vector2 position = new Vector2(x, y);
|
||||
_activeTouches[index] = position;
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly(EntryPoint = "touch_moved")]
|
||||
public static void TouchMovedAtPoint(float x, float y, int index)
|
||||
{
|
||||
if (_activeTouches.ContainsKey(index))
|
||||
{
|
||||
_activeTouches[index] = new Vector2(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly(EntryPoint = "touch_ended")]
|
||||
public static void TouchEndedForIndex(int index)
|
||||
{
|
||||
if (_activeTouches.ContainsKey(index))
|
||||
{
|
||||
_activeTouches.Remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdatePosition()
|
||||
{
|
||||
if (_activeTouches.Count > 0)
|
||||
{
|
||||
var touch = _activeTouches.Values.GetEnumerator();
|
||||
touch.MoveNext();
|
||||
Vector2 position = touch.Current;
|
||||
|
||||
if (CurrentPosition != position)
|
||||
{
|
||||
CurrentPosition = position;
|
||||
_lastCursorMoveTime = Stopwatch.GetTimestamp();
|
||||
}
|
||||
}
|
||||
|
||||
CheckIdle();
|
||||
}
|
||||
|
||||
private void CheckIdle()
|
||||
{
|
||||
if (_hideCursorMode != HideCursorMode.OnIdle)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
long cursorMoveDelta = Stopwatch.GetTimestamp() - _lastCursorMoveTime;
|
||||
|
||||
if (cursorMoveDelta >= CursorHideIdleTime * Stopwatch.Frequency)
|
||||
{
|
||||
if (!_isHidden)
|
||||
{
|
||||
Logger.Debug?.Print(LogClass.Application, "Hiding cursor due to inactivity.");
|
||||
_isHidden = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_isHidden)
|
||||
{
|
||||
Logger.Debug?.Print(LogClass.Application, "Showing cursor after activity.");
|
||||
_isHidden = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void SetClientSize(int width, int height)
|
||||
{
|
||||
ClientSize = new Size(width, height);
|
||||
}
|
||||
|
||||
public bool IsButtonPressed(MouseButton button)
|
||||
{
|
||||
if (_activeTouches.Count > 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public Size GetClientSize()
|
||||
{
|
||||
return ClientSize;
|
||||
}
|
||||
|
||||
public string DriverName => "iOSTouchDriver";
|
||||
|
||||
public event Action<string> OnGamepadConnected
|
||||
{
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
|
||||
public event Action<string> OnGamepadDisconnected
|
||||
{
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
|
||||
public ReadOnlySpan<string> GamepadsIds => new[] { "0" };
|
||||
|
||||
public IGamepad GetGamepad(string id)
|
||||
{
|
||||
return new iOSMouse(this);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_isDisposed = true;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue