Переглянути джерело

Somewhat better NvFlinger (I guess) (fixes #30)

gdkchan 8 роки тому
батько
коміт
2ed733b1d5

+ 60 - 0
Ryujinx.Core/OsHle/Objects/Android/GbpBuffer.cs

@@ -0,0 +1,60 @@
+using System.IO;
+
+namespace Ryujinx.Core.OsHle.Objects.Android
+{
+    struct GbpBuffer
+    {
+        public int Magic  { get; private set; }
+        public int Width  { get; private set; }
+        public int Height { get; private set; }
+        public int Stride { get; private set; }
+        public int Format { get; private set; }
+        public int Usage  { get; private set; }
+
+        public int Pid      { get; private set; }
+        public int RefCount { get; private set; }
+
+        public int FdsCount  { get; private set; }
+        public int IntsCount { get; private set; }
+
+        public byte[] RawData { get; private set; }
+
+        public int Size => RawData.Length + 10 * 4;
+
+        public GbpBuffer(BinaryReader Reader)
+        {
+            Magic  = Reader.ReadInt32();
+            Width  = Reader.ReadInt32();
+            Height = Reader.ReadInt32();
+            Stride = Reader.ReadInt32();
+            Format = Reader.ReadInt32();
+            Usage  = Reader.ReadInt32();
+
+            Pid      = Reader.ReadInt32();
+            RefCount = Reader.ReadInt32();
+
+            FdsCount  = Reader.ReadInt32();
+            IntsCount = Reader.ReadInt32();
+
+            RawData = Reader.ReadBytes((FdsCount + IntsCount) * 4);
+        }
+
+        public void Write(BinaryWriter Writer)
+        {
+            Writer.Write(Magic);
+            Writer.Write(Width);
+            Writer.Write(Height);
+            Writer.Write(Stride);
+            Writer.Write(Format);
+            Writer.Write(Usage);
+
+            Writer.Write(Pid);
+            Writer.Write(RefCount);
+
+            Writer.Write(FdsCount);
+            Writer.Write(IntsCount);
+
+            Writer.Write(RawData);
+        }
+    }
+}

+ 392 - 0
Ryujinx.Core/OsHle/Objects/Android/NvFlinger.cs

@@ -0,0 +1,392 @@
+using ChocolArm64.Memory;
+using Ryujinx.Core.OsHle.Handles;
+using System;
+using System.IO;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading;
+
+using static Ryujinx.Core.OsHle.Objects.Android.Parcel;
+
+namespace Ryujinx.Core.OsHle.Objects.Android
+{
+    class NvFlinger : IDisposable
+    {
+        private delegate long ServiceProcessParcel(ServiceCtx Context, BinaryReader ParcelReader);
+
+        private Dictionary<(string, int), ServiceProcessParcel> Commands;
+
+        private const int BufferQueueCount = 0x40;
+        private const int BufferQueueMask  = BufferQueueCount - 1;
+
+        [Flags]
+        private enum HalTransform
+        {
+            FlipX     = 1 << 0,
+            FlipY     = 1 << 1,
+            Rotate90  = 1 << 2
+        }
+
+        private enum BufferState
+        {
+            Free,
+            Dequeued,
+            Queued,
+            Acquired
+        }
+
+        private struct BufferEntry
+        {
+            public BufferState State;
+
+            public HalTransform Transform;
+
+            public GbpBuffer Data;
+        }
+
+        private BufferEntry[] BufferQueue;
+
+        private ManualResetEvent WaitBufferFree;
+
+        private bool KeepRunning;
+
+        public NvFlinger()
+        {
+            Commands = new Dictionary<(string, int), ServiceProcessParcel>()
+            {
+                { ("android.gui.IGraphicBufferProducer", 0x1), GbpRequestBuffer  },
+                { ("android.gui.IGraphicBufferProducer", 0x3), GbpDequeueBuffer  },
+                { ("android.gui.IGraphicBufferProducer", 0x7), GbpQueueBuffer    },
+                { ("android.gui.IGraphicBufferProducer", 0x8), GbpCancelBuffer   },
+                { ("android.gui.IGraphicBufferProducer", 0x9), GbpQuery          },
+                { ("android.gui.IGraphicBufferProducer", 0xa), GbpConnect        },
+                { ("android.gui.IGraphicBufferProducer", 0xe), GbpPreallocBuffer }
+            };
+
+            BufferQueue = new BufferEntry[0x40];
+
+            WaitBufferFree = new ManualResetEvent(false);
+
+            KeepRunning = true;
+        }
+
+        public long ProcessParcelRequest(ServiceCtx Context, byte[] ParcelData, int Code)
+        {
+            using (MemoryStream MS = new MemoryStream(ParcelData))
+            {
+                BinaryReader Reader = new BinaryReader(MS);
+
+                MS.Seek(4, SeekOrigin.Current);
+
+                int StrSize = Reader.ReadInt32();
+
+                string InterfaceName = Encoding.Unicode.GetString(Reader.ReadBytes(StrSize * 2));
+
+                long Remainder = MS.Position & 0xf;
+
+                if (Remainder != 0)
+                {
+                    MS.Seek(0x10 - Remainder, SeekOrigin.Current);
+                }
+
+                MS.Seek(0x50, SeekOrigin.Begin);
+
+                if (Commands.TryGetValue((InterfaceName, Code), out ServiceProcessParcel ProcReq))
+                {
+                    Logging.Debug($"{InterfaceName} {ProcReq.Method.Name}");
+
+                    return ProcReq(Context, Reader);
+                }
+                else
+                {
+                    throw new NotImplementedException($"{InterfaceName} {Code}");
+                }
+            }
+        }
+
+        private long GbpRequestBuffer(ServiceCtx Context, BinaryReader ParcelReader)
+        {
+            int Slot = ParcelReader.ReadInt32();
+
+            using (MemoryStream MS = new MemoryStream())
+            {
+                BinaryWriter Writer = new BinaryWriter(MS);
+                
+                BufferEntry Entry = BufferQueue[Slot];
+
+                int  BufferCount = 1; //?
+                long BufferSize  = Entry.Data.Size;
+
+                Writer.Write(BufferCount);
+                Writer.Write(BufferSize);
+
+                Entry.Data.Write(Writer);
+
+                Writer.Write(0);
+
+                return MakeReplyParcel(Context, MS.ToArray());
+            }
+        }
+
+        private long GbpDequeueBuffer(ServiceCtx Context, BinaryReader ParcelReader)
+        {
+            //TODO: Errors.
+            int Format        = ParcelReader.ReadInt32();
+            int Width         = ParcelReader.ReadInt32();
+            int Height        = ParcelReader.ReadInt32();
+            int GetTimestamps = ParcelReader.ReadInt32();
+            int Usage         = ParcelReader.ReadInt32();
+
+            int Slot = GetFreeSlotBlocking(Width, Height);
+
+            return MakeReplyParcel(Context, Slot, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
+        }
+
+        private long GbpQueueBuffer(ServiceCtx Context, BinaryReader ParcelReader)
+        {
+            //TODO: Errors.
+            int Slot            = ParcelReader.ReadInt32();
+            int Unknown4        = ParcelReader.ReadInt32();
+            int Unknown8        = ParcelReader.ReadInt32();
+            int Unknownc        = ParcelReader.ReadInt32();
+            int Timestamp       = ParcelReader.ReadInt32();
+            int IsAutoTimestamp = ParcelReader.ReadInt32();
+            int CropTop         = ParcelReader.ReadInt32();
+            int CropLeft        = ParcelReader.ReadInt32();
+            int CropRight       = ParcelReader.ReadInt32();
+            int CropBottom      = ParcelReader.ReadInt32();
+            int ScalingMode     = ParcelReader.ReadInt32();
+            int Transform       = ParcelReader.ReadInt32();
+            int StickyTransform = ParcelReader.ReadInt32();
+            int Unknown34       = ParcelReader.ReadInt32();
+            int Unknown38       = ParcelReader.ReadInt32();
+            int IsFenceValid    = ParcelReader.ReadInt32();
+            int Fence0Id        = ParcelReader.ReadInt32();
+            int Fence0Value     = ParcelReader.ReadInt32();
+            int Fence1Id        = ParcelReader.ReadInt32();
+            int Fence1Value     = ParcelReader.ReadInt32();
+
+            BufferQueue[Slot].Transform = (HalTransform)Transform;
+
+            BufferQueue[Slot].State = BufferState.Queued;
+
+            SendFrameBuffer(Context, Slot);
+
+            return MakeReplyParcel(Context, 1280, 720, 0, 0, 0);
+        }
+
+        private long GbpCancelBuffer(ServiceCtx Context, BinaryReader ParcelReader)
+        {
+            //TODO: Errors.
+            int Slot = ParcelReader.ReadInt32();
+
+            BufferQueue[Slot].State = BufferState.Free;
+
+            return MakeReplyParcel(Context, 0);
+        }
+
+        private long GbpQuery(ServiceCtx Context, BinaryReader ParcelReader)
+        {
+            return MakeReplyParcel(Context, 0, 0);
+        }
+
+        private long GbpConnect(ServiceCtx Context, BinaryReader ParcelReader)
+        {
+            return MakeReplyParcel(Context, 1280, 720, 0, 0, 0);
+        }
+
+        private long GbpPreallocBuffer(ServiceCtx Context, BinaryReader ParcelReader)
+        {
+            int Slot = ParcelReader.ReadInt32();
+            
+            int  BufferCount = ParcelReader.ReadInt32();
+            long BufferSize  = ParcelReader.ReadInt64();
+
+            BufferQueue[Slot].State = BufferState.Free;
+
+            BufferQueue[Slot].Data = new GbpBuffer(ParcelReader);
+
+            return MakeReplyParcel(Context, 0);
+        }
+
+        private long MakeReplyParcel(ServiceCtx Context, params int[] Ints)
+        {
+            using (MemoryStream MS = new MemoryStream())
+            {
+                BinaryWriter Writer = new BinaryWriter(MS);
+
+                foreach (int Int in Ints)
+                {
+                    Writer.Write(Int);
+                }
+
+                return MakeReplyParcel(Context, MS.ToArray());
+            }
+        }
+
+        private long MakeReplyParcel(ServiceCtx Context, byte[] Data)
+        {
+            long ReplyPos  = Context.Request.ReceiveBuff[0].Position;
+            long ReplySize = Context.Request.ReceiveBuff[0].Size;
+
+            byte[] Reply = MakeParcel(Data, new byte[0]);
+
+            AMemoryHelper.WriteBytes(Context.Memory, ReplyPos, Reply);
+
+            return 0;
+        }
+
+        private unsafe void SendFrameBuffer(ServiceCtx Context, int Slot)
+        {
+            int FbWidth  = BufferQueue[Slot].Data.Width;
+            int FbHeight = BufferQueue[Slot].Data.Height;
+
+            int FbSize = FbWidth * FbHeight * 4;
+
+            HNvMap NvMap = GetNvMap(Context, Slot);
+
+            if (NvMap.Address < 0 || NvMap.Address + FbSize > AMemoryMgr.AddrSize)
+            {
+                Logging.Error($"Frame buffer address {NvMap.Address:x16} is invalid!");
+
+                BufferQueue[Slot].State = BufferState.Free;
+
+                WaitBufferFree.Set();
+
+                return;
+            }
+
+            BufferQueue[Slot].State = BufferState.Acquired; 
+
+            float ScaleX = 1;
+            float ScaleY = 1;
+            float Rotate = 0;
+
+            if (BufferQueue[Slot].Transform.HasFlag(HalTransform.FlipX))
+            {
+                ScaleX = -1;
+            }
+
+            if (BufferQueue[Slot].Transform.HasFlag(HalTransform.FlipY))
+            {
+                ScaleY = -1;
+            }
+
+            if (BufferQueue[Slot].Transform.HasFlag(HalTransform.Rotate90))
+            {
+                Rotate = MathF.PI * 0.5f;
+            }
+
+            byte* Fb = (byte*)Context.Ns.Ram + NvMap.Address;
+
+            Context.Ns.Gpu.Renderer.QueueAction(delegate()
+            {
+                Context.Ns.Gpu.Renderer.SetFrameBuffer(
+                    Fb,
+                    FbWidth,
+                    FbHeight,
+                    ScaleX,
+                    ScaleY,
+                    Rotate);
+
+                BufferQueue[Slot].State = BufferState.Free;
+
+                WaitBufferFree.Set();
+            });
+        }
+
+        private HNvMap GetNvMap(ServiceCtx Context, int Slot)
+        {
+            int NvMapHandle = BitConverter.ToInt32(BufferQueue[Slot].Data.RawData, 0x4c);
+
+            if (!BitConverter.IsLittleEndian)
+            {
+                byte[] RawValue = BitConverter.GetBytes(NvMapHandle);
+
+                Array.Reverse(RawValue);
+
+                NvMapHandle = BitConverter.ToInt32(RawValue, 0);
+            }
+
+            return Context.Ns.Os.Handles.GetData<HNvMap>(NvMapHandle);
+        }
+
+        private int GetFreeSlotBlocking(int Width, int Height)
+        {
+            int Slot;
+
+            do
+            {
+                if ((Slot = GetFreeSlot(Width, Height)) != -1)
+                {
+                    break;
+                }
+
+                Logging.Debug("Waiting for a free BufferQueue slot...");
+
+                lock (WaitBufferFree)
+                {
+                    if (!KeepRunning)
+                    {
+                        break;
+                    }
+
+                    WaitBufferFree.Reset();
+                }
+
+                WaitBufferFree.WaitOne();
+            }
+            while (KeepRunning);
+
+            Logging.Debug($"Found free BufferQueue slot {Slot}!");
+
+            return Slot;
+        }
+
+        private int GetFreeSlot(int Width, int Height)
+        {
+            lock (BufferQueue)
+            {
+                for (int Slot = 0; Slot < BufferQueue.Length; Slot++)
+                {
+                    if (BufferQueue[Slot].State != BufferState.Free)
+                    {
+                        continue;
+                    }
+
+                    GbpBuffer Data = BufferQueue[Slot].Data;
+
+                    if (Data.Width  == Width &&
+                        Data.Height == Height)
+                    {
+                        BufferQueue[Slot].State = BufferState.Dequeued;
+
+                        return Slot;
+                    }
+                }
+            }
+
+            return -1;
+        }
+
+        public void Dispose()
+        {
+            Dispose(true);
+        }
+
+        protected virtual void Dispose(bool disposing)
+        {
+            if (disposing)
+            {
+                lock (WaitBufferFree)
+                {
+                    KeepRunning = false;
+
+                    WaitBufferFree.Set();
+                }
+
+                WaitBufferFree.Dispose();
+            }
+        }
+    }
+}

+ 0 - 0
Ryujinx.Core/OsHle/Objects/Parcel.cs → Ryujinx.Core/OsHle/Objects/Android/Parcel.cs


+ 0 - 2
Ryujinx.Core/OsHle/Objects/ErrorCode.cs

@@ -1,5 +1,3 @@
-using System;
-
 namespace Ryujinx.Core.OsHle.Objects
 {
     static class ErrorCode

+ 0 - 1
Ryujinx.Core/OsHle/Objects/Hid/IActiveVibrationDeviceList.cs

@@ -1,4 +1,3 @@
-using Ryujinx.Core.OsHle.Handles;
 using Ryujinx.Core.OsHle.Ipc;
 using System.Collections.Generic;
 

+ 20 - 158
Ryujinx.Core/OsHle/Objects/Vi/IHOSBinderDriver.cs

@@ -1,34 +1,18 @@
 using ChocolArm64.Memory;
-using Ryujinx.Core.OsHle.Handles;
 using Ryujinx.Core.OsHle.Ipc;
-using Ryujinx.Core.OsHle.Utilities;
+using Ryujinx.Core.OsHle.Objects.Android;
 using System;
 using System.Collections.Generic;
-using System.IO;
-using System.Text;
-
-using static Ryujinx.Core.OsHle.Objects.Android.Parcel;
 
 namespace Ryujinx.Core.OsHle.Objects.Vi
 {
-    class IHOSBinderDriver : IIpcInterface
+    class IHOSBinderDriver : IIpcInterface, IDisposable
     {
-        private delegate long ServiceProcessParcel(ServiceCtx Context, byte[] ParcelData);
-
         private Dictionary<int, ServiceProcessRequest> m_Commands;
 
-        private Dictionary<(string, int), ServiceProcessParcel> m_Methods;
-
-        public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;       
+        public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
 
-        private class BufferObj
-        {
-
-        }
-
-        private IdPoolWithObj BufferSlots;
-
-        private byte[] Gbfr;
+        private NvFlinger Flinger;
 
         public IHOSBinderDriver()
         {
@@ -39,18 +23,7 @@ namespace Ryujinx.Core.OsHle.Objects.Vi
                 { 2, GetNativeHandle }
             };
 
-            m_Methods = new Dictionary<(string, int), ServiceProcessParcel>()
-            {
-                { ("android.gui.IGraphicBufferProducer", 0x1), GraphicBufferProducerRequestBuffer },
-                { ("android.gui.IGraphicBufferProducer", 0x3), GraphicBufferProducerDequeueBuffer },
-                { ("android.gui.IGraphicBufferProducer", 0x7), GraphicBufferProducerQueueBuffer   },
-                { ("android.gui.IGraphicBufferProducer", 0x8), GraphicBufferProducerCancelBuffer  },
-                { ("android.gui.IGraphicBufferProducer", 0x9), GraphicBufferProducerQuery         },
-                { ("android.gui.IGraphicBufferProducer", 0xa), GraphicBufferProducerConnect       },
-                { ("android.gui.IGraphicBufferProducer", 0xe), GraphicBufferPreallocateBuffer     }
-            };
-
-            BufferSlots = new IdPoolWithObj();
+            Flinger = new NvFlinger();
         }
 
         public long TransactParcel(ServiceCtx Context)
@@ -63,133 +36,9 @@ namespace Ryujinx.Core.OsHle.Objects.Vi
 
             byte[] Data = AMemoryHelper.ReadBytes(Context.Memory, DataPos, (int)DataSize);
 
-            Data = GetParcelData(Data);
-
-            using (MemoryStream MS = new MemoryStream(Data))
-            {
-                BinaryReader Reader = new BinaryReader(MS);
-
-                MS.Seek(4, SeekOrigin.Current);
-
-                int StrSize = Reader.ReadInt32();
-
-                string InterfaceName = Encoding.Unicode.GetString(Data, 8, StrSize * 2);
-
-                if (m_Methods.TryGetValue((InterfaceName, Code), out ServiceProcessParcel ProcReq))
-                {
-                    return ProcReq(Context, Data);
-                }
-                else
-                {
-                    throw new NotImplementedException($"{InterfaceName} {Code}");
-                }
-            }
-        }
-
-        private long GraphicBufferProducerRequestBuffer(ServiceCtx Context, byte[] ParcelData)
-        {
-            int GbfrSize = Gbfr?.Length ?? 0;
-
-            byte[] Data = new byte[GbfrSize + 4];
-
-            if (Gbfr != null)
-            {
-                Buffer.BlockCopy(Gbfr, 0, Data, 0, GbfrSize);
-            }
-
-            return MakeReplyParcel(Context, Data);
-        }
-
-        private long GraphicBufferProducerDequeueBuffer(ServiceCtx Context, byte[] ParcelData)
-        {
-            //Note: It seems that the maximum number of slots is 64, because if we return
-            //a Slot number > 63, it seems to cause a buffer overrun and it reads garbage.
-            //Note 2: The size of each object associated with the slot is 0x30.
-            int Slot = BufferSlots.GenerateId(new BufferObj());
-
-            return MakeReplyParcel(Context, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
-        }
-
-        private long GraphicBufferProducerQueueBuffer(ServiceCtx Context, byte[] ParcelData)
-        {
-            return MakeReplyParcel(Context, 1280, 720, 0, 0, 0);
-        }
-
-        private long GraphicBufferProducerCancelBuffer(ServiceCtx Context, byte[] ParcelData)
-        {
-            using (MemoryStream MS = new MemoryStream(ParcelData))
-            {
-                BinaryReader Reader = new BinaryReader(MS);
-
-                MS.Seek(0x50, SeekOrigin.Begin);
-
-                int Slot = Reader.ReadInt32();
-
-                BufferSlots.Delete(Slot);
-
-                return MakeReplyParcel(Context, 0);
-            }
-        }
-
-        private long GraphicBufferProducerQuery(ServiceCtx Context, byte[] ParcelData)
-        {
-            return MakeReplyParcel(Context, 0, 0);
-        }
+            Data = Parcel.GetParcelData(Data);
 
-        private long GraphicBufferProducerConnect(ServiceCtx Context, byte[] ParcelData)
-        {
-            return MakeReplyParcel(Context, 1280, 720, 0, 0, 0);
-        }
-
-        private long GraphicBufferPreallocateBuffer(ServiceCtx Context, byte[] ParcelData)
-        {
-            int GbfrSize = ParcelData.Length - 0x54;
-
-            Gbfr = new byte[GbfrSize];
-
-            Buffer.BlockCopy(ParcelData, 0x54, Gbfr, 0, GbfrSize);
-
-            using (MemoryStream MS = new MemoryStream(ParcelData))
-            {
-                BinaryReader Reader = new BinaryReader(MS);
-
-                MS.Seek(0xd4, SeekOrigin.Begin);
-
-                int Handle = Reader.ReadInt32();
-
-                HNvMap NvMap = Context.Ns.Os.Handles.GetData<HNvMap>(Handle);
-
-                Context.Ns.Gpu.Renderer.FrameBufferPtr = NvMap.Address;
-            }
-
-            return MakeReplyParcel(Context, 0);
-        }
-
-        private long MakeReplyParcel(ServiceCtx Context, params int[] Ints)
-        {
-            using (MemoryStream MS = new MemoryStream())
-            {
-                BinaryWriter Writer = new BinaryWriter(MS);
-
-                foreach (int Int in Ints)
-                {
-                    Writer.Write(Int);
-                }
-
-                return MakeReplyParcel(Context, MS.ToArray());
-            }
-        }
-
-        private long MakeReplyParcel(ServiceCtx Context, byte[] Data)
-        {
-            long ReplyPos  = Context.Request.ReceiveBuff[0].Position;
-            long ReplySize = Context.Request.ReceiveBuff[0].Position;
-
-            byte[] Reply = MakeParcel(Data, new byte[0]);
-
-            AMemoryHelper.WriteBytes(Context.Memory, ReplyPos, Reply);
-
-            return 0;
+            return Flinger.ProcessParcelRequest(Context, Data, Code);
         }
 
         public long AdjustRefcount(ServiceCtx Context)
@@ -210,5 +59,18 @@ namespace Ryujinx.Core.OsHle.Objects.Vi
 
             return 0;
         }
+
+        public void Dispose()
+        {
+            Dispose(true);
+        }
+
+        protected virtual void Dispose(bool disposing)
+        {
+            if (disposing)
+            {
+                Flinger.Dispose();
+            }
+        }
     }
 }

+ 20 - 0
Ryujinx.Graphics/Gal/EmbeddedResource.cs

@@ -0,0 +1,20 @@
+using System.IO;
+using System.Reflection;
+
+namespace Ryujinx.Graphics.Gal
+{
+    static class EmbeddedResource
+    {
+        public static string GetString(string Name)
+        {
+            Assembly Asm = typeof(EmbeddedResource).Assembly;
+
+            using (Stream ResStream = Asm.GetManifestResourceStream(Name))
+            {
+                StreamReader Reader = new StreamReader(ResStream);
+
+                return Reader.ReadToEnd();
+            }
+        }
+    }
+}

+ 4 - 3
Ryujinx.Graphics/Gal/IGalRenderer.cs

@@ -2,14 +2,15 @@ using System;
 
 namespace Ryujinx.Graphics.Gal
 {
-    public interface IGalRenderer
+    public unsafe interface IGalRenderer
     {
-        long FrameBufferPtr { get; set; }
-
         void QueueAction(Action ActionMthd);
         void RunActions();
 
+        void InitializeFrameBuffer();
         void Render();
+        void SetWindowSize(int Width, int Height);
+        void SetFrameBuffer(byte* Fb, int Width, int Height, float SX, float SY, float R);
         void SendVertexBuffer(int Index, byte[] Buffer, int Stride, GalVertexAttrib[] Attribs);
         void SendR8G8B8A8Texture(int Index, byte[] Buffer, int Width, int Height);
         void BindTexture(int Index);

+ 13 - 0
Ryujinx.Graphics/Gal/OpenGL/FbFragShader.glsl

@@ -0,0 +1,13 @@
+#version 330 core
+
+precision highp float;
+
+uniform sampler2D tex;
+
+in vec2 tex_coord;
+
+out vec4 out_frag_color;
+
+void main(void) {
+    out_frag_color = texture(tex, tex_coord);
+}

+ 26 - 0
Ryujinx.Graphics/Gal/OpenGL/FbVtxShader.glsl

@@ -0,0 +1,26 @@
+#version 330 core
+
+precision highp float;
+
+uniform vec2 window_size;
+uniform mat2 transform;
+
+layout(location = 0) in vec2 in_position;
+layout(location = 1) in vec2 in_tex_coord;
+
+out vec2 tex_coord;
+
+// Have a fixed aspect ratio, fit the image within the available space.
+vec2 get_scale_ratio(void) {
+    vec2 native_size = vec2(1280, 720);
+    vec2 ratio = vec2(
+        (window_size.y * native_size.x) / (native_size.y * window_size.x),
+        (window_size.x * native_size.y) / (native_size.x * window_size.y)
+    );
+    return min(ratio, 1);
+}
+
+void main(void) {
+    tex_coord = in_tex_coord;
+    gl_Position = vec4((transform * in_position) * get_scale_ratio(), 0, 1);
+}

+ 228 - 0
Ryujinx.Graphics/Gal/OpenGL/FrameBuffer.cs

@@ -0,0 +1,228 @@
+using OpenTK;
+using OpenTK.Graphics.OpenGL;
+using System;
+
+namespace Ryujinx.Graphics.Gal.OpenGL
+{
+    unsafe class FrameBuffer
+    {
+        public int WindowWidth  { get; set; }
+        public int WindowHeight { get; set; }
+
+        private int VtxShaderHandle;
+        private int FragShaderHandle;
+        private int PrgShaderHandle;
+
+        private int TexHandle;
+        private int TexWidth;
+        private int TexHeight;
+
+        private int VaoHandle;
+        private int VboHandle;
+
+        private int[] Pixels;
+
+        private byte* FbPtr;
+
+        public FrameBuffer(int Width, int Height)
+        {
+            if (Width < 0)
+            {
+                throw new ArgumentOutOfRangeException(nameof(Width));
+            }
+
+            if (Height < 0)
+            {
+                throw new ArgumentOutOfRangeException(nameof(Height));
+            }
+
+            TexWidth  = Width;
+            TexHeight = Height;
+
+            WindowWidth  = Width;
+            WindowHeight = Height;
+
+            SetupShaders();
+            SetupTexture();
+            SetupVertex();
+        }
+
+        private void SetupShaders()
+        {
+            VtxShaderHandle  = GL.CreateShader(ShaderType.VertexShader);
+            FragShaderHandle = GL.CreateShader(ShaderType.FragmentShader);
+
+            string VtxShaderSource  = EmbeddedResource.GetString("GlFbVtxShader");
+            string FragShaderSource = EmbeddedResource.GetString("GlFbFragShader");
+
+            GL.ShaderSource(VtxShaderHandle, VtxShaderSource);
+            GL.ShaderSource(FragShaderHandle, FragShaderSource);
+            GL.CompileShader(VtxShaderHandle);
+            GL.CompileShader(FragShaderHandle);
+
+            PrgShaderHandle = GL.CreateProgram();
+
+            GL.AttachShader(PrgShaderHandle, VtxShaderHandle);
+            GL.AttachShader(PrgShaderHandle, FragShaderHandle);
+            GL.LinkProgram(PrgShaderHandle);
+            GL.UseProgram(PrgShaderHandle);
+
+            int TexUniformLocation = GL.GetUniformLocation(PrgShaderHandle, "tex");
+
+            GL.Uniform1(TexUniformLocation, 0);
+
+            int WindowSizeUniformLocation = GL.GetUniformLocation(PrgShaderHandle, "window_size");
+
+            GL.Uniform2(WindowSizeUniformLocation, new Vector2(1280.0f, 720.0f));
+        }
+
+        private void SetupTexture()
+        {
+            Pixels = new int[TexWidth * TexHeight];
+
+            if (TexHandle == 0)
+            {
+                TexHandle = GL.GenTexture();
+            }
+
+            GL.BindTexture(TextureTarget.Texture2D, TexHandle);
+            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
+            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
+            GL.TexImage2D(TextureTarget.Texture2D,
+                0,
+                PixelInternalFormat.Rgba,
+                TexWidth,
+                TexHeight,
+                0,
+                PixelFormat.Rgba,
+                PixelType.UnsignedByte,
+                IntPtr.Zero);
+        }
+
+        private void SetupVertex()
+        {
+            VaoHandle = GL.GenVertexArray();
+            VboHandle = GL.GenBuffer();
+
+            float[] Buffer = new float[]
+            {
+                -1,  1,  0,  0,
+                 1,  1,  1,  0,
+                -1, -1,  0,  1,
+                 1, -1,  1,  1
+            };
+
+            IntPtr Length = new IntPtr(Buffer.Length * 4);
+
+            GL.BindBuffer(BufferTarget.ArrayBuffer, VboHandle);
+            GL.BufferData(BufferTarget.ArrayBuffer, Length, Buffer, BufferUsageHint.StreamDraw);
+            GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
+
+            GL.BindVertexArray(VaoHandle);
+
+            GL.EnableVertexAttribArray(0);
+
+            GL.BindBuffer(BufferTarget.ArrayBuffer, VboHandle);
+
+            GL.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, 16, 0);
+
+            GL.EnableVertexAttribArray(1);
+
+            GL.BindBuffer(BufferTarget.ArrayBuffer, VboHandle);
+
+            GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, 16, 8);
+
+            GL.BindVertexArray(0);
+        }
+
+        public unsafe void Set(byte* Fb, int Width, int Height, Matrix2 Transform)
+        {
+            if (Fb == null)
+            {
+                throw new ArgumentNullException(nameof(Fb));
+            }
+
+            if (Width < 0)
+            {
+                throw new ArgumentOutOfRangeException(nameof(Width));
+            }
+
+            if (Height < 0)
+            {
+                throw new ArgumentOutOfRangeException(nameof(Height));
+            }
+
+            FbPtr = Fb;
+
+            if (Width  != TexWidth ||
+                Height != TexHeight)
+            {
+                TexWidth  = Width;
+                TexHeight = Height;
+
+                SetupTexture();
+            }
+
+            GL.UseProgram(PrgShaderHandle);
+
+            int TransformUniformLocation = GL.GetUniformLocation(PrgShaderHandle, "transform");
+
+            GL.UniformMatrix2(TransformUniformLocation, false, ref Transform);
+
+            int WindowSizeUniformLocation = GL.GetUniformLocation(PrgShaderHandle, "window_size");
+
+            GL.Uniform2(WindowSizeUniformLocation, new Vector2(WindowWidth, WindowHeight));
+        }
+
+        public void Render()
+        {
+            if (FbPtr == null)
+            {
+                return;
+            }
+
+            for (int Y = 0; Y < TexHeight; Y++)
+            for (int X = 0; X < TexWidth;  X++)
+            {
+                Pixels[X + Y * TexWidth] = *((int*)(FbPtr + GetSwizzleOffset(X, Y)));
+            }
+
+            GL.BindTexture(TextureTarget.Texture2D, TexHandle);
+            GL.TexSubImage2D(TextureTarget.Texture2D,
+                0,
+                0,
+                0,
+                TexWidth,
+                TexHeight,
+                PixelFormat.Rgba,
+                PixelType.UnsignedByte,
+                Pixels);
+            
+            GL.ActiveTexture(TextureUnit.Texture0);
+
+            GL.BindVertexArray(VaoHandle);
+
+            GL.UseProgram(PrgShaderHandle);
+
+            GL.DrawArrays(PrimitiveType.TriangleStrip, 0, 4);
+        }
+
+        private int GetSwizzleOffset(int X, int Y)
+        {
+            int Pos;
+
+            Pos  = (Y & 0x7f) >> 4;
+            Pos += (X >> 4) << 3;
+            Pos += (Y >> 7) * ((TexWidth >> 4) << 3);
+            Pos *= 1024;
+            Pos += ((Y & 0xf) >> 3) << 9;
+            Pos += ((X & 0xf) >> 3) << 8;
+            Pos += ((Y & 0x7) >> 1) << 6;
+            Pos += ((X & 0x7) >> 2) << 5;
+            Pos += ((Y & 0x1) >> 0) << 4;
+            Pos += ((X & 0x3) >> 0) << 2;
+
+            return Pos;
+        }
+    }
+}

+ 37 - 3
Ryujinx.Graphics/Gal/OpenGL/OpenGLRenderer.cs

@@ -1,7 +1,9 @@
+using OpenTK;
 using OpenTK.Graphics.OpenGL;
 using System;
 using System.Collections.Generic;
 
+
 namespace Ryujinx.Graphics.Gal.OpenGL
 {
     public class OpenGLRenderer : IGalRenderer
@@ -25,6 +27,8 @@ namespace Ryujinx.Graphics.Gal.OpenGL
 
         private Queue<Action> ActionsQueue;
 
+        private FrameBuffer FbRenderer;
+
         public long FrameBufferPtr { get; set; }
 
         public OpenGLRenderer()
@@ -36,6 +40,11 @@ namespace Ryujinx.Graphics.Gal.OpenGL
             ActionsQueue = new Queue<Action>();
         }
 
+        public void InitializeFrameBuffer()
+        {
+            FbRenderer = new FrameBuffer(1280, 720);
+        }
+
         public void QueueAction(Action ActionMthd)
         {
             ActionsQueue.Enqueue(ActionMthd);
@@ -43,14 +52,18 @@ namespace Ryujinx.Graphics.Gal.OpenGL
 
         public void RunActions()
         {
-            while (ActionsQueue.Count > 0)
+            int Count = ActionsQueue.Count;
+
+            while (Count-- > 0)
             {
                 ActionsQueue.Dequeue()();
             }
-        }
+        }        
 
         public void Render()
         {
+            FbRenderer.Render();
+
             for (int Index = 0; Index < VertexBuffers.Count; Index++)
             {
                 VertexBuffer Vb = VertexBuffers[Index];
@@ -62,7 +75,28 @@ namespace Ryujinx.Graphics.Gal.OpenGL
                     GL.DrawArrays(PrimitiveType.TriangleStrip, 0, Vb.PrimCount);
                 }
             }
-            
+        }
+
+        public void SetWindowSize(int Width, int Height)
+        {
+            FbRenderer.WindowWidth  = Width;
+            FbRenderer.WindowHeight = Height;
+        }
+
+        public unsafe void SetFrameBuffer(
+            byte* Fb,
+            int   Width,
+            int   Height,
+            float ScaleX,
+            float ScaleY,
+            float Rotate)
+        {
+            Matrix2 Transform;
+
+            Transform  = Matrix2.CreateScale(ScaleX, ScaleY);
+            Transform *= Matrix2.CreateRotation(Rotate);
+
+            FbRenderer.Set(Fb, Width, Height, Transform);
         }
 
         public void SendVertexBuffer(int Index, byte[] Buffer, int Stride, GalVertexAttrib[] Attribs)

