diff --git a/ImGuiScene/D3DTextureWrap.cs b/ImGuiScene/D3D11TextureWrap.cs
similarity index 62%
rename from ImGuiScene/D3DTextureWrap.cs
rename to ImGuiScene/D3D11TextureWrap.cs
index 48656bd..217fec2 100644
--- a/ImGuiScene/D3DTextureWrap.cs
+++ b/ImGuiScene/D3D11TextureWrap.cs
@@ -2,36 +2,31 @@
using SharpDX.Direct3D11;
using System;
-
///
/// DX11 Implementation of .
-/// Provides a simple wrapped view of the disposeable resource as well as the handle for ImGui.
+/// Provides a simple wrapped view of the disposable resource as well as the handle for ImGui.
///
-public class D3DTextureWrap : TextureWrap
-{
+public class D3D11TextureWrap : TextureWrap {
// hold onto this directly for easier dispose etc and in case we need it later
- private ShaderResourceView _resourceView = null;
+ private ShaderResourceView _resourceView;
public int Width { get; }
public int Height { get; }
- public IntPtr ImGuiHandle => (_resourceView == null) ? IntPtr.Zero : _resourceView.NativePointer;
+ public IntPtr ImGuiHandle => _resourceView?.NativePointer ?? IntPtr.Zero;
- public D3DTextureWrap(ShaderResourceView texView, int width, int height)
- {
+ public D3D11TextureWrap(ShaderResourceView texView, int width, int height) {
_resourceView = texView;
Width = width;
Height = height;
}
#region IDisposable Support
- private bool disposedValue = false; // To detect redundant calls
-
- protected virtual void Dispose(bool disposing)
- {
- if (!disposedValue)
- {
- if (disposing)
- {
+
+ private bool disposedValue; // To detect redundant calls
+
+ protected virtual void Dispose(bool disposing) {
+ if (!disposedValue) {
+ if (disposing) {
// TODO: dispose managed state (managed objects).
}
@@ -45,17 +40,16 @@ protected virtual void Dispose(bool disposing)
}
}
- ~D3DTextureWrap()
- {
+ ~D3D11TextureWrap() {
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(false);
}
- public void Dispose()
- {
+ public void Dispose() {
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(true);
GC.SuppressFinalize(this);
}
+
#endregion
}
diff --git a/ImGuiScene/D3D12TextureManager.cs b/ImGuiScene/D3D12TextureManager.cs
new file mode 100644
index 0000000..5ad8995
--- /dev/null
+++ b/ImGuiScene/D3D12TextureManager.cs
@@ -0,0 +1,253 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using SharpDX.Direct3D12;
+using SharpDX.DXGI;
+
+using Device = SharpDX.Direct3D12.Device;
+using Resource = SharpDX.Direct3D12.Resource;
+
+using static ImGuiScene.NativeMethods;
+
+namespace ImGuiScene {
+ ///
+ /// Class that manages the creation, lookup, and synchronization of textures as .
+ ///
+ public class D3D12TextureManager : IDisposable {
+ private readonly Device _device;
+ private volatile bool _disposed;
+
+ private TextureInfo[] _staticBoundTextures;
+ private readonly Dictionary _dynamicBoundTextures = new();
+ private readonly object _textureLock = new();
+ private readonly int _staticTextureCount;
+ private readonly int _descriptorCount;
+
+ public D3D12TextureManager(Device device, int staticTextureCount, int maxDynamicTexturesPerFrame = 1023) {
+ this._device = device;
+ this._staticBoundTextures = new TextureInfo[staticTextureCount];
+ this._descriptorCount = staticTextureCount + maxDynamicTexturesPerFrame;
+ this._staticTextureCount = staticTextureCount;
+
+ this.CbvSrvHeap = this._device.CreateDescriptorHeap(new DescriptorHeapDescription {
+ Type = DescriptorHeapType.ConstantBufferViewShaderResourceViewUnorderedAccessView,
+ DescriptorCount = this._descriptorCount,
+ Flags = DescriptorHeapFlags.ShaderVisible
+ });
+ }
+
+ public DescriptorHeap CbvSrvHeap { get; }
+
+ // TODO: Use a LRU cache to swap out textures instead of clearing the dynamic texture binds each frame.
+ public void ClearDynamicTextures() {
+ lock (this._textureLock) {
+ this._dynamicBoundTextures.Clear();
+ }
+ }
+
+ public void BindStaticTexture(D3D12TextureWrap wrap, int index) {
+ Debug.Assert(index < this._staticTextureCount, "index < this._staticTextureCount");
+
+ lock (this._textureLock) {
+ var (cpuHandle, _) = this.GetSrvHandles(index);
+ this._device.CreateShaderResourceView(wrap.Info.Resource, wrap.Info.SrvDesc, cpuHandle);
+
+ this._staticBoundTextures[index]?.Dispose();
+ this._staticBoundTextures[index] = wrap.Info;
+ }
+ }
+
+ public unsafe D3D12TextureWrap CreateTexture(void* pixelData, int width, int height, int bytesPerPixel,
+ Format format) {
+ // TODO: Figure out if we need to implement other sizes.
+ Debug.Assert(bytesPerPixel == 4, "bytesPerPixel == 4");
+
+ // Upload texture to graphics system
+ var props = new HeapProperties {
+ Type = HeapType.Default,
+ CPUPageProperty = CpuPageProperty.Unknown,
+ MemoryPoolPreference = MemoryPool.Unknown
+ };
+
+ var desc = new ResourceDescription {
+ Dimension = ResourceDimension.Texture2D,
+ Alignment = 0,
+ Width = width,
+ Height = height,
+ DepthOrArraySize = 1,
+ MipLevels = 1,
+ Format = format,
+ SampleDescription = { Count = 1, Quality = 0 },
+ Layout = TextureLayout.Unknown,
+ Flags = ResourceFlags.None
+ };
+
+ var texture =
+ this._device.CreateCommittedResource(props, HeapFlags.None, desc, ResourceStates.CopyDestination);
+
+ var uploadPitch = (width * bytesPerPixel + D3D12_TEXTURE_DATA_PITCH_ALIGNMENT - 1u) &
+ ~(D3D12_TEXTURE_DATA_PITCH_ALIGNMENT - 1u);
+ var uploadSize = height * uploadPitch;
+ desc.Dimension = ResourceDimension.Buffer;
+ desc.Alignment = 0;
+ desc.Width = uploadSize;
+ desc.Height = 1;
+ desc.DepthOrArraySize = 1;
+ desc.MipLevels = 1;
+ desc.Format = Format.Unknown;
+ desc.SampleDescription.Count = 1;
+ desc.SampleDescription.Quality = 0;
+ desc.Layout = TextureLayout.RowMajor;
+ desc.Flags = ResourceFlags.None;
+
+ props.Type = HeapType.Upload;
+ props.CPUPageProperty = CpuPageProperty.Unknown;
+ props.MemoryPoolPreference = MemoryPool.Unknown;
+
+ var uploadBuffer =
+ this._device.CreateCommittedResource(props, HeapFlags.None, desc, ResourceStates.GenericRead);
+
+ var range = new Range { Begin = 0, End = uploadSize };
+ var mapped = uploadBuffer.Map(0, range);
+
+ var lenToCopy = width * bytesPerPixel;
+ for (var y = 0; y < height; y++) {
+ Buffer.MemoryCopy((void*)((IntPtr)pixelData + y * width * bytesPerPixel),
+ (void*)(mapped + y * (nint)uploadPitch), lenToCopy, lenToCopy);
+ }
+
+ uploadBuffer.Unmap(0, range);
+
+ var srcLocation = new TextureCopyLocation(uploadBuffer, new PlacedSubResourceFootprint {
+ Footprint = new SubResourceFootprint {
+ Format = format,
+ Width = width,
+ Height = height,
+ Depth = 1,
+ RowPitch = (int)uploadPitch,
+ }
+ });
+
+ var dstLocation = new TextureCopyLocation(texture, 0);
+
+ var barrier = new ResourceBarrier {
+ Type = ResourceBarrierType.Transition,
+ Flags = ResourceBarrierFlags.None,
+ Transition = new ResourceTransitionBarrier(texture, D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES,
+ ResourceStates.CopyDestination, ResourceStates.PixelShaderResource)
+ };
+
+ var fence = this._device.CreateFence(0, FenceFlags.None);
+
+ var ev = CreateEvent(IntPtr.Zero, false, false, null);
+ Debug.Assert(ev != IntPtr.Zero, "ev != IntPtr.Zero");
+
+ var queueDesc = new CommandQueueDescription {
+ Type = CommandListType.Direct,
+ Flags = CommandQueueFlags.None,
+ NodeMask = 1
+ };
+
+ var cmdQueue = this._device.CreateCommandQueue(queueDesc);
+ var cmdAlloc = this._device.CreateCommandAllocator(CommandListType.Direct);
+ var cmdList = this._device.CreateCommandList(0, CommandListType.Direct, cmdAlloc, null);
+
+ cmdList.CopyTextureRegion(dstLocation, 0, 0, 0, srcLocation, null);
+ cmdList.ResourceBarrier(barrier);
+ cmdList.Close();
+
+ cmdQueue.ExecuteCommandLists(cmdList);
+ cmdQueue.Signal(fence, 1);
+
+ fence.SetEventOnCompletion(1, ev);
+ WaitForSingleObject(ev, INFINITE);
+
+ cmdList.Dispose();
+ cmdAlloc.Dispose();
+ cmdQueue.Dispose();
+ CloseHandle(ev);
+ fence.Dispose();
+ uploadBuffer.Dispose();
+
+ // Create texture view
+ var srvDesc = new ShaderResourceViewDescription {
+ Format = format,
+ Dimension = ShaderResourceViewDimension.Texture2D,
+ Texture2D = { MipLevels = desc.MipLevels, MostDetailedMip = 0 },
+ Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING
+ };
+
+ // no sampler for now because the ImGui implementation we copied doesn't allow for changing it
+ return new D3D12TextureWrap(new TextureInfo {
+ Resource = texture,
+ SrvDesc = srvDesc,
+ }, width, height, this.AwaitTextureDispose, this.GetOrBindTextureData);
+ }
+
+ public void Dispose() {
+ this._disposed = true;
+
+ foreach (var texture in this._staticBoundTextures) {
+ texture.Dispose();
+ }
+
+ this._staticBoundTextures = null;
+ }
+
+ private (CpuDescriptorHandle, GpuDescriptorHandle) GetSrvHandles(int textureIndex) {
+ var srvHandleSize = this._device.GetDescriptorHandleIncrementSize(DescriptorHeapType
+ .ConstantBufferViewShaderResourceViewUnorderedAccessView);
+
+ var cpuHandle = this.CbvSrvHeap.CPUDescriptorHandleForHeapStart;
+ cpuHandle.Ptr += textureIndex * srvHandleSize;
+
+ var gpuHandle = this.CbvSrvHeap.GPUDescriptorHandleForHeapStart;
+ gpuHandle.Ptr += textureIndex * srvHandleSize;
+
+ return (cpuHandle, gpuHandle);
+ }
+
+ private void AwaitTextureDispose(D3D12TextureWrap wrap) {
+ lock (this._textureLock) {
+ wrap.Info.Resource.Dispose();
+ wrap.Info = null;
+ }
+ }
+
+ private IntPtr GetOrBindTextureData(D3D12TextureWrap wrap) {
+ if (this._disposed) return IntPtr.Zero;
+
+ lock (this._textureLock) {
+ if (this._dynamicBoundTextures.TryGetValue(wrap.Info, out var handle)) {
+ return (IntPtr)handle.Ptr;
+ }
+
+ for (var i = 0; i < this._staticBoundTextures.Length; i++) {
+ var info = this._staticBoundTextures[i];
+ if (info == wrap.Info) {
+ return (IntPtr)this.GetSrvHandles(i).Item2.Ptr;
+ }
+ }
+
+ var nextDescriptorIndex = this._staticBoundTextures.Length + this._dynamicBoundTextures.Count;
+ if (nextDescriptorIndex >= this._descriptorCount) {
+ throw new OutOfMemoryException("Ran out of heap descriptors");
+ }
+
+ var (cpuHandle, gpuHandle) = this.GetSrvHandles(nextDescriptorIndex);
+ this._device.CreateShaderResourceView(wrap.Info.Resource, wrap.Info.SrvDesc, cpuHandle);
+ this._dynamicBoundTextures.Add(wrap.Info, gpuHandle);
+ return (IntPtr)gpuHandle.Ptr;
+ }
+ }
+
+ internal record TextureInfo : IDisposable {
+ public Resource Resource;
+ public ShaderResourceViewDescription SrvDesc;
+
+ public void Dispose() {
+ this.Resource.Dispose();
+ }
+ }
+ }
+}
diff --git a/ImGuiScene/D3D12TextureWrap.cs b/ImGuiScene/D3D12TextureWrap.cs
new file mode 100644
index 0000000..db58f2f
--- /dev/null
+++ b/ImGuiScene/D3D12TextureWrap.cs
@@ -0,0 +1,58 @@
+using System;
+
+namespace ImGuiScene {
+ ///
+ /// DX12 Implementation of .
+ /// Provides a simple wrapped view of the disposable resource as well as the handle for ImGui.
+ ///
+ public class D3D12TextureWrap : TextureWrap {
+ private readonly Action _disposing;
+ private readonly Func _getImGuiHandle;
+
+ internal D3D12TextureManager.TextureInfo Info { get; set; }
+
+ public int Width { get; }
+ public int Height { get; }
+ public IntPtr ImGuiHandle => this._getImGuiHandle(this);
+
+ internal D3D12TextureWrap(D3D12TextureManager.TextureInfo info, int width, int height,
+ Action disposing,
+ Func getImGuiHandle) {
+ this.Info = info;
+ this.Width = width;
+ this.Height = height;
+ this._disposing = disposing;
+ this._getImGuiHandle = getImGuiHandle;
+ }
+
+ #region IDisposable Support
+
+ private bool _disposedValue; // To detect redundant calls
+
+ protected virtual void Dispose(bool disposing) {
+ if (this._disposedValue) return;
+
+ if (disposing) {
+ this._disposing(this);
+ }
+
+ // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
+ // TODO: set large fields to null.
+
+ this._disposedValue = true;
+ }
+
+ ~D3D12TextureWrap() {
+ // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
+ this.Dispose(false);
+ }
+
+ public void Dispose() {
+ // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
+ this.Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ #endregion
+ }
+}
diff --git a/ImGuiScene/ImGuiScene.csproj b/ImGuiScene/ImGuiScene.csproj
index 4d6de87..9a317f3 100644
--- a/ImGuiScene/ImGuiScene.csproj
+++ b/ImGuiScene/ImGuiScene.csproj
@@ -51,6 +51,7 @@
+
diff --git a/ImGuiScene/ImGui_Impl/Custom.cs b/ImGuiScene/ImGui_Impl/Custom.cs
new file mode 100644
index 0000000..87cda8c
--- /dev/null
+++ b/ImGuiScene/ImGui_Impl/Custom.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace ImGuiScene.ImGui_Impl {
+ // Custom cimgui functions we use for utility purposes
+ internal static class Custom {
+ [DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)]
+ public static extern void igCustom_ClearStacks();
+ }
+}
diff --git a/ImGuiScene/ImGui_Impl/Native/ImGui_ImplDX12_Native.cs b/ImGuiScene/ImGui_Impl/Native/ImGui_ImplDX12_Native.cs
new file mode 100644
index 0000000..4e4d1df
--- /dev/null
+++ b/ImGuiScene/ImGui_Impl/Native/ImGui_ImplDX12_Native.cs
@@ -0,0 +1,229 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using ImGuiNET;
+using SharpDX.Direct3D12;
+using SharpDX.DXGI;
+
+using Device = SharpDX.Direct3D12.Device;
+using Resource = SharpDX.Direct3D12.Resource;
+
+using static ImGuiScene.NativeMethods;
+
+namespace ImGuiScene.ImGui_Impl.Native {
+ public unsafe class ImGui_ImplDX12_Native : IDisposable {
+ [DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)]
+ private static extern bool igImpls_ImplDX12_Init(IntPtr device, int numFramesInFlight, Format rtvFormat,
+ IntPtr cbvSrvHeap, CpuDescriptorHandle fontSrvCpuDescHandle, GpuDescriptorHandle fontSrvGpuDescHandle);
+
+ [DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)]
+ private static extern void igImpls_ImplDX12_Shutdown();
+
+ [DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)]
+ private static extern void igImpls_ImplDX12_NewFrame();
+
+ [DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)]
+ private static extern void igImpls_ImplDX12_RenderDrawData(ImDrawData* drawData, IntPtr graphicsCommandList);
+
+ // Only needed to support recreating the device, doubt we need this ever
+ /*
+ [DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)]
+ private static extern void igImpls_ImplDX12_InvalidateDeviceObjects();
+ [DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)]
+ private static extern bool igImpls_ImplDX12_CreateDeviceObjects();
+ */
+
+ private Device _device;
+ private SwapChain3 _swapChain;
+
+ // TODO: Detect format from render target?
+ private const Format DXGI_FORMAT = Format.R8G8B8A8_UNorm;
+
+ private D3D12TextureManager _textureManager;
+ private List _fontTextures = new();
+ private DescriptorHeap _rtvDescriptorHeap;
+ private readonly List<(Resource, CpuDescriptorHandle)> _mainRtv = new();
+
+ private CommandQueue _commandQueue;
+ private CommandAllocator _commandAllocator;
+ private GraphicsCommandList _commandList;
+
+ private Fence _fence;
+ private IntPtr _fenceEvent;
+ private long _fenceValue;
+
+ public void Init(Device device, SwapChain3 swapChain, CommandQueue commandQueue, int numFramesInFlight) {
+ this._device = device;
+ this._swapChain = swapChain;
+ // Reserve one static descriptor for the ImGui font.
+ // TODO: Make this work for multiple ImGui fonts?
+ this._textureManager = new D3D12TextureManager(device, 1);
+
+ var init = igImpls_ImplDX12_Init(
+ device.NativePointer,
+ numFramesInFlight,
+ DXGI_FORMAT,
+ this._textureManager.CbvSrvHeap.NativePointer,
+ this._textureManager.CbvSrvHeap.CPUDescriptorHandleForHeapStart,
+ this._textureManager.CbvSrvHeap.GPUDescriptorHandleForHeapStart
+ );
+ Debug.Assert(init, "Couldn't init native dx12 backend");
+
+ this._commandQueue = commandQueue;
+ this._commandAllocator = this._device.CreateCommandAllocator(CommandListType.Direct);
+ this._commandList = this._device.CreateCommandList(CommandListType.Direct, this._commandAllocator, null);
+ this._commandList.Close();
+
+ this._fence = this._device.CreateFence(this._fenceValue, FenceFlags.None);
+ this._fenceEvent = CreateEvent(IntPtr.Zero, false, false, null);
+ }
+
+ public void NewFrame() {
+ this._textureManager.ClearDynamicTextures();
+ igImpls_ImplDX12_NewFrame();
+ }
+
+ public void RenderDrawData(ImDrawDataPtr drawDataPtr) {
+ if (this._mainRtv.Count == 0)
+ return;
+
+ var backBufferIndex = this._swapChain.CurrentBackBufferIndex;
+ this._commandAllocator.Reset();
+
+ var barrier = new ResourceBarrier {
+ Type = ResourceBarrierType.Transition,
+ Flags = ResourceBarrierFlags.None,
+ Transition = new ResourceTransitionBarrier(this._mainRtv[backBufferIndex].Item1,
+ D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, ResourceStates.Present, ResourceStates.RenderTarget),
+ };
+ this._commandList.Reset(this._commandAllocator, null);
+ this._commandList.ResourceBarrier(barrier);
+
+ this._commandList.SetRenderTargets(this._mainRtv[backBufferIndex].Item2, null);
+ // TODO: Idk if this is necessary since ImGui might do it? Test without it.
+ this._commandList.SetDescriptorHeaps(this._textureManager.CbvSrvHeap);
+
+ igImpls_ImplDX12_RenderDrawData(drawDataPtr.NativePtr, this._commandList.NativePointer);
+
+ barrier.Transition = new ResourceTransitionBarrier(this._mainRtv[backBufferIndex].Item1,
+ D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, ResourceStates.RenderTarget, ResourceStates.Present);
+ this._commandList.ResourceBarrier(barrier);
+ this._commandList.Close();
+
+ this._commandQueue.ExecuteCommandLists(this._commandList);
+ }
+
+ public void Shutdown() {
+ this.WaitForLastSubmittedFrame();
+ igImpls_ImplDX12_Shutdown();
+ }
+
+ public void OnPostPresent() {
+ var fenceValue = this._fenceValue + 1;
+ this._commandQueue.Signal(this._fence, fenceValue);
+ this._fenceValue = fenceValue;
+ }
+
+ public void InvalidateRenderTargets() {
+ foreach (var (resource, _) in this._mainRtv) {
+ resource.Dispose();
+ }
+
+ this._mainRtv.Clear();
+ this._rtvDescriptorHeap?.Dispose();
+ this._rtvDescriptorHeap = null;
+ }
+
+ public void CreateRenderTargets(int bufferCount) {
+ this.InvalidateRenderTargets();
+
+ var desc = new DescriptorHeapDescription {
+ Type = DescriptorHeapType.RenderTargetView,
+ DescriptorCount = bufferCount,
+ Flags = DescriptorHeapFlags.None,
+ NodeMask = 1
+ };
+
+ this._rtvDescriptorHeap = this._device.CreateDescriptorHeap(desc);
+
+ var rtvDescriptorSize =
+ this._device.GetDescriptorHandleIncrementSize(DescriptorHeapType.RenderTargetView);
+ var rtvHandle = this._rtvDescriptorHeap.CPUDescriptorHandleForHeapStart;
+
+ for (var i = 0; i < bufferCount; i++) {
+ var backBuffer = this._swapChain.GetBackBuffer(i);
+
+ Debug.Assert(backBuffer is not null, "backBuffer was null in CreateRenderTargets");
+
+ this._device.CreateRenderTargetView(backBuffer, null, rtvHandle);
+ this._mainRtv.Add((backBuffer, rtvHandle));
+
+ rtvHandle.Ptr += rtvDescriptorSize;
+ }
+ }
+
+ public TextureWrap CreateTexture(void* pixelData, int width, int height, int bytesPerPixel) {
+ return this._textureManager.CreateTexture(pixelData, width, height, bytesPerPixel, DXGI_FORMAT);
+ }
+
+ // Added to support dynamic rebuilding of the font texture
+ // for adding fonts after initialization time
+ public void RebuildFontTexture() {
+ foreach (var font in this._fontTextures) {
+ font.Dispose();
+ }
+
+ this._fontTextures.Clear();
+ this.CreateFontsTexture();
+ }
+
+ private void WaitForLastSubmittedFrame() {
+ var fenceValue = this._fenceValue;
+ if (fenceValue == 0)
+ return; // No fence was signaled
+
+ this._fenceValue = 0;
+ if (this._fence.CompletedValue >= fenceValue)
+ return;
+
+ this._fence.SetEventOnCompletion(fenceValue, this._fenceEvent);
+ // TODO: I'm not sure if we should wait infinitely here as sometimes the game can crash and not render any more frames meaning this deadlocks.
+ WaitForSingleObject(this._fenceEvent, INFINITE);
+ }
+
+ private void CreateFontsTexture() {
+ var io = ImGui.GetIO();
+ if (io.Fonts.Textures.Size == 0)
+ io.Fonts.Build();
+
+ for (int textureIndex = 0, textureCount = io.Fonts.Textures.Size;
+ textureIndex < textureCount;
+ textureIndex++) {
+ // Build texture atlas
+ io.Fonts.GetTexDataAsRGBA32(textureIndex, out IntPtr fontPixels, out var fontWidth,
+ out var fontHeight,
+ out var fontBytesPerPixel);
+
+ var wrap = this._textureManager.CreateTexture((void*)fontPixels, fontWidth, fontHeight,
+ fontBytesPerPixel, DXGI_FORMAT);
+ // TODO: Multiple fonts?
+ this._textureManager.BindStaticTexture(wrap, textureIndex);
+
+ this._fontTextures.Add(wrap);
+ // TODO: I'm pretty sure this just overrides the global font over and over. Verify.
+ io.Fonts.SetTexID(textureIndex, wrap.ImGuiHandle);
+ }
+
+ io.Fonts.ClearTexData();
+ }
+
+ public void Dispose() {
+ CloseHandle(this._fenceEvent);
+ this._fence.Dispose();
+ this.InvalidateRenderTargets();
+ this._fontTextures.Clear();
+ this._textureManager.Dispose();
+ }
+ }
+}
diff --git a/ImGuiScene/NativeMethods.cs b/ImGuiScene/NativeMethods.cs
new file mode 100644
index 0000000..501308d
--- /dev/null
+++ b/ImGuiScene/NativeMethods.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Runtime.ConstrainedExecution;
+using System.Runtime.InteropServices;
+using System.Security;
+
+namespace ImGuiScene
+{
+ internal static class NativeMethods
+ {
+ [DllImport("kernel32.dll")]
+ public static extern IntPtr CreateEvent(IntPtr lpEventAttributes, bool bManualReset, bool bInitialState, string lpName);
+
+ [DllImport("kernel32.dll", SetLastError=true)]
+ public static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);
+
+ [DllImport("kernel32.dll")]
+ public static extern uint WaitForMultipleObjects(uint nCount, IntPtr[] lpHandles, bool bWaitAll, uint dwMilliseconds);
+
+ [DllImport("kernel32.dll", SetLastError=true)]
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
+ [SuppressUnmanagedCodeSecurity]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ public static extern bool CloseHandle(IntPtr hObject);
+
+ public const int D3D12_TEXTURE_DATA_PITCH_ALIGNMENT = 256;
+ public const int D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES = -1;
+ public const int D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING = 5768;
+ public const uint INFINITE = 0xFFFFFFFF;
+ }
+}
\ No newline at end of file
diff --git a/ImGuiScene/RawDX11Scene.cs b/ImGuiScene/RawDX11Scene.cs
index d01012d..7f6b77c 100644
--- a/ImGuiScene/RawDX11Scene.cs
+++ b/ImGuiScene/RawDX11Scene.cs
@@ -8,8 +8,10 @@
using System;
using System.IO;
using ImGuiScene.ImGui_Impl.Native;
+using ImGuiScene.ImGui_Impl;
using ImGuizmoNET;
using ImPlotNET;
+
using Device = SharpDX.Direct3D11.Device;
namespace ImGuiScene
@@ -178,6 +180,12 @@ public void InvalidateFonts()
this.imguiRenderer.RebuildFontTexture();
}
+ // It is pretty much required that this is called from a handler attached
+ // to OnNewRenderFrame
+ public void ClearStacksOnContext() {
+ Custom.igCustom_ClearStacks();
+ }
+
public bool IsImGuiCursor(IntPtr hCursor)
{
return this.imguiInput.IsImGuiCursor(hCursor);
@@ -256,7 +264,7 @@ private unsafe TextureWrap CreateTexture(void* pixelData, int width, int height,
// no sampler for now because the ImGui implementation we copied doesn't allow for changing it
- return new D3DTextureWrap(resView, width, height);
+ return new D3D11TextureWrap(resView, width, height);
}
public byte[] CaptureScreenshot()
@@ -357,4 +365,4 @@ public void Dispose()
}
#endregion
}
-}
\ No newline at end of file
+}
diff --git a/ImGuiScene/RawDX12Scene.cs b/ImGuiScene/RawDX12Scene.cs
new file mode 100644
index 0000000..453242b
--- /dev/null
+++ b/ImGuiScene/RawDX12Scene.cs
@@ -0,0 +1,302 @@
+using ImGuiNET;
+using PInvoke;
+using SharpDX.Direct3D12;
+using SharpDX.DXGI;
+using StbiSharp;
+using System;
+using System.ComponentModel;
+using System.IO;
+using ImGuiScene.ImGui_Impl;
+using ImGuiScene.ImGui_Impl.Native;
+using ImGuizmoNET;
+using ImPlotNET;
+
+using Device = SharpDX.Direct3D12.Device;
+
+namespace ImGuiScene {
+ // This class will likely eventually be unified a bit more with other scenes, but for
+ // now it should be directly useable
+ public sealed class RawDX12Scene : IDisposable {
+ public Device Device { get; private set; }
+ public IntPtr WindowHandlePtr { get; private set; }
+
+ // IDXGISwapChain3 is required to call GetCurrentBackBufferIndex.
+ public SwapChain3 SwapChain { get; private set; }
+ public CommandQueue CommandQueue { get; private set; }
+
+ public bool UpdateCursor {
+ get => this.imguiInput.UpdateCursor;
+ set => this.imguiInput.UpdateCursor = value;
+ }
+
+ private int targetWidth;
+ private int targetHeight;
+
+ private ImGui_ImplDX12_Native imguiRenderer;
+ private ImGui_Input_Impl_Direct imguiInput;
+
+ public delegate void BuildUIDelegate();
+
+ public delegate void NewInputFrameDelegate();
+
+ public delegate void NewRenderFrameDelegate();
+
+ ///
+ /// User methods invoked every ImGui frame to construct custom UIs.
+ ///
+ public BuildUIDelegate OnBuildUI;
+
+ public NewInputFrameDelegate OnNewInputFrame;
+ public NewRenderFrameDelegate OnNewRenderFrame;
+
+ private string imguiIniPath;
+
+ public string ImGuiIniPath {
+ get => this.imguiIniPath;
+ set {
+ this.imguiIniPath = value;
+ this.imguiInput.SetIniPath(this.imguiIniPath);
+ }
+ }
+
+ ///
+ /// Creates an instance of the class .
+ ///
+ /// Pointer to an IDXGISwapChain.
+ /// Pointer to the ID3D12CommandQueue used to initialize
+ /// Pointer to a native ID3D12Device. By default the device will be derived from .
+ ///
+ /// Ensure was the command queue used to initialize , otherwise rendering will crash.
+ ///
+ public RawDX12Scene(IntPtr nativeSwapChain, IntPtr nativeCommandQueue, IntPtr? nativeDevice = null) {
+ this.SwapChain = new SwapChain(nativeSwapChain).QueryInterfaceOrNull()
+ ?? throw new InvalidEnumArgumentException("Failed to query SwapChain3 interface");
+ this.CommandQueue = new CommandQueue(nativeCommandQueue);
+ this.Device = nativeDevice is null ? this.SwapChain.GetDevice() : new Device((IntPtr)nativeDevice);
+
+ this.Initialize();
+ }
+
+ private void Initialize() {
+ // could also do things with GetClientRect() for WindowHandlePtr, not sure if that is necessary
+ this.targetWidth = this.SwapChain.Description.ModeDescription.Width;
+ this.targetHeight = this.SwapChain.Description.ModeDescription.Height;
+
+ this.WindowHandlePtr = this.SwapChain.Description.OutputHandle;
+
+ this.InitializeImGui();
+ }
+
+ private void InitializeImGui() {
+ this.imguiRenderer = new ImGui_ImplDX12_Native();
+
+ var ctx = ImGui.CreateContext();
+ ImGuizmo.SetImGuiContext(ctx);
+ ImPlot.SetImGuiContext(ctx);
+ ImPlot.CreateContext();
+
+ ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.DockingEnable | ImGuiConfigFlags.ViewportsEnable;
+
+ this.imguiRenderer.Init(this.Device, this.SwapChain, this.CommandQueue, 1);
+ this.imguiInput = new ImGui_Input_Impl_Direct(this.WindowHandlePtr);
+ }
+
+ ///
+ /// Processes window messages.
+ ///
+ /// Handle of the window.
+ /// Type of window message.
+ /// wParam.
+ /// lParam.
+ /// Return value.
+ public unsafe IntPtr? ProcessWndProcW(IntPtr hWnd, User32.WindowMessage msg, void* wParam, void* lParam) {
+ return this.imguiInput.ProcessWndProcW(hWnd, msg, wParam, lParam);
+ }
+
+ public void Render() {
+ this.imguiRenderer.NewFrame();
+ this.OnNewRenderFrame?.Invoke();
+ this.imguiInput.NewFrame(targetWidth, targetHeight);
+ this.OnNewInputFrame?.Invoke();
+
+ ImGui.NewFrame();
+ ImGuizmo.BeginFrame();
+
+ this.OnBuildUI?.Invoke();
+
+ ImGui.Render();
+
+ this.imguiRenderer.RenderDrawData(ImGui.GetDrawData());
+ ImGui.UpdatePlatformWindows();
+ ImGui.RenderPlatformWindowsDefault();
+ }
+
+ ///
+ /// This should be called after the swapchain present call has finished.
+ ///
+ public void OnPostPresent() {
+ this.imguiRenderer.OnPostPresent();
+ }
+
+ public void OnPreResize() {
+ this.imguiRenderer.InvalidateRenderTargets();
+ }
+
+ // TODO: If resize buffers is never called we don't initialize the render view targets, maybe add OnPostGetBuffer?
+ public void OnPostResize(int bufferCount, int newWidth, int newHeight, int newFormat) {
+ this.imguiRenderer.CreateRenderTargets(bufferCount);
+
+ this.targetWidth = newWidth;
+ this.targetHeight = newHeight;
+ }
+
+ // It is pretty much required that this is called from a handler attached
+ // to OnNewRenderFrame
+ public void InvalidateFonts() {
+ // TODO
+ this.imguiRenderer.RebuildFontTexture();
+ }
+
+ // It is pretty much required that this is called from a handler attached
+ // to OnNewRenderFrame
+ public void ClearStacksOnContext() {
+ Custom.igCustom_ClearStacks();
+ }
+
+ public bool IsImGuiCursor(IntPtr hCursor) {
+ return this.imguiInput.IsImGuiCursor(hCursor);
+ }
+
+ public TextureWrap LoadImage(string path) {
+ using var fs = new FileStream(path, FileMode.Open, FileAccess.Read);
+ using var ms = new MemoryStream();
+ fs.CopyTo(ms);
+ var image = Stbi.LoadFromMemory(ms, 4);
+ return this.LoadImage_Internal(image);
+ }
+
+ public TextureWrap LoadImage(byte[] imageBytes) {
+ using var ms = new MemoryStream(imageBytes, 0, imageBytes.Length, false, true);
+ var image = Stbi.LoadFromMemory(ms, 4);
+ return this.LoadImage_Internal(image);
+ }
+
+ public unsafe TextureWrap LoadImageRaw(byte[] imageData, int width, int height, int numChannels = 4) {
+ // StbiSharp doesn't expose a constructor, even just to wrap existing data, which means
+ // short of something awful like below, or creating another wrapper layer, we can't avoid
+ // adding divergent code paths into CreateTexture
+ //var mock = new { Width = width, Height = height, NumChannels = numChannels, Data = imageData };
+ //var image = Unsafe.As(mock);
+ //return LoadImage_Internal(image);
+
+ fixed (void* pixelData = imageData) {
+ return this.CreateTexture(pixelData, width, height, numChannels);
+ }
+ }
+
+ private unsafe TextureWrap LoadImage_Internal(StbiImage image) {
+ fixed (void* pixelData = image.Data) {
+ return this.CreateTexture(pixelData, image.Width, image.Height, image.NumChannels);
+ }
+ }
+
+ private unsafe TextureWrap CreateTexture(void* pixelData, int width, int height, int bytesPerPixel) {
+ return this.imguiRenderer.CreateTexture(pixelData, width, height, bytesPerPixel);
+ }
+
+ public byte[] CaptureScreenshot() {
+ throw new NotImplementedException();
+ // using (var backBuffer = this.SwapChain.GetBackBuffer(0))
+ // {
+ // Texture2DDescription desc = backBuffer.Description;
+ // desc.CpuAccessFlags = CpuAccessFlags.Read;
+ // desc.Usage = ResourceUsage.Staging;
+ // desc.OptionFlags = ResourceOptionFlags.None;
+ // desc.BindFlags = BindFlags.None;
+ //
+ // using (var tex = new Texture2D(this.Device, desc))
+ // {
+ // this.deviceContext.CopyResource(backBuffer, tex);
+ // using (var surf = tex.QueryInterface())
+ // {
+ // var map = surf.Map(SharpDX.DXGI.MapFlags.Read, out DataStream dataStream);
+ // var pixelData = new byte[surf.Description.Width * surf.Description.Height * surf.Description.Format.SizeOfInBytes()];
+ // var dataCounter = 0;
+ //
+ // while (dataCounter < pixelData.Length)
+ // {
+ // //var curPixel = dataStream.Read();
+ // var x = dataStream.Read();
+ // var y = dataStream.Read();
+ // var z = dataStream.Read();
+ // var w = dataStream.Read();
+ //
+ // pixelData[dataCounter++] = z;
+ // pixelData[dataCounter++] = y;
+ // pixelData[dataCounter++] = x;
+ // pixelData[dataCounter++] = w;
+ // }
+ //
+ // // TODO: test this on a thread
+ // //var gch = GCHandle.Alloc(pixelData, GCHandleType.Pinned);
+ // //using (var bitmap = new Bitmap(surf.Description.Width, surf.Description.Height, map.Pitch, PixelFormat.Format32bppRgb, gch.AddrOfPinnedObject()))
+ // //{
+ // // bitmap.Save(path);
+ // //}
+ // //gch.Free();
+ //
+ // surf.Unmap();
+ // dataStream.Dispose();
+ //
+ // return pixelData;
+ // }
+ // }
+ // }
+ }
+
+ #region IDisposable Support
+
+ private bool disposedValue; // To detect redundant calls
+
+ private void Dispose(bool disposing) {
+ if (this.disposedValue) return;
+
+ if (disposing) {
+ // TODO: dispose managed state (managed objects).
+ }
+
+ this.imguiRenderer?.Shutdown();
+ this.imguiInput?.Dispose();
+
+ ImGui.DestroyContext();
+
+ this.imguiRenderer?.Dispose();
+
+ // Not actually sure how sharpdx does ref management, but hopefully they
+ // addref when we create our wrappers, so this should just release that count
+
+ // Originally it was thought these lines were needed because it was assumed that SharpDX does
+ // proper refcounting to handle disposing, but disposing these would cause the game to crash
+ // on resizing after unloading Dalamud
+ // this.SwapChain?.Dispose();
+ // this.deviceContext?.Dispose();
+ // this.Device?.Dispose();
+
+ this.disposedValue = true;
+ }
+
+ ~RawDX12Scene() {
+ // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
+ this.Dispose(false);
+ }
+
+ // This code added to correctly implement the disposable pattern.
+ public void Dispose() {
+ // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
+ this.Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ #endregion
+ }
+}
diff --git a/ImGuiScene/Renderers/SimpleD3D.cs b/ImGuiScene/Renderers/SimpleD3D.cs
index 562afaf..95fb309 100644
--- a/ImGuiScene/Renderers/SimpleD3D.cs
+++ b/ImGuiScene/Renderers/SimpleD3D.cs
@@ -170,7 +170,7 @@ public unsafe TextureWrap CreateTexture(void* pixelData, int width, int height,
// no sampler for now because the ImGui implementation we copied doesn't allow for changing it
- return new D3DTextureWrap(resView, width, height);
+ return new D3D11TextureWrap(resView, width, height);
}
#region ImGui forwarding