diff --git a/Ryujinx.ImGui/Config.cs b/Ryujinx.ImGui/Config.cs new file mode 100644 index 000000000..b437a006d --- /dev/null +++ b/Ryujinx.ImGui/Config.cs @@ -0,0 +1,114 @@ +using Ryujinx.HLE.Input; +using Ryujinx.HLE.Logging; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; + +namespace Ryujinx +{ + public static class Config + { + public static JoyCon FakeJoyCon { get; private set; } + + public static void Read(Logger Log) + { + string IniFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); + + string IniPath = Path.Combine(IniFolder, "Ryujinx.conf"); + + IniParser Parser = new IniParser(IniPath); + + AOptimizations.DisableMemoryChecks = !Convert.ToBoolean(Parser.Value("Enable_Memory_Checks")); + + Log.SetEnable(LogLevel.Debug, Convert.ToBoolean(Parser.Value("Logging_Enable_Debug"))); + Log.SetEnable(LogLevel.Stub, Convert.ToBoolean(Parser.Value("Logging_Enable_Stub"))); + Log.SetEnable(LogLevel.Info, Convert.ToBoolean(Parser.Value("Logging_Enable_Info"))); + Log.SetEnable(LogLevel.Warning, Convert.ToBoolean(Parser.Value("Logging_Enable_Warn"))); + Log.SetEnable(LogLevel.Error, Convert.ToBoolean(Parser.Value("Logging_Enable_Error"))); + + string[] FilteredLogClasses = Parser.Value("Logging_Filtered_Classes").Split(',', StringSplitOptions.RemoveEmptyEntries); + + //When the classes are specified on the list, we only + //enable the classes that are on the list. + //So, first disable everything, then enable + //the classes that the user added to the list. + if (FilteredLogClasses.Length > 0) + { + foreach (LogClass Class in Enum.GetValues(typeof(LogClass))) + { + Log.SetEnable(Class, false); + } + } + + foreach (string LogClass in FilteredLogClasses) + { + if (!string.IsNullOrEmpty(LogClass.Trim())) + { + foreach (LogClass Class in Enum.GetValues(typeof(LogClass))) + { + if (Class.ToString().ToLower().Contains(LogClass.Trim().ToLower())) + { + Log.SetEnable(Class, true); + } + } + } + } + + FakeJoyCon = new JoyCon + { + Left = new JoyConLeft + { + StickUp = Convert.ToInt16(Parser.Value("Controls_Left_FakeJoycon_Stick_Up")), + StickDown = Convert.ToInt16(Parser.Value("Controls_Left_FakeJoycon_Stick_Down")), + StickLeft = Convert.ToInt16(Parser.Value("Controls_Left_FakeJoycon_Stick_Left")), + StickRight = Convert.ToInt16(Parser.Value("Controls_Left_FakeJoycon_Stick_Right")), + StickButton = Convert.ToInt16(Parser.Value("Controls_Left_FakeJoycon_Stick_Button")), + DPadUp = Convert.ToInt16(Parser.Value("Controls_Left_FakeJoycon_DPad_Up")), + DPadDown = Convert.ToInt16(Parser.Value("Controls_Left_FakeJoycon_DPad_Down")), + DPadLeft = Convert.ToInt16(Parser.Value("Controls_Left_FakeJoycon_DPad_Left")), + DPadRight = Convert.ToInt16(Parser.Value("Controls_Left_FakeJoycon_DPad_Right")), + ButtonMinus = Convert.ToInt16(Parser.Value("Controls_Left_FakeJoycon_Button_Minus")), + ButtonL = Convert.ToInt16(Parser.Value("Controls_Left_FakeJoycon_Button_L")), + ButtonZL = Convert.ToInt16(Parser.Value("Controls_Left_FakeJoycon_Button_ZL")) + }, + + Right = new JoyConRight + { + StickUp = Convert.ToInt16(Parser.Value("Controls_Right_FakeJoycon_Stick_Up")), + StickDown = Convert.ToInt16(Parser.Value("Controls_Right_FakeJoycon_Stick_Down")), + StickLeft = Convert.ToInt16(Parser.Value("Controls_Right_FakeJoycon_Stick_Left")), + StickRight = Convert.ToInt16(Parser.Value("Controls_Right_FakeJoycon_Stick_Right")), + StickButton = Convert.ToInt16(Parser.Value("Controls_Right_FakeJoycon_Stick_Button")), + ButtonA = Convert.ToInt16(Parser.Value("Controls_Right_FakeJoycon_Button_A")), + ButtonB = Convert.ToInt16(Parser.Value("Controls_Right_FakeJoycon_Button_B")), + ButtonX = Convert.ToInt16(Parser.Value("Controls_Right_FakeJoycon_Button_X")), + ButtonY = Convert.ToInt16(Parser.Value("Controls_Right_FakeJoycon_Button_Y")), + ButtonPlus = Convert.ToInt16(Parser.Value("Controls_Right_FakeJoycon_Button_Plus")), + ButtonR = Convert.ToInt16(Parser.Value("Controls_Right_FakeJoycon_Button_R")), + ButtonZR = Convert.ToInt16(Parser.Value("Controls_Right_FakeJoycon_Button_ZR")) + } + }; + } + } + + // https://stackoverflow.com/a/37772571 + public class IniParser + { + private readonly Dictionary Values; + + public IniParser(string Path) + { + Values = File.ReadLines(Path) + .Where(Line => !string.IsNullOrWhiteSpace(Line) && !Line.StartsWith('#')) + .Select(Line => Line.Split('=', 2)) + .ToDictionary(Parts => Parts[0].Trim(), Parts => Parts.Length > 1 ? Parts[1].Trim() : null); + } + + public string Value(string Name) + { + return Values.TryGetValue(Name, out string Value) ? Value : null; + } + } +} diff --git a/Ryujinx.ImGui/ConsoleLog.cs b/Ryujinx.ImGui/ConsoleLog.cs new file mode 100644 index 000000000..1a2899946 --- /dev/null +++ b/Ryujinx.ImGui/ConsoleLog.cs @@ -0,0 +1,51 @@ +using Ryujinx.HLE.Logging; +using System; +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx +{ + static class ConsoleLog + { + private static Dictionary LogColors; + + private static object ConsoleLock; + + static ConsoleLog() + { + LogColors = new Dictionary() + { + { LogLevel.Stub, ConsoleColor.DarkGray }, + { LogLevel.Info, ConsoleColor.White }, + { LogLevel.Warning, ConsoleColor.Yellow }, + { LogLevel.Error, ConsoleColor.Red } + }; + + ConsoleLock = new object(); + } + + public static void PrintLog(object sender, LogEventArgs e) + { + string FormattedTime = e.Time.ToString(@"hh\:mm\:ss\.fff"); + + string CurrentThread = Thread.CurrentThread.ManagedThreadId.ToString("d4"); + + string Message = FormattedTime + " | " + CurrentThread + " " + e.Message; + + if (LogColors.TryGetValue(e.Level, out ConsoleColor Color)) + { + lock (ConsoleLock) + { + Console.ForegroundColor = Color; + + Console.WriteLine(Message); + Console.ResetColor(); + } + } + else + { + Console.WriteLine(Message); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.ImGui/GUI/MainUI.cs b/Ryujinx.ImGui/GUI/MainUI.cs new file mode 100644 index 000000000..fae534831 --- /dev/null +++ b/Ryujinx.ImGui/GUI/MainUI.cs @@ -0,0 +1,312 @@ +using ImGuiNET; +using OpenTK; +using Ryujinx.Audio; +using Ryujinx.Audio.OpenAL; +using Ryujinx.Graphics.Gal; +using Ryujinx.Graphics.Gal.OpenGL; +using Ryujinx.HLE; +using Ryujinx.HLE.Input; +using OpenTK.Graphics; +using OpenTK.Input; +using System; +using System.IO; + +namespace Ryujinx.UI +{ + class MainUI : WindowHelper + { + //toggles + private bool ShowUI = true; + private bool ShowFileDialog = false; + private bool _isRunning = false; + private bool IsRunning + { + get => _isRunning; + set + { + _isRunning = value; + if (!value) + { + ShowUI = true; + } + } + } + + private string CurrentPath = Environment.CurrentDirectory; + private string PackagePath = string.Empty; + + private const int TouchScreenWidth = 1280; + private const int TouchScreenHeight = 720; + + private const float TouchScreenRatioX = (float)TouchScreenWidth / TouchScreenHeight; + private const float TouchScreenRatioY = (float)TouchScreenHeight / TouchScreenWidth; + + FilePicker FileDialog; + + IGalRenderer Renderer; + IAalOutput AudioOut; + Switch Ns; + + public MainUI() : base("Test") + { + FileDialog = FilePicker.GetFilePicker("rom",null); + + Renderer = new OpenGLRenderer(); + + AudioOut = new OpenALAudioOut(); + + Ns = new Switch(Renderer, AudioOut); + + Config.Read(Ns.Log); + + Ns.Log.Updated += ConsoleLog.PrintLog; + } + + protected override void OnLoad(EventArgs e) + { + base.OnLoad(e); + + VSync = VSyncMode.On; + + Renderer.SetWindowSize(Width, Height); + } + + protected override void OnRenderFrame(FrameEventArgs e) + { + _deltaTime = (float)e.Time; + if (ShowUI) + { + StartFrame(); + RenderUI(); + EndFrame(); + } + else + { + Ns.Statistics.StartSystemFrame(); + + Title = $"Ryujinx Screen - (Vsync: {VSync} - FPS: {Ns.Statistics.SystemFrameRate:0} - Guest FPS: " + + $"{Ns.Statistics.GameFrameRate:0})"; + + Renderer.RunActions(); + Renderer.Render(); + + SwapBuffers(); + + Ns.Statistics.EndSystemFrame(); + + Ns.Os.SignalVsync(); + } + } + + protected override void OnUpdateFrame(FrameEventArgs e) + { + if (!ShowUI) + { + HidControllerButtons CurrentButton = 0; + HidJoystickPosition LeftJoystick; + HidJoystickPosition RightJoystick; + + int LeftJoystickDX = 0; + int LeftJoystickDY = 0; + int RightJoystickDX = 0; + int RightJoystickDY = 0; + + //RightJoystick + if (Keyboard[(Key)Config.FakeJoyCon.Left.StickUp]) LeftJoystickDY = short.MaxValue; + if (Keyboard[(Key)Config.FakeJoyCon.Left.StickDown]) LeftJoystickDY = -short.MaxValue; + if (Keyboard[(Key)Config.FakeJoyCon.Left.StickLeft]) LeftJoystickDX = -short.MaxValue; + if (Keyboard[(Key)Config.FakeJoyCon.Left.StickRight]) LeftJoystickDX = short.MaxValue; + + //LeftButtons + if (Keyboard[(Key)Config.FakeJoyCon.Left.StickButton]) CurrentButton |= HidControllerButtons.KEY_LSTICK; + if (Keyboard[(Key)Config.FakeJoyCon.Left.DPadUp]) CurrentButton |= HidControllerButtons.KEY_DUP; + if (Keyboard[(Key)Config.FakeJoyCon.Left.DPadDown]) CurrentButton |= HidControllerButtons.KEY_DDOWN; + if (Keyboard[(Key)Config.FakeJoyCon.Left.DPadLeft]) CurrentButton |= HidControllerButtons.KEY_DLEFT; + if (Keyboard[(Key)Config.FakeJoyCon.Left.DPadRight]) CurrentButton |= HidControllerButtons.KEY_DRIGHT; + if (Keyboard[(Key)Config.FakeJoyCon.Left.ButtonMinus]) CurrentButton |= HidControllerButtons.KEY_MINUS; + if (Keyboard[(Key)Config.FakeJoyCon.Left.ButtonL]) CurrentButton |= HidControllerButtons.KEY_L; + if (Keyboard[(Key)Config.FakeJoyCon.Left.ButtonZL]) CurrentButton |= HidControllerButtons.KEY_ZL; + + //RightJoystick + if (Keyboard[(Key)Config.FakeJoyCon.Right.StickUp]) RightJoystickDY = short.MaxValue; + if (Keyboard[(Key)Config.FakeJoyCon.Right.StickDown]) RightJoystickDY = -short.MaxValue; + if (Keyboard[(Key)Config.FakeJoyCon.Right.StickLeft]) RightJoystickDX = -short.MaxValue; + if (Keyboard[(Key)Config.FakeJoyCon.Right.StickRight]) RightJoystickDX = short.MaxValue; + + //RightButtons + if (Keyboard[(Key)Config.FakeJoyCon.Right.StickButton]) CurrentButton |= HidControllerButtons.KEY_RSTICK; + if (Keyboard[(Key)Config.FakeJoyCon.Right.ButtonA]) CurrentButton |= HidControllerButtons.KEY_A; + if (Keyboard[(Key)Config.FakeJoyCon.Right.ButtonB]) CurrentButton |= HidControllerButtons.KEY_B; + if (Keyboard[(Key)Config.FakeJoyCon.Right.ButtonX]) CurrentButton |= HidControllerButtons.KEY_X; + if (Keyboard[(Key)Config.FakeJoyCon.Right.ButtonY]) CurrentButton |= HidControllerButtons.KEY_Y; + if (Keyboard[(Key)Config.FakeJoyCon.Right.ButtonPlus]) CurrentButton |= HidControllerButtons.KEY_PLUS; + if (Keyboard[(Key)Config.FakeJoyCon.Right.ButtonR]) CurrentButton |= HidControllerButtons.KEY_R; + if (Keyboard[(Key)Config.FakeJoyCon.Right.ButtonZR]) CurrentButton |= HidControllerButtons.KEY_ZR; + + LeftJoystick = new HidJoystickPosition + { + DX = LeftJoystickDX, + DY = LeftJoystickDY + }; + + RightJoystick = new HidJoystickPosition + { + DX = RightJoystickDX, + DY = RightJoystickDY + }; + + bool HasTouch = false; + + //Get screen touch position from left mouse click + //OpenTK always captures mouse events, even if out of focus, so check if window is focused. + if (Focused && Mouse?.GetState().LeftButton == ButtonState.Pressed) + { + int ScrnWidth = Width; + int ScrnHeight = Height; + + if (Width > Height * TouchScreenRatioX) + { + ScrnWidth = (int)(Height * TouchScreenRatioX); + } + else + { + ScrnHeight = (int)(Width * TouchScreenRatioY); + } + + int StartX = (Width - ScrnWidth) >> 1; + int StartY = (Height - ScrnHeight) >> 1; + + int EndX = StartX + ScrnWidth; + int EndY = StartY + ScrnHeight; + + if (Mouse.X >= StartX && + Mouse.Y >= StartY && + Mouse.X < EndX && + Mouse.Y < EndY) + { + int ScrnMouseX = Mouse.X - StartX; + int ScrnMouseY = Mouse.Y - StartY; + + int MX = (int)(((float)ScrnMouseX / ScrnWidth) * TouchScreenWidth); + int MY = (int)(((float)ScrnMouseY / ScrnHeight) * TouchScreenHeight); + + HidTouchPoint CurrentPoint = new HidTouchPoint + { + X = MX, + Y = MY, + + //Placeholder values till more data is acquired + DiameterX = 10, + DiameterY = 10, + Angle = 90 + }; + + HasTouch = true; + + Ns.Hid.SetTouchPoints(CurrentPoint); + } + } + + if (!HasTouch) + { + Ns.Hid.SetTouchPoints(); + } + + Ns.Hid.SetJoyconButton( + HidControllerId.CONTROLLER_HANDHELD, + HidControllerLayouts.Handheld_Joined, + CurrentButton, + LeftJoystick, + RightJoystick); + + Ns.Hid.SetJoyconButton( + HidControllerId.CONTROLLER_HANDHELD, + HidControllerLayouts.Main, + CurrentButton, + LeftJoystick, + RightJoystick); + } + } + + private void RenderUI() + { + if (ShowUI) + { + ImGui.SetNextWindowPos(System.Numerics.Vector2.Zero, Condition.Always, + System.Numerics.Vector2.Zero); + ImGui.SetNextWindowSize(new System.Numerics.Vector2(Width, Height),Condition.Always); + if (ImGui.BeginWindow("MainWindow",ref ShowUI, WindowFlags.NoTitleBar + | WindowFlags.NoMove | WindowFlags.AlwaysAutoResize)) + { + if(ImGui.BeginChildFrame(0, new System.Numerics.Vector2(-1,-1), + WindowFlags.AlwaysAutoResize)) + { + ImGuiNative.igBeginGroup(); + if(ImGui.Button("Load Package", new System.Numerics.Vector2(Values.ButtonWidth, + Values.ButtonHeight))){ + ShowFileDialog = true; + } + ImGuiNative.igEndGroup(); + ImGui.SameLine(); + + if(ImGui.BeginChildFrame(1, ImGui.GetContentRegionAvailable(), + WindowFlags.AlwaysAutoResize)) + { + if (ShowFileDialog) + { + string output = CurrentPath; + if (FileDialog.Draw(ref output, false)) + { + if (!string.IsNullOrWhiteSpace(output)) + { + PackagePath = output; + ShowFileDialog = false; + LoadPackage(PackagePath); + } + } + } + ImGui.EndChildFrame(); + } + ImGui.EndChildFrame(); + } + ImGui.EndWindow(); + } + } + } + + public void LoadPackage(string path) + { + if (Directory.Exists(path)) + { + string[] RomFsFiles = Directory.GetFiles(path, "*.istorage"); + + if (RomFsFiles.Length == 0) + { + RomFsFiles = Directory.GetFiles(path, "*.romfs"); + } + + if (RomFsFiles.Length > 0) + { + Console.WriteLine("Loading as cart with RomFS."); + + Ns.LoadCart(path, RomFsFiles[0]); + } + else + { + Console.WriteLine("Loading as cart WITHOUT RomFS."); + + Ns.LoadCart(path); + } + } + else if (File.Exists(path)) + { + Console.WriteLine("Loading as homebrew."); + + Ns.LoadProgram(path); + } + IsRunning = true; + ShowUI = false; + } + + } +} diff --git a/Ryujinx.ImGui/GUI/Values.cs b/Ryujinx.ImGui/GUI/Values.cs new file mode 100644 index 000000000..0f7460ab6 --- /dev/null +++ b/Ryujinx.ImGui/GUI/Values.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Numerics; + +namespace ImGuiNET +{ + public struct Values + { + public const float ButtonWidth = 170f; + public const float ButtonHeight = 50f; + public const float DefaultWindowScale = 1.0f; + public const float SelectibleHeight = 20.0f; + public static float CurrentWindowScale = 1.0f; + public static float CurrentFontScale = 1.2f; + + public struct Color + { + public static Vector4 Yellow = new Vector4(1.0f, 1.0f, 0, 1.0f); + } + } +} diff --git a/Ryujinx.ImGui/GUI/Widgets/FilePicker.cs b/Ryujinx.ImGui/GUI/Widgets/FilePicker.cs new file mode 100644 index 000000000..804db7733 --- /dev/null +++ b/Ryujinx.ImGui/GUI/Widgets/FilePicker.cs @@ -0,0 +1,148 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Numerics; + +namespace ImGuiNET +{ + /// + /// Adapted from Mellinoe's file picker for imgui + /// https://github.com/mellinoe/synthapp/blob/master/src/synthapp/Widgets/FilePicker.cs + /// + public class FilePicker + { + private const string FilePickerID = "###FilePicker"; + private static readonly Dictionary s_filePickers = new Dictionary(); + private static readonly Vector2 DefaultFilePickerSize = new Vector2(600, 400); + + public string CurrentFolder { get; set; } + public string SelectedFile { get; set; } + + public static FilePicker GetFilePicker(object o, string startingPath) + { + if (File.Exists(startingPath)) + { + startingPath = new FileInfo(startingPath).DirectoryName; + } + else if (string.IsNullOrEmpty(startingPath) || !Directory.Exists(startingPath)) + { + startingPath = Environment.CurrentDirectory; + if (string.IsNullOrEmpty(startingPath)) + { + startingPath = AppContext.BaseDirectory; + } + } + + if (!s_filePickers.TryGetValue(o, out FilePicker fp)) + { + fp = new FilePicker(); + fp.CurrentFolder = startingPath; + s_filePickers.Add(o, fp); + } + + return fp; + } + + public bool Draw(ref string selected, bool returnOnSelection) + { + bool result = false; + result = DrawFolder(ref selected, returnOnSelection); + return result; + } + + private bool DrawFolder(ref string selected, bool returnOnSelection = false) + { + ImGui.Text("Current Folder: " + CurrentFolder); + bool result = false; + + if (ImGui.BeginChildFrame(1, ImGui.GetContentRegionAvailable() - new Vector2(20, Values.ButtonHeight), + WindowFlags.Default)) + { + DirectoryInfo di = new DirectoryInfo(CurrentFolder); + if (di.Exists) + { + if (di.Parent != null) + { + ImGui.PushStyleColor(ColorTarget.Text, Values.Color.Yellow); + + if (ImGui.Selectable("../", false, SelectableFlags.DontClosePopups + , new Vector2(ImGui.GetContentRegionAvailableWidth(), Values.SelectibleHeight))) + { + CurrentFolder = di.Parent.FullName; + } + + ImGui.PopStyleColor(); + } + foreach (var dir in Directory.EnumerateFileSystemEntries(di.FullName)) + { + if (Directory.Exists(dir)) + { + string name = Path.GetFileName(dir); + bool isSelected = SelectedFile == dir; + + ImGui.PushStyleColor(ColorTarget.Text, Values.Color.Yellow); + + if (ImGui.Selectable(name + "/", isSelected, SelectableFlags.DontClosePopups + , new Vector2(ImGui.GetContentRegionAvailableWidth(), Values.SelectibleHeight))) + { + SelectedFile = dir; + selected = SelectedFile; + } + + if (SelectedFile != null) + if (ImGui.IsMouseDoubleClicked(0) && SelectedFile.Equals(dir)) + { + SelectedFile = null; + selected = null; + CurrentFolder = dir; + } + + ImGui.PopStyleColor(); + } + } + foreach (var file in Directory.EnumerateFiles(di.FullName)) + { + string name = Path.GetFileName(file); + bool isSelected = SelectedFile == file; + + if (ImGui.Selectable(name, isSelected, SelectableFlags.DontClosePopups + , new Vector2(ImGui.GetContentRegionAvailableWidth(), Values.SelectibleHeight))) + { + SelectedFile = file; + if (returnOnSelection) + { + selected = SelectedFile; + } + } + + if (SelectedFile != null) + if (ImGui.IsMouseDoubleClicked(0) && SelectedFile.Equals(file)) + { + selected = file; + result = true; + } + } + } + } + ImGui.EndChildFrame(); + + + if (ImGui.Button("Cancel", new Vector2(Values.ButtonWidth, Values.ButtonHeight))) + { + result = false; + } + + if (SelectedFile != null) + { + ImGui.SameLine(); + if (ImGui.Button("Open", new Vector2(Values.ButtonWidth, Values.ButtonHeight))) + { + result = true; + selected = SelectedFile; + } + } + + return result; + } + } +} diff --git a/Ryujinx.ImGui/GUI/WindowHelper.cs b/Ryujinx.ImGui/GUI/WindowHelper.cs new file mode 100644 index 000000000..7a6717e1c --- /dev/null +++ b/Ryujinx.ImGui/GUI/WindowHelper.cs @@ -0,0 +1,210 @@ +using System; +using System.Collections.Generic; +using System.Text; +using OpenTK; +using OpenTK.Input; +using OpenTK.Graphics; +using OpenTK.Graphics.OpenGL; +using ImGuiNET; + +namespace Ryujinx.UI +{ + class WindowHelper : GameWindow + { + protected float _deltaTime; + bool IsWindowOpened = false; + int s_fontTexture; + float _wheelPosition; + + public WindowHelper(string title) : base(1280, 720, GraphicsMode.Default, title, GameWindowFlags.Default + , DisplayDevice.Default, 3, 3, GraphicsContextFlags.ForwardCompatible) + { + Title = title; + IsWindowOpened = true; + + Location = new Point( + (DisplayDevice.Default.Width / 2) - (Width / 2), + (DisplayDevice.Default.Height / 2) - (Height / 2)); + } + + public void ShowDemo() + { + ImGuiNative.igShowDemoWindow(ref IsWindowOpened); + } + + public void StartFrame() + { + IO io = ImGui.GetIO(); + io.DisplaySize = new System.Numerics.Vector2(Width, Height); + io.DisplayFramebufferScale = new System.Numerics.Vector2(Values.CurrentWindowScale); + io.DeltaTime = _deltaTime; + ImGui.NewFrame(); + HandleInput(io); + } + + public unsafe void EndFrame() + { + ImGui.Render(); + DrawData* data = ImGui.GetDrawData(); + RenderImDrawData(data); + } + + protected unsafe override void OnLoad(EventArgs e) + { + ImGui.GetIO().FontAtlas.AddDefaultFont(); + + IO io = ImGui.GetIO(); + + io.FontAllowUserScaling = true; + + ImGuiNative.igGetIO()->FontGlobalScale = Values.CurrentFontScale; + + // Build texture atlas + FontTextureData texData = io.FontAtlas.GetTexDataAsAlpha8(); + + // Create OpenGL texture + s_fontTexture = GL.GenTexture(); + GL.BindTexture(TextureTarget.Texture2D, s_fontTexture); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)All.Linear); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)All.Linear); + GL.TexImage2D( + TextureTarget.Texture2D, + 0, + PixelInternalFormat.Alpha, + texData.Width, + texData.Height, + 0, + PixelFormat.Alpha, + PixelType.UnsignedByte, + new IntPtr(texData.Pixels)); + + // Store the texture identifier in the ImFontAtlas substructure. + io.FontAtlas.SetTexID(s_fontTexture); + + // Cleanup (don't clear the input data if you want to append new fonts later) + //io.Fonts->ClearInputData(); + io.FontAtlas.ClearTexData(); + GL.BindTexture(TextureTarget.Texture2D, 0); + } + + void HandleInput(IO io) + { + MouseState cursorState = Mouse.GetCursorState(); + MouseState mouseState = Mouse.GetState(); + + if (Focused) + { + Point windowPoint = PointToClient(new Point(cursorState.X, cursorState.Y)); + io.MousePosition = new System.Numerics.Vector2(windowPoint.X / io.DisplayFramebufferScale.X, windowPoint.Y / io.DisplayFramebufferScale.Y); + } + else + { + io.MousePosition = new System.Numerics.Vector2(-1f, -1f); + } + + io.MouseDown[0] = mouseState.LeftButton == ButtonState.Pressed; + io.MouseDown[1] = mouseState.RightButton == ButtonState.Pressed; + io.MouseDown[2] = mouseState.MiddleButton == ButtonState.Pressed; + + float newWheelPos = mouseState.WheelPrecise; + float delta = newWheelPos - _wheelPosition; + _wheelPosition = newWheelPos; + io.MouseWheel = delta; + } + + private unsafe void RenderImDrawData(DrawData* draw_data) + { + // Rendering + int display_w, display_h; + display_w = Width; + display_h = Height; + + Vector4 clear_color = new Vector4(114f / 255f, 144f / 255f, 154f / 255f, 1.0f); + GL.Viewport(0, 0, display_w, display_h); + GL.ClearColor(clear_color.X, clear_color.Y, clear_color.Z, clear_color.W); + GL.Clear(ClearBufferMask.ColorBufferBit); + + // We are using the OpenGL fixed pipeline to make the example code simpler to read! + // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, vertex/texcoord/color pointers. + int last_texture; + GL.GetInteger(GetPName.TextureBinding2D, out last_texture); + GL.PushAttrib(AttribMask.EnableBit | AttribMask.ColorBufferBit | AttribMask.TransformBit); + GL.Enable(EnableCap.Blend); + GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha); + GL.Disable(EnableCap.CullFace); + GL.Disable(EnableCap.DepthTest); + GL.Enable(EnableCap.ScissorTest); + GL.EnableClientState(ArrayCap.VertexArray); + GL.EnableClientState(ArrayCap.TextureCoordArray); + GL.EnableClientState(ArrayCap.ColorArray); + GL.Enable(EnableCap.Texture2D); + + GL.UseProgram(0); + + // Handle cases of screen coordinates != from framebuffer coordinates (e.g. retina displays) + IO io = ImGui.GetIO(); + ImGui.ScaleClipRects(draw_data, io.DisplayFramebufferScale); + + // Setup orthographic projection matrix + GL.MatrixMode(MatrixMode.Projection); + GL.PushMatrix(); + GL.LoadIdentity(); + GL.Ortho( + 0.0f, + io.DisplaySize.X / io.DisplayFramebufferScale.X, + io.DisplaySize.Y / io.DisplayFramebufferScale.Y, + 0.0f, + -1.0f, + 1.0f); + GL.MatrixMode(MatrixMode.Modelview); + GL.PushMatrix(); + GL.LoadIdentity(); + + // Render command lists + + for (int n = 0; n < draw_data->CmdListsCount; n++) + { + NativeDrawList* cmd_list = draw_data->CmdLists[n]; + byte* vtx_buffer = (byte*)cmd_list->VtxBuffer.Data; + ushort* idx_buffer = (ushort*)cmd_list->IdxBuffer.Data; + + GL.VertexPointer(2, VertexPointerType.Float, sizeof(DrawVert), new IntPtr(vtx_buffer + DrawVert.PosOffset)); + GL.TexCoordPointer(2, TexCoordPointerType.Float, sizeof(DrawVert), new IntPtr(vtx_buffer + DrawVert.UVOffset)); + GL.ColorPointer(4, ColorPointerType.UnsignedByte, sizeof(DrawVert), new IntPtr(vtx_buffer + DrawVert.ColOffset)); + + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) + { + DrawCmd* pcmd = &(((DrawCmd*)cmd_list->CmdBuffer.Data)[cmd_i]); + if (pcmd->UserCallback != IntPtr.Zero) + { + throw new NotImplementedException(); + } + else + { + GL.BindTexture(TextureTarget.Texture2D, pcmd->TextureId.ToInt32()); + GL.Scissor( + (int)pcmd->ClipRect.X, + (int)(io.DisplaySize.Y - pcmd->ClipRect.W), + (int)(pcmd->ClipRect.Z - pcmd->ClipRect.X), + (int)(pcmd->ClipRect.W - pcmd->ClipRect.Y)); + GL.DrawElements(PrimitiveType.Triangles, (int)pcmd->ElemCount, DrawElementsType.UnsignedShort, new IntPtr(idx_buffer)); + } + idx_buffer += pcmd->ElemCount; + } + } + + // Restore modified state + GL.DisableClientState(ArrayCap.ColorArray); + GL.DisableClientState(ArrayCap.TextureCoordArray); + GL.DisableClientState(ArrayCap.VertexArray); + GL.BindTexture(TextureTarget.Texture2D, last_texture); + GL.MatrixMode(MatrixMode.Modelview); + GL.PopMatrix(); + GL.MatrixMode(MatrixMode.Projection); + GL.PopMatrix(); + GL.PopAttrib(); + + SwapBuffers(); + } + } +} diff --git a/Ryujinx.ImGui/Program.cs b/Ryujinx.ImGui/Program.cs new file mode 100644 index 000000000..a874a7ed8 --- /dev/null +++ b/Ryujinx.ImGui/Program.cs @@ -0,0 +1,15 @@ +using System; + +namespace Ryujinx.UI +{ + class Program + { + static void Main(string[] args) + { + MainUI mainUI = new MainUI(); + mainUI.Run(60.0, 60.0); + + Environment.Exit(0); + } + } +} diff --git a/Ryujinx.ImGui/Ryujinx.UI.csproj b/Ryujinx.ImGui/Ryujinx.UI.csproj new file mode 100644 index 000000000..1cde6c289 --- /dev/null +++ b/Ryujinx.ImGui/Ryujinx.UI.csproj @@ -0,0 +1,35 @@ + + + + Exe + netcoreapp2.1 + + + + true + + + + true + true + + + + + + + + + + + + + + + + + PreserveNewest + + + + diff --git a/Ryujinx.ImGui/Ryujinx.conf b/Ryujinx.ImGui/Ryujinx.conf new file mode 100644 index 000000000..611f32071 --- /dev/null +++ b/Ryujinx.ImGui/Ryujinx.conf @@ -0,0 +1,47 @@ +#Enable cpu memory checks (slow) +Enable_Memory_Checks = false + +#Enable print debug logs +Logging_Enable_Debug = false + +#Enable print stubbed calls logs +Logging_Enable_Stub = true + +#Enable print informations logs +Logging_Enable_Info = true + +#Enable print warning logs +Logging_Enable_Warn = true + +#Enable print error logs +Logging_Enable_Error = true + +#Filtered log classes, seperated by ", ", eg. `Logging_Filtered_Classes = Loader, ServiceFS` +Logging_Filtered_Classes = + +#https://github.com/opentk/opentk/blob/develop/src/OpenTK/Input/Key.cs +Controls_Left_FakeJoycon_Stick_Up = 105 +Controls_Left_FakeJoycon_Stick_Down = 101 +Controls_Left_FakeJoycon_Stick_Left = 83 +Controls_Left_FakeJoycon_Stick_Right = 86 +Controls_Left_FakeJoycon_Stick_Button = 88 +Controls_Left_FakeJoycon_DPad_Up = 45 +Controls_Left_FakeJoycon_DPad_Down = 46 +Controls_Left_FakeJoycon_DPad_Left = 47 +Controls_Left_FakeJoycon_DPad_Right = 48 +Controls_Left_FakeJoycon_Button_Minus = 120 +Controls_Left_FakeJoycon_Button_L = 87 +Controls_Left_FakeJoycon_Button_ZL = 99 + +Controls_Right_FakeJoycon_Stick_Up = 91 +Controls_Right_FakeJoycon_Stick_Down = 93 +Controls_Right_FakeJoycon_Stick_Left = 92 +Controls_Right_FakeJoycon_Stick_Right = 94 +Controls_Right_FakeJoycon_Stick_Button = 90 +Controls_Right_FakeJoycon_Button_A = 108 +Controls_Right_FakeJoycon_Button_B = 106 +Controls_Right_FakeJoycon_Button_X = 85 +Controls_Right_FakeJoycon_Button_Y = 104 +Controls_Right_FakeJoycon_Button_Plus = 121 +Controls_Right_FakeJoycon_Button_R = 103 +Controls_Right_FakeJoycon_Button_ZR = 97 \ No newline at end of file diff --git a/Ryujinx.sln b/Ryujinx.sln index cd04dabc2..97fe49d62 100644 --- a/Ryujinx.sln +++ b/Ryujinx.sln @@ -15,9 +15,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics", "Ryujinx EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio", "Ryujinx.Audio\Ryujinx.Audio.csproj", "{5C1D818E-682A-46A5-9D54-30006E26C270}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.ShaderTools", "Ryujinx.ShaderTools\Ryujinx.ShaderTools.csproj", "{3AB294D0-2230-468F-9EB3-BDFCAEAE99A5}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.ShaderTools", "Ryujinx.ShaderTools\Ryujinx.ShaderTools.csproj", "{3AB294D0-2230-468F-9EB3-BDFCAEAE99A5}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Luea", "Ryujinx.LLE\Luea.csproj", "{8E7D36DD-9626-47E2-8EF5-8F2F66751C9C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Luea", "Ryujinx.LLE\Luea.csproj", "{8E7D36DD-9626-47E2-8EF5-8F2F66751C9C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.UI", "Ryujinx.ImGui\Ryujinx.UI.csproj", "{00117502-1661-4C8B-8C07-177C1A8AA455}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -57,6 +59,10 @@ Global {8E7D36DD-9626-47E2-8EF5-8F2F66751C9C}.Debug|Any CPU.Build.0 = Debug|Any CPU {8E7D36DD-9626-47E2-8EF5-8F2F66751C9C}.Release|Any CPU.ActiveCfg = Release|Any CPU {8E7D36DD-9626-47E2-8EF5-8F2F66751C9C}.Release|Any CPU.Build.0 = Release|Any CPU + {00117502-1661-4C8B-8C07-177C1A8AA455}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {00117502-1661-4C8B-8C07-177C1A8AA455}.Debug|Any CPU.Build.0 = Debug|Any CPU + {00117502-1661-4C8B-8C07-177C1A8AA455}.Release|Any CPU.ActiveCfg = Release|Any CPU + {00117502-1661-4C8B-8C07-177C1A8AA455}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE