diff --git a/Ryujinx.Core/OsHle/Objects/Android/GbpBuffer.cs b/Ryujinx.Core/OsHle/Objects/Android/GbpBuffer.cs new file mode 100644 index 00000000..edd11523 --- /dev/null +++ b/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); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Objects/Android/NvFlinger.cs b/Ryujinx.Core/OsHle/Objects/Android/NvFlinger.cs new file mode 100644 index 00000000..23863099 --- /dev/null +++ b/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(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(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Objects/Parcel.cs b/Ryujinx.Core/OsHle/Objects/Android/Parcel.cs similarity index 100% rename from Ryujinx.Core/OsHle/Objects/Parcel.cs rename to Ryujinx.Core/OsHle/Objects/Android/Parcel.cs diff --git a/Ryujinx.Core/OsHle/Objects/ErrorCode.cs b/Ryujinx.Core/OsHle/Objects/ErrorCode.cs index 20f97f84..659ed8b9 100644 --- a/Ryujinx.Core/OsHle/Objects/ErrorCode.cs +++ b/Ryujinx.Core/OsHle/Objects/ErrorCode.cs @@ -1,5 +1,3 @@ -using System; - namespace Ryujinx.Core.OsHle.Objects { static class ErrorCode diff --git a/Ryujinx.Core/OsHle/Objects/Hid/IActiveVibrationDeviceList.cs b/Ryujinx.Core/OsHle/Objects/Hid/IActiveVibrationDeviceList.cs index 1f0c8592..aae3c38d 100644 --- a/Ryujinx.Core/OsHle/Objects/Hid/IActiveVibrationDeviceList.cs +++ b/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; diff --git a/Ryujinx.Core/OsHle/Objects/Vi/IHOSBinderDriver.cs b/Ryujinx.Core/OsHle/Objects/Vi/IHOSBinderDriver.cs index cfd271e8..bbea3368 100644 --- a/Ryujinx.Core/OsHle/Objects/Vi/IHOSBinderDriver.cs +++ b/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 m_Commands; - private Dictionary<(string, int), ServiceProcessParcel> m_Methods; + public IReadOnlyDictionary Commands => m_Commands; - public IReadOnlyDictionary 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); + Data = Parcel.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); - } - - 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(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(); + } + } } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/EmbeddedResource.cs b/Ryujinx.Graphics/Gal/EmbeddedResource.cs new file mode 100644 index 00000000..45b77da7 --- /dev/null +++ b/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(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/IGalRenderer.cs b/Ryujinx.Graphics/Gal/IGalRenderer.cs index 1870aca5..5854c54a 100644 --- a/Ryujinx.Graphics/Gal/IGalRenderer.cs +++ b/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); diff --git a/Ryujinx.Graphics/Gal/OpenGL/FbFragShader.glsl b/Ryujinx.Graphics/Gal/OpenGL/FbFragShader.glsl new file mode 100644 index 00000000..74e33bd7 --- /dev/null +++ b/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); +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/FbVtxShader.glsl b/Ryujinx.Graphics/Gal/OpenGL/FbVtxShader.glsl new file mode 100644 index 00000000..933fa6aa --- /dev/null +++ b/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); +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/FrameBuffer.cs b/Ryujinx.Graphics/Gal/OpenGL/FrameBuffer.cs new file mode 100644 index 00000000..c66c0cb7 --- /dev/null +++ b/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; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OpenGLRenderer.cs b/Ryujinx.Graphics/Gal/OpenGL/OpenGLRenderer.cs index 7429569b..6bf17d09 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OpenGLRenderer.cs +++ b/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 ActionsQueue; + private FrameBuffer FbRenderer; + public long FrameBufferPtr { get; set; } public OpenGLRenderer() @@ -36,6 +40,11 @@ namespace Ryujinx.Graphics.Gal.OpenGL ActionsQueue = new Queue(); } + 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) diff --git a/Ryujinx.Graphics/Ryujinx.Graphics.csproj b/Ryujinx.Graphics/Ryujinx.Graphics.csproj index 657beb82..01bf41f8 100644 --- a/Ryujinx.Graphics/Ryujinx.Graphics.csproj +++ b/Ryujinx.Graphics/Ryujinx.Graphics.csproj @@ -4,6 +4,14 @@ netcoreapp2.0 + + true + + + + true + + @@ -12,4 +20,13 @@ + + + GlFbVtxShader + + + GlFbFragShader + + + diff --git a/Ryujinx/Ui/GLScreen.cs b/Ryujinx/Ui/GLScreen.cs index 325e38cd..3219303e 100644 --- a/Ryujinx/Ui/GLScreen.cs +++ b/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); } } } \ No newline at end of file