ryujinx-mirror/Ryujinx/Ui/GLScreen.cs
Thog 01a4c80ed5
Rewrite the configuration system (#831)
The configuration system was quite fragile and too dependent on everything, this fix #812 .

The changes:

    The file configuration is now entirely independent from the internal configuration state.
    The file configuration is versioned (current version is 1).
    Every configuration elements are now reactive properties that the emulator can register on to handle initialization and configuration changes.
    The configuration system is now in Ryujinx.Common to be accessible on every projects.
    Discord integration is now independent from the UI and can be reloaded.
    The primary controller is now configurable at runtime (NOTE: the UI currently doesn't have any options to configure real controller).
    The logger is entirely reloadable.
    You can now hotplug your controller when the emulator is running.
    The logger now takes name for every LogTarget to make them removable at runtime.
    The logger now always add the default "console" target to avoid loosing early init logs.
    The configuration system now generates a default file configuration if it's missing or too new.
    General system stability improvements to enhance the user's experience
2019-12-21 20:52:31 +01:00

388 lines
11 KiB
C#

using OpenTK;
using OpenTK.Graphics;
using OpenTK.Input;
using Ryujinx.Configuration;
using Ryujinx.Graphics.Gal;
using Ryujinx.HLE;
using Ryujinx.HLE.Input;
using Ryujinx.Profiler.UI;
using Ryujinx.Ui;
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 IGalRenderer _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;
#if USE_PROFILING
private ProfileWindowManager _profileWindow;
#endif
public GlScreen(Switch device, IGalRenderer renderer)
: base(1280, 720,
new GraphicsMode(), "Ryujinx", 0,
DisplayDevice.Default, 3, 3,
GraphicsContextFlags.ForwardCompatible)
{
_device = device;
_renderer = renderer;
_primaryController = new Input.NpadController(ConfigurationState.Instance.Hid.JoystickControls);
Location = new Point(
(DisplayDevice.Default.Width / 2) - (Width / 2),
(DisplayDevice.Default.Height / 2) - (Height / 2));
#if USE_PROFILING
// Start profile window, it will handle itself from there
_profileWindow = new ProfileWindowManager();
#endif
}
private void RenderLoop()
{
MakeCurrent();
Stopwatch chrono = new Stopwatch();
chrono.Start();
long ticksPerFrame = Stopwatch.Frequency / TargetFps;
long ticks = 0;
while (Exists && !IsExiting)
{
if (_device.WaitFifo())
{
_device.ProcessFrame();
}
_renderer.RunActions();
if (_resizeEvent)
{
_resizeEvent = false;
_renderer.RenderTarget.SetWindowSize(Width, Height);
}
ticks += chrono.ElapsedTicks;
chrono.Restart();
if (ticks >= ticksPerFrame)
{
RenderFrame();
// Queue max. 1 vsync
ticks = Math.Min(ticks - ticksPerFrame, ticksPerFrame);
}
}
}
public void MainLoop()
{
VSync = VSyncMode.Off;
Visible = true;
_renderer.RenderTarget.SetWindowSize(Width, Height);
Context.MakeCurrent(null);
// OpenTK doesn't like sleeps in its thread, to avoid this a renderer thread is created
_renderThread = new Thread(RenderLoop);
_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;
#if USE_PROFILING
// Profiler input, lets the profiler get access to the main windows keyboard state
_profileWindow.UpdateKeyInput(keyboard);
#endif
// 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()
{
_renderer.RenderTarget.Render();
_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.TitleId) ? string.Empty
: " | " + _device.System.TitleId.ToUpper();
_newTitle = $"Ryujinx{titleNameSection}{titleIDSection} | Host FPS: {hostFps:0.0} | Game FPS: {gameFps:0.0} | " +
$"Game Vsync: {(_device.EnableDeviceVsync ? "On" : "Off")}";
_titleEvent = true;
SwapBuffers();
_device.System.SignalVsync();
_device.VsyncEvent.Set();
}
protected override void OnUnload(EventArgs e)
{
#if USE_PROFILING
_profileWindow.Close();
#endif
_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;
}
}
}