diff --git a/Ryujinx/Program.cs b/Ryujinx/Program.cs index 24fbb9b8b5..4aaa5e9f2c 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/Ui/GLRenderer.cs b/Ryujinx/Ui/GLRenderer.cs new file mode 100644 index 0000000000..66f9ea25fc --- /dev/null +++ b/Ryujinx/Ui/GLRenderer.cs @@ -0,0 +1,154 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using Ryujinx.HLE; +using Ryujinx.Graphics.OpenGL; +using System.Text; +using OpenTK.Graphics; +using Gdk; + +namespace Ryujinx.Ui +{ + public class GLRenderer : GLWidget + { + public ManualResetEvent waitEvent { get; set; } + + public bool IsActive { get; set; } + public bool IsStopped { get; set; } + + private const int TargetFps = 60; + + private readonly long _ticksPerFrame; + + private long _ticks = 0; + + private System.Diagnostics.Stopwatch _chrono; + + private Switch _device; + + private Renderer _renderer; + + public GLRenderer(Switch device) :base (new GraphicsMode(), 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; + } + + private void GLRenderer_Destroyed(object sender, EventArgs e) + { + Exit(); + + this.Dispose(); + } + + private void GLRenderer_Initialized(object sender, EventArgs e) + { + Start(); + } + + protected override bool OnConfigureEvent(EventConfigure evnt) + { + var result = base.OnConfigureEvent(evnt); + + _renderer.Window.SetSize(AllocatedWidth, AllocatedHeight); + + return result; + } + + public void Start() + { + _renderer.Initialize(); + + _chrono.Restart(); + + IsActive = true; + + GLib.Idle.Add(Render, GLib.Priority.HighIdle); + } + + public void Exit() + { + _device.DisposeGpu(); + + IsStopped = true; + + 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 bool Render() + { + if (!IsActive) + { + return true; + } + + if (IsStopped) + { + return false; + } + + _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(); + + string newTitle = $"Ryujinx{titleNameSection}{titleIdSection} | Host FPS: {hostFps:0.0} | Game FPS: {gameFps:0.0} | " + + $"Game Vsync: {(_device.EnableDeviceVsync ? "On" : "Off")}"; + + this.ParentWindow.Title = newTitle; + + _device.System.SignalVsync(); + + _device.VsyncEvent.Set(); + + _ticks = Math.Min(_ticks - _ticksPerFrame, _ticksPerFrame); + } + + return true; + } + + public void SwapBuffers() + { + OpenTK.Graphics.GraphicsContext.CurrentContext.SwapBuffers(); + } + } +} diff --git a/Ryujinx/Ui/GLWidget.cs b/Ryujinx/Ui/GLWidget.cs new file mode 100644 index 0000000000..3b93c946d4 --- /dev/null +++ b/Ryujinx/Ui/GLWidget.cs @@ -0,0 +1,556 @@ +//https://github.com/Nihlus/GLWidgetTest/blob/master/GLWidget/GLWidgetGTK3/GLWidget.cs +#region License +// +// The Open Toolkit Library License +// +// Copyright (c) 2006 - 2009 the Open Toolkit library, except where noted. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +#endregion + +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Security; +using System.Threading; +using System.ComponentModel; + + +using OpenTK; +using OpenTK.Graphics; +using OpenTK.Platform; + +using Gtk; + +namespace Ryujinx.Ui +{ + [ToolboxItem(true)] + public class GLWidget : DrawingArea + { + + #region Static attrs. + + private static int _graphicsContextCount; + private static bool _sharedContextInitialized; + + #endregion + + #region Attributes + + private IGraphicsContext _graphicsContext; + private IWindowInfo _windowInfo; + private bool _initialized; + + #endregion + + #region Properties + + /// Use a single buffer versus a double buffer. + [Browsable(true)] + public bool SingleBuffer { get; set; } + + /// Color Buffer Bits-Per-Pixel + public int ColorBPP { get; set; } + + /// Accumulation Buffer Bits-Per-Pixel + public int AccumulatorBPP { get; set; } + + /// Depth Buffer Bits-Per-Pixel + public int DepthBPP { get; set; } + + /// Stencil Buffer Bits-Per-Pixel + public int StencilBPP { get; set; } + + /// Number of samples + public int Samples { get; set; } + + /// Indicates if steropic renderering is enabled + public bool Stereo { get; set; } + + /// The major version of OpenGL to use. + public int GLVersionMajor { get; set; } + + /// The minor version of OpenGL to use. + public int GLVersionMinor { get; set; } + + public GraphicsContextFlags GraphicsContextFlags + { + get; + set; + } + + #endregion + + #region Construction/Destruction + + /// Constructs a new GLWidget. + public GLWidget() + : this(GraphicsMode.Default) + { + } + + /// Constructs a new GLWidget using a given GraphicsMode + public GLWidget(GraphicsMode graphicsMode) + : this(graphicsMode, 1, 0, GraphicsContextFlags.Default) + { + } + + /// Constructs a new GLWidget + public GLWidget(GraphicsMode graphicsMode, int glVersionMajor, int glVersionMinor, GraphicsContextFlags graphicsContextFlags) + { + SingleBuffer = graphicsMode.Buffers == 1; + ColorBPP = graphicsMode.ColorFormat.BitsPerPixel; + AccumulatorBPP = graphicsMode.AccumulatorFormat.BitsPerPixel; + DepthBPP = graphicsMode.Depth; + StencilBPP = graphicsMode.Stencil; + Samples = graphicsMode.Samples; + Stereo = graphicsMode.Stereo; + + GLVersionMajor = glVersionMajor; + GLVersionMinor = glVersionMinor; + GraphicsContextFlags = graphicsContextFlags; + } + + ~GLWidget() + { + Dispose(false); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _graphicsContext.MakeCurrent(_windowInfo); + + OnShuttingDown(); + + if (GraphicsContext.ShareContexts && (Interlocked.Decrement(ref _graphicsContextCount) == 0)) + { + OnGraphicsContextShuttingDown(); + _sharedContextInitialized = false; + } + + _graphicsContext.Dispose(); + } + } + + #endregion + + #region New Events + + // Called when the first GraphicsContext is created in the case of GraphicsContext.ShareContexts == True; + public static event EventHandler GraphicsContextInitialized; + + private static void OnGraphicsContextInitialized() + { + if (GraphicsContextInitialized != null) + { + GraphicsContextInitialized(null, EventArgs.Empty); + } + } + + // Called when the first GraphicsContext is being destroyed in the case of GraphicsContext.ShareContexts == True; + public static event EventHandler GraphicsContextShuttingDown; + + private static void OnGraphicsContextShuttingDown() + { + if (GraphicsContextShuttingDown != null) + { + GraphicsContextShuttingDown(null, EventArgs.Empty); + } + } + + // Called when this GLWidget has a valid GraphicsContext + public event EventHandler Initialized; + + protected virtual void OnInitialized() + { + if (Initialized != null) + { + Initialized(this, EventArgs.Empty); + } + } + + // Called when this GLWidget needs to render a frame + public event EventHandler RenderFrame; + + protected virtual void OnRenderFrame() + { + if (RenderFrame != null) + { + RenderFrame(this, EventArgs.Empty); + } + } + + // Called when this GLWidget is being Disposed + public event EventHandler ShuttingDown; + + protected virtual void OnShuttingDown() + { + if (ShuttingDown != null) + { + ShuttingDown(this, EventArgs.Empty); + } + } + + #endregion + + // Called when a widget is realized. (window handles and such are valid) + // protected override void OnRealized() { base.OnRealized(); } + + // Called when the widget needs to be (fully or partially) redrawn. + + protected override bool OnDrawn(Cairo.Context cr) + { + if (!_initialized) + Initialize(); + else + _graphicsContext.MakeCurrent(_windowInfo); + + return true; + } + + // Called on Resize + protected override bool OnConfigureEvent(Gdk.EventConfigure evnt) + { + if (_graphicsContext != null) + { + _graphicsContext.Update(_windowInfo); + } + + return true; + } + + private void Initialize() + { + _initialized = true; + + // If this looks uninitialized... initialize. + if (ColorBPP == 0) + { + ColorBPP = 32; + + if (DepthBPP == 0) + { + DepthBPP = 16; + } + } + + ColorFormat colorBufferColorFormat = new ColorFormat(ColorBPP); + + ColorFormat accumulationColorFormat = new ColorFormat(AccumulatorBPP); + + int buffers = 2; + if (SingleBuffer) + { + buffers--; + } + + GraphicsMode graphicsMode = new GraphicsMode(colorBufferColorFormat, DepthBPP, StencilBPP, Samples, accumulationColorFormat, buffers, Stereo); + + if (OpenTK.Configuration.RunningOnWindows) + { + Console.WriteLine("OpenTK running on windows"); + } + else if (OpenTK.Configuration.RunningOnMacOS) + { + Console.WriteLine("OpenTK running on OSX"); + } + else + { + Console.WriteLine("OpenTK running on X11"); + } + + this.Window.EnsureNative(); + + // IWindowInfo + if (OpenTK.Configuration.RunningOnWindows) + { + _windowInfo = InitializeWindows(); + } + else if (OpenTK.Configuration.RunningOnMacOS) + { + _windowInfo = InitializeOSX(); + } + else + { + _windowInfo = InitializeX(graphicsMode); + } + + // GraphicsContext + _graphicsContext = new GraphicsContext(graphicsMode, _windowInfo, GLVersionMajor, GLVersionMinor, GraphicsContextFlags); + _graphicsContext.MakeCurrent(_windowInfo); + + if (GraphicsContext.ShareContexts) + { + Interlocked.Increment(ref _graphicsContextCount); + + if (!_sharedContextInitialized) + { + _sharedContextInitialized = true; + ((IGraphicsContextInternal)_graphicsContext).LoadAll(); + OnGraphicsContextInitialized(); + } + } + else + { + ((IGraphicsContextInternal)_graphicsContext).LoadAll(); + OnGraphicsContextInitialized(); + } + + OnInitialized(); + } + + #region Windows Specific initalization + + IWindowInfo InitializeWindows() + { + IntPtr windowHandle = gdk_win32_window_get_handle(this.Window.Handle); + return Utilities.CreateWindowsWindowInfo(windowHandle); + } + + [SuppressUnmanagedCodeSecurity, DllImport("libgdk-3-0.dll")] + public static extern IntPtr gdk_win32_window_get_handle(IntPtr d); + + #endregion + + #region OSX Specific Initialization + + IWindowInfo InitializeOSX() + { + IntPtr windowHandle = gdk_quartz_window_get_nswindow(this.Window.Handle); + //IntPtr viewHandle = gdk_quartz_window_get_nsview(this.GdkWindow.Handle); + return Utilities.CreateMacOSCarbonWindowInfo(windowHandle, true, true); + } + + [SuppressUnmanagedCodeSecurity, DllImport("libgdk-quartz-2.0.0.dylib")] + static extern IntPtr gdk_quartz_window_get_nswindow(IntPtr handle); + + [SuppressUnmanagedCodeSecurity, DllImport("libgdk-quartz-2.0.0.dylib")] + static extern IntPtr gdk_quartz_window_get_nsview(IntPtr handle); + + #endregion + + #region X Specific Initialization + + const string UnixLibGdkName = "libgdk-3.so.0"; + + const string UnixLibX11Name = "libX11.so.6"; + const string UnixLibGLName = "libGL.so.1"; + + const int GLX_NONE = 0; + const int GLX_USE_GL = 1; + const int GLX_BUFFER_SIZE = 2; + const int GLX_LEVEL = 3; + const int GLX_RGBA = 4; + const int GLX_DOUBLEBUFFER = 5; + const int GLX_STEREO = 6; + const int GLX_AUX_BUFFERS = 7; + const int GLX_RED_SIZE = 8; + const int GLX_GREEN_SIZE = 9; + const int GLX_BLUE_SIZE = 10; + const int GLX_ALPHA_SIZE = 11; + const int GLX_DEPTH_SIZE = 12; + const int GLX_STENCIL_SIZE = 13; + const int GLX_ACCUM_RED_SIZE = 14; + const int GLX_ACCUM_GREEN_SIZE = 15; + const int GLX_ACCUM_BLUE_SIZE = 16; + const int GLX_ACCUM_ALPHA_SIZE = 17; + + public enum XVisualClass + { + StaticGray = 0, + GrayScale = 1, + StaticColor = 2, + PseudoColor = 3, + TrueColor = 4, + DirectColor = 5, + } + + [StructLayout(LayoutKind.Sequential)] + struct XVisualInfo + { + public IntPtr Visual; + public IntPtr VisualID; + public int Screen; + public int Depth; + public XVisualClass Class; + public long RedMask; + public long GreenMask; + public long blueMask; + public int ColormapSize; + public int BitsPerRgb; + + public override string ToString() + { + return $"id ({VisualID}), screen ({Screen}), depth ({Depth}), class ({Class})"; + } + } + + [Flags] + internal enum XVisualInfoMask + { + No = 0x0, + ID = 0x1, + Screen = 0x2, + Depth = 0x4, + Class = 0x8, + Red = 0x10, + Green = 0x20, + Blue = 0x40, + ColormapSize = 0x80, + BitsPerRGB = 0x100, + All = 0x1FF, + } + + private IWindowInfo InitializeX(GraphicsMode mode) + { + IntPtr display = gdk_x11_display_get_xdisplay(Display.Handle); + int screen = Screen.Number; + + IntPtr windowHandle = gdk_x11_window_get_xid(Window.Handle); + IntPtr rootWindow = gdk_x11_window_get_xid(RootWindow.Handle); + + IntPtr visualInfo; + + if (mode.Index.HasValue) + { + XVisualInfo info = new XVisualInfo(); + info.VisualID = mode.Index.Value; + int dummy; + visualInfo = XGetVisualInfo(display, XVisualInfoMask.ID, ref info, out dummy); + } + else + { + visualInfo = GetVisualInfo(display); + } + + IWindowInfo retval = Utilities.CreateX11WindowInfo(display, screen, windowHandle, rootWindow, visualInfo); + XFree(visualInfo); + + return retval; + } + + private static IntPtr XGetVisualInfo(IntPtr display, XVisualInfoMask vinfo_mask, ref XVisualInfo template, out int nitems) + { + return XGetVisualInfoInternal(display, (IntPtr)(int)vinfo_mask, ref template, out nitems); + } + + private IntPtr GetVisualInfo(IntPtr display) + { + try + { + int[] attributes = AttributeList.ToArray(); + return glXChooseVisual(display, Screen.Number, attributes); + } + catch (DllNotFoundException e) + { + throw new DllNotFoundException("OpenGL dll not found!", e); + } + catch (EntryPointNotFoundException enf) + { + throw new EntryPointNotFoundException("Glx entry point not found!", enf); + } + } + + private List AttributeList + { + get + { + List attributeList = new List(24); + + attributeList.Add(GLX_RGBA); + + if (!SingleBuffer) + attributeList.Add(GLX_DOUBLEBUFFER); + + if (Stereo) + attributeList.Add(GLX_STEREO); + + attributeList.Add(GLX_RED_SIZE); + attributeList.Add(ColorBPP / 4); // TODO support 16-bit + + attributeList.Add(GLX_GREEN_SIZE); + attributeList.Add(ColorBPP / 4); // TODO support 16-bit + + attributeList.Add(GLX_BLUE_SIZE); + attributeList.Add(ColorBPP / 4); // TODO support 16-bit + + attributeList.Add(GLX_ALPHA_SIZE); + attributeList.Add(ColorBPP / 4); // TODO support 16-bit + + attributeList.Add(GLX_DEPTH_SIZE); + attributeList.Add(DepthBPP); + + attributeList.Add(GLX_STENCIL_SIZE); + attributeList.Add(StencilBPP); + + attributeList.Add(GLX_ACCUM_RED_SIZE); + attributeList.Add(AccumulatorBPP / 4);// TODO support 16-bit + + attributeList.Add(GLX_ACCUM_GREEN_SIZE); + attributeList.Add(AccumulatorBPP / 4);// TODO support 16-bit + + attributeList.Add(GLX_ACCUM_BLUE_SIZE); + attributeList.Add(AccumulatorBPP / 4);// TODO support 16-bit + + attributeList.Add(GLX_ACCUM_ALPHA_SIZE); + attributeList.Add(AccumulatorBPP / 4);// TODO support 16-bit + + attributeList.Add(GLX_NONE); + + return attributeList; + } + } + + [DllImport(UnixLibX11Name, EntryPoint = "XGetVisualInfo")] + private static extern IntPtr XGetVisualInfoInternal(IntPtr display, IntPtr vinfo_mask, ref XVisualInfo template, out int nitems); + + [SuppressUnmanagedCodeSecurity, DllImport(UnixLibX11Name)] + private static extern void XFree(IntPtr handle); + + /// Returns the X resource (window or pixmap) belonging to a GdkDrawable. + /// XID gdk_x11_drawable_get_xid(GdkDrawable *drawable); + /// The GdkDrawable. + /// The ID of drawable's X resource. + [SuppressUnmanagedCodeSecurity, DllImport(UnixLibGdkName)] + private static extern IntPtr gdk_x11_drawable_get_xid(IntPtr gdkDisplay); + + /// Returns the X resource (window or pixmap) belonging to a GdkDrawable. + /// XID gdk_x11_drawable_get_xid(GdkDrawable *drawable); + /// The GdkDrawable. + /// The ID of drawable's X resource. + [SuppressUnmanagedCodeSecurity, DllImport(UnixLibGdkName)] + private static extern IntPtr gdk_x11_window_get_xid(IntPtr gdkDisplay); + + /// Returns the X display of a GdkDisplay. + /// Display* gdk_x11_display_get_xdisplay(GdkDisplay *display); + /// The GdkDrawable. + /// The X Display of the GdkDisplay. + [SuppressUnmanagedCodeSecurity, DllImport(UnixLibGdkName)] + private static extern IntPtr gdk_x11_display_get_xdisplay(IntPtr gdkDisplay); + + [SuppressUnmanagedCodeSecurity, DllImport(UnixLibGLName)] + private static extern IntPtr glXChooseVisual(IntPtr display, int screen, int[] attr); + + #endregion + } +} \ No newline at end of file diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs index 734103fed2..57cb5a8530 100644 --- a/Ryujinx/Ui/MainWindow.cs +++ b/Ryujinx/Ui/MainWindow.cs @@ -30,7 +30,8 @@ namespace Ryujinx.Ui private static HLE.Switch _emulationContext; - private static GlScreen _screen; + // private static GlScreen _screen; + private static GLRenderer _gLWigdet; private static AutoResetEvent _screenExitStatus = new AutoResetEvent(false); @@ -384,15 +385,37 @@ namespace Ryujinx.Ui { device.Hid.InitializePrimaryController(ConfigurationState.Instance.Hid.ControllerType); - using (_screen = new GlScreen(device)) + _gLWigdet?.Exit(); + _gLWigdet?.Dispose(); + _gLWigdet = new GLRenderer(_emulationContext); + + Application.Invoke(delegate { - _screen.MainLoop(); - } + Window window = new Window("Test"); + + window.HeightRequest = 720; + window.WidthRequest = 1280; + _gLWigdet.Expand = true; + window.Child = _gLWigdet; + + window.ShowAll(); + }); + + _gLWigdet.waitEvent.WaitOne(); + + Application.Invoke(delegate + { + _gLWigdet.Dispose(); + + if(_gLWigdet.Window != this.Window && _gLWigdet.Window != null) + { + _gLWigdet.Window.Dispose(); + } + }); device.Dispose(); _emulationContext = null; - _screen = null; _gameLoaded = false; DiscordIntegrationModule.SwitchToMainMenu(); @@ -595,17 +618,21 @@ namespace Ryujinx.Ui private void Exit_Pressed(object sender, EventArgs args) { + _gLWigdet?.Exit(); + End(_emulationContext); } private void Window_Close(object sender, DeleteEventArgs args) { + _gLWigdet?.Exit(); + End(_emulationContext); } private void StopEmulation_Pressed(object sender, EventArgs args) { - _screen?.Exit(); + _gLWigdet?.Exit(); } private void Installer_File_Pressed(object o, EventArgs args)