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

This commit is contained in:
gdkchan 2018-02-23 18:48:27 -03:00
parent eafc58c9f2
commit 2ed733b1d5
14 changed files with 820 additions and 444 deletions

View file

@ -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);
}
}
}

View file

@ -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();
}
}
}
}

View file

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

View file

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

View file

@ -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);
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<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();
}
}
}
}

View file

@ -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();
}
}
}
}

View file

@ -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);

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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;
}
}
}

View file

@ -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)

View file

@ -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>

View file

@ -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);
}
}
}