+ 17 - 0
Ryujinx.Graphics/Ryujinx.Graphics.csproj

@@ -4,6 +4,14 @@
     <TargetFramework>netcoreapp2.0</TargetFramework>
   </PropertyGroup>
 
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+  </PropertyGroup>
+
   <ItemGroup>
     <PackageReference Include="OpenTK.NETCore" Version="1.1.2749.6433" />
   </ItemGroup>
@@ -12,4 +20,13 @@
     <ProjectReference Include="..\ChocolArm64\ChocolArm64.csproj" />
   </ItemGroup>
 
+  <ItemGroup>
+    <EmbeddedResource Include="Gal\OpenGL\FbVtxShader.glsl">
+        <LogicalName>GlFbVtxShader</LogicalName>
+    </EmbeddedResource>
+    <EmbeddedResource Include="Gal\OpenGL\FbFragShader.glsl">
+        <LogicalName>GlFbFragShader</LogicalName>
+    </EmbeddedResource>
+  </ItemGroup>
+
 </Project>

+ 3 - 277
Ryujinx/Ui/GLScreen.cs

@@ -1,7 +1,3 @@
-// This code was written for the OpenTK library and has been released
-// to the Public Domain.
-// It is provided "as is" without express or implied warranty of any kind.
-
 using OpenTK;
 using OpenTK.Graphics;
 using OpenTK.Graphics.OpenGL;
