From 3aa3c4261a57e0daa595decbe689973bd0be473d Mon Sep 17 00:00:00 2001 From: emmauss Date: Wed, 12 Feb 2020 00:56:19 +0000 Subject: [PATCH] Add inbuilt Opengl renderer to window (#922) * add gl rendering widget * embed renderer into main window * add input * fix mouse input * fix mouse coords * refresh game list after closing game, remove profiler method * rebase, hide game list progress bar while game is running * Some bug fixes Changelog: - Reapply some changes that got lost while rebasing from #904 - Make sure to guarantee exclusivity on the GL context (fixing multiple possible race conditions on Windows) - Avoid making GLRenderer disposed multiple time * add fullscreen, enable input on focus, disable aplha * addressed comments * Disable transparency in the window * fix fullscreen state, fix focus, addressed comments * nit * addressed nit Co-authored-by: Thog --- Ryujinx.Audio/Ryujinx.Audio.csproj | 2 +- Ryujinx.Common/Ryujinx.Common.csproj | 3 +- .../Ryujinx.Graphics.OpenGL.csproj | 2 +- Ryujinx.sln | 11 +- Ryujinx/Program.cs | 7 + Ryujinx/Ryujinx.csproj | 3 +- Ryujinx/Ui/GLRenderer.cs | 499 ++++++++++++++++++ Ryujinx/Ui/GLScreen.cs | 375 ------------- Ryujinx/Ui/MainWindow.cs | 147 ++++-- Ryujinx/Ui/MainWindow.glade | 129 +++-- Ryujinx/Ui/ScopedGlContext.cs | 35 ++ 11 files changed, 744 insertions(+), 469 deletions(-) create mode 100644 Ryujinx/Ui/GLRenderer.cs delete mode 100644 Ryujinx/Ui/GLScreen.cs create mode 100644 Ryujinx/Ui/ScopedGlContext.cs diff --git a/Ryujinx.Audio/Ryujinx.Audio.csproj b/Ryujinx.Audio/Ryujinx.Audio.csproj index b541043c..80940c77 100644 --- a/Ryujinx.Audio/Ryujinx.Audio.csproj +++ b/Ryujinx.Audio/Ryujinx.Audio.csproj @@ -27,7 +27,7 @@ - + diff --git a/Ryujinx.Common/Ryujinx.Common.csproj b/Ryujinx.Common/Ryujinx.Common.csproj index 43a853a4..1b7cf8af 100644 --- a/Ryujinx.Common/Ryujinx.Common.csproj +++ b/Ryujinx.Common/Ryujinx.Common.csproj @@ -28,7 +28,8 @@ - + + diff --git a/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj b/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj index f2a93777..27540055 100644 --- a/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj +++ b/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj @@ -7,7 +7,7 @@ - + diff --git a/Ryujinx.sln b/Ryujinx.sln index f023368b..40a086e5 100644 --- a/Ryujinx.sln +++ b/Ryujinx.sln @@ -32,7 +32,8 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Shader", "Ryujinx.Graphics.Shader\Ryujinx.Graphics.Shader.csproj", "{03B955CD-AD84-4B93-AAA7-BF17923BBAA5}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Graphics.Nvdec", "Ryujinx.Graphics.Nvdec\Ryujinx.Graphics.Nvdec.csproj", "{85A0FA56-DC01-4A42-8808-70DAC76BD66D}" -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Debugger", "Ryujinx.Debugger\Ryujinx.Debugger.csproj", "{2E02B7F3-245E-43B1-AE5B-44167A0FDA20}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Debugger", "Ryujinx.Debugger\Ryujinx.Debugger.csproj", "{79E4EE34-9C5F-4BE6-8529-A49D32B5B0CC}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -170,6 +171,14 @@ Global {2E02B7F3-245E-43B1-AE5B-44167A0FDA20}.Profile Release|Any CPU.Build.0 = Profile Release|Any CPU {2E02B7F3-245E-43B1-AE5B-44167A0FDA20}.Release|Any CPU.ActiveCfg = Release|Any CPU {2E02B7F3-245E-43B1-AE5B-44167A0FDA20}.Release|Any CPU.Build.0 = Release|Any CPU + {79E4EE34-9C5F-4BE6-8529-A49D32B5B0CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {79E4EE34-9C5F-4BE6-8529-A49D32B5B0CC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {79E4EE34-9C5F-4BE6-8529-A49D32B5B0CC}.Profile Debug|Any CPU.ActiveCfg = Profile Debug|Any CPU + {79E4EE34-9C5F-4BE6-8529-A49D32B5B0CC}.Profile Debug|Any CPU.Build.0 = Profile Debug|Any CPU + {79E4EE34-9C5F-4BE6-8529-A49D32B5B0CC}.Profile Release|Any CPU.ActiveCfg = Profile Release|Any CPU + {79E4EE34-9C5F-4BE6-8529-A49D32B5B0CC}.Profile Release|Any CPU.Build.0 = Profile Release|Any CPU + {79E4EE34-9C5F-4BE6-8529-A49D32B5B0CC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {79E4EE34-9C5F-4BE6-8529-A49D32B5B0CC}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Ryujinx/Program.cs b/Ryujinx/Program.cs index 24fbb9b8..4aaa5e9f 100644 --- a/Ryujinx/Program.cs +++ b/Ryujinx/Program.cs @@ -3,6 +3,7 @@ using Ryujinx.Common.Logging; using Ryujinx.Configuration; using Ryujinx.Debugger.Profiler; using Ryujinx.Ui; +using OpenTK; using System; using System.IO; @@ -12,6 +13,12 @@ namespace Ryujinx { static void Main(string[] args) { + Toolkit.Init(new ToolkitOptions + { + Backend = PlatformBackend.PreferNative, + EnableHighResolution = true + }); + Console.Title = "Ryujinx Console"; string systemPath = Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.Machine); diff --git a/Ryujinx/Ryujinx.csproj b/Ryujinx/Ryujinx.csproj index cc8e6d54..bde01b24 100644 --- a/Ryujinx/Ryujinx.csproj +++ b/Ryujinx/Ryujinx.csproj @@ -72,9 +72,10 @@ + - + diff --git a/Ryujinx/Ui/GLRenderer.cs b/Ryujinx/Ui/GLRenderer.cs new file mode 100644 index 00000000..dff72b36 --- /dev/null +++ b/Ryujinx/Ui/GLRenderer.cs @@ -0,0 +1,499 @@ +using Gdk; +using OpenTK; +using OpenTK.Graphics; +using OpenTK.Graphics.OpenGL; +using OpenTK.Input; +using OpenTK.Platform; +using Ryujinx.Configuration; +using Ryujinx.Graphics.OpenGL; +using Ryujinx.HLE; +using Ryujinx.HLE.Input; +using Ryujinx.Ui; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; + +namespace Ryujinx.Ui +{ + public class GLRenderer : GLWidget + { + private const int TouchScreenWidth = 1280; + private const int TouchScreenHeight = 720; + private const int TargetFps = 60; + + public ManualResetEvent WaitEvent { get; set; } + + public bool IsActive { get; set; } + public bool IsStopped { get; set; } + public bool IsFocused { get; set; } + + private double _mouseX; + private double _mouseY; + private bool _mousePressed; + + private bool _titleEvent; + + private bool _toggleFullscreen; + + private string _newTitle; + + private readonly long _ticksPerFrame; + + private long _ticks = 0; + + private System.Diagnostics.Stopwatch _chrono; + + private Switch _device; + + private Renderer _renderer; + + private HotkeyButtons _prevHotkeyButtons = 0; + + private Input.NpadController _primaryController; + + public GLRenderer(Switch device) + : base (new GraphicsMode(new ColorFormat(24)), + 3, 3, + GraphicsContextFlags.ForwardCompatible) + { + WaitEvent = new ManualResetEvent(false); + + _device = device; + + this.Initialized += GLRenderer_Initialized; + this.Destroyed += GLRenderer_Destroyed; + + Initialize(); + + _chrono = new System.Diagnostics.Stopwatch(); + + _ticksPerFrame = System.Diagnostics.Stopwatch.Frequency / TargetFps; + + _primaryController = new Input.NpadController(ConfigurationState.Instance.Hid.JoystickControls); + + AddEvents((int)(Gdk.EventMask.ButtonPressMask + | Gdk.EventMask.ButtonReleaseMask + | Gdk.EventMask.PointerMotionMask + | Gdk.EventMask.KeyPressMask + | Gdk.EventMask.KeyReleaseMask)); + + this.Shown += Renderer_Shown; + } + + 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) + { + Exit(); + + this.Dispose(); + } + + protected void Renderer_Shown(object sender, EventArgs e) + { + IsFocused = this.ParentWindow.State.HasFlag(Gdk.WindowState.Focused); + } + + public void HandleScreenState(KeyboardState keyboard) + { + bool toggleFullscreen = keyboard.IsKeyDown(OpenTK.Input.Key.F11) + || ((keyboard.IsKeyDown(OpenTK.Input.Key.AltLeft) + || keyboard.IsKeyDown(OpenTK.Input.Key.AltRight)) + && keyboard.IsKeyDown(OpenTK.Input.Key.Enter)); + + if (toggleFullscreen == _toggleFullscreen) + { + return; + } + + _toggleFullscreen = toggleFullscreen; + + Gtk.Application.Invoke(delegate + { + if (this.ParentWindow.State.HasFlag(Gdk.WindowState.Fullscreen)) + { + if (keyboard.IsKeyDown(OpenTK.Input.Key.Escape) || _toggleFullscreen) + { + this.ParentWindow.Unfullscreen(); + (this.Toplevel as MainWindow)?.ToggleExtraWidgets(true); + } + } + else + { + if (keyboard.IsKeyDown(OpenTK.Input.Key.Escape)) + { + Exit(); + } + + if (_toggleFullscreen) + { + this.ParentWindow.Fullscreen(); + (this.Toplevel as MainWindow)?.ToggleExtraWidgets(false); + } + } + }); + } + + private void GLRenderer_Initialized(object sender, EventArgs e) + { + // Release the GL exclusivity that OpenTK gave us. + GraphicsContext.MakeCurrent(null); + + WaitEvent.Set(); + } + + protected override bool OnConfigureEvent(EventConfigure evnt) + { + var result = base.OnConfigureEvent(evnt); + + _renderer.Window.SetSize(AllocatedWidth, AllocatedHeight); + + return result; + } + + public void Start() + { + IsRenderHandler = true; + + _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(); + }); + + Thread renderLoopThread = new Thread(Render) + { + Name = "GUI.RenderLoop" + }; + renderLoopThread.Start(); + + MainLoop(); + + renderLoopThread.Join(); + + Exit(); + } + + 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; + } + + return false; + } + + public void Exit() + { + if (IsStopped) + { + return; + } + + IsStopped = true; + IsActive = false; + + using (ScopedGlContext scopedGLContext = new ScopedGlContext(WindowInfo, GraphicsContext)) + { + _device.DisposeGpu(); + } + + WaitEvent.Set(); + } + + public void Initialize() + { + if (!(_device.Gpu.Renderer is Renderer)) + { + throw new NotSupportedException($"GPU renderer must be an OpenGL renderer when using GLRenderer!"); + } + + _renderer = (Renderer)_device.Gpu.Renderer; + } + + public void Render() + { + using (ScopedGlContext scopedGLContext = new ScopedGlContext(WindowInfo, GraphicsContext)) + { + _renderer.Initialize(); + + SwapBuffers(); + } + + while (IsActive) + { + if (IsStopped) + { + return; + } + + using (ScopedGlContext scopedGLContext = new ScopedGlContext(WindowInfo, GraphicsContext)) + { + _ticks += _chrono.ElapsedTicks; + + _chrono.Restart(); + + if (_device.WaitFifo()) + { + _device.ProcessFrame(); + } + + if (_ticks >= _ticksPerFrame) + { + _device.PresentFrame(SwapBuffers); + + _device.Statistics.RecordSystemFrameTime(); + + double hostFps = _device.Statistics.GetSystemFrameRate(); + double gameFps = _device.Statistics.GetGameFrameRate(); + + string titleNameSection = string.IsNullOrWhiteSpace(_device.System.TitleName) ? string.Empty + : " | " + _device.System.TitleName; + + string titleIdSection = string.IsNullOrWhiteSpace(_device.System.TitleIdText) ? string.Empty + : " | " + _device.System.TitleIdText.ToUpper(); + + _newTitle = $"Ryujinx{titleNameSection}{titleIdSection} | Host FPS: {hostFps:0.0} | Game FPS: {gameFps:0.0} | " + + $"Game Vsync: {(_device.EnableDeviceVsync ? "On" : "Off")}"; + + _titleEvent = true; + + _device.System.SignalVsync(); + + _device.VsyncEvent.Set(); + + _ticks = Math.Min(_ticks - _ticksPerFrame, _ticksPerFrame); + } + } + } + } + + public void SwapBuffers() + { + OpenTK.Graphics.GraphicsContext.CurrentContext.SwapBuffers(); + } + + public void MainLoop() + { + while (IsActive) + { + if (_titleEvent) + { + _titleEvent = false; + + Gtk.Application.Invoke(delegate + { + this.ParentWindow.Title = _newTitle; + }); + } + + if (IsFocused) + { + UpdateFrame(); + } + + // Polling becomes expensive if it's not slept + Thread.Sleep(1); + } + } + + private bool UpdateFrame() + { + if (!IsActive) + { + return true; + } + + if (IsStopped) + { + return false; + } + + HotkeyButtons currentHotkeyButtons = 0; + ControllerButtons currentButton = 0; + JoystickPosition leftJoystick; + JoystickPosition rightJoystick; + HLE.Input.Keyboard? hidKeyboard = null; + + KeyboardState keyboard = OpenTK.Input.Keyboard.GetState(); + + Gtk.Application.Invoke(delegate + { + HandleScreenState(keyboard); + }); + + int leftJoystickDx = 0; + int leftJoystickDy = 0; + int rightJoystickDx = 0; + int rightJoystickDy = 0; + + // Normal Input + currentHotkeyButtons = KeyboardControls.GetHotkeyButtons(ConfigurationState.Instance.Hid.KeyboardControls, keyboard); + currentButton = KeyboardControls.GetButtons(ConfigurationState.Instance.Hid.KeyboardControls, keyboard); + + if (ConfigurationState.Instance.Hid.EnableKeyboard) + { + hidKeyboard = KeyboardControls.GetKeysDown(ConfigurationState.Instance.Hid.KeyboardControls, keyboard); + } + + (leftJoystickDx, leftJoystickDy) = KeyboardControls.GetLeftStick(ConfigurationState.Instance.Hid.KeyboardControls, keyboard); + (rightJoystickDx, rightJoystickDy) = KeyboardControls.GetRightStick(ConfigurationState.Instance.Hid.KeyboardControls, keyboard); + + if (!hidKeyboard.HasValue) + { + hidKeyboard = new HLE.Input.Keyboard + { + Modifier = 0, + Keys = new int[0x8] + }; + } + + currentButton |= _primaryController.GetButtons(); + + // Keyboard has priority stick-wise + if (leftJoystickDx == 0 && leftJoystickDy == 0) + { + (leftJoystickDx, leftJoystickDy) = _primaryController.GetLeftStick(); + } + + if (rightJoystickDx == 0 && rightJoystickDy == 0) + { + (rightJoystickDx, rightJoystickDy) = _primaryController.GetRightStick(); + } + + leftJoystick = new JoystickPosition + { + Dx = leftJoystickDx, + Dy = leftJoystickDy + }; + + rightJoystick = new JoystickPosition + { + Dx = rightJoystickDx, + Dy = rightJoystickDy + }; + + currentButton |= _device.Hid.UpdateStickButtons(leftJoystick, rightJoystick); + + 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) + { + int screenWidth = AllocatedWidth; + int screenHeight = AllocatedHeight; + + if (AllocatedWidth > (AllocatedHeight * TouchScreenWidth) / TouchScreenHeight) + { + screenWidth = (AllocatedHeight * TouchScreenWidth) / TouchScreenHeight; + } + else + { + screenHeight = (AllocatedWidth * TouchScreenHeight) / TouchScreenWidth; + } + + 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 * TouchScreenWidth) / screenWidth; + int mY = (screenMouseY * TouchScreenHeight) / screenHeight; + + TouchPoint currentPoint = new TouchPoint + { + X = mX, + Y = mY, + + // Placeholder values till more data is acquired + DiameterX = 10, + DiameterY = 10, + Angle = 90 + }; + + hasTouch = true; + + _device.Hid.SetTouchPoints(currentPoint); + } + } + + if (!hasTouch) + { + _device.Hid.SetTouchPoints(); + } + + if (ConfigurationState.Instance.Hid.EnableKeyboard && hidKeyboard.HasValue) + { + _device.Hid.WriteKeyboard(hidKeyboard.Value); + } + + BaseController controller = _device.Hid.PrimaryController; + + controller.SendInput(currentButton, leftJoystick, rightJoystick); + + // Toggle vsync + if (currentHotkeyButtons.HasFlag(HotkeyButtons.ToggleVSync) && + !_prevHotkeyButtons.HasFlag(HotkeyButtons.ToggleVSync)) + { + _device.EnableDeviceVsync = !_device.EnableDeviceVsync; + } + + _prevHotkeyButtons = currentHotkeyButtons; + + return true; + } + } +} diff --git a/Ryujinx/Ui/GLScreen.cs b/Ryujinx/Ui/GLScreen.cs deleted file mode 100644 index 5e83458a..00000000 --- a/Ryujinx/Ui/GLScreen.cs +++ /dev/null @@ -1,375 +0,0 @@ -using OpenTK; -using OpenTK.Graphics; -using OpenTK.Input; -using Ryujinx.Configuration; -using Ryujinx.Graphics.OpenGL; -using Ryujinx.HLE; -using Ryujinx.HLE.Input; -using System; -using System.Threading; - -using Stopwatch = System.Diagnostics.Stopwatch; - -namespace Ryujinx.Ui -{ - public class GlScreen : GameWindow - { - private const int TouchScreenWidth = 1280; - private const int TouchScreenHeight = 720; - - private const int TargetFps = 60; - - private Switch _device; - - private Renderer _renderer; - - private HotkeyButtons _prevHotkeyButtons = 0; - - private KeyboardState? _keyboard = null; - - private MouseState? _mouse = null; - - private Input.NpadController _primaryController; - - private Thread _renderThread; - - private bool _resizeEvent; - - private bool _titleEvent; - - private string _newTitle; - - public GlScreen(Switch device) - : base(1280, 720, - new GraphicsMode(), "Ryujinx", 0, - DisplayDevice.Default, 3, 3, - GraphicsContextFlags.ForwardCompatible) - { - _device = device; - - if (!(device.Gpu.Renderer is Renderer)) - { - throw new NotSupportedException($"GPU renderer must be an OpenGL renderer when using GlScreen!"); - } - - _renderer = (Renderer)device.Gpu.Renderer; - - _primaryController = new Input.NpadController(ConfigurationState.Instance.Hid.JoystickControls); - - Location = new Point( - (DisplayDevice.Default.Width / 2) - (Width / 2), - (DisplayDevice.Default.Height / 2) - (Height / 2)); - } - - private void RenderLoop() - { - MakeCurrent(); - - _renderer.Initialize(); - - Stopwatch chrono = new Stopwatch(); - - chrono.Start(); - - long ticksPerFrame = Stopwatch.Frequency / TargetFps; - - long ticks = 0; - - while (Exists && !IsExiting) - { - if (_device.WaitFifo()) - { - _device.ProcessFrame(); - } - - if (_resizeEvent) - { - _resizeEvent = false; - - _renderer.Window.SetSize(Width, Height); - } - - ticks += chrono.ElapsedTicks; - - chrono.Restart(); - - if (ticks >= ticksPerFrame) - { - RenderFrame(); - - // Queue max. 1 vsync - ticks = Math.Min(ticks - ticksPerFrame, ticksPerFrame); - } - } - - _device.DisposeGpu(); - } - - public void MainLoop() - { - VSync = VSyncMode.Off; - - Visible = true; - - Context.MakeCurrent(null); - - // OpenTK doesn't like sleeps in its thread, to avoid this a renderer thread is created - _renderThread = new Thread(RenderLoop) - { - Name = "GUI.RenderThread" - }; - - _renderThread.Start(); - - while (Exists && !IsExiting) - { - ProcessEvents(); - - if (!IsExiting) - { - UpdateFrame(); - - if (_titleEvent) - { - _titleEvent = false; - - Title = _newTitle; - } - } - - // Polling becomes expensive if it's not slept - Thread.Sleep(1); - } - } - - private new void UpdateFrame() - { - HotkeyButtons currentHotkeyButtons = 0; - ControllerButtons currentButton = 0; - JoystickPosition leftJoystick; - JoystickPosition rightJoystick; - HLE.Input.Keyboard? hidKeyboard = null; - - int leftJoystickDx = 0; - int leftJoystickDy = 0; - int rightJoystickDx = 0; - int rightJoystickDy = 0; - - // Keyboard Input - if (_keyboard.HasValue) - { - KeyboardState keyboard = _keyboard.Value; - - // Normal Input - currentHotkeyButtons = KeyboardControls.GetHotkeyButtons(ConfigurationState.Instance.Hid.KeyboardControls, keyboard); - currentButton = KeyboardControls.GetButtons(ConfigurationState.Instance.Hid.KeyboardControls, keyboard); - - if (ConfigurationState.Instance.Hid.EnableKeyboard) - { - hidKeyboard = KeyboardControls.GetKeysDown(ConfigurationState.Instance.Hid.KeyboardControls, keyboard); - } - - (leftJoystickDx, leftJoystickDy) = KeyboardControls.GetLeftStick(ConfigurationState.Instance.Hid.KeyboardControls, keyboard); - (rightJoystickDx, rightJoystickDy) = KeyboardControls.GetRightStick(ConfigurationState.Instance.Hid.KeyboardControls, keyboard); - } - - if (!hidKeyboard.HasValue) - { - hidKeyboard = new HLE.Input.Keyboard - { - Modifier = 0, - Keys = new int[0x8] - }; - } - - currentButton |= _primaryController.GetButtons(); - - // Keyboard has priority stick-wise - if (leftJoystickDx == 0 && leftJoystickDy == 0) - { - (leftJoystickDx, leftJoystickDy) = _primaryController.GetLeftStick(); - } - - if (rightJoystickDx == 0 && rightJoystickDy == 0) - { - (rightJoystickDx, rightJoystickDy) = _primaryController.GetRightStick(); - } - - leftJoystick = new JoystickPosition - { - Dx = leftJoystickDx, - Dy = leftJoystickDy - }; - - rightJoystick = new JoystickPosition - { - Dx = rightJoystickDx, - Dy = rightJoystickDy - }; - - currentButton |= _device.Hid.UpdateStickButtons(leftJoystick, rightJoystick); - - 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 (Focused && _mouse?.LeftButton == ButtonState.Pressed) - { - MouseState mouse = _mouse.Value; - - int scrnWidth = Width; - int scrnHeight = Height; - - if (Width > (Height * TouchScreenWidth) / TouchScreenHeight) - { - scrnWidth = (Height * TouchScreenWidth) / TouchScreenHeight; - } - else - { - scrnHeight = (Width * TouchScreenHeight) / TouchScreenWidth; - } - - int startX = (Width - scrnWidth) >> 1; - int startY = (Height - scrnHeight) >> 1; - - int endX = startX + scrnWidth; - int endY = startY + scrnHeight; - - if (mouse.X >= startX && - mouse.Y >= startY && - mouse.X < endX && - mouse.Y < endY) - { - int scrnMouseX = mouse.X - startX; - int scrnMouseY = mouse.Y - startY; - - int mX = (scrnMouseX * TouchScreenWidth) / scrnWidth; - int mY = (scrnMouseY * TouchScreenHeight) / scrnHeight; - - TouchPoint currentPoint = new TouchPoint - { - X = mX, - Y = mY, - - // Placeholder values till more data is acquired - DiameterX = 10, - DiameterY = 10, - Angle = 90 - }; - - hasTouch = true; - - _device.Hid.SetTouchPoints(currentPoint); - } - } - - if (!hasTouch) - { - _device.Hid.SetTouchPoints(); - } - - if (ConfigurationState.Instance.Hid.EnableKeyboard && hidKeyboard.HasValue) - { - _device.Hid.WriteKeyboard(hidKeyboard.Value); - } - - BaseController controller = _device.Hid.PrimaryController; - - controller.SendInput(currentButton, leftJoystick, rightJoystick); - - // Toggle vsync - if (currentHotkeyButtons.HasFlag(HotkeyButtons.ToggleVSync) && - !_prevHotkeyButtons.HasFlag(HotkeyButtons.ToggleVSync)) - { - _device.EnableDeviceVsync = !_device.EnableDeviceVsync; - } - - _prevHotkeyButtons = currentHotkeyButtons; - } - - private new void RenderFrame() - { - _device.PresentFrame(SwapBuffers); - - _device.Statistics.RecordSystemFrameTime(); - - double hostFps = _device.Statistics.GetSystemFrameRate(); - double gameFps = _device.Statistics.GetGameFrameRate(); - - string titleNameSection = string.IsNullOrWhiteSpace(_device.System.TitleName) ? string.Empty - : " | " + _device.System.TitleName; - - string titleIdSection = string.IsNullOrWhiteSpace(_device.System.TitleIdText) ? string.Empty - : " | " + _device.System.TitleIdText.ToUpper(); - - _newTitle = $"Ryujinx{titleNameSection}{titleIdSection} | Host FPS: {hostFps:0.0} | Game FPS: {gameFps:0.0} | " + - $"Game Vsync: {(_device.EnableDeviceVsync ? "On" : "Off")}"; - - _titleEvent = true; - - _device.System.SignalVsync(); - - _device.VsyncEvent.Set(); - } - - protected override void OnUnload(EventArgs e) - { - _renderThread.Join(); - - base.OnUnload(e); - } - - protected override void OnResize(EventArgs e) - { - _resizeEvent = true; - } - - protected override void OnKeyDown(KeyboardKeyEventArgs e) - { - bool toggleFullscreen = e.Key == Key.F11 || - (e.Modifiers.HasFlag(KeyModifiers.Alt) && e.Key == Key.Enter); - - if (WindowState == WindowState.Fullscreen) - { - if (e.Key == Key.Escape || toggleFullscreen) - { - WindowState = WindowState.Normal; - } - } - else - { - if (e.Key == Key.Escape) - { - Exit(); - } - - if (toggleFullscreen) - { - WindowState = WindowState.Fullscreen; - } - } - - _keyboard = e.Keyboard; - } - - protected override void OnKeyUp(KeyboardKeyEventArgs e) - { - _keyboard = e.Keyboard; - } - - protected override void OnMouseDown(MouseButtonEventArgs e) - { - _mouse = e.Mouse; - } - - protected override void OnMouseUp(MouseButtonEventArgs e) - { - _mouse = e.Mouse; - } - - protected override void OnMouseMove(MouseMoveEventArgs e) - { - _mouse = e.Mouse; - } - } -} diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs index 734103fe..6c771bb9 100644 --- a/Ryujinx/Ui/MainWindow.cs +++ b/Ryujinx/Ui/MainWindow.cs @@ -8,7 +8,6 @@ using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.OpenGL; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem.Content; -using Ryujinx.HLE.FileSystem; using System; using System.Diagnostics; using System.IO; @@ -30,7 +29,7 @@ namespace Ryujinx.Ui private static HLE.Switch _emulationContext; - private static GlScreen _screen; + private static GLRenderer _gLWidget; private static AutoResetEvent _screenExitStatus = new AutoResetEvent(false); @@ -43,32 +42,38 @@ namespace Ryujinx.Ui private static TreeView _treeView; - private static Debugger.Debugger _debugger; + private static Ryujinx.Debugger.Debugger _debugger; #pragma warning disable CS0649 #pragma warning disable IDE0044 - [GUI] Window _mainWin; - [GUI] CheckMenuItem _fullScreen; - [GUI] MenuItem _stopEmulation; - [GUI] CheckMenuItem _favToggle; - [GUI] MenuItem _firmwareInstallFile; - [GUI] MenuItem _firmwareInstallDirectory; - [GUI] CheckMenuItem _iconToggle; - [GUI] CheckMenuItem _appToggle; - [GUI] CheckMenuItem _developerToggle; - [GUI] CheckMenuItem _versionToggle; - [GUI] CheckMenuItem _timePlayedToggle; - [GUI] CheckMenuItem _lastPlayedToggle; - [GUI] CheckMenuItem _fileExtToggle; - [GUI] CheckMenuItem _fileSizeToggle; - [GUI] CheckMenuItem _pathToggle; - [GUI] TreeView _gameTable; - [GUI] TreeSelection _gameTableSelection; - [GUI] Label _progressLabel; - [GUI] Label _firmwareVersionLabel; - [GUI] LevelBar _progressBar; - [GUI] MenuItem _openDebugger; - [GUI] MenuItem _toolsMenu; + + [GUI] Window _mainWin; + [GUI] MenuBar _menuBar; + [GUI] Box _footerBox; + [GUI] MenuItem _fullScreen; + [GUI] MenuItem _stopEmulation; + [GUI] CheckMenuItem _favToggle; + [GUI] MenuItem _firmwareInstallFile; + [GUI] MenuItem _firmwareInstallDirectory; + [GUI] MenuItem _openDebugger; + [GUI] CheckMenuItem _iconToggle; + [GUI] CheckMenuItem _appToggle; + [GUI] CheckMenuItem _developerToggle; + [GUI] CheckMenuItem _versionToggle; + [GUI] CheckMenuItem _timePlayedToggle; + [GUI] CheckMenuItem _lastPlayedToggle; + [GUI] CheckMenuItem _fileExtToggle; + [GUI] CheckMenuItem _fileSizeToggle; + [GUI] CheckMenuItem _pathToggle; + [GUI] TreeView _gameTable; + [GUI] ScrolledWindow _gameTableWindow; + [GUI] TreeSelection _gameTableSelection; + [GUI] Label _progressLabel; + [GUI] Label _firmwareVersionLabel; + [GUI] LevelBar _progressBar; + [GUI] Box _viewBox; + [GUI] Box _listStatusBox; + #pragma warning restore CS0649 #pragma warning restore IDE0044 @@ -130,7 +135,7 @@ namespace Ryujinx.Ui _debugger = new Debugger.Debugger(); _openDebugger.Activated += _openDebugger_Opened; #else - _openDebugger.Visible = false; + _openDebugger.Hide(); #endif _gameTable.Model = _tableStore = new ListStore( @@ -154,6 +159,8 @@ namespace Ryujinx.Ui UpdateGameTable(); Task.Run(RefreshFirmwareLabel); + + _fullScreen.Activated += FullScreen_Toggled; } #if USE_DEBUGGING @@ -384,27 +391,85 @@ namespace Ryujinx.Ui { device.Hid.InitializePrimaryController(ConfigurationState.Instance.Hid.ControllerType); - using (_screen = new GlScreen(device)) + _gLWidget?.Exit(); + _gLWidget?.Dispose(); + _gLWidget = new GLRenderer(_emulationContext); + + Application.Invoke(delegate { - _screen.MainLoop(); - } + _viewBox.Remove(_gameTableWindow); + _gLWidget.Expand = true; + _viewBox.Child = _gLWidget; + + _gLWidget.ShowAll(); + _listStatusBox.Hide(); + }); + + _gLWidget.WaitEvent.WaitOne(); + + _gLWidget.Start(); + + Application.Invoke(delegate + { + _viewBox.Remove(_gLWidget); + _gLWidget.Exit(); + + if(_gLWidget.Window != this.Window && _gLWidget.Window != null) + { + _gLWidget.Window.Dispose(); + } + + _viewBox.Add(_gameTableWindow); + + _gameTableWindow.Expand = true; + + this.Window.Title = "Ryujinx"; + + _listStatusBox.ShowAll(); + + UpdateColumns(); + UpdateGameTable(); + + Task.Run(RefreshFirmwareLabel); + }); device.Dispose(); _emulationContext = null; - _screen = null; _gameLoaded = false; + _gLWidget = null; DiscordIntegrationModule.SwitchToMainMenu(); - _screenExitStatus.Set(); - Application.Invoke(delegate { _stopEmulation.Sensitive = false; _firmwareInstallFile.Sensitive = true; _firmwareInstallDirectory.Sensitive = true; }); + + _screenExitStatus.Set(); + } + + public void ToggleExtraWidgets(bool show) + { + if (_gLWidget != null) + { + if (show) + { + _menuBar.ShowAll(); + _footerBox.ShowAll(); + } + else + { + _menuBar.Hide(); + _footerBox.Hide(); + } + } + + bool fullScreenToggled = this.Window.State.HasFlag(Gdk.WindowState.Fullscreen); + + _fullScreen.Label = !fullScreenToggled ? "Exit Fullscreen" : "Enter Fullscreen"; } private static void UpdateGameMetadata(string titleId) @@ -439,9 +504,9 @@ namespace Ryujinx.Ui { UpdateGameMetadata(device.System.TitleIdText); - if (_screen != null) + if (_gLWidget != null) { - _screen.Exit(); + _gLWidget.Exit(); _screenExitStatus.WaitOne(); } } @@ -605,7 +670,7 @@ namespace Ryujinx.Ui private void StopEmulation_Pressed(object sender, EventArgs args) { - _screen?.Exit(); + _gLWidget?.Exit(); } private void Installer_File_Pressed(object o, EventArgs args) @@ -803,13 +868,23 @@ namespace Ryujinx.Ui private void FullScreen_Toggled(object o, EventArgs args) { - if (_fullScreen.Active) + bool fullScreenToggled = this.Window.State.HasFlag(Gdk.WindowState.Fullscreen); + + if (!fullScreenToggled) { Fullscreen(); + + _fullScreen.Label = "Exit Fullscreen"; + + ToggleExtraWidgets(false); } else { Unfullscreen(); + + _fullScreen.Label = "Enter Fullscreen"; + + ToggleExtraWidgets(true); } } diff --git a/Ryujinx/Ui/MainWindow.glade b/Ryujinx/Ui/MainWindow.glade index d3cdc593..8477f392 100644 --- a/Ryujinx/Ui/MainWindow.glade +++ b/Ryujinx/Ui/MainWindow.glade @@ -17,7 +17,7 @@ False vertical - + True False @@ -97,13 +97,11 @@ True False - + True False - Fullscreens the window - Fullscreen + Enter Fullscreen True - @@ -364,21 +362,35 @@ False vertical - + + 1280 + 720 True - True - in + False + vertical - + True True - True - True - - - + in + + + True + True + True + True + + + + + + + True + True + 0 + @@ -388,59 +400,70 @@ - + True False - + True False - 5 - - - RefreshList + True False - gtk-refresh + 5 + + + + RefreshList + True + False + gtk-refresh + + + + False + False + 0 + + + + + True + False + 10 + 5 + 2 + 2 + 0/0 Games Loaded + + + False + True + 1 + + + + + 200 + True + False + start + 10 + 5 + + + True + True + 2 + - - - False - True - 0 - - - - - True - False - 10 - 5 - 2 - 2 - 0/0 Games Loaded - - - False - True - 1 - - - - - 200 - True - False - start - 10 - 5 True True - 2 + 1 diff --git a/Ryujinx/Ui/ScopedGlContext.cs b/Ryujinx/Ui/ScopedGlContext.cs new file mode 100644 index 00000000..8dc9246e --- /dev/null +++ b/Ryujinx/Ui/ScopedGlContext.cs @@ -0,0 +1,35 @@ +using OpenTK.Graphics; +using OpenTK.Platform; +using System; +using System.Threading; + +namespace Ryujinx.Ui +{ + class ScopedGlContext : IDisposable + { + private IGraphicsContext _graphicsContext; + + private static readonly object _lock = new object(); + + public ScopedGlContext(IWindowInfo windowInfo, IGraphicsContext graphicsContext) + { + _graphicsContext = graphicsContext; + + Monitor.Enter(_lock); + + MakeCurrent(windowInfo); + } + + private void MakeCurrent(IWindowInfo windowInfo) + { + _graphicsContext.MakeCurrent(windowInfo); + } + + public void Dispose() + { + MakeCurrent(null); + + Monitor.Exit(_lock); + } + } +}