From 416ddd0f6e9b5c9e9b3da627cc90c98bb5a3a56b Mon Sep 17 00:00:00 2001
From: Thog <me@thog.eu>
Date: Thu, 13 Feb 2020 18:43:29 +0100
Subject: [PATCH] Some fixes for the new GLRenderer (#930)

* Some fixes for the new GLRenderer

Changelog:
- Fix transparency of the window on some games on Windows.
- Fix escape key not being able to exit emulation.
- Fix inverted logic in fullscreen event handling.
- Fix a race condition when stoping emulation causing a hang.
- Fix a memory leak of the OpenGL context when stoping emulation (saving ~200MB of RAM when stoping emulation).
- Simplify and document behaviours when exiting the emulator while the
emulation is running.

* Make sure to clear alpha channel when presenting Texture

This fix once and for all the transparency issue on Windows.

* Enforce footer bar size to avoid gl widget to get resized to 1280x724

* Fix full screen inversion in MainWindow and make sure _listStatusBox don't come back when not needed

* Remove previous transparency clear attempt that is useless now

* Remove an extra line return
---
 Ryujinx.Graphics.OpenGL/Window.cs | 40 ++++++++++++------
 Ryujinx/Ui/GLRenderer.cs          | 69 ++++++++++++++++---------------
 Ryujinx/Ui/MainWindow.cs          | 54 +++++++++++++-----------
 Ryujinx/Ui/MainWindow.glade       |  6 ++-
 4 files changed, 97 insertions(+), 72 deletions(-)

diff --git a/Ryujinx.Graphics.OpenGL/Window.cs b/Ryujinx.Graphics.OpenGL/Window.cs
index 26fc6a64b8..2689a7c4a6 100644
--- a/Ryujinx.Graphics.OpenGL/Window.cs
+++ b/Ryujinx.Graphics.OpenGL/Window.cs
@@ -22,15 +22,30 @@ namespace Ryujinx.Graphics.OpenGL
 
         public void Present(ITexture texture, ImageCrop crop)
         {
-            TextureView view = (TextureView)texture;
-
             GL.Disable(EnableCap.FramebufferSrgb);
 
+            CopyTextureToFrameBufferRGB(0, GetCopyFramebufferHandleLazy(), (TextureView)texture, crop);
+
+            GL.Enable(EnableCap.FramebufferSrgb);
+        }
+
+        public void SetSize(int width, int height)
+        {
+            _width  = width;
+            _height = height;
+        }
+
+
+        private void CopyTextureToFrameBufferRGB(int drawFramebuffer, int readFramebuffer, TextureView view, ImageCrop crop)
+        {
+            bool[] oldFramebufferColorWritemask = new bool[4];
+
             int oldReadFramebufferHandle = GL.GetInteger(GetPName.ReadFramebufferBinding);
             int oldDrawFramebufferHandle = GL.GetInteger(GetPName.DrawFramebufferBinding);
+            GL.GetBoolean(GetIndexedPName.ColorWritemask, drawFramebuffer, oldFramebufferColorWritemask);
 
-            GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, 0);
-            GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, GetCopyFramebufferHandleLazy());
+            GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, drawFramebuffer);
+            GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, readFramebuffer);
 
             GL.FramebufferTexture(
                 FramebufferTarget.ReadFramebuffer,
@@ -93,16 +108,17 @@ namespace Ryujinx.Graphics.OpenGL
                 ClearBufferMask.ColorBufferBit,
                 BlitFramebufferFilter.Linear);
 
+            // Remove Alpha channel
+            GL.ColorMask(drawFramebuffer, false, false, false, true);
+            GL.ClearBuffer(ClearBuffer.Color, 0, new float[] { 0.0f, 0.0f, 0.0f, 1.0f });
+            GL.ColorMask(drawFramebuffer,
+                oldFramebufferColorWritemask[0],
+                oldFramebufferColorWritemask[1],
+                oldFramebufferColorWritemask[2],
+                oldFramebufferColorWritemask[3]);
+
             GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, oldReadFramebufferHandle);
             GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, oldDrawFramebufferHandle);
