From 1bfe6a9c22c55086f171bd9c7cd7ff1855415ff8 Mon Sep 17 00:00:00 2001 From: Merry Date: Fri, 16 Feb 2018 00:04:38 +0000 Subject: Add some tests (#18) * Add tests * Add some simple Alu instruction tests * travis: Run tests * CpuTest: Add TearDown --- .travis.yml | 1 + GLScreen.cs | 378 ------------------------------------- Program.cs | 65 ------- Ryujinx.Tests/Cpu/CpuTest.cs | 73 +++++++ Ryujinx.Tests/Cpu/CpuTestAlu.cs | 65 +++++++ Ryujinx.Tests/Ryujinx.Tests.csproj | 17 ++ Ryujinx.conf | 20 -- Ryujinx.csproj | 17 -- Ryujinx.sln | 8 +- Ryujinx/Cpu/AThread.cs | 2 +- Ryujinx/Ryujinx.conf | 20 ++ Ryujinx/Ryujinx.csproj | 17 ++ Ryujinx/Ui/GLScreen.cs | 378 +++++++++++++++++++++++++++++++++++++ Ryujinx/Ui/Program.cs | 65 +++++++ 14 files changed, 644 insertions(+), 482 deletions(-) delete mode 100644 GLScreen.cs delete mode 100644 Program.cs create mode 100644 Ryujinx.Tests/Cpu/CpuTest.cs create mode 100644 Ryujinx.Tests/Cpu/CpuTestAlu.cs create mode 100644 Ryujinx.Tests/Ryujinx.Tests.csproj delete mode 100644 Ryujinx.conf delete mode 100644 Ryujinx.csproj create mode 100644 Ryujinx/Ryujinx.conf create mode 100644 Ryujinx/Ryujinx.csproj create mode 100644 Ryujinx/Ui/GLScreen.cs create mode 100644 Ryujinx/Ui/Program.cs diff --git a/.travis.yml b/.travis.yml index 15c55336..9c6ca236 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,3 +6,4 @@ dotnet: 2.0.0 script: - dotnet restore - dotnet build + - cd Ryujinx.Tests && dotnet test diff --git a/GLScreen.cs b/GLScreen.cs deleted file mode 100644 index cd650f2c..00000000 --- a/GLScreen.cs +++ /dev/null @@ -1,378 +0,0 @@ -// 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 Gal; -using OpenTK; -using OpenTK.Graphics; -using OpenTK.Graphics.OpenGL; -using Ryujinx.OsHle; -using System; - -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; - -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; - -void main(void) { - color = in_color; - tex_coord = in_tex_coord; - gl_Position = vec4((in_position + vec3(-960, 270, 0)) / vec3(1920, 270, 1), 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 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.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[] - { - 0xc4700000, 0x80000000, 0x00000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, - 0x45340000, 0x80000000, 0x00000000, 0xffffffff, 0x00000000, 0x3f800000, 0x00000000, - 0xc4700000, 0xc4070000, 0x00000000, 0xffffffff, 0x00000000, 0x00000000, 0x3f800000, - 0x45340000, 0xc4070000, 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); - } - - protected override void OnUpdateFrame(FrameEventArgs e) - { - unsafe - { - long HidOffset = Ns.Os.GetVirtHidOffset(); - - if (HidOffset == 0 || HidOffset + Horizon.HidSize > uint.MaxValue) - { - return; - } - - byte* Ptr = (byte*)Ns.Ram + (uint)HidOffset; - - int State = 0; - - if (Keyboard[OpenTK.Input.Key.Up]) - { - State |= 0x2000; - } - - if (Keyboard[OpenTK.Input.Key.Down]) - { - State |= 0x8000; - } - - if (Keyboard[OpenTK.Input.Key.Left]) - { - State |= 0x1000; - } - - if (Keyboard[OpenTK.Input.Key.Right]) - { - State |= 0x4000; - } - - if (Keyboard[OpenTK.Input.Key.A]) - { - State |= 0x1; - } - - if (Keyboard[OpenTK.Input.Key.S]) - { - State |= 0x2; - } - - if (Keyboard[OpenTK.Input.Key.Z]) - { - State |= 0x4; - } - - if (Keyboard[OpenTK.Input.Key.X]) - { - State |= 0x8; - } - - if (Keyboard[OpenTK.Input.Key.Enter]) - { - State |= 0x400; - } - - if (Keyboard[OpenTK.Input.Key.Tab]) - { - State |= 0x800; - } - - *((int*)(Ptr + 0xae38)) = (int)State; - } - - if (Keyboard[OpenTK.Input.Key.Escape]) - { - this.Exit(); - } - } - - protected override void OnRenderFrame(FrameEventArgs e) - { - GL.Viewport(0, 0, 1280, 720); - - Title = $"Ryujinx Screen - (Vsync: {VSync} - FPS: {1f / e.Time:0})"; - - GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); - - RenderFb(); - - GL.UseProgram(PrgShaderHandle); - - Renderer.RunActions(); - Renderer.BindTexture(0); - Renderer.Render(); - - SwapBuffers(); - } - - void RenderFb() - { - GL.ActiveTexture(TextureUnit.Texture0); - GL.BindTexture(TextureTarget.Texture2D, ScreenTex.Texture); - GL.BindVertexArray(VaoHandle); - GL.DrawArrays(PrimitiveType.TriangleStrip, 0, 4); - } - } -} \ No newline at end of file diff --git a/Program.cs b/Program.cs deleted file mode 100644 index 88a8a117..00000000 --- a/Program.cs +++ /dev/null @@ -1,65 +0,0 @@ -using Gal; -using Gal.OpenGL; -using System; -using System.IO; - -namespace Ryujinx -{ - class Program - { - static void Main(string[] args) - { - Config.Read(); - - Console.Title = "Ryujinx Console"; - - IGalRenderer Renderer = new OpenGLRenderer(); - - Switch Ns = new Switch(Renderer); - - if (args.Length == 1) - { - if (Directory.Exists(args[0])) - { - string[] RomFsFiles = Directory.GetFiles(args[0], "*.istorage"); - - if (RomFsFiles.Length > 0) - { - Logging.Info("Loading as cart with RomFS."); - - Ns.Os.LoadCart(args[0], RomFsFiles[0]); - } - else - { - Logging.Info("Loading as cart WITHOUT RomFS."); - - Ns.Os.LoadCart(args[0]); - } - } - else if (File.Exists(args[0])) - { - Logging.Info("Loading as homebrew."); - - Ns.Os.LoadProgram(args[0]); - } - } - else - { - Logging.Error("Please specify the folder with the NSOs/IStorage or a NSO/NRO."); - } - - using (GLScreen Screen = new GLScreen(Ns, Renderer)) - { - Ns.Finish += (Sender, Args) => { - Screen.Exit(); - }; - - Screen.Run(60.0); - } - - Ns.Os.FinalizeAllProcesses(); - - Ns.Dispose(); - } - } -} diff --git a/Ryujinx.Tests/Cpu/CpuTest.cs b/Ryujinx.Tests/Cpu/CpuTest.cs new file mode 100644 index 00000000..75d8e6b9 --- /dev/null +++ b/Ryujinx.Tests/Cpu/CpuTest.cs @@ -0,0 +1,73 @@ +using ChocolArm64; +using ChocolArm64.Memory; +using ChocolArm64.State; +using NUnit.Framework; +using System; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Ryujinx.Tests.Cpu +{ + [TestFixture] + public partial class CpuTest + { + IntPtr Ram; + AMemoryAlloc Allocator; + AMemory Memory; + + [SetUp] + public void Setup() + { + Ram = Marshal.AllocHGlobal((IntPtr)AMemoryMgr.RamSize); + Allocator = new AMemoryAlloc(); + Memory = new AMemory(Ram, Allocator); + Memory.Manager.MapPhys(0x1000, 0x1000, 2, AMemoryPerm.Read | AMemoryPerm.Write | AMemoryPerm.Execute); + } + + [TearDown] + public void Teardown() + { + Marshal.FreeHGlobal(Ram); + } + + private void Execute(AThread Thread) + { + AutoResetEvent Wait = new AutoResetEvent(false); + Thread.Registers.Break += (sender, e) => Thread.StopExecution(); + Thread.WorkFinished += (sender, e) => Wait.Set(); + + Wait.Reset(); + Thread.Execute(); + Wait.WaitOne(); + } + + private ARegisters SingleOpcode(uint Opcode, + ulong X0 = 0, ulong X1 = 0, ulong X2 = 0, + AVec V0 = new AVec(), AVec V1 = new AVec(), AVec V2 = new AVec()) + { + Memory.WriteUInt32(0x1000, Opcode); + Memory.WriteUInt32(0x1004, 0xD4200000); // BRK #0 + Memory.WriteUInt32(0x1008, 0xD65F03C0); // RET + + AThread Thread = new AThread(Memory, ThreadPriority.Normal, 0x1000); + Thread.Registers.X0 = X0; + Thread.Registers.X1 = X1; + Thread.Registers.X2 = X2; + Thread.Registers.V0 = V0; + Thread.Registers.V1 = V1; + Thread.Registers.V2 = V2; + Execute(Thread); + return Thread.Registers; + } + + [Test] + public void SanityCheck() + { + uint Opcode = 0xD503201F; // NOP + Assert.AreEqual(SingleOpcode(Opcode, X0: 0).X0, 0); + Assert.AreEqual(SingleOpcode(Opcode, X0: 1).X0, 1); + Assert.AreEqual(SingleOpcode(Opcode, X0: 2).X0, 2); + Assert.AreEqual(SingleOpcode(Opcode, X0: 42).X0, 42); + } + } +} diff --git a/Ryujinx.Tests/Cpu/CpuTestAlu.cs b/Ryujinx.Tests/Cpu/CpuTestAlu.cs new file mode 100644 index 00000000..3b82d759 --- /dev/null +++ b/Ryujinx.Tests/Cpu/CpuTestAlu.cs @@ -0,0 +1,65 @@ +using ChocolArm64.State; +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + [TestFixture] + public partial class CpuTest + { + [Test] + public void Add() + { + // ADD X0, X1, X2 + ARegisters Registers = SingleOpcode(0x8B020020, X1: 1, X2: 2); + Assert.AreEqual(3, Registers.X0); + } + + [Test] + public void Ands() + { + // ANDS W0, W1, W2 + uint Opcode = 0x6A020020; + var tests = new[] + { + new { W1 = 0xFFFFFFFFul, W2 = 0xFFFFFFFFul, Result = 0xFFFFFFFFul, Negative = true, Zero = false }, + new { W1 = 0xFFFFFFFFul, W2 = 0x00000000ul, Result = 0x00000000ul, Negative = false, Zero = true }, + new { W1 = 0x12345678ul, W2 = 0x7324A993ul, Result = 0x12240010ul, Negative = false, Zero = false }, + }; + + foreach (var test in tests) + { + ARegisters Registers = SingleOpcode(Opcode, X1: test.W1, X2: test.W2); + Assert.AreEqual(test.Result, Registers.X0); + Assert.AreEqual(test.Negative, Registers.Negative); + Assert.AreEqual(test.Zero, Registers.Zero); + } + } + + [Test] + public void OrrBitmasks() + { + // ORR W0, WZR, #0x01010101 + Assert.AreEqual(0x01010101, SingleOpcode(0x3200C3E0).X0); + // ORR W1, WZR, #0x00F000F0 + Assert.AreEqual(0x00F000F0, SingleOpcode(0x320C8FE1).X1); + // ORR W2, WZR, #1 + Assert.AreEqual(0x00000001, SingleOpcode(0x320003E2).X2); + } + + [Test] + public void RevX0X0() + { + // REV X0, X0 + ARegisters Registers = SingleOpcode(0xDAC00C00, X0: 0xAABBCCDDEEFF1100); + Assert.AreEqual(0x0011FFEEDDCCBBAA, Registers.X0); + } + + [Test] + public void RevW1W1() + { + // REV W1, W1 + ARegisters Registers = SingleOpcode(0x5AC00821, X1: 0x12345678); + Assert.AreEqual(0x78563412, Registers.X1); + } + } +} diff --git a/Ryujinx.Tests/Ryujinx.Tests.csproj b/Ryujinx.Tests/Ryujinx.Tests.csproj new file mode 100644 index 00000000..f8b430e6 --- /dev/null +++ b/Ryujinx.Tests/Ryujinx.Tests.csproj @@ -0,0 +1,17 @@ + + + netcoreapp2.0 + false + + + false + + + + + + + + + + diff --git a/Ryujinx.conf b/Ryujinx.conf deleted file mode 100644 index ee21ad90..00000000 --- a/Ryujinx.conf +++ /dev/null @@ -1,20 +0,0 @@ -#Enabled print informations logs -Logging_Enable_Info = true - -#Enabled print trace logs -Logging_Enable_Trace = false - -#Enabled print debug logs -Logging_Enable_Debug = false - -#Enabled print warning logs -Logging_Enable_Warn = true - -#Enabled print error logs -Logging_Enable_Error = true - -#Enabled print fatal logs -Logging_Enable_Fatal = true - -#Saved logs into Ryujinx.log -Logging_Enable_LogFile = false diff --git a/Ryujinx.csproj b/Ryujinx.csproj deleted file mode 100644 index 9b0e7396..00000000 --- a/Ryujinx.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - Exe - netcoreapp2.0 - true - win10-x64;osx-x64 - - - - - - - - PreserveNewest - - - \ No newline at end of file diff --git a/Ryujinx.sln b/Ryujinx.sln index c75364f7..77753988 100644 --- a/Ryujinx.sln +++ b/Ryujinx.sln @@ -3,7 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.26730.8 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx", "Ryujinx.csproj", "{074045D4-3ED2-4711-9169-E385F2BFB5A0}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx", "Ryujinx\Ryujinx.csproj", "{074045D4-3ED2-4711-9169-E385F2BFB5A0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Tests", "Ryujinx.Tests\Ryujinx.Tests.csproj", "{EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -15,6 +17,10 @@ Global {074045D4-3ED2-4711-9169-E385F2BFB5A0}.Debug|Any CPU.Build.0 = Debug|Any CPU {074045D4-3ED2-4711-9169-E385F2BFB5A0}.Release|Any CPU.ActiveCfg = Release|Any CPU {074045D4-3ED2-4711-9169-E385F2BFB5A0}.Release|Any CPU.Build.0 = Release|Any CPU + {EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Ryujinx/Cpu/AThread.cs b/Ryujinx/Cpu/AThread.cs index 93331825..400e700d 100644 --- a/Ryujinx/Cpu/AThread.cs +++ b/Ryujinx/Cpu/AThread.cs @@ -5,7 +5,7 @@ using System.Threading; namespace ChocolArm64 { - class AThread + public class AThread { public ARegisters Registers { get; private set; } public AMemory Memory { get; private set; } diff --git a/Ryujinx/Ryujinx.conf b/Ryujinx/Ryujinx.conf new file mode 100644 index 00000000..ee21ad90 --- /dev/null +++ b/Ryujinx/Ryujinx.conf @@ -0,0 +1,20 @@ +#Enabled print informations logs +Logging_Enable_Info = true + +#Enabled print trace logs +Logging_Enable_Trace = false + +#Enabled print debug logs +Logging_Enable_Debug = false + +#Enabled print warning logs +Logging_Enable_Warn = true + +#Enabled print error logs +Logging_Enable_Error = true + +#Enabled print fatal logs +Logging_Enable_Fatal = true + +#Saved logs into Ryujinx.log +Logging_Enable_LogFile = false diff --git a/Ryujinx/Ryujinx.csproj b/Ryujinx/Ryujinx.csproj new file mode 100644 index 00000000..9b0e7396 --- /dev/null +++ b/Ryujinx/Ryujinx.csproj @@ -0,0 +1,17 @@ + + + Exe + netcoreapp2.0 + true + win10-x64;osx-x64 + + + + + + + + PreserveNewest + + + \ No newline at end of file diff --git a/Ryujinx/Ui/GLScreen.cs b/Ryujinx/Ui/GLScreen.cs new file mode 100644 index 00000000..cd650f2c --- /dev/null +++ b/Ryujinx/Ui/GLScreen.cs @@ -0,0 +1,378 @@ +// 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 Gal; +using OpenTK; +using OpenTK.Graphics; +using OpenTK.Graphics.OpenGL; +using Ryujinx.OsHle; +using System; + +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; + +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; + +void main(void) { + color = in_color; + tex_coord = in_tex_coord; + gl_Position = vec4((in_position + vec3(-960, 270, 0)) / vec3(1920, 270, 1), 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 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.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[] + { + 0xc4700000, 0x80000000, 0x00000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, + 0x45340000, 0x80000000, 0x00000000, 0xffffffff, 0x00000000, 0x3f800000, 0x00000000, + 0xc4700000, 0xc4070000, 0x00000000, 0xffffffff, 0x00000000, 0x00000000, 0x3f800000, + 0x45340000, 0xc4070000, 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); + } + + protected override void OnUpdateFrame(FrameEventArgs e) + { + unsafe + { + long HidOffset = Ns.Os.GetVirtHidOffset(); + + if (HidOffset == 0 || HidOffset + Horizon.HidSize > uint.MaxValue) + { + return; + } + + byte* Ptr = (byte*)Ns.Ram + (uint)HidOffset; + + int State = 0; + + if (Keyboard[OpenTK.Input.Key.Up]) + { + State |= 0x2000; + } + + if (Keyboard[OpenTK.Input.Key.Down]) + { + State |= 0x8000; + } + + if (Keyboard[OpenTK.Input.Key.Left]) + { + State |= 0x1000; + } + + if (Keyboard[OpenTK.Input.Key.Right]) + { + State |= 0x4000; + } + + if (Keyboard[OpenTK.Input.Key.A]) + { + State |= 0x1; + } + + if (Keyboard[OpenTK.Input.Key.S]) + { + State |= 0x2; + } + + if (Keyboard[OpenTK.Input.Key.Z]) + { + State |= 0x4; + } + + if (Keyboard[OpenTK.Input.Key.X]) + { + State |= 0x8; + } + + if (Keyboard[OpenTK.Input.Key.Enter]) + { + State |= 0x400; + } + + if (Keyboard[OpenTK.Input.Key.Tab]) + { + State |= 0x800; + } + + *((int*)(Ptr + 0xae38)) = (int)State; + } + + if (Keyboard[OpenTK.Input.Key.Escape]) + { + this.Exit(); + } + } + + protected override void OnRenderFrame(FrameEventArgs e) + { + GL.Viewport(0, 0, 1280, 720); + + Title = $"Ryujinx Screen - (Vsync: {VSync} - FPS: {1f / e.Time:0})"; + + GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); + + RenderFb(); + + GL.UseProgram(PrgShaderHandle); + + Renderer.RunActions(); + Renderer.BindTexture(0); + Renderer.Render(); + + SwapBuffers(); + } + + void RenderFb() + { + GL.ActiveTexture(TextureUnit.Texture0); + GL.BindTexture(TextureTarget.Texture2D, ScreenTex.Texture); + GL.BindVertexArray(VaoHandle); + GL.DrawArrays(PrimitiveType.TriangleStrip, 0, 4); + } + } +} \ No newline at end of file diff --git a/Ryujinx/Ui/Program.cs b/Ryujinx/Ui/Program.cs new file mode 100644 index 00000000..88a8a117 --- /dev/null +++ b/Ryujinx/Ui/Program.cs @@ -0,0 +1,65 @@ +using Gal; +using Gal.OpenGL; +using System; +using System.IO; + +namespace Ryujinx +{ + class Program + { + static void Main(string[] args) + { + Config.Read(); + + Console.Title = "Ryujinx Console"; + + IGalRenderer Renderer = new OpenGLRenderer(); + + Switch Ns = new Switch(Renderer); + + if (args.Length == 1) + { + if (Directory.Exists(args[0])) + { + string[] RomFsFiles = Directory.GetFiles(args[0], "*.istorage"); + + if (RomFsFiles.Length > 0) + { + Logging.Info("Loading as cart with RomFS."); + + Ns.Os.LoadCart(args[0], RomFsFiles[0]); + } + else + { + Logging.Info("Loading as cart WITHOUT RomFS."); + + Ns.Os.LoadCart(args[0]); + } + } + else if (File.Exists(args[0])) + { + Logging.Info("Loading as homebrew."); + + Ns.Os.LoadProgram(args[0]); + } + } + else + { + Logging.Error("Please specify the folder with the NSOs/IStorage or a NSO/NRO."); + } + + using (GLScreen Screen = new GLScreen(Ns, Renderer)) + { + Ns.Finish += (Sender, Args) => { + Screen.Exit(); + }; + + Screen.Run(60.0); + } + + Ns.Os.FinalizeAllProcesses(); + + Ns.Dispose(); + } + } +} -- cgit v1.2.3