ryujinx-mirror/Ryujinx/Ui/GLRenderer.cs
LDj3SNuD 5e724cf24e
Add Profiled Persistent Translation Cache. (#769)
* Delete DelegateTypes.cs

* Delete DelegateCache.cs

* Add files via upload

* Update Horizon.cs

* Update Program.cs

* Update MainWindow.cs

* Update Aot.cs

* Update RelocEntry.cs

* Update Translator.cs

* Update MemoryManager.cs

* Update InstEmitMemoryHelper.cs

* Update Delegates.cs

* Nit.

* Nit.

* Nit.

* 10 fewer MSIL bytes for us

* Add comment. Nits.

* Update Translator.cs

* Update Aot.cs

* Nits.

* Opt..

* Opt..

* Opt..

* Opt..

* Allow to change compression level.

* Update MemoryManager.cs

* Update Translator.cs

* Manage corner cases during the save phase. Nits.

* Update Aot.cs

* Translator response tweak for Aot disabled. Nit.

* Nit.

* Nits.

* Create DelegateHelpers.cs

* Update Delegates.cs

* Nit.

* Nit.

* Nits.

* Fix due to #784.

* Fixes due to #757 & #841.

* Fix due to #846.

* Fix due to #847.

* Use MethodInfo for managed method calls.

Use IR methods instead of managed methods about Max/Min (S/U).
Follow-ups & Nits.

* Add missing exception messages.

Reintroduce slow path for Fmov_Vi.
Implement slow path for Fmov_Si.

* Switch to the new folder structure.

Nits.

* Impl. index-based relocation information. Impl. cache file version field.

* Nit.

* Address gdkchan comments.

Mainly:
- fixed cache file corruption issue on exit; - exposed a way to disable AOT on the GUI.

* Address AcK77 comment.

* Address Thealexbarney, jduncanator & emmauss comments.

Header magic, CpuId (FI) & Aot -> Ptc.

* Adaptation to the new application reloading system.

Improvements to the call system of managed methods.
Follow-ups.
Nits.

* Get the same boot times as on master when PTC is disabled.

* Profiled Aot.

* A32 support (#897).

* #975 support (1 of 2).

* #975 support (2 of 2).

* Rebase fix & nits.

* Some fixes and nits (still one bug left).

* One fix & nits.

* Tests fix (by gdk) & nits.

* Support translations not only in high quality and rejit.

Nits.

* Added possibility to skip translations and continue execution, using `ESC` key.

* Update SettingsWindow.cs

* Update GLRenderer.cs

* Update Ptc.cs

* Disabled Profiled PTC by default as requested in the past by gdk.

* Fix rejit bug. Increased number of parallel translations. Add stack unwinding stuffs support (1 of 2).

Nits.

* Add stack unwinding stuffs support (2 of 2). Tuned number of parallel translations.

* Restored the ability to assemble jumps with 8-bit offset when Profiled PTC is disabled or during profiling.

Modifications due to rebase.
Nits.

* Limited profiling of the functions to be translated to the addresses belonging to the range of static objects only.

* Nits.

* Nits.

* Update Delegates.cs

* Nit.

* Update InstEmitSimdArithmetic.cs

* Address riperiperi comments.

* Fixed the issue of unjustifiably longer boot times at the second boot than at the first boot, measured at the same time or reference point and with the same number of translated functions.

* Implemented a simple redundant load/save mechanism.

Halved the value of Decoder.MaxInstsPerFunction more appropriate for the current performance of the Translator.
Replaced by Logger.PrintError to Logger.PrintDebug in TexturePool.cs about the supposed invalid texture format to avoid the spawn of the log.
Nits.

* Nit.

Improved Logger.PrintError in TexturePool.cs to avoid log spawn.
Added missing code for FZ handling (in output) for fp max/min instructions (slow paths).

* Add configuration migration for PTC

Co-authored-by: Thog <me@thog.eu>
2020-06-16 20:28:02 +02:00

565 lines
18 KiB
C#

using ARMeilleure.Translation.PTC;
using Gdk;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
using OpenTK.Input;
using Ryujinx.Configuration;
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Graphics.OpenGL;
using Ryujinx.HLE;
using Ryujinx.HLE.HOS.Services.Hid;
using System;
using System.Collections.Generic;
using System.Threading;
namespace Ryujinx.Ui
{
public class GlRenderer : GLWidget
{
private const int SwitchPanelWidth = 1280;
private const int SwitchPanelHeight = 720;
private const int TargetFps = 60;
public ManualResetEvent WaitEvent { get; set; }
public static event EventHandler<StatusUpdatedEventArgs> StatusUpdatedEvent;
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 _toggleFullscreen;
private readonly long _ticksPerFrame;
private long _ticks = 0;
private System.Diagnostics.Stopwatch _chrono;
private Switch _device;
private Renderer _renderer;
private HotkeyButtons _prevHotkeyButtons;
public GlRenderer(Switch device)
: base (GetGraphicsMode(),
3, 3,
GraphicsContextFlags.ForwardCompatible)
{
WaitEvent = new ManualResetEvent(false);
_device = device;
this.Initialized += GLRenderer_Initialized;
this.Destroyed += GLRenderer_Destroyed;
this.ShuttingDown += GLRenderer_ShuttingDown;
Initialize();
_chrono = new System.Diagnostics.Stopwatch();
_ticksPerFrame = System.Diagnostics.Stopwatch.Frequency / TargetFps;
AddEvents((int)(EventMask.ButtonPressMask
| EventMask.ButtonReleaseMask
| EventMask.PointerMotionMask
| EventMask.KeyPressMask
| EventMask.KeyReleaseMask));
this.Shown += Renderer_Shown;
}
private static GraphicsMode GetGraphicsMode()
{
return Environment.OSVersion.Platform == PlatformID.Unix ? new GraphicsMode(new ColorFormat(24)) : new GraphicsMode(new ColorFormat());
}
private void GLRenderer_ShuttingDown(object sender, EventArgs args)
{
_device.DisposeGpu();
}
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)
{
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))
|| keyboard.IsKeyDown(OpenTK.Input.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.IsKeyDown(OpenTK.Input.Key.Escape))
{
if (GtkDialog.CreateExitDialog())
{
Exit();
}
}
else
{
ParentWindow.Fullscreen();
(Toplevel as MainWindow)?.ToggleExtraWidgets(false);
}
}
}
}
_toggleFullscreen = toggleFullscreen;
}
private void GLRenderer_Initialized(object sender, EventArgs e)
{
// Release the GL exclusivity that OpenTK gave us as we aren't going to use it in GTK Thread.
GraphicsContext.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()
{
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();
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();
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;
}
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()
{
if (IsStopped)
{
return;
}
IsStopped = true;
IsActive = false;
}
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()
{
// First take exclusivity on the OpenGL context.
GraphicsContext.MakeCurrent(WindowInfo);
_renderer.Initialize();
// Make sure the first frame is not transparent.
GL.ClearColor(OpenTK.Color.Black);
GL.Clear(ClearBufferMask.ColorBufferBit);
SwapBuffers();
while (IsActive)
{
if (IsStopped)
{
return;
}
_ticks += _chrono.ElapsedTicks;
_chrono.Restart();
if (_device.WaitFifo())
{
_device.ProcessFrame();
}
string dockedMode = ConfigurationState.Instance.System.EnableDockedMode ? "Docked" : "Handheld";
if (_ticks >= _ticksPerFrame)
{
_device.PresentFrame(SwapBuffers);
_device.Statistics.RecordSystemFrameTime();
StatusUpdatedEvent?.Invoke(this, new StatusUpdatedEventArgs(
_device.EnableDeviceVsync,
dockedMode,
$"Host: {_device.Statistics.GetSystemFrameRate():00.00} FPS",
$"Game: {_device.Statistics.GetGameFrameRate():00.00} FPS",
$"GPU: {_renderer.GpuVendor}"));
_ticks = Math.Min(_ticks - _ticksPerFrame, _ticksPerFrame);
}
}
}
public void SwapBuffers()
{
OpenTK.Graphics.GraphicsContext.CurrentContext.SwapBuffers();
}
public void MainLoop()
{
while (IsActive)
{
UpdateFrame();
// Polling becomes expensive if it's not slept
Thread.Sleep(1);
}
}
private bool UpdateFrame()
{
if (!IsActive)
{
return true;
}
if (IsStopped)
{
return false;
}
if (IsFocused)
{
Gtk.Application.Invoke(delegate
{
KeyboardState keyboard = OpenTK.Input.Keyboard.GetState();
HandleScreenState(keyboard);
if (keyboard.IsKeyDown(OpenTK.Input.Key.Delete))
{
if (!ParentWindow.State.HasFlag(Gdk.WindowState.Fullscreen))
{
Ptc.Continue();
}
}
});
}
List<GamepadInput> gamepadInputs = new List<GamepadInput>();
foreach (InputConfig inputConfig in ConfigurationState.Instance.Hid.InputConfig.Value)
{
ControllerKeys currentButton = 0;
JoystickPosition leftJoystick = new JoystickPosition();
JoystickPosition rightJoystick = new JoystickPosition();
KeyboardInput? hidKeyboard = null;
int leftJoystickDx = 0;
int leftJoystickDy = 0;
int rightJoystickDx = 0;
int rightJoystickDy = 0;
if (inputConfig is KeyboardConfig keyboardConfig)
{
if (IsFocused)
{
// Keyboard Input
KeyboardController keyboardController = new KeyboardController(keyboardConfig);
currentButton = keyboardController.GetButtons();
(leftJoystickDx, leftJoystickDy) = keyboardController.GetLeftStick();
(rightJoystickDx, rightJoystickDy) = keyboardController.GetRightStick();
leftJoystick = new JoystickPosition
{
Dx = leftJoystickDx,
Dy = leftJoystickDy
};
rightJoystick = new JoystickPosition
{
Dx = rightJoystickDx,
Dy = rightJoystickDy
};
if (ConfigurationState.Instance.Hid.EnableKeyboard)
{
hidKeyboard = keyboardController.GetKeysDown();
}
if (!hidKeyboard.HasValue)
{
hidKeyboard = new KeyboardInput
{
Modifier = 0,
Keys = new int[0x8]
};
}
if (ConfigurationState.Instance.Hid.EnableKeyboard)
{
_device.Hid.Keyboard.Update(hidKeyboard.Value);
}
// Toggle vsync
HotkeyButtons currentHotkeyButtons = keyboardController.GetHotkeyButtons();
if (currentHotkeyButtons.HasFlag(HotkeyButtons.ToggleVSync) &&
!_prevHotkeyButtons.HasFlag(HotkeyButtons.ToggleVSync))
{
_device.EnableDeviceVsync = !_device.EnableDeviceVsync;
}
_prevHotkeyButtons = currentHotkeyButtons;
}
}
else if (inputConfig is Common.Configuration.Hid.ControllerConfig controllerConfig)
{
// Controller Input
JoystickController joystickController = new JoystickController(controllerConfig);
currentButton |= joystickController.GetButtons();
(leftJoystickDx, leftJoystickDy) = joystickController.GetLeftStick();
(rightJoystickDx, rightJoystickDy) = joystickController.GetRightStick();
leftJoystick = new JoystickPosition
{
Dx = controllerConfig.LeftJoycon.InvertStickX ? -leftJoystickDx : leftJoystickDx,
Dy = controllerConfig.LeftJoycon.InvertStickY ? -leftJoystickDy : leftJoystickDy
};
rightJoystick = new JoystickPosition
{
Dx = controllerConfig.RightJoycon.InvertStickX ? -rightJoystickDx : rightJoystickDx,
Dy = controllerConfig.RightJoycon.InvertStickY ? -rightJoystickDy : rightJoystickDy
};
}
currentButton |= _device.Hid.UpdateStickButtons(leftJoystick, rightJoystick);
gamepadInputs.Add(new GamepadInput
{
PlayerId = (HLE.HOS.Services.Hid.PlayerIndex)inputConfig.PlayerIndex,
Buttons = currentButton,
LStick = leftJoystick,
RStick = rightJoystick
});
}
_device.Hid.Npads.SetGamepadsInput(gamepadInputs.ToArray());
//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)
{
int screenWidth = AllocatedWidth;
int screenHeight = AllocatedHeight;
if (AllocatedWidth > (AllocatedHeight * SwitchPanelWidth) / SwitchPanelHeight)
{
screenWidth = (AllocatedHeight * SwitchPanelWidth) / SwitchPanelHeight;
}
else
{
screenHeight = (AllocatedWidth * SwitchPanelHeight) / SwitchPanelWidth;
}
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 * SwitchPanelWidth) / 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;
}
}
}