-
-            GL.Enable(EnableCap.FramebufferSrgb);
-        }
-
-        public void SetSize(int width, int height)
-        {
-            _width  = width;
-            _height = height;
         }
 
         private int GetCopyFramebufferHandleLazy()
diff --git a/Ryujinx/Ui/GLRenderer.cs b/Ryujinx/Ui/GLRenderer.cs
index 7506821737..737a1fdd74 100644
--- a/Ryujinx/Ui/GLRenderer.cs
+++ b/Ryujinx/Ui/GLRenderer.cs
@@ -53,7 +53,7 @@ namespace Ryujinx.Ui
         private Input.NpadController _primaryController;
 
         public GLRenderer(Switch device) 
-            : base (new GraphicsMode(new ColorFormat(24)), 
+            : base (new GraphicsMode(new ColorFormat()),
             3, 3, 
             GraphicsContextFlags.ForwardCompatible)
         {
@@ -63,6 +63,7 @@ namespace Ryujinx.Ui
 
             this.Initialized += GLRenderer_Initialized;
             this.Destroyed += GLRenderer_Destroyed;
+            this.ShuttingDown += GLRenderer_ShuttingDown;
 
             Initialize();
 
@@ -81,6 +82,11 @@ namespace Ryujinx.Ui
             this.Shown += Renderer_Shown;
         }
 
+        private void GLRenderer_ShuttingDown(object sender, EventArgs args)
+        {
+            Exit();
+        }
+
         private void Parent_FocusOutEvent(object o, Gtk.FocusOutEventArgs args)
         {
             IsFocused = false;
@@ -93,9 +99,7 @@ namespace Ryujinx.Ui
 
         private void GLRenderer_Destroyed(object sender, EventArgs e)
         {
-            Exit();
-
-            this.Dispose();
+            Dispose();
         }
 
         protected void Renderer_Shown(object sender, EventArgs e)
@@ -106,41 +110,38 @@ namespace Ryujinx.Ui
         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.AltLeft) 
+                                ||   keyboard.IsKeyDown(OpenTK.Input.Key.AltRight)) 
+                                &&   keyboard.IsKeyDown(OpenTK.Input.Key.Enter))
+                                || keyboard.IsKeyDown(OpenTK.Input.Key.Escape);
 
-            if (toggleFullscreen == _toggleFullscreen)
+            bool fullScreenToggled = ParentWindow.State.HasFlag(Gdk.WindowState.Fullscreen);
+
+            if (toggleFullscreen != _toggleFullscreen)
             {
-                return;
+                if (toggleFullscreen)
+                {
+                    if (fullScreenToggled)
+                    {
+                        ParentWindow.Unfullscreen();
+                        (Toplevel as MainWindow)?.ToggleExtraWidgets(true);
+                    }
+                    else
+                    {
+                        if (keyboard.IsKeyDown(OpenTK.Input.Key.Escape))
+                        {
+                            Exit();
+                        }
+                        else
+                        {
+                            ParentWindow.Fullscreen();
+                            (Toplevel as MainWindow)?.ToggleExtraWidgets(false);
+                        }
+                    }
+                }
             }
 
             _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)
diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs
index 6c771bb96f..8c500b30ee 100644
--- a/Ryujinx/Ui/MainWindow.cs
+++ b/Ryujinx/Ui/MainWindow.cs
@@ -31,7 +31,7 @@ namespace Ryujinx.Ui
 
         private static GLRenderer _gLWidget;
 
-        private static AutoResetEvent _screenExitStatus = new AutoResetEvent(false);
+        private static AutoResetEvent _deviceExitStatus = new AutoResetEvent(false);
 
         private static ListStore _tableStore;
 
@@ -356,7 +356,7 @@ namespace Ryujinx.Ui
 
                 _emulationContext = device;
 
-                _screenExitStatus.Reset();
+                _deviceExitStatus.Reset();
 
 #if MACOS_BUILD
                 CreateGameWindow(device);
@@ -391,8 +391,6 @@ namespace Ryujinx.Ui
         {
             device.Hid.InitializePrimaryController(ConfigurationState.Instance.Hid.ControllerType);
 
-            _gLWidget?.Exit();
-            _gLWidget?.Dispose();
             _gLWidget = new GLRenderer(_emulationContext);
 
             Application.Invoke(delegate
@@ -402,13 +400,17 @@ namespace Ryujinx.Ui
                 _viewBox.Child = _gLWidget;
 
                 _gLWidget.ShowAll();
-                _listStatusBox.Hide();
+                ClearFooterForGameRender();
             });
 
             _gLWidget.WaitEvent.WaitOne();
 
             _gLWidget.Start();
 
