Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add RawDX12Scene #12

Draft
wants to merge 6 commits into
base: backends
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 14 additions & 20 deletions ImGuiScene/D3DTextureWrap.cs → ImGuiScene/D3D11TextureWrap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,31 @@
using SharpDX.Direct3D11;
using System;


/// <summary>
/// DX11 Implementation of <see cref="TextureWrap"/>.
/// 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.
/// </summary>
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).
}

Expand All @@ -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
}
253 changes: 253 additions & 0 deletions ImGuiScene/D3D12TextureManager.cs
Original file line number Diff line number Diff line change
@@ -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 {
/// <summary>
/// Class that manages the creation, lookup, and synchronization of textures as <see cref="D3D12TextureWrap"/>.
/// </summary>
public class D3D12TextureManager : IDisposable {
private readonly Device _device;
private volatile bool _disposed;

private TextureInfo[] _staticBoundTextures;
private readonly Dictionary<TextureInfo, GpuDescriptorHandle> _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();
}
}
}
}
58 changes: 58 additions & 0 deletions ImGuiScene/D3D12TextureWrap.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using System;

namespace ImGuiScene {
/// <summary>
/// DX12 Implementation of <see cref="TextureWrap"/>.
/// Provides a simple wrapped view of the disposable resource as well as the handle for ImGui.
/// </summary>
public class D3D12TextureWrap : TextureWrap {
private readonly Action<D3D12TextureWrap> _disposing;
private readonly Func<D3D12TextureWrap, IntPtr> _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<D3D12TextureWrap> disposing,
Func<D3D12TextureWrap, IntPtr> 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
}
}
1 change: 1 addition & 0 deletions ImGuiScene/ImGuiScene.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
<PackageReference Include="PInvoke.User32" Version="0.7.104" />
<PackageReference Include="PInvoke.Win32" Version="0.7.104" />
<PackageReference Include="SharpDX.Direct3D11" Version="4.2.0" />
<PackageReference Include="SharpDX.Direct3D12" Version="4.2.0" />
<PackageReference Include="SharpDX.Mathematics" Version="4.2.0" />
<PackageReference Include="Silk.NET.OpenGL" Version="2.4.0" />
<PackageReference Include="Silk.NET.Windowing" Version="2.4.0" />
Expand Down
9 changes: 9 additions & 0 deletions ImGuiScene/ImGui_Impl/Custom.cs
Original file line number Diff line number Diff line change
@@ -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();
}
}
Loading