diff --git a/Dalamud.sln b/Dalamud.sln index 22953b287..f469adee7 100644 --- a/Dalamud.sln +++ b/Dalamud.sln @@ -70,6 +70,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dalamud.Bindings.ImPlot", " EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Bindings", "Bindings", "{A217B3DF-607A-4EFB-B107-3C4809348043}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StandaloneImGuiTestbed", "imgui\StandaloneImGuiTestbed\StandaloneImGuiTestbed.csproj", "{4702A911-2513-478C-A434-2776393FDE77}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -156,6 +158,10 @@ Global {9C70BD06-D52C-425E-9C14-5D66BC6046EF}.Debug|Any CPU.Build.0 = Debug|x64 {9C70BD06-D52C-425E-9C14-5D66BC6046EF}.Release|Any CPU.ActiveCfg = Release|x64 {9C70BD06-D52C-425E-9C14-5D66BC6046EF}.Release|Any CPU.Build.0 = Release|x64 + {4702A911-2513-478C-A434-2776393FDE77}.Debug|Any CPU.ActiveCfg = Debug|x64 + {4702A911-2513-478C-A434-2776393FDE77}.Debug|Any CPU.Build.0 = Debug|x64 + {4702A911-2513-478C-A434-2776393FDE77}.Release|Any CPU.ActiveCfg = Release|x64 + {4702A911-2513-478C-A434-2776393FDE77}.Release|Any CPU.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -177,6 +183,7 @@ Global {B0AA8737-33A3-4766-8CBE-A48F2EF283BA} = {A217B3DF-607A-4EFB-B107-3C4809348043} {5E6EDD75-AE95-43A6-9D67-95B840EB4B71} = {A217B3DF-607A-4EFB-B107-3C4809348043} {9C70BD06-D52C-425E-9C14-5D66BC6046EF} = {A217B3DF-607A-4EFB-B107-3C4809348043} + {4702A911-2513-478C-A434-2776393FDE77} = {A217B3DF-607A-4EFB-B107-3C4809348043} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {79B65AC9-C940-410E-AB61-7EA7E12C7599} diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs index 7c18e59d1..785e7deb8 100644 --- a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs @@ -154,7 +154,6 @@ internal unsafe class SeStringRenderer : IInternalDisposableService // Handle cases where ImGui.AlignTextToFramePadding has been called. var context = ImGui.GetCurrentContext(); var currLineTextBaseOffset = 0f; - /* if (!context.IsNull) { var currentWindow = context.CurrentWindow; @@ -163,7 +162,6 @@ internal unsafe class SeStringRenderer : IInternalDisposableService currLineTextBaseOffset = currentWindow.DC.CurrLineTextBaseOffset; } } - */ var itemSize = size; if (currLineTextBaseOffset != 0f) diff --git a/imgui/StandaloneImGuiTestbed/ImGuiBackend.cs b/imgui/StandaloneImGuiTestbed/ImGuiBackend.cs new file mode 100644 index 000000000..5cf147ec3 --- /dev/null +++ b/imgui/StandaloneImGuiTestbed/ImGuiBackend.cs @@ -0,0 +1,638 @@ +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using Dalamud.Bindings.ImGui; + +using Veldrid; +using Veldrid.Sdl2; + +namespace StandaloneImGuiTestbed; + +public class ImGuiBackend : IDisposable +{ + private GraphicsDevice gd; + private bool frameBegun; + + // Veldrid objects + private DeviceBuffer? vertexBuffer; + private DeviceBuffer? indexBuffer; + private DeviceBuffer? projMatrixBuffer; + private Texture? fontTexture; + private TextureView? fontTextureView; + private Shader? vertexShader; + private Shader? fragmentShader; + private ResourceLayout? layout; + private ResourceLayout? textureLayout; + private Pipeline? pipeline; + private ResourceSet? mainResourceSet; + private ResourceSet? fontTextureResourceSet; + + private readonly IntPtr fontAtlasId = (IntPtr)1; + private bool controlDown; + private bool shiftDown; + private bool altDown; + private bool winKeyDown; + + private IntPtr iniPathPtr; + + private int windowWidth; + private int windowHeight; + private Vector2 scaleFactor = Vector2.One; + + // Image trackers + private readonly Dictionary setsByView = new(); + + private readonly Dictionary autoViewsByTexture = new(); + + private readonly Dictionary viewsById = new Dictionary(); + private readonly List ownedResources = new List(); + private int lastAssignedID = 100; + + private delegate void SetClipboardTextDelegate(IntPtr userData, string text); + + private delegate string GetClipboardTextDelegate(); + + // variables because they need to exist for the program duration without being gc'd + private SetClipboardTextDelegate setText; + private GetClipboardTextDelegate getText; + + /// + /// Constructs a new ImGuiBackend. + /// + public unsafe ImGuiBackend(GraphicsDevice gd, OutputDescription outputDescription, int width, int height, FileInfo iniPath, float fontPxSize) + { + this.gd = gd; + windowWidth = width; + windowHeight = height; + + ImGui.CreateContext(); + + ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.NavEnableKeyboard | ImGuiConfigFlags.NavEnableGamepad; + ImGui.GetIO().BackendFlags |= ImGuiBackendFlags.HasGamepad; + + SetIniPath(iniPath.FullName); + + setText = SetClipboardText; + getText = GetClipboardText; + + var io = ImGui.GetIO(); + io.SetClipboardTextFn = Marshal.GetFunctionPointerForDelegate(setText).ToPointer(); + io.GetClipboardTextFn = Marshal.GetFunctionPointerForDelegate(getText).ToPointer(); + io.ClipboardUserData = null; + + CreateDeviceResources(gd, outputDescription, fontPxSize); + SetKeyMappings(); + + SetPerFrameImGuiData(1f / 60f); + + ImGui.NewFrame(); + frameBegun = true; + } + + private void SetIniPath(string iniPath) + { + if (iniPathPtr != IntPtr.Zero) + { + Marshal.FreeHGlobal(iniPathPtr); + } + + iniPathPtr = Marshal.StringToHGlobalAnsi(iniPath); + + unsafe + { + var io = ImGui.GetIO(); + io.IniFilename = (byte*)iniPathPtr.ToPointer(); + } + } + + private static void SetClipboardText(IntPtr userData, string text) + { + // text always seems to have an extra newline, but I'll leave it for now + Sdl2Native.SDL_SetClipboardText(text); + } + + private static string GetClipboardText() + { + return Sdl2Native.SDL_GetClipboardText(); + } + + public void WindowResized(int width, int height) + { + windowWidth = width; + windowHeight = height; + } + + public void DestroyDeviceObjects() + { + Dispose(); + } + + public void CreateDeviceResources(GraphicsDevice gd, OutputDescription outputDescription, float fontPxSize) + { + this.gd = gd; + var factory = gd.ResourceFactory; + vertexBuffer = factory.CreateBuffer(new BufferDescription(10000, BufferUsage.VertexBuffer | BufferUsage.Dynamic)); + vertexBuffer.Name = "ImGui.NET Vertex Buffer"; + indexBuffer = factory.CreateBuffer(new BufferDescription(2000, BufferUsage.IndexBuffer | BufferUsage.Dynamic)); + indexBuffer.Name = "ImGui.NET Index Buffer"; + + var ioFonts = ImGui.GetIO().Fonts; + + ImGui.GetIO().Fonts.Clear(); + ImGui.GetIO().Fonts.AddFontDefault(); + ImGui.GetIO().Fonts.Build(); + + RecreateFontDeviceTexture(gd); + + projMatrixBuffer = factory.CreateBuffer(new BufferDescription(64, BufferUsage.UniformBuffer | BufferUsage.Dynamic)); + projMatrixBuffer.Name = "ImGui.NET Projection Buffer"; + + var vertexShaderBytes = LoadEmbeddedShaderCode(gd.ResourceFactory, "imgui-vertex", ShaderStages.Vertex); + var fragmentShaderBytes = LoadEmbeddedShaderCode(gd.ResourceFactory, "imgui-frag", ShaderStages.Fragment); + vertexShader = factory.CreateShader(new ShaderDescription(ShaderStages.Vertex, vertexShaderBytes, gd.BackendType == GraphicsBackend.Metal ? "VS" : "main")); + fragmentShader = factory.CreateShader(new ShaderDescription(ShaderStages.Fragment, fragmentShaderBytes, gd.BackendType == GraphicsBackend.Metal ? "FS" : "main")); + + var vertexLayouts = new VertexLayoutDescription[] + { + new VertexLayoutDescription( + new VertexElementDescription("in_position", VertexElementSemantic.Position, VertexElementFormat.Float2), + new VertexElementDescription("in_texCoord", VertexElementSemantic.TextureCoordinate, VertexElementFormat.Float2), + new VertexElementDescription("in_color", VertexElementSemantic.Color, VertexElementFormat.Byte4_Norm)) + }; + + layout = factory.CreateResourceLayout(new ResourceLayoutDescription( + new ResourceLayoutElementDescription("ProjectionMatrixBuffer", ResourceKind.UniformBuffer, ShaderStages.Vertex), + new ResourceLayoutElementDescription("MainSampler", ResourceKind.Sampler, ShaderStages.Fragment))); + textureLayout = factory.CreateResourceLayout(new ResourceLayoutDescription( + new ResourceLayoutElementDescription("MainTexture", ResourceKind.TextureReadOnly, ShaderStages.Fragment))); + + var pd = new GraphicsPipelineDescription( + BlendStateDescription.SingleAlphaBlend, + new DepthStencilStateDescription(false, false, ComparisonKind.Always), + new RasterizerStateDescription(FaceCullMode.None, PolygonFillMode.Solid, FrontFace.Clockwise, false, true), + PrimitiveTopology.TriangleList, + new ShaderSetDescription(vertexLayouts, new[] { vertexShader, fragmentShader }), + new ResourceLayout[] { layout, textureLayout }, + outputDescription, + ResourceBindingModel.Default); + pipeline = factory.CreateGraphicsPipeline(ref pd); + + mainResourceSet = factory.CreateResourceSet(new ResourceSetDescription(layout, + projMatrixBuffer, + gd.PointSampler)); + + fontTextureResourceSet = factory.CreateResourceSet(new ResourceSetDescription(textureLayout, fontTextureView)); + } + + /// + /// Gets or creates a handle for a texture to be drawn with ImGui. + /// Pass the returned handle to Image() or ImageButton(). + /// + public IntPtr GetOrCreateImGuiBinding(ResourceFactory factory, TextureView textureView) + { + if (!setsByView.TryGetValue(textureView, out var rsi)) + { + var resourceSet = factory.CreateResourceSet(new ResourceSetDescription(textureLayout, textureView)); + rsi = new ResourceSetInfo(this.GetNextImGuiBindingId(), resourceSet); + + setsByView.Add(textureView, rsi); + viewsById.Add(rsi.ImGuiBinding, rsi); + ownedResources.Add(resourceSet); + } + + return rsi.ImGuiBinding; + } + + private IntPtr GetNextImGuiBindingId() + { + var newId = lastAssignedID++; + return (IntPtr)newId; + } + + /// + /// Gets or creates a handle for a texture to be drawn with ImGui. + /// Pass the returned handle to Image() or ImageButton(). + /// + public IntPtr GetOrCreateImGuiBinding(ResourceFactory factory, Texture texture) + { + if (!autoViewsByTexture.TryGetValue(texture, out var textureView)) + { + textureView = factory.CreateTextureView(texture); + autoViewsByTexture.Add(texture, textureView); + ownedResources.Add(textureView); + } + + return GetOrCreateImGuiBinding(factory, textureView); + } + + /// + /// Retrieves the shader texture binding for the given helper handle. + /// + public ResourceSet GetImageResourceSet(IntPtr imGuiBinding) + { + if (!viewsById.TryGetValue(imGuiBinding, out var tvi)) + { + throw new InvalidOperationException("No registered ImGui binding with id " + imGuiBinding.ToString()); + } + + return tvi.ResourceSet; + } + + public void ClearCachedImageResources() + { + foreach (var resource in ownedResources) + { + resource.Dispose(); + } + + ownedResources.Clear(); + setsByView.Clear(); + viewsById.Clear(); + autoViewsByTexture.Clear(); + lastAssignedID = 100; + } + + public static byte[] GetEmbeddedResourceBytes(string resourceName) + { + var assembly = typeof(ImGuiBackend).Assembly; + + using var s = assembly.GetManifestResourceStream(resourceName); + if (s == null) + throw new ArgumentException($"Resource {resourceName} not found", nameof(resourceName)); + + var ret = new byte[s.Length]; + s.ReadExactly(ret, 0, (int)s.Length); + return ret; + } + + private byte[] LoadEmbeddedShaderCode(ResourceFactory factory, string name, ShaderStages stage) + { + switch (factory.BackendType) + { + case GraphicsBackend.Direct3D11: + { + var resourceName = name + ".hlsl.bytes"; + return GetEmbeddedResourceBytes(resourceName); + } + + case GraphicsBackend.OpenGL: + { + var resourceName = name + ".glsl"; + return GetEmbeddedResourceBytes(resourceName); + } + + case GraphicsBackend.Vulkan: + { + var resourceName = name + ".spv"; + return GetEmbeddedResourceBytes(resourceName); + } + + case GraphicsBackend.Metal: + { + var resourceName = name + ".metallib"; + return GetEmbeddedResourceBytes(resourceName); + } + + default: + throw new NotImplementedException(); + } + } + + /// + /// Recreates the device texture used to render text. + /// + public unsafe void RecreateFontDeviceTexture(GraphicsDevice gd) + { + var io = ImGui.GetIO(); + // Build + byte* pixels = null; + int width, height, bytesPerPixel; + io.Fonts.GetTexDataAsRGBA32(0, &pixels, &width, &height, &bytesPerPixel); + // Store our identifier + io.Fonts.SetTexID(0, new ImTextureID(this.fontAtlasId)); + + fontTexture = gd.ResourceFactory.CreateTexture(TextureDescription.Texture2D( + (uint)width, + (uint)height, + 1, + 1, + PixelFormat.R8_G8_B8_A8_UNorm, + TextureUsage.Sampled)); + fontTexture.Name = "ImGui.NET Font Texture"; + gd.UpdateTexture( + fontTexture, + new IntPtr(pixels), + (uint)(bytesPerPixel * width * height), + 0, + 0, + 0, + (uint)width, + (uint)height, + 1, + 0, + 0); + fontTextureView = gd.ResourceFactory.CreateTextureView(fontTexture); + + io.Fonts.ClearTexData(); + } + + /// + /// Renders the ImGui draw list data. + /// This method requires a because it may create new DeviceBuffers if the size of vertex + /// or index data has increased beyond the capacity of the existing buffers. + /// A is needed to submit drawing and resource update commands. + /// + public void Render(GraphicsDevice gd, CommandList cl) + { + if (frameBegun) + { + frameBegun = false; + ImGui.Render(); + RenderImDrawData(ImGui.GetDrawData(), gd, cl); + } + } + + /// + /// Updates ImGui input and IO configuration state. + /// + public void Update(float deltaSeconds, InputSnapshot snapshot) + { + if (frameBegun) + { + ImGui.Render(); + } + + SetPerFrameImGuiData(deltaSeconds); + UpdateImGuiInput(snapshot); + + frameBegun = true; + ImGui.NewFrame(); + } + + /// + /// Sets per-frame data based on the associated window. + /// This is called by Update(float). + /// + private void SetPerFrameImGuiData(float deltaSeconds) + { + var io = ImGui.GetIO(); + io.DisplaySize = new Vector2( + windowWidth / scaleFactor.X, + windowHeight / scaleFactor.Y); + io.DisplayFramebufferScale = scaleFactor; + io.DeltaTime = deltaSeconds; // DeltaTime is in seconds. + } + + private void UpdateImGuiInput(InputSnapshot snapshot) + { + var io = ImGui.GetIO(); + + var mousePosition = snapshot.MousePosition; + + // Determine if any of the mouse buttons were pressed during this snapshot period, even if they are no longer held. + var leftPressed = false; + var middlePressed = false; + var rightPressed = false; + + foreach (var me in snapshot.MouseEvents) + { + if (me.Down) + { + switch (me.MouseButton) + { + case MouseButton.Left: + leftPressed = true; + break; + + case MouseButton.Middle: + middlePressed = true; + break; + + case MouseButton.Right: + rightPressed = true; + break; + } + } + } + + io.MouseDown[0] = leftPressed || snapshot.IsMouseDown(MouseButton.Left); + io.MouseDown[1] = rightPressed || snapshot.IsMouseDown(MouseButton.Right); + io.MouseDown[2] = middlePressed || snapshot.IsMouseDown(MouseButton.Middle); + io.MousePos = mousePosition; + io.MouseWheel = snapshot.WheelDelta; + + var keyCharPresses = snapshot.KeyCharPresses; + + for (var i = 0; i < keyCharPresses.Count; i++) + { + var c = keyCharPresses[i]; + io.AddInputCharacter(c); + } + + var keyEvents = snapshot.KeyEvents; + + for (var i = 0; i < keyEvents.Count; i++) + { + var keyEvent = keyEvents[i]; + io.KeysDown[(int)keyEvent.Key] = keyEvent.Down; + + if (keyEvent.Key == Key.ControlLeft) + { + controlDown = keyEvent.Down; + } + + if (keyEvent.Key == Key.ShiftLeft) + { + shiftDown = keyEvent.Down; + } + + if (keyEvent.Key == Key.AltLeft) + { + altDown = keyEvent.Down; + } + + if (keyEvent.Key == Key.WinLeft) + { + winKeyDown = keyEvent.Down; + } + } + + io.KeyCtrl = controlDown; + io.KeyAlt = altDown; + io.KeyShift = shiftDown; + io.KeySuper = winKeyDown; + } + + private static void SetKeyMappings() + { + var io = ImGui.GetIO(); + io.KeyMap[(int)ImGuiKey.Tab] = (int)Key.Tab; + io.KeyMap[(int)ImGuiKey.LeftArrow] = (int)Key.Left; + io.KeyMap[(int)ImGuiKey.RightArrow] = (int)Key.Right; + io.KeyMap[(int)ImGuiKey.UpArrow] = (int)Key.Up; + io.KeyMap[(int)ImGuiKey.DownArrow] = (int)Key.Down; + io.KeyMap[(int)ImGuiKey.PageUp] = (int)Key.PageUp; + io.KeyMap[(int)ImGuiKey.PageDown] = (int)Key.PageDown; + io.KeyMap[(int)ImGuiKey.Home] = (int)Key.Home; + io.KeyMap[(int)ImGuiKey.End] = (int)Key.End; + io.KeyMap[(int)ImGuiKey.Delete] = (int)Key.Delete; + io.KeyMap[(int)ImGuiKey.Backspace] = (int)Key.BackSpace; + io.KeyMap[(int)ImGuiKey.Enter] = (int)Key.Enter; + io.KeyMap[(int)ImGuiKey.Escape] = (int)Key.Escape; + io.KeyMap[(int)ImGuiKey.Space] = (int)Key.Space; + io.KeyMap[(int)ImGuiKey.KeypadEnter] = (int)Key.KeypadEnter; + io.KeyMap[(int)ImGuiKey.A] = (int)Key.A; + io.KeyMap[(int)ImGuiKey.C] = (int)Key.C; + io.KeyMap[(int)ImGuiKey.V] = (int)Key.V; + io.KeyMap[(int)ImGuiKey.X] = (int)Key.X; + io.KeyMap[(int)ImGuiKey.Y] = (int)Key.Y; + io.KeyMap[(int)ImGuiKey.Z] = (int)Key.Z; + } + + private unsafe void RenderImDrawData(ImDrawDataPtr drawData, GraphicsDevice gd, CommandList cl) + { + uint vertexOffsetInVertices = 0; + uint indexOffsetInElements = 0; + + if (drawData.CmdListsCount == 0) + { + return; + } + + var totalVbSize = (uint)(drawData.TotalVtxCount * Unsafe.SizeOf()); + + if (totalVbSize > vertexBuffer!.SizeInBytes) + { + gd.DisposeWhenIdle(vertexBuffer); + vertexBuffer = gd.ResourceFactory.CreateBuffer(new BufferDescription((uint)(totalVbSize * 1.5f), BufferUsage.VertexBuffer | BufferUsage.Dynamic)); + } + + var totalIbSize = (uint)(drawData.TotalIdxCount * sizeof(ushort)); + + if (totalIbSize > indexBuffer!.SizeInBytes) + { + gd.DisposeWhenIdle(indexBuffer); + indexBuffer = gd.ResourceFactory.CreateBuffer(new BufferDescription((uint)(totalIbSize * 1.5f), BufferUsage.IndexBuffer | BufferUsage.Dynamic)); + } + + for (var i = 0; i < drawData.CmdListsCount; i++) + { + ImDrawListPtr cmdList = drawData.CmdLists[i]; + + cl.UpdateBuffer( + vertexBuffer, + vertexOffsetInVertices * (uint)Unsafe.SizeOf(), + new IntPtr(cmdList.VtxBuffer.Data), + (uint)(cmdList.VtxBuffer.Size * Unsafe.SizeOf())); + + cl.UpdateBuffer( + indexBuffer, + indexOffsetInElements * sizeof(ushort), + new IntPtr(cmdList.IdxBuffer.Data), + (uint)(cmdList.IdxBuffer.Size * sizeof(ushort))); + + vertexOffsetInVertices += (uint)cmdList.VtxBuffer.Size; + indexOffsetInElements += (uint)cmdList.IdxBuffer.Size; + } + + // Setup orthographic projection matrix into our constant buffer + var io = ImGui.GetIO(); + var mvp = Matrix4x4.CreateOrthographicOffCenter( + 0f, + io.DisplaySize.X, + io.DisplaySize.Y, + 0.0f, + -1.0f, + 1.0f); + + this.gd.UpdateBuffer(this.projMatrixBuffer, 0, ref mvp); + + cl.SetVertexBuffer(0, vertexBuffer); + cl.SetIndexBuffer(indexBuffer, IndexFormat.UInt16); + cl.SetPipeline(pipeline); + cl.SetGraphicsResourceSet(0, mainResourceSet); + + drawData.ScaleClipRects(io.DisplayFramebufferScale); + + // Render command lists + var globalIdxOffset = 0; + var globalVtxOffset = 0; + + for (var n = 0; n < drawData.CmdListsCount; n++) + { + ImDrawListPtr cmdList = drawData.CmdLists[n]; + + for (var cmdI = 0; cmdI < cmdList.CmdBuffer.Size; cmdI++) + { + var pcmd = cmdList.CmdBuffer[cmdI]; + + if (pcmd.UserCallback != null) + { + throw new NotImplementedException(); + } + else + { + if (pcmd.TextureId != IntPtr.Zero) + { + if (pcmd.TextureId == this.fontAtlasId) + { + cl.SetGraphicsResourceSet(1, fontTextureResourceSet); + } + else + { + cl.SetGraphicsResourceSet(1, GetImageResourceSet((nint)pcmd.TextureId.Handle)); + } + } + + cl.SetScissorRect( + 0, + (uint)pcmd.ClipRect.X, + (uint)pcmd.ClipRect.Y, + (uint)(pcmd.ClipRect.Z - pcmd.ClipRect.X), + (uint)(pcmd.ClipRect.W - pcmd.ClipRect.Y)); + + cl.DrawIndexed(pcmd.ElemCount, 1, pcmd.IdxOffset + (uint)globalIdxOffset, (int)pcmd.VtxOffset + globalVtxOffset, 0); + } + } + + globalIdxOffset += cmdList.IdxBuffer.Size; + globalVtxOffset += cmdList.VtxBuffer.Size; + } + } + + /// + /// Frees all graphics resources used by the renderer. + /// + public void Dispose() + { + vertexBuffer?.Dispose(); + indexBuffer?.Dispose(); + projMatrixBuffer?.Dispose(); + fontTexture?.Dispose(); + fontTextureView?.Dispose(); + vertexShader?.Dispose(); + fragmentShader?.Dispose(); + layout?.Dispose(); + textureLayout?.Dispose(); + pipeline?.Dispose(); + mainResourceSet?.Dispose(); + + foreach (var resource in ownedResources) + { + resource.Dispose(); + } + } + + private struct ResourceSetInfo + { + public readonly IntPtr ImGuiBinding; + public readonly ResourceSet ResourceSet; + + public ResourceSetInfo(IntPtr imGuiBinding, ResourceSet resourceSet) + { + ImGuiBinding = imGuiBinding; + ResourceSet = resourceSet; + } + } +} diff --git a/imgui/StandaloneImGuiTestbed/Program.cs b/imgui/StandaloneImGuiTestbed/Program.cs new file mode 100644 index 000000000..417c05880 --- /dev/null +++ b/imgui/StandaloneImGuiTestbed/Program.cs @@ -0,0 +1,61 @@ +using Veldrid; +using Veldrid.Sdl2; +using Veldrid.StartupUtilities; + +namespace StandaloneImGuiTestbed; + +class Program +{ + static void Main(string[] args) + { + Sdl2Window window; + GraphicsDevice gd; + + ImGuiBackend? backend = null; + + VeldridStartup.CreateWindowAndGraphicsDevice( + new WindowCreateInfo(50, 50, 1280, 800, WindowState.Normal, "Dalamud Standalone ImGui Testbed"), + new GraphicsDeviceOptions(false, null, true, ResourceBindingModel.Improved, true, true), + out window, + out gd); + + window.Resized += () => + { + gd.MainSwapchain.Resize((uint)window.Width, (uint)window.Height); + backend!.WindowResized(window.Width, window.Height); + }; + + var cl = gd.ResourceFactory.CreateCommandList(); + backend = new ImGuiBackend(gd, gd.MainSwapchain.Framebuffer.OutputDescription, window.Width, window.Height, new FileInfo("imgui.ini"), 21.0f); + + var testbed = new Testbed(); + + while (window.Exists) + { + Thread.Sleep(50); + + var snapshot = window.PumpEvents(); + + if (!window.Exists) + break; + + backend.Update(1f / 60f, snapshot); + + testbed.Draw(); + + cl.Begin(); + cl.SetFramebuffer(gd.MainSwapchain.Framebuffer); + cl.ClearColorTarget(0, new RgbaFloat(0, 0, 0, 1f)); + backend.Render(gd, cl); + cl.End(); + gd.SubmitCommands(cl); + gd.SwapBuffers(gd.MainSwapchain); + } + + // Clean up Veldrid resources + gd.WaitForIdle(); + backend.Dispose(); + cl.Dispose(); + gd.Dispose(); + } +} diff --git a/imgui/StandaloneImGuiTestbed/Shaders/GLSL/imgui-frag.glsl b/imgui/StandaloneImGuiTestbed/Shaders/GLSL/imgui-frag.glsl new file mode 100644 index 000000000..85e5ee981 --- /dev/null +++ b/imgui/StandaloneImGuiTestbed/Shaders/GLSL/imgui-frag.glsl @@ -0,0 +1,13 @@ +#version 330 core + +uniform sampler2D FontTexture; + +in vec4 color; +in vec2 texCoord; + +out vec4 outputColor; + +void main() +{ + outputColor = color * texture(FontTexture, texCoord); +} diff --git a/imgui/StandaloneImGuiTestbed/Shaders/GLSL/imgui-vertex.glsl b/imgui/StandaloneImGuiTestbed/Shaders/GLSL/imgui-vertex.glsl new file mode 100644 index 000000000..997ce0d1e --- /dev/null +++ b/imgui/StandaloneImGuiTestbed/Shaders/GLSL/imgui-vertex.glsl @@ -0,0 +1,20 @@ +#version 330 core + +uniform ProjectionMatrixBuffer +{ + mat4 projection_matrix; +}; + +in vec2 in_position; +in vec2 in_texCoord; +in vec4 in_color; + +out vec4 color; +out vec2 texCoord; + +void main() +{ + gl_Position = projection_matrix * vec4(in_position, 0, 1); + color = in_color; + texCoord = in_texCoord; +} diff --git a/imgui/StandaloneImGuiTestbed/Shaders/HLSL/imgui-frag.hlsl b/imgui/StandaloneImGuiTestbed/Shaders/HLSL/imgui-frag.hlsl new file mode 100644 index 000000000..63d175f0c --- /dev/null +++ b/imgui/StandaloneImGuiTestbed/Shaders/HLSL/imgui-frag.hlsl @@ -0,0 +1,15 @@ +struct PS_INPUT +{ + float4 pos : SV_POSITION; + float4 col : COLOR0; + float2 uv : TEXCOORD0; +}; + +Texture2D FontTexture : register(t0); +sampler FontSampler : register(s0); + +float4 FS(PS_INPUT input) : SV_Target +{ + float4 out_col = input.col * FontTexture.Sample(FontSampler, input.uv); + return out_col; +} \ No newline at end of file diff --git a/imgui/StandaloneImGuiTestbed/Shaders/HLSL/imgui-frag.hlsl.bytes b/imgui/StandaloneImGuiTestbed/Shaders/HLSL/imgui-frag.hlsl.bytes new file mode 100644 index 000000000..2a9fbf5a3 Binary files /dev/null and b/imgui/StandaloneImGuiTestbed/Shaders/HLSL/imgui-frag.hlsl.bytes differ diff --git a/imgui/StandaloneImGuiTestbed/Shaders/HLSL/imgui-vertex.hlsl b/imgui/StandaloneImGuiTestbed/Shaders/HLSL/imgui-vertex.hlsl new file mode 100644 index 000000000..55793c19e --- /dev/null +++ b/imgui/StandaloneImGuiTestbed/Shaders/HLSL/imgui-vertex.hlsl @@ -0,0 +1,27 @@ +cbuffer ProjectionMatrixBuffer : register(b0) +{ + float4x4 ProjectionMatrix; +}; + +struct VS_INPUT +{ + float2 pos : POSITION; + float2 uv : TEXCOORD0; + float4 col : COLOR0; +}; + +struct PS_INPUT +{ + float4 pos : SV_POSITION; + float4 col : COLOR0; + float2 uv : TEXCOORD0; +}; + +PS_INPUT VS(VS_INPUT input) +{ + PS_INPUT output; + output.pos = mul(ProjectionMatrix, float4(input.pos.xy, 0.f, 1.f)); + output.col = input.col; + output.uv = input.uv; + return output; +} \ No newline at end of file diff --git a/imgui/StandaloneImGuiTestbed/Shaders/HLSL/imgui-vertex.hlsl.bytes b/imgui/StandaloneImGuiTestbed/Shaders/HLSL/imgui-vertex.hlsl.bytes new file mode 100644 index 000000000..572a04538 Binary files /dev/null and b/imgui/StandaloneImGuiTestbed/Shaders/HLSL/imgui-vertex.hlsl.bytes differ diff --git a/imgui/StandaloneImGuiTestbed/Shaders/Metal/imgui-frag.metal b/imgui/StandaloneImGuiTestbed/Shaders/Metal/imgui-frag.metal new file mode 100644 index 000000000..ff3cbe6bd --- /dev/null +++ b/imgui/StandaloneImGuiTestbed/Shaders/Metal/imgui-frag.metal @@ -0,0 +1,18 @@ +#include +using namespace metal; + +struct PS_INPUT +{ + float4 pos [[ position ]]; + float4 col; + float2 uv; +}; + +fragment float4 FS( + PS_INPUT input [[ stage_in ]], + texture2d FontTexture [[ texture(0) ]], + sampler FontSampler [[ sampler(0) ]]) +{ + float4 out_col = input.col * FontTexture.sample(FontSampler, input.uv); + return out_col; +} \ No newline at end of file diff --git a/imgui/StandaloneImGuiTestbed/Shaders/Metal/imgui-frag.metallib b/imgui/StandaloneImGuiTestbed/Shaders/Metal/imgui-frag.metallib new file mode 100644 index 000000000..18a3d73b2 Binary files /dev/null and b/imgui/StandaloneImGuiTestbed/Shaders/Metal/imgui-frag.metallib differ diff --git a/imgui/StandaloneImGuiTestbed/Shaders/Metal/imgui-vertex.metal b/imgui/StandaloneImGuiTestbed/Shaders/Metal/imgui-vertex.metal new file mode 100644 index 000000000..73d649e8b --- /dev/null +++ b/imgui/StandaloneImGuiTestbed/Shaders/Metal/imgui-vertex.metal @@ -0,0 +1,27 @@ +#include +using namespace metal; + +struct VS_INPUT +{ + float2 pos [[ attribute(0) ]]; + float2 uv [[ attribute(1) ]]; + float4 col [[ attribute(2) ]]; +}; + +struct PS_INPUT +{ + float4 pos [[ position ]]; + float4 col; + float2 uv; +}; + +vertex PS_INPUT VS( + VS_INPUT input [[ stage_in ]], + constant float4x4 &ProjectionMatrix [[ buffer(1) ]]) +{ + PS_INPUT output; + output.pos = ProjectionMatrix * float4(input.pos.xy, 0.f, 1.f); + output.col = input.col; + output.uv = input.uv; + return output; +} \ No newline at end of file diff --git a/imgui/StandaloneImGuiTestbed/Shaders/Metal/imgui-vertex.metallib b/imgui/StandaloneImGuiTestbed/Shaders/Metal/imgui-vertex.metallib new file mode 100644 index 000000000..2998b9480 Binary files /dev/null and b/imgui/StandaloneImGuiTestbed/Shaders/Metal/imgui-vertex.metallib differ diff --git a/imgui/StandaloneImGuiTestbed/Shaders/SPIR-V/generate-spirv.bat b/imgui/StandaloneImGuiTestbed/Shaders/SPIR-V/generate-spirv.bat new file mode 100644 index 000000000..62d1d996b --- /dev/null +++ b/imgui/StandaloneImGuiTestbed/Shaders/SPIR-V/generate-spirv.bat @@ -0,0 +1,2 @@ +glslangvalidator -V imgui-vertex.glsl -o imgui-vertex.spv -S vert +glslangvalidator -V imgui-frag.glsl -o imgui-frag.spv -S frag diff --git a/imgui/StandaloneImGuiTestbed/Shaders/SPIR-V/imgui-frag.glsl b/imgui/StandaloneImGuiTestbed/Shaders/SPIR-V/imgui-frag.glsl new file mode 100644 index 000000000..f94fa4859 --- /dev/null +++ b/imgui/StandaloneImGuiTestbed/Shaders/SPIR-V/imgui-frag.glsl @@ -0,0 +1,16 @@ +#version 450 + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +layout(set = 1, binding = 0) uniform texture2D FontTexture; +layout(set = 0, binding = 1) uniform sampler FontSampler; + +layout (location = 0) in vec4 color; +layout (location = 1) in vec2 texCoord; +layout (location = 0) out vec4 outputColor; + +void main() +{ + outputColor = color * texture(sampler2D(FontTexture, FontSampler), texCoord); +} \ No newline at end of file diff --git a/imgui/StandaloneImGuiTestbed/Shaders/SPIR-V/imgui-frag.spv b/imgui/StandaloneImGuiTestbed/Shaders/SPIR-V/imgui-frag.spv new file mode 100644 index 000000000..5d3d96f32 Binary files /dev/null and b/imgui/StandaloneImGuiTestbed/Shaders/SPIR-V/imgui-frag.spv differ diff --git a/imgui/StandaloneImGuiTestbed/Shaders/SPIR-V/imgui-vertex.glsl b/imgui/StandaloneImGuiTestbed/Shaders/SPIR-V/imgui-vertex.glsl new file mode 100644 index 000000000..6249a36dd --- /dev/null +++ b/imgui/StandaloneImGuiTestbed/Shaders/SPIR-V/imgui-vertex.glsl @@ -0,0 +1,28 @@ +#version 450 + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +layout (location = 0) in vec2 in_position; +layout (location = 1) in vec2 in_texCoord; +layout (location = 2) in vec4 in_color; + +layout (binding = 0) uniform ProjectionMatrixBuffer +{ + mat4 projection_matrix; +}; + +layout (location = 0) out vec4 color; +layout (location = 1) out vec2 texCoord; + +out gl_PerVertex +{ + vec4 gl_Position; +}; + +void main() +{ + gl_Position = projection_matrix * vec4(in_position, 0, 1); + color = in_color; + texCoord = in_texCoord; +} diff --git a/imgui/StandaloneImGuiTestbed/Shaders/SPIR-V/imgui-vertex.spv b/imgui/StandaloneImGuiTestbed/Shaders/SPIR-V/imgui-vertex.spv new file mode 100644 index 000000000..b40ec8ca5 Binary files /dev/null and b/imgui/StandaloneImGuiTestbed/Shaders/SPIR-V/imgui-vertex.spv differ diff --git a/imgui/StandaloneImGuiTestbed/StandaloneImGuiTestbed.csproj b/imgui/StandaloneImGuiTestbed/StandaloneImGuiTestbed.csproj new file mode 100644 index 000000000..21a6a4c66 --- /dev/null +++ b/imgui/StandaloneImGuiTestbed/StandaloneImGuiTestbed.csproj @@ -0,0 +1,40 @@ + + + + Exe + enable + enable + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/imgui/StandaloneImGuiTestbed/Testbed.cs b/imgui/StandaloneImGuiTestbed/Testbed.cs new file mode 100644 index 000000000..9025d4b52 --- /dev/null +++ b/imgui/StandaloneImGuiTestbed/Testbed.cs @@ -0,0 +1,36 @@ +using Dalamud.Bindings.ImGui; + +namespace StandaloneImGuiTestbed; + +public class Testbed +{ + private bool showDemoWindow = false; + + public unsafe void Draw() + { + if (ImGui.Begin("Testbed")) + { + ImGui.Text("Hello!"); + + if (ImGui.Button("Open demo")) + { + this.showDemoWindow = true; + } + + if (this.showDemoWindow) + { + ImGui.ShowDemoWindow(ref this.showDemoWindow); + } + + if (ImGui.Button("Access context")) + { + var context = ImGui.GetCurrentContext(); + var currentWindow = context.CurrentWindow; + ref var dc = ref currentWindow.DC; // BREAKPOINT HERE, currentWindow will be invalid + dc.CurrLineTextBaseOffset = 0; + } + + ImGui.End(); + } + } +}