+            device.Dispose();
+            _deviceExitStatus.Set();
+
+            // NOTE: Everything that is here will not be executed when you close the UI.
             Application.Invoke(delegate
             {
                 _viewBox.Remove(_gLWidget);
@@ -419,36 +421,41 @@ namespace Ryujinx.Ui
                     _gLWidget.Window.Dispose();
                 }
 
+                _gLWidget.Dispose();
+
                 _viewBox.Add(_gameTableWindow);
 
                 _gameTableWindow.Expand = true;
 
                 this.Window.Title = "Ryujinx";
 
-                _listStatusBox.ShowAll();
+                _emulationContext = null;
+                _gameLoaded       = false;
+                _gLWidget         = null;
+
+                DiscordIntegrationModule.SwitchToMainMenu();
+
+                RecreateFooterForMenu();
 
                 UpdateColumns();
                 UpdateGameTable();
 
                 Task.Run(RefreshFirmwareLabel);
-            });
 
-            device.Dispose();
-
-            _emulationContext = null;
-            _gameLoaded       = false;
-            _gLWidget         = null;
-
-            DiscordIntegrationModule.SwitchToMainMenu();
-
-            Application.Invoke(delegate
-            {
                 _stopEmulation.Sensitive            = false;
                 _firmwareInstallFile.Sensitive      = true;
                 _firmwareInstallDirectory.Sensitive = true;
             });
+        }
 
-            _screenExitStatus.Set();
+        private void RecreateFooterForMenu()
+        {
+            _footerBox.Add(_listStatusBox);
+        }
+
+        private void ClearFooterForGameRender()
+        {
+            _footerBox.Remove(_listStatusBox);
         }
 
         public void ToggleExtraWidgets(bool show)
@@ -469,7 +476,7 @@ namespace Ryujinx.Ui
 
             bool fullScreenToggled = this.Window.State.HasFlag(Gdk.WindowState.Fullscreen);
 
-            _fullScreen.Label = !fullScreenToggled ? "Exit Fullscreen" : "Enter Fullscreen";
+            _fullScreen.Label = fullScreenToggled ? "Exit Fullscreen" : "Enter Fullscreen";
         }
 
         private static void UpdateGameMetadata(string titleId)
@@ -506,8 +513,11 @@ namespace Ryujinx.Ui
 
                 if (_gLWidget != null)
                 {
+                    // We tell the widget that we are exiting
                     _gLWidget.Exit();
-                    _screenExitStatus.WaitOne();
+
+                    // Wait for the other thread to dispose the HLE context before exiting.
+                    _deviceExitStatus.WaitOne();
                 }
             }
 
@@ -874,16 +884,12 @@ namespace Ryujinx.Ui
             {
                 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 8477f392a4..6eeab7a41e 100644
--- a/Ryujinx/Ui/MainWindow.glade
+++ b/Ryujinx/Ui/MainWindow.glade
@@ -7,7 +7,7 @@
     <property name="title" translatable="yes">Ryujinx</property>
     <property name="window_position">center</property>
     <property name="default_width">1280</property>
-    <property name="default_height">750</property>
+    <property name="default_height">760</property>
     <child type="titlebar">
       <placeholder/>
     </child>
@@ -357,7 +357,7 @@
           </packing>
         </child>
         <child>
-          <object class="GtkBox" id="MainBox">
+          <object class="GtkBox" id="_mainBox">
             <property name="visible">True</property>
             <property name="can_focus">False</property>
             <property name="orientation">vertical</property>
@@ -403,6 +403,8 @@
               <object class="GtkBox" id="_footerBox">
                 <property name="visible">True</property>
                 <property name="can_focus">False</property>
+                <property name="width_request">1280</property>
+                <property name="height_request">19</property>
                 <child>
                   <object class="GtkBox" id="_listStatusBox">
                     <property name="visible">True</property>