diff --git a/Ryujinx/Ryujinx.csproj b/Ryujinx/Ryujinx.csproj
index 13442c953e..6a441a610e 100644
--- a/Ryujinx/Ryujinx.csproj
+++ b/Ryujinx/Ryujinx.csproj
@@ -18,7 +18,7 @@
-
+
diff --git a/Ryujinx/Ui/Applet/GtkHostUiHandler.cs b/Ryujinx/Ui/Applet/GtkHostUiHandler.cs
index 804a1a2799..d74ea3d596 100644
--- a/Ryujinx/Ui/Applet/GtkHostUiHandler.cs
+++ b/Ryujinx/Ui/Applet/GtkHostUiHandler.cs
@@ -132,7 +132,7 @@ namespace Ryujinx.Ui.Applet
public void ExecuteProgram(HLE.Switch device, ProgramSpecifyKind kind, ulong value)
{
device.UserChannelPersistence.ExecuteProgram(kind, value);
- ((MainWindow)_parent).GlRendererWidget?.Exit();
+ ((MainWindow)_parent).RendererWidget?.Exit();
}
public bool DisplayErrorAppletDialog(string title, string message, string[] buttons)
diff --git a/Ryujinx/Ui/GLRenderer.cs b/Ryujinx/Ui/GLRenderer.cs
index 99c4698acd..4c3f8ce40c 100644
--- a/Ryujinx/Ui/GLRenderer.cs
+++ b/Ryujinx/Ui/GLRenderer.cs
@@ -1,10 +1,10 @@
using ARMeilleure.Translation;
using ARMeilleure.Translation.PTC;
using Gdk;
+using Gtk;
using OpenTK.Graphics.OpenGL;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
-using Ryujinx.Common.Logging;
using Ryujinx.Configuration;
using Ryujinx.Graphics.OpenGL;
using Ryujinx.HLE.HOS.Services.Hid;
@@ -13,9 +13,14 @@ using Ryujinx.Input.HLE;
using Ryujinx.Ui.Widgets;
using SPB.Graphics;
using SPB.Graphics.OpenGL;
+using SPB.Platform;
+using SPB.Platform.GLX;
+using SPB.Platform.WGL;
+using SPB.Windowing;
using System;
using System.Diagnostics;
using System.Linq;
+using System.Runtime.InteropServices;
using System.Threading;
using Key = Ryujinx.Input.Key;
@@ -24,606 +29,123 @@ namespace Ryujinx.Ui
{
using Switch = HLE.Switch;
- public class GlRenderer : GLWidget
+ public class GlRenderer : RendererWidgetBase
{
- private const int SwitchPanelWidth = 1280;
- private const int SwitchPanelHeight = 720;
- private const int TargetFps = 60;
-
- public ManualResetEvent WaitEvent { get; set; }
- public NpadManager NpadManager { get; }
-
- public static event EventHandler StatusUpdatedEvent;
-
- private bool _isActive;
- private bool _isStopped;
- private bool _isFocused;
-
- private double _mouseX;
- private double _mouseY;
- private bool _mousePressed;
-
- private bool _toggleFullscreen;
- private bool _toggleDockedMode;
-
- private readonly long _ticksPerFrame;
-
- private long _ticks = 0;
-
- private readonly Stopwatch _chrono;
-
- private readonly Switch _device;
-
- private Renderer _renderer;
-
- private KeyboardHotkeyState _prevHotkeyState;
-
private GraphicsDebugLevel _glLogLevel;
- private readonly ManualResetEvent _exitEvent;
-
- // Hide Cursor
- const int CursorHideIdleTime = 8; // seconds
- private static readonly Cursor _invisibleCursor = new Cursor(Display.Default, CursorType.BlankCursor);
- private long _lastCursorMoveTime;
- private bool _hideCursorOnIdle;
- private InputManager _inputManager;
- private IKeyboard _keyboardInterface;
+ private bool _initializedOpenGL;
- public GlRenderer(Switch device, InputManager inputManager, GraphicsDebugLevel glLogLevel)
- : base (GetGraphicsMode(),
- 3, 3,
- glLogLevel == GraphicsDebugLevel.None
- ? OpenGLContextFlags.Compat
- : OpenGLContextFlags.Compat | OpenGLContextFlags.Debug)
+ private OpenGLContextBase _openGLContext;
+ private SwappableNativeWindowBase _nativeWindow;
+
+ public GlRenderer(InputManager inputManager, GraphicsDebugLevel glLogLevel) : base(inputManager, glLogLevel)
{
- _inputManager = inputManager;
- NpadManager = _inputManager.CreateNpadManager();
- _keyboardInterface = (IKeyboard)_inputManager.KeyboardDriver.GetGamepad("0");
-
- NpadManager.ReloadConfiguration(ConfigurationState.Instance.Hid.InputConfig.Value.ToList());
-
- WaitEvent = new ManualResetEvent(false);
-
- _device = device;
-
- Initialized += GLRenderer_Initialized;
- Destroyed += GLRenderer_Destroyed;
- ShuttingDown += GLRenderer_ShuttingDown;
-
- Initialize();
-
- _chrono = new Stopwatch();
-
- _ticksPerFrame = Stopwatch.Frequency / TargetFps;
-
- AddEvents((int)(EventMask.ButtonPressMask
- | EventMask.ButtonReleaseMask
- | EventMask.PointerMotionMask
- | EventMask.KeyPressMask
- | EventMask.KeyReleaseMask));
-
- Shown += Renderer_Shown;
-
_glLogLevel = glLogLevel;
-
- _exitEvent = new ManualResetEvent(false);
-
- _hideCursorOnIdle = ConfigurationState.Instance.HideCursorOnIdle;
- _lastCursorMoveTime = Stopwatch.GetTimestamp();
-
- ConfigurationState.Instance.HideCursorOnIdle.Event += HideCursorStateChanged;
}
- private void HideCursorStateChanged(object sender, ReactiveEventArgs state)
+ protected override bool OnDrawn(Cairo.Context cr)
{
- Gtk.Application.Invoke(delegate
+ if (!_initializedOpenGL)
{
- _hideCursorOnIdle = state.NewValue;
+ IntializeOpenGL();
+ }
- if (_hideCursorOnIdle)
- {
- _lastCursorMoveTime = Stopwatch.GetTimestamp();
- }
- else
- {
- Window.Cursor = null;
- }
- });
+ return true;
}
+ private void IntializeOpenGL()
+ {
+ _nativeWindow = RetrieveNativeWindow();
+
+ Window.EnsureNative();
+
+ _openGLContext = PlatformHelper.CreateOpenGLContext(GetGraphicsMode(), 3, 3, _glLogLevel == GraphicsDebugLevel.None ? OpenGLContextFlags.Compat : OpenGLContextFlags.Compat | OpenGLContextFlags.Debug);
+ _openGLContext.Initialize(_nativeWindow);
+ _openGLContext.MakeCurrent(_nativeWindow);
+
+ // Release the GL exclusivity that SPB gave us as we aren't going to use it in GTK Thread.
+ _openGLContext.MakeCurrent(null);
+
+ WaitEvent.Set();
+
+ _initializedOpenGL = true;
+ }
+
+ private SwappableNativeWindowBase RetrieveNativeWindow()
+ {
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ IntPtr windowHandle = gdk_win32_window_get_handle(Window.Handle);
+
+ return new WGLWindow(new NativeHandle(windowHandle));
+ }
+ else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ {
+ IntPtr displayHandle = gdk_x11_display_get_xdisplay(Display.Handle);
+ IntPtr windowHandle = gdk_x11_window_get_xid(Window.Handle);
+
+ return new GLXWindow(new NativeHandle(displayHandle), new NativeHandle(windowHandle));
+ }
+
+ throw new NotImplementedException();
+ }
+
+ [DllImport("libgdk-3-0.dll")]
+ private static extern IntPtr gdk_win32_window_get_handle(IntPtr d);
+
+ [DllImport("libgdk-3.so.0")]
+ private static extern IntPtr gdk_x11_display_get_xdisplay(IntPtr gdkDisplay);
+
+ [DllImport("libgdk-3.so.0")]
+ private static extern IntPtr gdk_x11_window_get_xid(IntPtr gdkWindow);
+
private static FramebufferFormat GetGraphicsMode()
{
return Environment.OSVersion.Platform == PlatformID.Unix ? new FramebufferFormat(new ColorFormat(8, 8, 8, 0), 16, 0, ColorFormat.Zero, 0, 2, false) : FramebufferFormat.Default;
}
- private void GLRenderer_ShuttingDown(object sender, EventArgs args)
- {
- _device.DisposeGpu();
- NpadManager.Dispose();
- }
-
- private void Parent_FocusOutEvent(object o, Gtk.FocusOutEventArgs args)
- {
- _isFocused = false;
- }
-
- private void Parent_FocusInEvent(object o, Gtk.FocusInEventArgs args)
- {
- _isFocused = true;
- }
-
- private void GLRenderer_Destroyed(object sender, EventArgs e)
- {
- ConfigurationState.Instance.HideCursorOnIdle.Event -= HideCursorStateChanged;
-
- NpadManager.Dispose();
- Dispose();
- }
-
- protected void Renderer_Shown(object sender, EventArgs e)
- {
- _isFocused = this.ParentWindow.State.HasFlag(Gdk.WindowState.Focused);
- }
-
- public void HandleScreenState(KeyboardStateSnapshot keyboard)
- {
- bool toggleFullscreen = keyboard.IsPressed(Key.F11)
- || ((keyboard.IsPressed(Key.AltLeft)
- || keyboard.IsPressed(Key.AltRight))
- && keyboard.IsPressed(Key.Enter))
- || keyboard.IsPressed(Key.Escape);
-
- bool fullScreenToggled = ParentWindow.State.HasFlag(Gdk.WindowState.Fullscreen);
-
- if (toggleFullscreen != _toggleFullscreen)
- {
- if (toggleFullscreen)
- {
- if (fullScreenToggled)
- {
- ParentWindow.Unfullscreen();
- (Toplevel as MainWindow)?.ToggleExtraWidgets(true);
- }
- else
- {
- if (keyboard.IsPressed(Key.Escape))
- {
- if (!ConfigurationState.Instance.ShowConfirmExit || GtkDialog.CreateExitDialog())
- {
- Exit();
- }
- }
- else
- {
- ParentWindow.Fullscreen();
- (Toplevel as MainWindow)?.ToggleExtraWidgets(false);
- }
- }
- }
- }
-
- _toggleFullscreen = toggleFullscreen;
-
- bool toggleDockedMode = keyboard.IsPressed(Key.F9);
-
- if (toggleDockedMode != _toggleDockedMode)
- {
- if (toggleDockedMode)
- {
- ConfigurationState.Instance.System.EnableDockedMode.Value =
- !ConfigurationState.Instance.System.EnableDockedMode.Value;
- }
- }
-
- _toggleDockedMode = toggleDockedMode;
-
- if (_hideCursorOnIdle)
- {
- long cursorMoveDelta = Stopwatch.GetTimestamp() - _lastCursorMoveTime;
- Window.Cursor = (cursorMoveDelta >= CursorHideIdleTime * Stopwatch.Frequency) ? _invisibleCursor : null;
- }
- }
-
- private void GLRenderer_Initialized(object sender, EventArgs e)
- {
- // Release the GL exclusivity that SPB gave us as we aren't going to use it in GTK Thread.
- OpenGLContext.MakeCurrent(null);
-
- WaitEvent.Set();
- }
-
- protected override bool OnConfigureEvent(EventConfigure evnt)
- {
- bool result = base.OnConfigureEvent(evnt);
-
- Gdk.Monitor monitor = Display.GetMonitorAtWindow(Window);
-
- _renderer.Window.SetSize(evnt.Width * monitor.ScaleFactor, evnt.Height * monitor.ScaleFactor);
-
- return result;
- }
-
- public void Start()
- {
- _chrono.Restart();
-
- _isActive = true;
-
- Gtk.Window parent = this.Toplevel as Gtk.Window;
-
- parent.FocusInEvent += Parent_FocusInEvent;
- parent.FocusOutEvent += Parent_FocusOutEvent;
-
- Gtk.Application.Invoke(delegate
- {
- parent.Present();
-
- string titleNameSection = string.IsNullOrWhiteSpace(_device.Application.TitleName) ? string.Empty
- : $" - {_device.Application.TitleName}";
-
- string titleVersionSection = string.IsNullOrWhiteSpace(_device.Application.DisplayVersion) ? string.Empty
- : $" v{_device.Application.DisplayVersion}";
-
- string titleIdSection = string.IsNullOrWhiteSpace(_device.Application.TitleIdText) ? string.Empty
- : $" ({_device.Application.TitleIdText.ToUpper()})";
-
- string titleArchSection = _device.Application.TitleIs64Bit ? " (64-bit)" : " (32-bit)";
-
- parent.Title = $"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}";
- });
-
- Thread renderLoopThread = new Thread(Render)
- {
- Name = "GUI.RenderLoop"
- };
- renderLoopThread.Start();
-
- Thread nvStutterWorkaround = new Thread(NVStutterWorkaround)
- {
- Name = "GUI.NVStutterWorkaround"
- };
- nvStutterWorkaround.Start();
-
- MainLoop();
-
- renderLoopThread.Join();
- nvStutterWorkaround.Join();
-
- Exit();
- }
-
- private void NVStutterWorkaround()
- {
- while (_isActive)
- {
- // When NVIDIA Threaded Optimization is on, the driver will snapshot all threads in the system whenever the application creates any new ones.
- // The ThreadPool has something called a "GateThread" which terminates itself after some inactivity.
- // However, it immediately starts up again, since the rules regarding when to terminate and when to start differ.
- // This creates a new thread every second or so.
- // The main problem with this is that the thread snapshot can take 70ms, is on the OpenGL thread and will delay rendering any graphics.
- // This is a little over budget on a frame time of 16ms, so creates a large stutter.
- // The solution is to keep the ThreadPool active so that it never has a reason to terminate the GateThread.
-
- // TODO: This should be removed when the issue with the GateThread is resolved.
-
- ThreadPool.QueueUserWorkItem((state) => { });
- Thread.Sleep(300);
- }
- }
-
- protected override bool OnButtonPressEvent(EventButton evnt)
- {
- _mouseX = evnt.X;
- _mouseY = evnt.Y;
-
- if (evnt.Button == 1)
- {
- _mousePressed = true;
- }
-
- return false;
- }
-
- protected override bool OnButtonReleaseEvent(EventButton evnt)
- {
- if (evnt.Button == 1)
- {
- _mousePressed = false;
- }
-
- return false;
- }
-
- protected override bool OnMotionNotifyEvent(EventMotion evnt)
- {
- if (evnt.Device.InputSource == InputSource.Mouse)
- {
- _mouseX = evnt.X;
- _mouseY = evnt.Y;
- }
-
- if (_hideCursorOnIdle)
- {
- _lastCursorMoveTime = Stopwatch.GetTimestamp();
- }
-
- return false;
- }
-
- protected override void OnGetPreferredHeight(out int minimumHeight, out int naturalHeight)
- {
- Gdk.Monitor monitor = Display.GetMonitorAtWindow(Window);
-
- // If the monitor is at least 1080p, use the Switch panel size as minimal size.
- if (monitor.Geometry.Height >= 1080)
- {
- minimumHeight = SwitchPanelHeight;
- }
- // Otherwise, we default minimal size to 480p 16:9.
- else
- {
- minimumHeight = 480;
- }
-
- naturalHeight = minimumHeight;
- }
-
- protected override void OnGetPreferredWidth(out int minimumWidth, out int naturalWidth)
- {
- Gdk.Monitor monitor = Display.GetMonitorAtWindow(Window);
-
- // If the monitor is at least 1080p, use the Switch panel size as minimal size.
- if (monitor.Geometry.Height >= 1080)
- {
- minimumWidth = SwitchPanelWidth;
- }
- // Otherwise, we default minimal size to 480p 16:9.
- else
- {
- minimumWidth = 854;
- }
-
- naturalWidth = minimumWidth;
- }
-
- public void Exit()
- {
- NpadManager?.Dispose();
-
- if (_isStopped)
- {
- return;
- }
-
- _isStopped = true;
- _isActive = false;
-
- _exitEvent.WaitOne();
- _exitEvent.Dispose();
- }
-
- public void Initialize()
- {
- if (!(_device.Gpu.Renderer is Renderer))
- {
- throw new NotSupportedException($"GPU renderer must be an OpenGL renderer when using {typeof(Renderer).Name}!");
- }
-
- _renderer = (Renderer)_device.Gpu.Renderer;
- }
-
- public void Render()
+ public override void InitializeRenderer()
{
// First take exclusivity on the OpenGL context.
- _renderer.InitializeBackgroundContext(SPBOpenGLContext.CreateBackgroundContext(OpenGLContext));
+ ((Renderer)Renderer).InitializeBackgroundContext(SPBOpenGLContext.CreateBackgroundContext(_openGLContext));
- Gtk.Window parent = Toplevel as Gtk.Window;
- parent.Present();
+ _openGLContext.MakeCurrent(_nativeWindow);
- OpenGLContext.MakeCurrent(NativeWindow);
-
- _device.Gpu.Renderer.Initialize(_glLogLevel);
-
- // Make sure the first frame is not transparent.
GL.ClearColor(0, 0, 0, 1.0f);
GL.Clear(ClearBufferMask.ColorBufferBit);
SwapBuffers();
-
- _device.Gpu.InitializeShaderCache();
- Translator.IsReadyForTranslation.Set();
-
- while (_isActive)
- {
- if (_isStopped)
- {
- return;
- }
-
- _ticks += _chrono.ElapsedTicks;
-
- _chrono.Restart();
-
- if (_device.WaitFifo())
- {
- _device.Statistics.RecordFifoStart();
- _device.ProcessFrame();
- _device.Statistics.RecordFifoEnd();
- }
-
- while (_device.ConsumeFrameAvailable())
- {
- _device.PresentFrame(SwapBuffers);
- }
-
- if (_ticks >= _ticksPerFrame)
- {
- string dockedMode = ConfigurationState.Instance.System.EnableDockedMode ? "Docked" : "Handheld";
- float scale = Graphics.Gpu.GraphicsConfig.ResScale;
- if (scale != 1)
- {
- dockedMode += $" ({scale}x)";
- }
-
- StatusUpdatedEvent?.Invoke(this, new StatusUpdatedEventArgs(
- _device.EnableDeviceVsync,
- dockedMode,
- ConfigurationState.Instance.Graphics.AspectRatio.Value.ToText(),
- $"Game: {_device.Statistics.GetGameFrameRate():00.00} FPS",
- $"FIFO: {_device.Statistics.GetFifoPercent():0.00} %",
- $"GPU: {_renderer.GpuVendor}"));
-
- _ticks = Math.Min(_ticks - _ticksPerFrame, _ticksPerFrame);
- }
- }
}
- public void SwapBuffers()
+ public override void SwapBuffers()
{
- NativeWindow.SwapBuffers();
+ _nativeWindow.SwapBuffers();
}
- public void MainLoop()
+ public override string GetGpuVendorName()
{
- while (_isActive)
- {
- UpdateFrame();
-
- // Polling becomes expensive if it's not slept
- Thread.Sleep(1);
- }
-
- _exitEvent.Set();
+ return ((Renderer)Renderer).GpuVendor;
}
- private bool UpdateFrame()
+ protected override void Dispose(bool disposing)
{
- if (!_isActive)
+ // Try to bind the OpenGL context before calling the shutdown event
+ try
{
- return true;
+ _openGLContext?.MakeCurrent(_nativeWindow);
}
+ catch (Exception) { }
- if (_isStopped)
+ Device.DisposeGpu();
+ NpadManager.Dispose();
+
+ // Unbind context and destroy everything
+ try
{
- return false;
+ _openGLContext?.MakeCurrent(null);
}
+ catch (Exception) { }
- if (_isFocused)
- {
- Gtk.Application.Invoke(delegate
- {
- KeyboardStateSnapshot keyboard = _keyboardInterface.GetKeyboardStateSnapshot();
-
- HandleScreenState(keyboard);
-
- if (keyboard.IsPressed(Key.Delete))
- {
- if (!ParentWindow.State.HasFlag(WindowState.Fullscreen))
- {
- Ptc.Continue();
- }
- }
- });
- }
-
- NpadManager.Update(_device.Hid, _device.TamperMachine);
-
- if(_isFocused)
- {
- KeyboardHotkeyState currentHotkeyState = GetHotkeyState();
-
- if (currentHotkeyState.HasFlag(KeyboardHotkeyState.ToggleVSync) &&
- !_prevHotkeyState.HasFlag(KeyboardHotkeyState.ToggleVSync))
- {
- _device.EnableDeviceVsync = !_device.EnableDeviceVsync;
- }
-
- _prevHotkeyState = currentHotkeyState;
- }
-
- //Touchscreen
- bool hasTouch = false;
-
- // Get screen touch position from left mouse click
- // OpenTK always captures mouse events, even if out of focus, so check if window is focused.
- if (_isFocused && _mousePressed)
- {
- float aspectWidth = SwitchPanelHeight * ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat();
-
- int screenWidth = AllocatedWidth;
- int screenHeight = AllocatedHeight;
-
- if (AllocatedWidth > AllocatedHeight * aspectWidth / SwitchPanelHeight)
- {
- screenWidth = (int)(AllocatedHeight * aspectWidth) / SwitchPanelHeight;
- }
- else
- {
- screenHeight = (AllocatedWidth * SwitchPanelHeight) / (int)aspectWidth;
- }
-
- int startX = (AllocatedWidth - screenWidth) >> 1;
- int startY = (AllocatedHeight - screenHeight) >> 1;
-
- int endX = startX + screenWidth;
- int endY = startY + screenHeight;
-
-
- if (_mouseX >= startX &&
- _mouseY >= startY &&
- _mouseX < endX &&
- _mouseY < endY)
- {
- int screenMouseX = (int)_mouseX - startX;
- int screenMouseY = (int)_mouseY - startY;
-
- int mX = (screenMouseX * (int)aspectWidth) / screenWidth;
- int mY = (screenMouseY * SwitchPanelHeight) / screenHeight;
-
- TouchPoint currentPoint = new TouchPoint
- {
- X = (uint)mX,
- Y = (uint)mY,
-
- // Placeholder values till more data is acquired
- DiameterX = 10,
- DiameterY = 10,
- Angle = 90
- };
-
- hasTouch = true;
-
- _device.Hid.Touchscreen.Update(currentPoint);
- }
- }
-
- if (!hasTouch)
- {
- _device.Hid.Touchscreen.Update();
- }
-
- _device.Hid.DebugPad.Update();
-
- return true;
- }
-
- [Flags]
- private enum KeyboardHotkeyState
- {
- None,
- ToggleVSync
- }
-
- private KeyboardHotkeyState GetHotkeyState()
- {
- KeyboardHotkeyState state = KeyboardHotkeyState.None;
-
- if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ToggleVsync))
- {
- state |= KeyboardHotkeyState.ToggleVSync;
- }
-
- return state;
+ _openGLContext.Dispose();
}
}
}
diff --git a/Ryujinx/Ui/GLWidget.cs b/Ryujinx/Ui/GLWidget.cs
deleted file mode 100644
index a465aeef29..0000000000
--- a/Ryujinx/Ui/GLWidget.cs
+++ /dev/null
@@ -1,118 +0,0 @@
-using Gtk;
-using SPB.Graphics;
-using SPB.Graphics.OpenGL;
-using SPB.Platform;
-using SPB.Platform.GLX;
-using SPB.Platform.WGL;
-using SPB.Windowing;
-using System;
-using System.ComponentModel;
-using System.Runtime.InteropServices;
-
-namespace Ryujinx.Ui
-{
- [ToolboxItem(true)]
- public class GLWidget : DrawingArea
- {
- private bool _initialized;
-
- public event EventHandler Initialized;
- public event EventHandler ShuttingDown;
-
- public OpenGLContextBase OpenGLContext { get; private set; }
- public NativeWindowBase NativeWindow { get; private set; }
-
- public FramebufferFormat FramebufferFormat { get; }
- public int GLVersionMajor { get; }
- public int GLVersionMinor { get; }
- public OpenGLContextFlags ContextFlags { get; }
-
- public bool DirectRendering { get; }
- public OpenGLContextBase SharedContext { get; }
-
- public GLWidget(FramebufferFormat framebufferFormat, int major, int minor, OpenGLContextFlags flags = OpenGLContextFlags.Default, bool directRendering = true, OpenGLContextBase sharedContext = null)
- {
- FramebufferFormat = framebufferFormat;
- GLVersionMajor = major;
- GLVersionMinor = minor;
- ContextFlags = flags;
- DirectRendering = directRendering;
- SharedContext = sharedContext;
- }
-
- protected override bool OnDrawn(Cairo.Context cr)
- {
- if (!_initialized)
- {
- Intialize();
- }
-
- return true;
- }
-
- private NativeWindowBase RetrieveNativeWindow()
- {
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
- {
- IntPtr windowHandle = gdk_win32_window_get_handle(Window.Handle);
-
- return new WGLWindow(new NativeHandle(windowHandle));
- }
- else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
- {
- IntPtr displayHandle = gdk_x11_display_get_xdisplay(Display.Handle);
- IntPtr windowHandle = gdk_x11_window_get_xid(Window.Handle);
-
- return new GLXWindow(new NativeHandle(displayHandle), new NativeHandle(windowHandle));
- }
-
- throw new NotImplementedException();
- }
-
- [DllImport("libgdk-3-0.dll")]
- private static extern IntPtr gdk_win32_window_get_handle(IntPtr d);
-
- [DllImport("libgdk-3.so.0")]
- private static extern IntPtr gdk_x11_display_get_xdisplay(IntPtr gdkDisplay);
-
- [DllImport("libgdk-3.so.0")]
- private static extern IntPtr gdk_x11_window_get_xid(IntPtr gdkWindow);
-
- private void Intialize()
- {
- NativeWindow = RetrieveNativeWindow();
-
- Window.EnsureNative();
-
- OpenGLContext = PlatformHelper.CreateOpenGLContext(FramebufferFormat, GLVersionMajor, GLVersionMinor, ContextFlags, DirectRendering, SharedContext);
-
- OpenGLContext.Initialize(NativeWindow);
- OpenGLContext.MakeCurrent(NativeWindow);
-
- _initialized = true;
-
- Initialized?.Invoke(this, EventArgs.Empty);
- }
-
- protected override void Dispose(bool disposing)
- {
- // Try to bind the OpenGL context before calling the shutdown event
- try
- {
- OpenGLContext?.MakeCurrent(NativeWindow);
- }
- catch (Exception) { }
-
- ShuttingDown?.Invoke(this, EventArgs.Empty);
-
- // Unbind context and destroy everything
- try
- {
- OpenGLContext?.MakeCurrent(null);
- }
- catch (Exception) { }
-
- OpenGLContext.Dispose();
- }
- }
-}
diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs
index 08527ea359..56dcf3ebcd 100644
--- a/Ryujinx/Ui/MainWindow.cs
+++ b/Ryujinx/Ui/MainWindow.cs
@@ -67,9 +67,11 @@ namespace Ryujinx.Ui
private string _lastScannedAmiiboId = "";
private bool _lastScannedAmiiboShowAll = false;
- public GlRenderer GlRendererWidget;
+ public RendererWidgetBase RendererWidget;
public InputManager InputManager;
+ private static bool UseVulkan = false;
+
#pragma warning disable CS0169, CS0649, IDE0044
[GUI] public MenuItem ExitMenuItem;
@@ -161,7 +163,7 @@ namespace Ryujinx.Ui
_gameTable.ButtonReleaseEvent += Row_Clicked;
_fullScreen.Activated += FullScreen_Toggled;
- GlRenderer.StatusUpdatedEvent += Update_StatusBar;
+ RendererWidgetBase.StatusUpdatedEvent += Update_StatusBar;
if (ConfigurationState.Instance.Ui.StartFullscreen)
{
@@ -312,7 +314,17 @@ namespace Ryujinx.Ui
{
_virtualFileSystem.Reload();
- IRenderer renderer = new Renderer();
+ IRenderer renderer;
+
+ if (UseVulkan)
+ {
+ throw new NotImplementedException();
+ }
+ else
+ {
+ renderer = new Renderer();
+ }
+
IHardwareDeviceDriver deviceDriver = new DummyHardwareDeviceDriver();
if (ConfigurationState.Instance.System.AudioBackend.Value == AudioBackend.SoundIo)
@@ -482,6 +494,10 @@ namespace Ryujinx.Ui
Logger.RestartTime();
+ RendererWidget = CreateRendererWidget();
+
+ SwitchToRenderWidget();
+
InitializeSwitchInstance();
UpdateGraphicsConfig();
@@ -507,6 +523,8 @@ namespace Ryujinx.Ui
UserErrorDialog.CreateUserErrorDialog(userError);
_emulationContext.Dispose();
+ SwitchToGameTable();
+ RendererWidget.Dispose();
return;
}
@@ -517,6 +535,8 @@ namespace Ryujinx.Ui
UserErrorDialog.CreateUserErrorDialog(userError);
_emulationContext.Dispose();
+ SwitchToGameTable();
+ RendererWidget.Dispose();
return;
}
@@ -538,6 +558,8 @@ namespace Ryujinx.Ui
UserErrorDialog.CreateUserErrorDialog(userError);
_emulationContext.Dispose();
+ SwitchToGameTable();
+ RendererWidget.Dispose();
return;
}
@@ -600,6 +622,7 @@ namespace Ryujinx.Ui
Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file.");
_emulationContext.Dispose();
+ RendererWidget.Dispose();
return;
}
@@ -640,6 +663,83 @@ namespace Ryujinx.Ui
}
}
+ private RendererWidgetBase CreateRendererWidget()
+ {
+ if (UseVulkan)
+ {
+ return new VKRenderer(InputManager, ConfigurationState.Instance.Logger.GraphicsDebugLevel);
+ }
+ else
+ {
+ return new GlRenderer(InputManager, ConfigurationState.Instance.Logger.GraphicsDebugLevel);
+ }
+ }
+
+ private void SwitchToRenderWidget()
+ {
+ _viewBox.Remove(_gameTableWindow);
+ RendererWidget.Expand = true;
+ _viewBox.Child = RendererWidget;
+
+ RendererWidget.ShowAll();
+ EditFooterForGameRenderer();
+
+ if (Window.State.HasFlag(Gdk.WindowState.Fullscreen))
+ {
+ ToggleExtraWidgets(false);
+ }
+ else if (ConfigurationState.Instance.Ui.StartFullscreen.Value)
+ {
+ FullScreen_Toggled(null, null);
+ }
+ }
+
+ private void SwitchToGameTable()
+ {
+ if (Window.State.HasFlag(Gdk.WindowState.Fullscreen))
+ {
+ ToggleExtraWidgets(true);
+ }
+
+ RendererWidget.Exit();
+
+ if (RendererWidget.Window != Window && RendererWidget.Window != null)
+ {
+ RendererWidget.Window.Dispose();
+ }
+
+ RendererWidget.Dispose();
+
+ _windowsMultimediaTimerResolution?.Dispose();
+ _windowsMultimediaTimerResolution = null;
+ DisplaySleep.Restore();
+
+ _viewBox.Remove(RendererWidget);
+ _viewBox.Add(_gameTableWindow);
+
+ _gameTableWindow.Expand = true;
+
+ Window.Title = $"Ryujinx {Program.Version}";
+
+ _emulationContext = null;
+ _gameLoaded = false;
+ RendererWidget = null;
+
+ DiscordIntegrationModule.SwitchToMainMenu();
+
+ RecreateFooterForMenu();
+
+ UpdateColumns();
+ UpdateGameTable();
+
+ Task.Run(RefreshFirmwareLabel);
+ Task.Run(HandleRelaunch);
+
+ _actionMenu.Sensitive = false;
+ _firmwareInstallFile.Sensitive = true;
+ _firmwareInstallDirectory.Sensitive = true;
+ }
+
private void CreateGameWindow()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
@@ -649,30 +749,11 @@ namespace Ryujinx.Ui
DisplaySleep.Prevent();
- GlRendererWidget = new GlRenderer(_emulationContext, InputManager, ConfigurationState.Instance.Logger.GraphicsDebugLevel);
+ RendererWidget.Initialize(_emulationContext);
- Application.Invoke(delegate
- {
- _viewBox.Remove(_gameTableWindow);
- GlRendererWidget.Expand = true;
- _viewBox.Child = GlRendererWidget;
+ RendererWidget.WaitEvent.WaitOne();
- GlRendererWidget.ShowAll();
- EditFooterForGameRenderer();
-
- if (Window.State.HasFlag(Gdk.WindowState.Fullscreen))
- {
- ToggleExtraWidgets(false);
- }
- else if (ConfigurationState.Instance.Ui.StartFullscreen.Value)
- {
- FullScreen_Toggled(null, null);
- }
- });
-
- GlRendererWidget.WaitEvent.WaitOne();
-
- GlRendererWidget.Start();
+ RendererWidget.Start();
Ptc.Close();
PtcProfiler.Stop();
@@ -683,48 +764,7 @@ namespace Ryujinx.Ui
// NOTE: Everything that is here will not be executed when you close the UI.
Application.Invoke(delegate
{
- if (Window.State.HasFlag(Gdk.WindowState.Fullscreen))
- {
- ToggleExtraWidgets(true);
- }
-
- GlRendererWidget.Exit();
-
- if (GlRendererWidget.Window != Window && GlRendererWidget.Window != null)
- {
- GlRendererWidget.Window.Dispose();
- }
-
- GlRendererWidget.Dispose();
-
- _windowsMultimediaTimerResolution?.Dispose();
- _windowsMultimediaTimerResolution = null;
- DisplaySleep.Restore();
-
- _viewBox.Remove(GlRendererWidget);
- _viewBox.Add(_gameTableWindow);
-
- _gameTableWindow.Expand = true;
-
- Window.Title = $"Ryujinx {Program.Version}";
-
- _emulationContext = null;
- _gameLoaded = false;
- GlRendererWidget = null;
-
- DiscordIntegrationModule.SwitchToMainMenu();
-
- RecreateFooterForMenu();
-
- UpdateColumns();
- UpdateGameTable();
-
- Task.Run(RefreshFirmwareLabel);
- Task.Run(HandleRelaunch);
-
- _actionMenu.Sensitive = false;
- _firmwareInstallFile.Sensitive = true;
- _firmwareInstallDirectory.Sensitive = true;
+ SwitchToGameTable();
});
}
@@ -742,7 +782,7 @@ namespace Ryujinx.Ui
public void ToggleExtraWidgets(bool show)
{
- if (GlRendererWidget != null)
+ if (RendererWidget != null)
{
if (show)
{
@@ -801,14 +841,14 @@ namespace Ryujinx.Ui
{
UpdateGameMetadata(_emulationContext.Application.TitleIdText);
- if (GlRendererWidget != null)
+ if (RendererWidget != null)
{
// We tell the widget that we are exiting.
- GlRendererWidget.Exit();
+ RendererWidget.Exit();
// Wait for the other thread to dispose the HLE context before exiting.
_deviceExitStatus.WaitOne();
- GlRendererWidget.Dispose();
+ RendererWidget.Dispose();
}
}
@@ -1027,7 +1067,7 @@ namespace Ryujinx.Ui
private void StopEmulation_Pressed(object sender, EventArgs args)
{
- GlRendererWidget?.Exit();
+ RendererWidget?.Exit();
}
private void Installer_File_Pressed(object o, EventArgs args)
diff --git a/Ryujinx/Ui/RendererWidgetBase.cs b/Ryujinx/Ui/RendererWidgetBase.cs
new file mode 100644
index 0000000000..5270e31e13
--- /dev/null
+++ b/Ryujinx/Ui/RendererWidgetBase.cs
@@ -0,0 +1,584 @@
+using ARMeilleure.Translation;
+using ARMeilleure.Translation.PTC;
+using Gdk;
+using Gtk;
+using Ryujinx.Common;
+using Ryujinx.Common.Configuration;
+using Ryujinx.Configuration;
+using Ryujinx.Graphics.GAL;
+using Ryujinx.HLE.HOS.Services.Hid;
+using Ryujinx.Input;
+using Ryujinx.Input.HLE;
+using Ryujinx.Ui.Widgets;
+using System;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading;
+
+namespace Ryujinx.Ui
+{
+ using Key = Input.Key;
+ using Switch = HLE.Switch;
+
+ public abstract class RendererWidgetBase : DrawingArea
+ {
+ private const int SwitchPanelWidth = 1280;
+ private const int SwitchPanelHeight = 720;
+ private const int TargetFps = 60;
+
+ public ManualResetEvent WaitEvent { get; set; }
+ public NpadManager NpadManager { get; }
+ public Switch Device { get; private set; }
+ public IRenderer Renderer { get; private set; }
+
+ public static event EventHandler StatusUpdatedEvent;
+
+ private bool _isActive;
+ private bool _isStopped;
+ private bool _isFocused;
+
+ private double _mouseX;
+ private double _mouseY;
+ private bool _mousePressed;
+
+ private bool _toggleFullscreen;
+ private bool _toggleDockedMode;
+
+ private readonly long _ticksPerFrame;
+
+ private long _ticks = 0;
+
+ private readonly Stopwatch _chrono;
+
+ private KeyboardHotkeyState _prevHotkeyState;
+
+ private readonly ManualResetEvent _exitEvent;
+
+ // Hide Cursor
+ const int CursorHideIdleTime = 8; // seconds
+ private static readonly Cursor _invisibleCursor = new Cursor(Display.Default, CursorType.BlankCursor);
+ private long _lastCursorMoveTime;
+ private bool _hideCursorOnIdle;
+ private InputManager _inputManager;
+ private IKeyboard _keyboardInterface;
+ private GraphicsDebugLevel _glLogLevel;
+ private string _gpuVendorName;
+
+ public RendererWidgetBase(InputManager inputManager, GraphicsDebugLevel glLogLevel)
+ {
+ _inputManager = inputManager;
+ NpadManager = _inputManager.CreateNpadManager();
+ _keyboardInterface = (IKeyboard)_inputManager.KeyboardDriver.GetGamepad("0");
+
+ NpadManager.ReloadConfiguration(ConfigurationState.Instance.Hid.InputConfig.Value.ToList());
+
+ WaitEvent = new ManualResetEvent(false);
+
+ _glLogLevel = glLogLevel;
+
+ Destroyed += Renderer_Destroyed;
+
+ _chrono = new Stopwatch();
+
+ _ticksPerFrame = Stopwatch.Frequency / TargetFps;
+
+ AddEvents((int)(EventMask.ButtonPressMask
+ | EventMask.ButtonReleaseMask
+ | EventMask.PointerMotionMask
+ | EventMask.KeyPressMask
+ | EventMask.KeyReleaseMask));
+
+ Shown += Renderer_Shown;
+
+ _exitEvent = new ManualResetEvent(false);
+
+ _hideCursorOnIdle = ConfigurationState.Instance.HideCursorOnIdle;
+ _lastCursorMoveTime = Stopwatch.GetTimestamp();
+
+ ConfigurationState.Instance.HideCursorOnIdle.Event += HideCursorStateChanged;
+ }
+
+ public abstract void InitializeRenderer();
+
+ public abstract void SwapBuffers();
+
+ public abstract string GetGpuVendorName();
+
+ private void HideCursorStateChanged(object sender, ReactiveEventArgs state)
+ {
+ Gtk.Application.Invoke(delegate
+ {
+ _hideCursorOnIdle = state.NewValue;
+
+ if (_hideCursorOnIdle)
+ {
+ _lastCursorMoveTime = Stopwatch.GetTimestamp();
+ }
+ else
+ {
+ Window.Cursor = null;
+ }
+ });
+ }
+
+ private void Parent_FocusOutEvent(object o, Gtk.FocusOutEventArgs args)
+ {
+ _isFocused = false;
+ }
+
+ private void Parent_FocusInEvent(object o, Gtk.FocusInEventArgs args)
+ {
+ _isFocused = true;
+ }
+
+ private void Renderer_Destroyed(object sender, EventArgs e)
+ {
+ ConfigurationState.Instance.HideCursorOnIdle.Event -= HideCursorStateChanged;
+
+ NpadManager.Dispose();
+ Dispose();
+ }
+
+ private void Renderer_Shown(object sender, EventArgs e)
+ {
+ _isFocused = ParentWindow.State.HasFlag(Gdk.WindowState.Focused);
+ }
+
+ protected override bool OnButtonPressEvent(EventButton evnt)
+ {
+ _mouseX = evnt.X;
+ _mouseY = evnt.Y;
+
+ if (evnt.Button == 1)
+ {
+ _mousePressed = true;
+ }
+
+ return false;
+ }
+
+ protected override bool OnButtonReleaseEvent(EventButton evnt)
+ {
+ if (evnt.Button == 1)
+ {
+ _mousePressed = false;
+ }
+
+ return false;
+ }
+
+ protected override bool OnMotionNotifyEvent(EventMotion evnt)
+ {
+ if (evnt.Device.InputSource == InputSource.Mouse)
+ {
+ _mouseX = evnt.X;
+ _mouseY = evnt.Y;
+ }
+
+ if (_hideCursorOnIdle)
+ {
+ _lastCursorMoveTime = Stopwatch.GetTimestamp();
+ }
+
+ return false;
+ }
+
+ protected override void OnGetPreferredHeight(out int minimumHeight, out int naturalHeight)
+ {
+ Gdk.Monitor monitor = Display.GetMonitorAtWindow(Window);
+
+ // If the monitor is at least 1080p, use the Switch panel size as minimal size.
+ if (monitor.Geometry.Height >= 1080)
+ {
+ minimumHeight = SwitchPanelHeight;
+ }
+ // Otherwise, we default minimal size to 480p 16:9.
+ else
+ {
+ minimumHeight = 480;
+ }
+
+ naturalHeight = minimumHeight;
+ }
+
+ protected override void OnGetPreferredWidth(out int minimumWidth, out int naturalWidth)
+ {
+ Gdk.Monitor monitor = Display.GetMonitorAtWindow(Window);
+
+ // If the monitor is at least 1080p, use the Switch panel size as minimal size.
+ if (monitor.Geometry.Height >= 1080)
+ {
+ minimumWidth = SwitchPanelWidth;
+ }
+ // Otherwise, we default minimal size to 480p 16:9.
+ else
+ {
+ minimumWidth = 854;
+ }
+
+ naturalWidth = minimumWidth;
+ }
+
+ protected override bool OnConfigureEvent(EventConfigure evnt)
+ {
+ bool result = base.OnConfigureEvent(evnt);
+
+ Gdk.Monitor monitor = Display.GetMonitorAtWindow(Window);
+
+ Renderer?.Window.SetSize(evnt.Width * monitor.ScaleFactor, evnt.Height * monitor.ScaleFactor);
+
+ return result;
+ }
+
+ private void HandleScreenState(KeyboardStateSnapshot keyboard)
+ {
+ bool toggleFullscreen = keyboard.IsPressed(Key.F11)
+ || ((keyboard.IsPressed(Key.AltLeft)
+ || keyboard.IsPressed(Key.AltRight))
+ && keyboard.IsPressed(Key.Enter))
+ || keyboard.IsPressed(Key.Escape);
+
+ bool fullScreenToggled = ParentWindow.State.HasFlag(Gdk.WindowState.Fullscreen);
+
+ if (toggleFullscreen != _toggleFullscreen)
+ {
+ if (toggleFullscreen)
+ {
+ if (fullScreenToggled)
+ {
+ ParentWindow.Unfullscreen();
+ (Toplevel as MainWindow)?.ToggleExtraWidgets(true);
+ }
+ else
+ {
+ if (keyboard.IsPressed(Key.Escape))
+ {
+ if (!ConfigurationState.Instance.ShowConfirmExit || GtkDialog.CreateExitDialog())
+ {
+ Exit();
+ }
+ }
+ else
+ {
+ ParentWindow.Fullscreen();
+ (Toplevel as MainWindow)?.ToggleExtraWidgets(false);
+ }
+ }
+ }
+ }
+
+ _toggleFullscreen = toggleFullscreen;
+
+ bool toggleDockedMode = keyboard.IsPressed(Key.F9);
+
+ if (toggleDockedMode != _toggleDockedMode)
+ {
+ if (toggleDockedMode)
+ {
+ ConfigurationState.Instance.System.EnableDockedMode.Value =
+ !ConfigurationState.Instance.System.EnableDockedMode.Value;
+ }
+ }
+
+ _toggleDockedMode = toggleDockedMode;
+
+ if (_hideCursorOnIdle)
+ {
+ long cursorMoveDelta = Stopwatch.GetTimestamp() - _lastCursorMoveTime;
+ Window.Cursor = (cursorMoveDelta >= CursorHideIdleTime * Stopwatch.Frequency) ? _invisibleCursor : null;
+ }
+ }
+
+ public void Initialize(Switch device)
+ {
+ Device = device;
+ Renderer = Device.Gpu.Renderer;
+ }
+
+ public void Render()
+ {
+ Gtk.Window parent = Toplevel as Gtk.Window;
+ parent.Present();
+
+ InitializeRenderer();
+
+ Device.Gpu.Renderer.Initialize(_glLogLevel);
+
+ _gpuVendorName = GetGpuVendorName();
+
+ Device.Gpu.InitializeShaderCache();
+ Translator.IsReadyForTranslation.Set();
+
+ while (_isActive)
+ {
+ if (_isStopped)
+ {
+ return;
+ }
+
+ _ticks += _chrono.ElapsedTicks;
+
+ _chrono.Restart();
+
+ if (Device.WaitFifo())
+ {
+ Device.Statistics.RecordFifoStart();
+ Device.ProcessFrame();
+ Device.Statistics.RecordFifoEnd();
+ }
+
+ while (Device.ConsumeFrameAvailable())
+ {
+ Device.PresentFrame(SwapBuffers);
+ }
+
+ if (_ticks >= _ticksPerFrame)
+ {
+ string dockedMode = ConfigurationState.Instance.System.EnableDockedMode ? "Docked" : "Handheld";
+ float scale = Graphics.Gpu.GraphicsConfig.ResScale;
+ if (scale != 1)
+ {
+ dockedMode += $" ({scale}x)";
+ }
+
+ StatusUpdatedEvent?.Invoke(this, new StatusUpdatedEventArgs(
+ Device.EnableDeviceVsync,
+ dockedMode,
+ ConfigurationState.Instance.Graphics.AspectRatio.Value.ToText(),
+ $"Game: {Device.Statistics.GetGameFrameRate():00.00} FPS",
+ $"FIFO: {Device.Statistics.GetFifoPercent():0.00} %",
+ $"GPU: {_gpuVendorName}"));
+
+ _ticks = Math.Min(_ticks - _ticksPerFrame, _ticksPerFrame);
+ }
+ }
+ }
+
+ public void Start()
+ {
+ _chrono.Restart();
+
+ _isActive = true;
+
+ Gtk.Window parent = this.Toplevel as Gtk.Window;
+
+ parent.FocusInEvent += Parent_FocusInEvent;
+ parent.FocusOutEvent += Parent_FocusOutEvent;
+
+ Application.Invoke(delegate
+ {
+ parent.Present();
+
+ string titleNameSection = string.IsNullOrWhiteSpace(Device.Application.TitleName) ? string.Empty
+ : $" - {Device.Application.TitleName}";
+
+ string titleVersionSection = string.IsNullOrWhiteSpace(Device.Application.DisplayVersion) ? string.Empty
+ : $" v{Device.Application.DisplayVersion}";
+
+ string titleIdSection = string.IsNullOrWhiteSpace(Device.Application.TitleIdText) ? string.Empty
+ : $" ({Device.Application.TitleIdText.ToUpper()})";
+
+ string titleArchSection = Device.Application.TitleIs64Bit ? " (64-bit)" : " (32-bit)";
+
+ parent.Title = $"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}";
+ });
+
+ Thread renderLoopThread = new Thread(Render)
+ {
+ Name = "GUI.RenderLoop"
+ };
+ renderLoopThread.Start();
+
+ Thread nvStutterWorkaround = new Thread(NVStutterWorkaround)
+ {
+ Name = "GUI.NVStutterWorkaround"
+ };
+ nvStutterWorkaround.Start();
+
+ MainLoop();
+
+ renderLoopThread.Join();
+ nvStutterWorkaround.Join();
+
+ Exit();
+ }
+
+ public void Exit()
+ {
+ NpadManager?.Dispose();
+
+ if (_isStopped)
+ {
+ return;
+ }
+
+ _isStopped = true;
+ _isActive = false;
+
+ _exitEvent.WaitOne();
+ _exitEvent.Dispose();
+ }
+
+ private void NVStutterWorkaround()
+ {
+ while (_isActive)
+ {
+ // When NVIDIA Threaded Optimization is on, the driver will snapshot all threads in the system whenever the application creates any new ones.
+ // The ThreadPool has something called a "GateThread" which terminates itself after some inactivity.
+ // However, it immediately starts up again, since the rules regarding when to terminate and when to start differ.
+ // This creates a new thread every second or so.
+ // The main problem with this is that the thread snapshot can take 70ms, is on the OpenGL thread and will delay rendering any graphics.
+ // This is a little over budget on a frame time of 16ms, so creates a large stutter.
+ // The solution is to keep the ThreadPool active so that it never has a reason to terminate the GateThread.
+
+ // TODO: This should be removed when the issue with the GateThread is resolved.
+
+ ThreadPool.QueueUserWorkItem((state) => { });
+ Thread.Sleep(300);
+ }
+ }
+
+ public void MainLoop()
+ {
+ while (_isActive)
+ {
+ UpdateFrame();
+
+ // Polling becomes expensive if it's not slept
+ Thread.Sleep(1);
+ }
+
+ _exitEvent.Set();
+ }
+
+ private bool UpdateFrame()
+ {
+ if (!_isActive)
+ {
+ return true;
+ }
+
+ if (_isStopped)
+ {
+ return false;
+ }
+
+ if (_isFocused)
+ {
+ Gtk.Application.Invoke(delegate
+ {
+ KeyboardStateSnapshot keyboard = _keyboardInterface.GetKeyboardStateSnapshot();
+
+ HandleScreenState(keyboard);
+
+ if (keyboard.IsPressed(Key.Delete))
+ {
+ if (!ParentWindow.State.HasFlag(WindowState.Fullscreen))
+ {
+ Ptc.Continue();
+ }
+ }
+ });
+ }
+
+ NpadManager.Update(Device.Hid, Device.TamperMachine);
+
+ if (_isFocused)
+ {
+ KeyboardHotkeyState currentHotkeyState = GetHotkeyState();
+
+ if (currentHotkeyState.HasFlag(KeyboardHotkeyState.ToggleVSync) &&
+ !_prevHotkeyState.HasFlag(KeyboardHotkeyState.ToggleVSync))
+ {
+ Device.EnableDeviceVsync = !Device.EnableDeviceVsync;
+ }
+
+ _prevHotkeyState = currentHotkeyState;
+ }
+
+ // Touchscreen
+ bool hasTouch = false;
+
+ // Get screen touch position from left mouse click
+ // OpenTK always captures mouse events, even if out of focus, so check if window is focused.
+ if (_isFocused && _mousePressed)
+ {
+ float aspectWidth = SwitchPanelHeight * ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat();
+
+ int screenWidth = AllocatedWidth;
+ int screenHeight = AllocatedHeight;
+
+ if (AllocatedWidth > AllocatedHeight * aspectWidth / SwitchPanelHeight)
+ {
+ screenWidth = (int)(AllocatedHeight * aspectWidth) / SwitchPanelHeight;
+ }
+ else
+ {
+ screenHeight = (AllocatedWidth * SwitchPanelHeight) / (int)aspectWidth;
+ }
+
+ int startX = (AllocatedWidth - screenWidth) >> 1;
+ int startY = (AllocatedHeight - screenHeight) >> 1;
+
+ int endX = startX + screenWidth;
+ int endY = startY + screenHeight;
+
+ if (_mouseX >= startX &&
+ _mouseY >= startY &&
+ _mouseX < endX &&
+ _mouseY < endY)
+ {
+ int screenMouseX = (int)_mouseX - startX;
+ int screenMouseY = (int)_mouseY - startY;
+
+ int mX = (screenMouseX * (int)aspectWidth) / screenWidth;
+ int mY = (screenMouseY * SwitchPanelHeight) / screenHeight;
+
+ TouchPoint currentPoint = new TouchPoint
+ {
+ X = (uint)mX,
+ Y = (uint)mY,
+
+ // Placeholder values till more data is acquired
+ DiameterX = 10,
+ DiameterY = 10,
+ Angle = 90
+ };
+
+ hasTouch = true;
+
+ Device.Hid.Touchscreen.Update(currentPoint);
+ }
+ }
+
+ if (!hasTouch)
+ {
+ Device.Hid.Touchscreen.Update();
+ }
+
+ Device.Hid.DebugPad.Update();
+
+ return true;
+ }
+
+
+ [Flags]
+ private enum KeyboardHotkeyState
+ {
+ None,
+ ToggleVSync
+ }
+
+ private KeyboardHotkeyState GetHotkeyState()
+ {
+ KeyboardHotkeyState state = KeyboardHotkeyState.None;
+
+ if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ToggleVsync))
+ {
+ state |= KeyboardHotkeyState.ToggleVSync;
+ }
+
+ return state;
+ }
+ }
+}
diff --git a/Ryujinx/Ui/SPBOpenGLContext.cs b/Ryujinx/Ui/SPBOpenGLContext.cs
index c2b5d6383d..e1a315c9d1 100644
--- a/Ryujinx/Ui/SPBOpenGLContext.cs
+++ b/Ryujinx/Ui/SPBOpenGLContext.cs
@@ -34,7 +34,7 @@ namespace Ryujinx.Ui
public static SPBOpenGLContext CreateBackgroundContext(OpenGLContextBase sharedContext)
{
OpenGLContextBase context = PlatformHelper.CreateOpenGLContext(FramebufferFormat.Default, 3, 3, OpenGLContextFlags.Compat, true, sharedContext);
- NativeWindowBase window = PlatformHelper.CreateWindow(FramebufferFormat.Default, 0, 0, 100, 100);
+ NativeWindowBase window = PlatformHelper.CreateOpenGLWindow(FramebufferFormat.Default, 0, 0, 100, 100);
context.Initialize(window);
context.MakeCurrent(window);
diff --git a/Ryujinx/Ui/VKRenderer.cs b/Ryujinx/Ui/VKRenderer.cs
new file mode 100644
index 0000000000..7b01f709ce
--- /dev/null
+++ b/Ryujinx/Ui/VKRenderer.cs
@@ -0,0 +1,80 @@
+using Gdk;
+using Gtk;
+using Ryujinx.Common.Configuration;
+using Ryujinx.Input.HLE;
+using SPB.Graphics.Vulkan;
+using SPB.Platform.Win32;
+using SPB.Platform.X11;
+using SPB.Windowing;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Ui
+{
+ public class VKRenderer : RendererWidgetBase
+ {
+ public NativeWindowBase NativeWindow { get; private set; }
+
+ public VKRenderer(InputManager inputManager, GraphicsDebugLevel glLogLevel) : base(inputManager, glLogLevel) { }
+
+ private NativeWindowBase RetrieveNativeWindow()
+ {
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ IntPtr windowHandle = gdk_win32_window_get_handle(Window.Handle);
+
+ return new SimpleWin32Window(new NativeHandle(windowHandle));
+ }
+ else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ {
+ IntPtr displayHandle = gdk_x11_display_get_xdisplay(Display.Handle);
+ IntPtr windowHandle = gdk_x11_window_get_xid(Window.Handle);
+
+ return new SimpleX11Window(new NativeHandle(displayHandle), new NativeHandle(windowHandle));
+ }
+
+ throw new NotImplementedException();
+ }
+
+ [DllImport("libgdk-3-0.dll")]
+ private static extern IntPtr gdk_win32_window_get_handle(IntPtr d);
+
+ [DllImport("libgdk-3.so.0")]
+ private static extern IntPtr gdk_x11_display_get_xdisplay(IntPtr gdkDisplay);
+
+ [DllImport("libgdk-3.so.0")]
+ private static extern IntPtr gdk_x11_window_get_xid(IntPtr gdkWindow);
+
+ protected override bool OnConfigureEvent(EventConfigure evnt)
+ {
+ if (NativeWindow == null)
+ {
+ NativeWindow = RetrieveNativeWindow();
+
+ WaitEvent.Set();
+ }
+
+ return base.OnConfigureEvent(evnt);
+ }
+
+ public unsafe IntPtr CreateWindowSurface(IntPtr instance)
+ {
+ return VulkanHelper.CreateWindowSurface(instance, NativeWindow);
+ }
+
+ public override void InitializeRenderer() { }
+
+ public override void SwapBuffers() { }
+
+ public override string GetGpuVendorName()
+ {
+ return "Vulkan (Unknown)";
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ Device.DisposeGpu();
+ NpadManager.Dispose();
+ }
+ }
+}
diff --git a/Ryujinx/Ui/Windows/ControllerWindow.cs b/Ryujinx/Ui/Windows/ControllerWindow.cs
index 6e876ad9c5..2732bdcbde 100644
--- a/Ryujinx/Ui/Windows/ControllerWindow.cs
+++ b/Ryujinx/Ui/Windows/ControllerWindow.cs
@@ -187,9 +187,9 @@ namespace Ryujinx.Ui.Windows
mainWindow.InputManager.GamepadDriver.OnGamepadConnected += HandleOnGamepadConnected;
mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected;
- if (_mainWindow.GlRendererWidget != null)
+ if (_mainWindow.RendererWidget != null)
{
- _mainWindow.GlRendererWidget.NpadManager.BlockInputUpdates();
+ _mainWindow.RendererWidget.NpadManager.BlockInputUpdates();
}
}
@@ -219,9 +219,9 @@ namespace Ryujinx.Ui.Windows
_mainWindow.InputManager.GamepadDriver.OnGamepadConnected -= HandleOnGamepadConnected;
_mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected -= HandleOnGamepadDisconnected;
- if (_mainWindow.GlRendererWidget != null)
+ if (_mainWindow.RendererWidget != null)
{
- _mainWindow.GlRendererWidget.NpadManager.UnblockInputUpdates();
+ _mainWindow.RendererWidget.NpadManager.UnblockInputUpdates();
}
_selectedGamepad?.Dispose();
@@ -1141,9 +1141,9 @@ namespace Ryujinx.Ui.Windows
}
}
- if (_mainWindow.GlRendererWidget != null)
+ if (_mainWindow.RendererWidget != null)
{
- _mainWindow.GlRendererWidget.NpadManager.ReloadConfiguration(newConfig);
+ _mainWindow.RendererWidget.NpadManager.ReloadConfiguration(newConfig);
}
// Atomically replace and signal input change.