@@ -13,281 +9,25 @@ namespace Ryujinx
 {
     public class GLScreen : GameWindow
     {
-        class ScreenTexture : IDisposable
-        {
-            private Switch Ns;
-            private IGalRenderer Renderer;
-
-            private int Width;
-            private int Height;
-            private int TexHandle;
-
-            private int[] Pixels;
-
-            public ScreenTexture(Switch Ns, IGalRenderer Renderer, int Width, int Height)
-            {
-                this.Ns = Ns;
-                this.Renderer = Renderer;
-                this.Width = Width;
-                this.Height = Height;
-
-                Pixels = new int[Width * Height];
-
-                TexHandle = GL.GenTexture();
-
-                GL.BindTexture(TextureTarget.Texture2D, TexHandle);
-                GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
-                GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
-                GL.TexImage2D(TextureTarget.Texture2D,
-                    0,
-                    PixelInternalFormat.Rgba,
-                    Width,
-                    Height,
-                    0,
-                    PixelFormat.Rgba,
-                    PixelType.UnsignedByte,
-                    IntPtr.Zero);
-            }
-
-            public int Texture
-            {
-                get
-                {
-                    UploadBitmap();
-
-                    return TexHandle;
-                }
-            }
-
-            unsafe void UploadBitmap()
-            {
-                int FbSize = Width * Height * 4;
-
-                if (Renderer.FrameBufferPtr == 0 || Renderer.FrameBufferPtr + FbSize > uint.MaxValue)
-                {
-                    return;
-                }
-
-                byte* SrcPtr = (byte*)Ns.Ram + (uint)Renderer.FrameBufferPtr;
-
-                for (int Y = 0; Y < Height; Y++)
-                {
-                    for (int X = 0; X < Width; X++)
-                    {
-                        int SrcOffs = GetSwizzleOffset(X, Y, 4);
-
-                        Pixels[X + Y * Width] = *((int*)(SrcPtr + SrcOffs));
-                    }
-                }
-
-                GL.BindTexture(TextureTarget.Texture2D, TexHandle);
-                GL.TexSubImage2D(TextureTarget.Texture2D,
-                    0,
-                    0,
-                    0,
-                    Width,
-                    Height,
-                    PixelFormat.Rgba,
-                    PixelType.UnsignedByte,
-                    Pixels);
-            }
-
-            private int GetSwizzleOffset(int X, int Y, int Bpp)
-            {
-                int Pos;
-
-                Pos = (Y & 0x7f) >> 4;
-                Pos += (X >> 4) << 3;
-                Pos += (Y >> 7) * ((Width >> 4) << 3);
-                Pos *= 1024;
-                Pos += ((Y & 0xf) >> 3) << 9;
-                Pos += ((X & 0xf) >> 3) << 8;
-                Pos += ((Y & 0x7) >> 1) << 6;
-                Pos += ((X & 0x7) >> 2) << 5;
-                Pos += ((Y & 0x1) >> 0) << 4;
-                Pos += ((X & 0x3) >> 0) << 2;
-
-                return Pos;
-            }
-
-            private bool disposed;
-
-            public void Dispose()
-            {
-                Dispose(true);
-
-                GC.SuppressFinalize(this);
-            }
-
-            void Dispose(bool disposing)
-            {
-                if (!disposed)
-                {
-                    if (disposing)
-                    {
-                        GL.DeleteTexture(TexHandle);
-                    }
-
-                    disposed = true;
-                }
-            }
-        }
-
-        private string VtxShaderSource = @"
-#version 330 core
-
-precision highp float;
-
-uniform vec2 window_size;
-
-layout(location = 0) in vec3 in_position;
-layout(location = 1) in vec4 in_color;
-layout(location = 2) in vec2 in_tex_coord;
-
-out vec4 color;
-out vec2 tex_coord;
-
-// Have a fixed aspect ratio, fit the image within the available space.
-vec3 get_scale_ratio() {
-    vec2 native_size = vec2(1280, 720);
-    vec2 ratio = vec2(
-        (window_size.y * native_size.x) / (native_size.y * window_size.x),
-        (window_size.x * native_size.y) / (native_size.x * window_size.y)
-    );
-    return vec3(min(ratio, vec2(1, 1)) * vec2(1, -1), 1);
-}
-
-void main(void) { 
-    color = in_color;
-    tex_coord = in_tex_coord;
-    gl_Position = vec4(in_position * get_scale_ratio(), 1);
-}";
-
-        private string FragShaderSource = @"
-#version 330 core
-
-precision highp float;
-
-uniform sampler2D tex;
-
-in vec4 color;
-in vec2 tex_coord;
-out vec4 out_frag_color;
-
-void main(void) {
-    out_frag_color = vec4(texture(tex, tex_coord).rgb, color.a);
-}";
-
-        private int VtxShaderHandle,
-                    FragShaderHandle,
-                    PrgShaderHandle;
-
-        private int WindowSizeUniformLocation;
-
-        private int VaoHandle;
-        private int VboHandle;
-
         private Switch Ns;
 
         private IGalRenderer Renderer;
 
-        private ScreenTexture ScreenTex;
-
         public GLScreen(Switch Ns, IGalRenderer Renderer)
             : base(1280, 720,
             new GraphicsMode(), "Ryujinx", 0,
             DisplayDevice.Default, 3, 3,
             GraphicsContextFlags.ForwardCompatible)
         {
-            this.Ns = Ns;
+            this.Ns       = Ns;
             this.Renderer = Renderer;
-
-            ScreenTex = new ScreenTexture(Ns, Renderer, 1280, 720);
         }
 
         protected override void OnLoad(EventArgs e)
         {
             VSync = VSyncMode.On;
 
-            CreateShaders();
-            CreateVbo();
-
-            GL.Enable(EnableCap.Blend);
-            GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha);
-        }
-
-        protected override void OnUnload(EventArgs e)
-        {
-            ScreenTex.Dispose();
-
-            GL.DeleteVertexArray(VaoHandle);
-            GL.DeleteBuffer(VboHandle);
-        }
-
-        private void CreateVbo()
-        {
-            VaoHandle = GL.GenVertexArray();
-            VboHandle = GL.GenBuffer();
-
-            uint[] Buffer = new uint[]
-            {
-                0xbf800000, 0x3f800000, 0x00000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000,
-                0x3f800000, 0x3f800000, 0x00000000, 0xffffffff, 0x00000000, 0x3f800000, 0x00000000,
-                0xbf800000, 0xbf800000, 0x00000000, 0xffffffff, 0x00000000, 0x00000000, 0x3f800000,
-                0x3f800000, 0xbf800000, 0x00000000, 0xffffffff, 0x00000000, 0x3f800000, 0x3f800000
-            };
-
-            IntPtr Length = new IntPtr(Buffer.Length * 4);
-
-            GL.BindBuffer(BufferTarget.ArrayBuffer, VboHandle);
-            GL.BufferData(BufferTarget.ArrayBuffer, Length, Buffer, BufferUsageHint.StreamDraw);
-            GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
-
-            GL.BindVertexArray(VaoHandle);
-
-            GL.EnableVertexAttribArray(0);
-
-            GL.BindBuffer(BufferTarget.ArrayBuffer, VboHandle);
-
-            GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, 28, 0);
-
-            GL.EnableVertexAttribArray(1);
-
-            GL.BindBuffer(BufferTarget.ArrayBuffer, VboHandle);
-
-            GL.VertexAttribPointer(1, 4, VertexAttribPointerType.UnsignedByte, false, 28, 12);
-
-            GL.EnableVertexAttribArray(2);
-
-            GL.BindBuffer(BufferTarget.ArrayBuffer, VboHandle);
-
-            GL.VertexAttribPointer(2, 2, VertexAttribPointerType.Float, false, 28, 20);
-
-            GL.BindVertexArray(0);
-        }
-
-        private void CreateShaders()
-        {
-            VtxShaderHandle = GL.CreateShader(ShaderType.VertexShader);
-            FragShaderHandle = GL.CreateShader(ShaderType.FragmentShader);
-
-            GL.ShaderSource(VtxShaderHandle, VtxShaderSource);
-            GL.ShaderSource(FragShaderHandle, FragShaderSource);
-            GL.CompileShader(VtxShaderHandle);
-            GL.CompileShader(FragShaderHandle);
-
-            PrgShaderHandle = GL.CreateProgram();
-
-            GL.AttachShader(PrgShaderHandle, VtxShaderHandle);
-            GL.AttachShader(PrgShaderHandle, FragShaderHandle);
-            GL.LinkProgram(PrgShaderHandle);
-            GL.UseProgram(PrgShaderHandle);
-
-            int TexLocation = GL.GetUniformLocation(PrgShaderHandle, "tex");
-            GL.Uniform1(TexLocation, 0);
-
-            WindowSizeUniformLocation = GL.GetUniformLocation(PrgShaderHandle, "window_size");
-            GL.Uniform2(WindowSizeUniformLocation, new Vector2(1280.0f, 720.0f));
+            Renderer.InitializeFrameBuffer();
         }
 
         protected override void OnUpdateFrame(FrameEventArgs e)
@@ -382,12 +122,7 @@ void main(void) {
 
             GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
 
-            RenderFb();
-
-            GL.UseProgram(PrgShaderHandle);
-
             Renderer.RunActions();
-            Renderer.BindTexture(0);
             Renderer.Render();
 
             SwapBuffers();
@@ -395,16 +130,7 @@ void main(void) {
 
         protected override void OnResize(EventArgs e)
         {
-            GL.UseProgram(PrgShaderHandle);
-            GL.Uniform2(WindowSizeUniformLocation, new Vector2(Width, Height));
-        }
-
-        void RenderFb()
-        {
-            GL.ActiveTexture(TextureUnit.Texture0);
-            GL.BindTexture(TextureTarget.Texture2D, ScreenTex.Texture);
-            GL.BindVertexArray(VaoHandle);
-            GL.DrawArrays(PrimitiveType.TriangleStrip, 0, 4);
+            Renderer.SetWindowSize(Width, Height);
         }
     }
 }