Refactor GPU resource management and rendering pipeline
- Introduced `Handle<T>` and `Identifier<T>` for lightweight, strongly-typed resource identifiers. - Replaced `BitSet` with `UnsafeBitSet` for improved performance and memory safety. - Refactored `Mesh` and `Material` into `MeshClass` and `MaterialClass` for better GPU resource handling. - Added `D3D12ResourceDatabase` to centralize GPU resource tracking and lifecycle management. - Updated `D3D12ShaderCompiler` to load shaders from disk and dynamically populate constant buffers and textures. - Enhanced `ICommandBuffer` with new upload operations for buffers and textures. - Refactored `Vertex` struct for simplified memory layout and better performance. - Updated `MeshBuilder` and rendering logic to align with new resource and shader structures. - Added `BindlessDescriptor` support to `TextureHandle` and `BufferHandle`. - Removed unused classes and performed general cleanup. - Updated unit tests and demos to reflect the new architecture.
This commit is contained in:
@@ -33,7 +33,7 @@ public unsafe class CommandList
|
||||
/// </summary>
|
||||
/// <param name="mesh">The mesh to draw</param>
|
||||
/// <param name="material">The bindless material to use</param>
|
||||
public void DrawMesh(Mesh mesh, Material material)
|
||||
public void DrawMesh(MeshClass mesh, MaterialClass material)
|
||||
{
|
||||
// Bind the bindless material (sets up root signature, pipeline state, and descriptor heaps)
|
||||
material.Bind(this);
|
||||
@@ -62,7 +62,7 @@ public unsafe class CommandList
|
||||
_commandList.Ptr->OMSetRenderTargets(1, pRtvHandle, false, pDsvHandle);
|
||||
}
|
||||
|
||||
public void ClearRenderTarget(RenderTexture renderTarget, Color16 color)
|
||||
public void ClearRenderTarget(RenderTexture renderTarget, Color128 color)
|
||||
{
|
||||
renderTarget.ClearColor(this, color);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Ghost.Graphics.Data;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||
using Win32;
|
||||
using Win32.Graphics.Direct3D;
|
||||
using Win32.Graphics.Direct3D12;
|
||||
@@ -16,23 +17,35 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
|
||||
private ComPtr<ID3D12GraphicsCommandList10> _commandList;
|
||||
|
||||
private readonly D3D12PipelineStateController _stateController;
|
||||
private readonly D3D12ResourceDatabase _resourceDatabase;
|
||||
private readonly D3D12ResourceAllocator _resourceAllocator;
|
||||
private readonly D3D12DescriptorAllocator _descriptorAllocator;
|
||||
|
||||
private readonly CommandBufferType _type;
|
||||
private bool _isRecording;
|
||||
private bool _disposed;
|
||||
|
||||
public CommandBufferType Type => _type;
|
||||
|
||||
public ID3D12GraphicsCommandList10* NativeCommandList => _commandList.Get();
|
||||
|
||||
public D3D12CommandBuffer(D3D12RenderDevice device, D3D12PipelineStateController stateController, D3D12DescriptorAllocator descriptorAllocator, CommandBufferType type)
|
||||
public D3D12CommandBuffer(
|
||||
D3D12RenderDevice device,
|
||||
D3D12PipelineStateController stateController,
|
||||
D3D12ResourceDatabase resourceDatabase,
|
||||
D3D12ResourceAllocator resourceAllocator,
|
||||
D3D12DescriptorAllocator descriptorAllocator,
|
||||
CommandBufferType type)
|
||||
{
|
||||
_type = type;
|
||||
var commandListType = ConvertCommandBufferType(type);
|
||||
|
||||
device.NativeDevice->CreateCommandAllocator(commandListType, __uuidof<ID3D12CommandAllocator>(), _allocator.GetVoidAddressOf());
|
||||
device.NativeDevice->CreateCommandList(0u, commandListType, _allocator.Get(), null, __uuidof<ID3D12GraphicsCommandList10>(), _commandList.GetVoidAddressOf());
|
||||
device.NativeDevice->CreateCommandList1(0u, commandListType, CommandListFlags.None, __uuidof<ID3D12GraphicsCommandList10>(), _commandList.GetVoidAddressOf());
|
||||
|
||||
_stateController = stateController;
|
||||
_resourceDatabase = resourceDatabase;
|
||||
_resourceAllocator = resourceAllocator;
|
||||
_descriptorAllocator = descriptorAllocator;
|
||||
|
||||
// Command lists are created in recording state, so close it
|
||||
@@ -40,8 +53,28 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
|
||||
_isRecording = false;
|
||||
}
|
||||
|
||||
~D3D12CommandBuffer()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
}
|
||||
|
||||
private void ThrowIfNotRecording()
|
||||
{
|
||||
if (!_isRecording)
|
||||
{
|
||||
throw new InvalidOperationException("Command buffer is not recording");
|
||||
}
|
||||
}
|
||||
|
||||
public void Begin()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
if (_isRecording)
|
||||
{
|
||||
throw new InvalidOperationException("Command buffer is already recording");
|
||||
@@ -54,16 +87,14 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
|
||||
|
||||
public void End()
|
||||
{
|
||||
if (!_isRecording)
|
||||
{
|
||||
throw new InvalidOperationException("Command buffer is not recording");
|
||||
}
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotRecording();
|
||||
|
||||
_commandList.Get()->Close();
|
||||
_isRecording = false;
|
||||
}
|
||||
|
||||
public void BeginRenderPass(IRenderTarget renderTarget, Color16 clearColor)
|
||||
public void BeginRenderPass(IRenderTarget renderTarget, Color128 clearColor)
|
||||
{
|
||||
// TODO: Implement render pass begin
|
||||
throw new NotImplementedException();
|
||||
@@ -77,18 +108,27 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
|
||||
|
||||
public void SetViewport(ViewportDesc viewport)
|
||||
{
|
||||
var d3d12Viewport = new Viewport(viewport.Width, viewport.Height, viewport.X, viewport.Y, viewport.MinDepth, viewport.MaxDepth);
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotRecording();
|
||||
|
||||
var d3d12Viewport = new Viewport(viewport.width, viewport.height, viewport.x, viewport.y, viewport.minDepth, viewport.maxDepth);
|
||||
_commandList.Get()->RSSetViewports(1, &d3d12Viewport);
|
||||
}
|
||||
|
||||
public void SetScissorRect(RectDesc rect)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotRecording();
|
||||
|
||||
var d3d12Rect = new Rect(rect.Left, rect.Top, rect.Right, rect.Bottom);
|
||||
_commandList.Get()->RSSetScissorRects(1, &d3d12Rect);
|
||||
}
|
||||
|
||||
public void ResourceBarrier(IResource resource, ResourceState before, ResourceState after)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotRecording();
|
||||
|
||||
if (resource is D3D12Texture d3d12Texture)
|
||||
{
|
||||
_commandList.Get()->ResourceBarrierTransition(d3d12Texture.NativeResource,
|
||||
@@ -118,8 +158,11 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
|
||||
}
|
||||
|
||||
// TODO: Batch draw calls by material to minimize state changes
|
||||
public void DrawMesh(Mesh mesh, Material material)
|
||||
public void DrawMesh(MeshClass mesh, MaterialClass material)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotRecording();
|
||||
|
||||
// Bind the bindless material (sets up root signature, pipeline state, and descriptor heaps)
|
||||
var shaderPipeline = _stateController.GetShaderPipeline(material.Shader);
|
||||
if (shaderPipeline is not D3D12ShaderPipeline d3d12Pipeline)
|
||||
@@ -128,8 +171,8 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
|
||||
}
|
||||
|
||||
// Set root signature and pipeline state
|
||||
_commandList.Get()->SetGraphicsRootSignature(d3d12Pipeline.rootSignature.Get());
|
||||
_commandList.Get()->SetPipelineState(d3d12Pipeline.pipelineState.Get());
|
||||
_commandList.Get()->SetGraphicsRootSignature(d3d12Pipeline.rootSignature.Get());
|
||||
|
||||
// Set descriptor heaps - CRUCIAL: Use the specialized bindless heap for SM 6.6
|
||||
var heaps = stackalloc ID3D12DescriptorHeap*[2];
|
||||
@@ -142,7 +185,8 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
|
||||
foreach (var cbufferInfo in material.Shader.ConstantBuffers)
|
||||
{
|
||||
var cache = material.CBufferCaches[(int)cbufferInfo.RegisterSlot];
|
||||
_commandList.Get()->SetGraphicsRootConstantBufferView(rootParamIndex++, cache.GpuResource.GPUAddress);
|
||||
var resource = _resourceDatabase.GetResource(cache.GpuResource.ResourceHandle);
|
||||
_commandList.Get()->SetGraphicsRootConstantBufferView(rootParamIndex++, resource->GetGPUVirtualAddress());
|
||||
}
|
||||
|
||||
// Bind sampler descriptor table (last root parameter)
|
||||
@@ -163,9 +207,67 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
|
||||
|
||||
public void Dispatch(uint threadGroupCountX, uint threadGroupCountY = 1, uint threadGroupCountZ = 1)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotRecording();
|
||||
|
||||
_commandList.Get()->Dispatch(threadGroupCountX, threadGroupCountY, threadGroupCountZ);
|
||||
}
|
||||
|
||||
public void Upload<T>(BufferHandle buffer, ReadOnlySpan<T> data)
|
||||
where T : unmanaged
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotRecording();
|
||||
|
||||
var sizeInBytes = (uint)(data.Length * sizeof(T));
|
||||
|
||||
var uploadHandle = _resourceAllocator.CreateUploadBuffer(sizeInBytes);
|
||||
var uploadResource = _resourceDatabase.GetResource(uploadHandle.ResourceHandle);
|
||||
|
||||
void* mappedData;
|
||||
uploadResource->Map(0, null, &mappedData);
|
||||
fixed (T* dataPtr = data)
|
||||
{
|
||||
MemoryUtilities.MemCpy(mappedData, dataPtr, sizeInBytes);
|
||||
}
|
||||
uploadResource->Unmap(0, null);
|
||||
|
||||
var resource = _resourceDatabase.GetResource(buffer.ResourceHandle);
|
||||
|
||||
// Copy from upload buffer to destination
|
||||
_commandList.Get()->CopyBufferRegion(resource, 0, uploadResource, 0, sizeInBytes);
|
||||
}
|
||||
|
||||
public void Upload(TextureHandle texture, uint firstSubresource, ref SubResourceData subresources, uint numSubresources)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotRecording();
|
||||
|
||||
var textureResource = _resourceDatabase.GetResource(texture.ResourceHandle);
|
||||
|
||||
var resourceDesc = textureResource->GetDesc();
|
||||
var requiredSize = GetRequiredIntermediateSize(textureResource, firstSubresource, numSubresources);
|
||||
|
||||
var uploadHandle = _resourceAllocator.CreateUploadBuffer(requiredSize);
|
||||
var uploadResource = _resourceDatabase.GetResource(uploadHandle.ResourceHandle);
|
||||
|
||||
var d3d12Subresources = new SubresourceData
|
||||
{
|
||||
pData = subresources.pData,
|
||||
RowPitch = subresources.rowPitch,
|
||||
SlicePitch = subresources.slicePitch
|
||||
};
|
||||
|
||||
UpdateSubresources(
|
||||
(ID3D12GraphicsCommandList*)_commandList.Get(),
|
||||
textureResource,
|
||||
uploadResource,
|
||||
0,
|
||||
firstSubresource,
|
||||
numSubresources,
|
||||
&d3d12Subresources);
|
||||
}
|
||||
|
||||
private static CommandListType ConvertCommandBufferType(CommandBufferType type)
|
||||
{
|
||||
return type switch
|
||||
@@ -197,11 +299,20 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isRecording)
|
||||
{
|
||||
throw new InvalidOperationException("Command buffer is still recording");
|
||||
}
|
||||
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_commandList.Dispose();
|
||||
_allocator.Dispose();
|
||||
_disposed = true;
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,11 @@ internal unsafe class D3D12CommandQueue : ICommandQueue
|
||||
pDevice->CreateFence(0, FenceFlags.None, __uuidof<ID3D12Fence1>(), _fence.GetVoidAddressOf());
|
||||
}
|
||||
|
||||
~D3D12CommandQueue()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public void Submit(ICommandBuffer commandBuffer)
|
||||
{
|
||||
if (commandBuffer is D3D12CommandBuffer d3d12CommandBuffer)
|
||||
@@ -113,12 +118,16 @@ internal unsafe class D3D12CommandQueue : ICommandQueue
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_fenceEvent?.Dispose();
|
||||
_fence.Dispose();
|
||||
_queue.Dispose();
|
||||
|
||||
_disposed = true;
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,12 +9,17 @@ internal unsafe class D3D12GraphicsEngine : IGraphicsEngine
|
||||
#endif
|
||||
|
||||
private readonly D3D12RenderDevice _device;
|
||||
private readonly D3D12PipelineStateController _stateController;
|
||||
private readonly D3D12DescriptorAllocator _descriptorAllocator;
|
||||
|
||||
private readonly D3D12ResourceDatabase _resourceDatabase;
|
||||
private readonly D3D12ResourceAllocator _resourceAllocator;
|
||||
|
||||
private readonly D3D12PipelineStateController _stateController;
|
||||
private readonly D3D12CommandBuffer _copyCommandBuffer;
|
||||
|
||||
|
||||
public IRenderDevice Device => _device;
|
||||
public IResourceDatabase ResourceDatabase => _resourceDatabase;
|
||||
public IResourceAllocator ResourceAllocator => _resourceAllocator;
|
||||
|
||||
public IPipelineStateController PipelineStateController => _stateController;
|
||||
@@ -24,12 +29,25 @@ internal unsafe class D3D12GraphicsEngine : IGraphicsEngine
|
||||
#if DEBUG
|
||||
_debugLayer = new();
|
||||
#endif
|
||||
|
||||
_device = new();
|
||||
_descriptorAllocator = new(_device);
|
||||
_resourceAllocator = new(renderSystem, _device, _descriptorAllocator);
|
||||
|
||||
_stateController = new(_device);
|
||||
_resourceDatabase = new(_descriptorAllocator);
|
||||
_resourceAllocator = new(renderSystem, _device, _descriptorAllocator, _resourceDatabase);
|
||||
|
||||
_stateController = new(_device, _resourceDatabase);
|
||||
_copyCommandBuffer = new(
|
||||
_device,
|
||||
_stateController,
|
||||
_resourceDatabase,
|
||||
_resourceAllocator,
|
||||
_descriptorAllocator,
|
||||
CommandBufferType.Copy);
|
||||
}
|
||||
|
||||
~D3D12GraphicsEngine()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public IRenderer CreateRenderer()
|
||||
@@ -39,7 +57,13 @@ internal unsafe class D3D12GraphicsEngine : IGraphicsEngine
|
||||
|
||||
public ICommandBuffer CreateCommandBuffer(CommandBufferType type = CommandBufferType.Graphics)
|
||||
{
|
||||
return new D3D12CommandBuffer(_device, _stateController, _descriptorAllocator, type);
|
||||
return new D3D12CommandBuffer(
|
||||
_device,
|
||||
_stateController,
|
||||
_resourceDatabase,
|
||||
_resourceAllocator,
|
||||
_descriptorAllocator,
|
||||
type);
|
||||
}
|
||||
|
||||
public ISwapChain CreateSwapChain(SwapChainDesc desc)
|
||||
@@ -47,15 +71,30 @@ internal unsafe class D3D12GraphicsEngine : IGraphicsEngine
|
||||
return new D3D12SwapChain(_device.DXGIFactory, ((D3D12CommandQueue)_device.ComputeQueue).NativeQueue, desc);
|
||||
}
|
||||
|
||||
public void BeginFrame()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void EndFrame()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_copyCommandBuffer.Dispose();
|
||||
_stateController.Dispose();
|
||||
_descriptorAllocator.Dispose();
|
||||
_resourceAllocator.Dispose();
|
||||
_device.Dispose();
|
||||
|
||||
_resourceAllocator.Dispose();
|
||||
_resourceDatabase.Dispose();
|
||||
|
||||
_descriptorAllocator.Dispose();
|
||||
_device.Dispose();
|
||||
#if DEBUG
|
||||
_debugLayer.Dispose();
|
||||
#endif
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Ghost.Graphics.D3D12.Utilities;
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.D3D12.Utilities;
|
||||
using Ghost.Graphics.Data;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Win32;
|
||||
@@ -8,7 +9,7 @@ using Win32.Graphics.Dxgi.Common;
|
||||
|
||||
namespace Ghost.Graphics.D3D12;
|
||||
|
||||
internal class D3D12ShaderPipeline : IShaderPipeline
|
||||
internal class D3D12ShaderPipeline : IShaderPipeline, IDisposable
|
||||
{
|
||||
public ComPtr<ID3D12RootSignature> rootSignature;
|
||||
public ComPtr<ID3D12PipelineState> pipelineState;
|
||||
@@ -21,42 +22,51 @@ internal class D3D12ShaderPipeline : IShaderPipeline
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
rootSignature.Dispose();
|
||||
pipelineState.Dispose();
|
||||
samplerHeap.Dispose();
|
||||
vsResult.Dispose();
|
||||
psResult.Dispose();
|
||||
csResult.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
internal unsafe class D3D12PipelineStateController : IPipelineStateController, IDisposable
|
||||
{
|
||||
private readonly ID3D12Device14* _device;
|
||||
private readonly D3D12ResourceDatabase _resourceDatabase;
|
||||
|
||||
private readonly Dictionary<Shader, D3D12ShaderPipeline> _shaderPipelines;
|
||||
private readonly Dictionary<Identifier<Shader>, D3D12ShaderPipeline> _shaderPipelines;
|
||||
|
||||
public D3D12PipelineStateController(D3D12RenderDevice device)
|
||||
public D3D12PipelineStateController(D3D12RenderDevice device, D3D12ResourceDatabase resourceDatabase)
|
||||
{
|
||||
_device = device.NativeDevice;
|
||||
_resourceDatabase = resourceDatabase;
|
||||
|
||||
_shaderPipelines = new();
|
||||
}
|
||||
|
||||
// TODO: Support compute shaders
|
||||
public void ColectionShader(ReadOnlySpan<Shader> shaders)
|
||||
public void CompileShader(Identifier<Shader> id, string shaderPath)
|
||||
{
|
||||
foreach (var shader in shaders)
|
||||
{
|
||||
_shaderPipelines.TryAdd(shader, new()
|
||||
{
|
||||
Type = PipelineType.Graphics
|
||||
});
|
||||
}
|
||||
}
|
||||
var vsResult = D3D12ShaderCompiler.Compile(shaderPath, D3D12ShaderCompiler.ShaderStage.VertexShader, D3D12ShaderCompiler.CompilerVersion.SM_6_6);
|
||||
var psResult = D3D12ShaderCompiler.Compile(shaderPath, D3D12ShaderCompiler.ShaderStage.PixelShader, D3D12ShaderCompiler.CompilerVersion.SM_6_6);
|
||||
|
||||
public void CompileCollected()
|
||||
{
|
||||
foreach (var kvp in _shaderPipelines)
|
||||
{
|
||||
var vsResult = D3D12ShaderCompiler.Compile(kvp.Key, D3D12ShaderCompiler.ShaderStage.VertexShader, D3D12ShaderCompiler.CompilerVersion.SM_6_6);
|
||||
var psResult = D3D12ShaderCompiler.Compile(kvp.Key, D3D12ShaderCompiler.ShaderStage.PixelShader, D3D12ShaderCompiler.CompilerVersion.SM_6_6);
|
||||
var shader = _resourceDatabase.GetShader(id);
|
||||
|
||||
kvp.Value.vsResult = vsResult;
|
||||
kvp.Value.psResult = psResult;
|
||||
}
|
||||
D3D12ShaderCompiler.PerformDXCReflection(shader, vsResult.reflection.Get());
|
||||
D3D12ShaderCompiler.PerformDXCReflection(shader, psResult.reflection.Get());
|
||||
|
||||
var shaderPipeline = new D3D12ShaderPipeline
|
||||
{
|
||||
Type = PipelineType.Graphics,
|
||||
vsResult = vsResult,
|
||||
psResult = psResult
|
||||
};
|
||||
|
||||
_shaderPipelines[id] = shaderPipeline;
|
||||
}
|
||||
|
||||
private void CreateRootSignature(Shader shader, D3D12ShaderPipeline shaderPipeline)
|
||||
@@ -219,31 +229,29 @@ internal unsafe class D3D12PipelineStateController : IPipelineStateController, I
|
||||
{
|
||||
foreach (var kvp in _shaderPipelines)
|
||||
{
|
||||
CreateRootSignature(kvp.Key, kvp.Value);
|
||||
var shader = _resourceDatabase.GetShader(kvp.Key);
|
||||
|
||||
CreateRootSignature(shader, kvp.Value);
|
||||
CreatePipelineStateObject(kvp.Value);
|
||||
CreateSamplerHeap(kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public IShaderPipeline GetShaderPipeline(Shader shader)
|
||||
public IShaderPipeline GetShaderPipeline(Identifier<Shader> id)
|
||||
{
|
||||
if (_shaderPipelines.TryGetValue(shader, out var pipeline))
|
||||
if (_shaderPipelines.TryGetValue(id, out var pipeline))
|
||||
{
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
throw new KeyNotFoundException($"Shader pipeline not found for shader: {shader}");
|
||||
throw new KeyNotFoundException($"Shader pipeline not found for shader ID: {id}");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var kvp in _shaderPipelines)
|
||||
{
|
||||
kvp.Value.rootSignature.Dispose();
|
||||
kvp.Value.pipelineState.Dispose();
|
||||
kvp.Value.samplerHeap.Dispose();
|
||||
kvp.Value.vsResult.Dispose();
|
||||
kvp.Value.psResult.Dispose();
|
||||
kvp.Value.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -169,7 +169,7 @@ public unsafe class D3D12Renderer : IRenderer
|
||||
|
||||
private void RenderScene(IRenderTarget target, ICommandBuffer cmd)
|
||||
{
|
||||
var clearColor = new Color16 { r = 1.0f, g = 0.0f, b = 1.0f, a = 1.0f };
|
||||
var clearColor = new Color128 { r = 1.0f, g = 0.0f, b = 1.0f, a = 1.0f };
|
||||
|
||||
cmd.BeginRenderPass(target, clearColor);
|
||||
|
||||
@@ -207,7 +207,7 @@ public unsafe class D3D12Renderer : IRenderer
|
||||
// 3. Apply post-processing effects (tone mapping, gamma correction, etc.)
|
||||
|
||||
// For now, just clear the destination (this should be replaced with actual blit)
|
||||
var clearColor = new Color16 { r = 0.0f, g = 0.0f, b = 0.0f, a = 1.0f };
|
||||
var clearColor = new Color128 { r = 0.0f, g = 0.0f, b = 0.0f, a = 1.0f };
|
||||
cmd.BeginRenderPass(destination, clearColor);
|
||||
cmd.EndRenderPass();
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Ghost.Graphics.Data;
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.Data;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using System.Runtime.CompilerServices;
|
||||
@@ -10,32 +11,8 @@ using static Win32.Graphics.D3D12MemoryAllocator.Apis;
|
||||
|
||||
namespace Ghost.Graphics.D3D12;
|
||||
|
||||
internal unsafe class D3D12ResourceAllocator : IResourceAllocator<ID3D12Resource>, IDisposable
|
||||
internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
|
||||
{
|
||||
private readonly struct AllocationInfo : IDisposable
|
||||
{
|
||||
public readonly Allocation allocation;
|
||||
public readonly uint cpuFenceValue;
|
||||
|
||||
public bool Allocated => allocation.IsNotNull;
|
||||
|
||||
public AllocationInfo(in Allocation allocation, uint cpuFenceValue)
|
||||
{
|
||||
this.allocation = allocation;
|
||||
this.cpuFenceValue = cpuFenceValue;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (allocation.IsNull)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
allocation.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private const uint _MAX_BYTES = D3D12_REQ_RESOURCE_SIZE_IN_MEGABYTES_EXPRESSION_A_TERM * 1024u * 1024u;
|
||||
private const uint _MAX_TEXTURE2D_DIMENSION = 16384u;
|
||||
private const uint _MAX_TEXTURE3D_DIMENSION = 2048u;
|
||||
@@ -45,8 +22,8 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator<ID3D12Resource
|
||||
private readonly Allocator _allocator;
|
||||
private readonly RenderSystem _renderSystem;
|
||||
private readonly D3D12DescriptorAllocator _descriptorAllocator;
|
||||
private readonly D3D12ResourceDatabase _resourceDatabase;
|
||||
|
||||
private UnsafeSlotMap<AllocationInfo> _allocations = new(64, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
|
||||
private UnsafeQueue<ResourceHandle> _temResources = new(64, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
|
||||
|
||||
private Guid* IID_NULL
|
||||
@@ -60,7 +37,7 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator<ID3D12Resource
|
||||
}
|
||||
}
|
||||
|
||||
public D3D12ResourceAllocator(RenderSystem renderSystem, D3D12RenderDevice device, D3D12DescriptorAllocator descriptorAllocator)
|
||||
public D3D12ResourceAllocator(RenderSystem renderSystem, D3D12RenderDevice device, D3D12DescriptorAllocator descriptorAllocator, D3D12ResourceDatabase resourceDatabase)
|
||||
{
|
||||
var desc = new AllocatorDesc
|
||||
{
|
||||
@@ -74,6 +51,12 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator<ID3D12Resource
|
||||
_device = device.NativeDevice;
|
||||
_renderSystem = renderSystem;
|
||||
_descriptorAllocator = descriptorAllocator;
|
||||
_resourceDatabase = resourceDatabase;
|
||||
}
|
||||
|
||||
~D3D12ResourceAllocator()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -94,11 +77,11 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator<ID3D12Resource
|
||||
}
|
||||
}
|
||||
|
||||
private ResourceHandle TrackResource(ref readonly Allocation allocation, bool isTemp)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private ResourceHandle TrackResource(ref readonly Allocation allocation, ResourceStates state, bool isTemp)
|
||||
{
|
||||
var id = _allocations.Add(new(in allocation, _renderSystem.CPUFenceValue), out var generation);
|
||||
var handle = _resourceDatabase.AddResource(in allocation, _renderSystem.CPUFenceValue, D3D12StatesToRHIState(state));
|
||||
|
||||
var handle = new ResourceHandle(id, generation);
|
||||
if (isTemp)
|
||||
{
|
||||
_temResources.Enqueue(handle);
|
||||
@@ -107,18 +90,40 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator<ID3D12Resource
|
||||
return handle;
|
||||
}
|
||||
|
||||
public TextureHandle CreateTextureHandle(ref readonly TextureDesc desc, bool tempResource = false)
|
||||
public TextureHandle CreateTexture(ref readonly TextureDesc desc, bool tempResource = false)
|
||||
{
|
||||
CheckTexture2DSize(desc.Width, desc.Height);
|
||||
|
||||
var resourceDesc = ResourceDescription.Tex2D(
|
||||
ConvertTextureFormat(desc.Format),
|
||||
desc.Width,
|
||||
desc.Height,
|
||||
mipLevels: (ushort)desc.MipLevels,
|
||||
arraySize: (ushort)desc.Slice,
|
||||
flags: ConvertTextureUsage(desc.Usage)
|
||||
);
|
||||
var d3d12Format = ConvertTextureFormat(desc.Format);
|
||||
var mipLevels = desc.MipLevels == 0 ? (ushort)(1 + Math.Floor(Math.Log2(Math.Max(desc.Width, desc.Height)))) : (ushort)desc.MipLevels;
|
||||
|
||||
var resourceDesc = desc.Dimension switch
|
||||
{
|
||||
TextureDimension.Texture2D => ResourceDescription.Tex2D(
|
||||
d3d12Format,
|
||||
desc.Width,
|
||||
desc.Height,
|
||||
mipLevels: mipLevels,
|
||||
flags: ConvertTextureUsage(desc.Usage)),
|
||||
TextureDimension.Texture3D => ResourceDescription.Tex3D(
|
||||
d3d12Format,
|
||||
desc.Width,
|
||||
desc.Height,
|
||||
(ushort)desc.Slice,
|
||||
flags: ConvertTextureUsage(desc.Usage)),
|
||||
//case TextureDimension.TextureCube:
|
||||
// break;
|
||||
TextureDimension.Texture2DArray => ResourceDescription.Tex2D(
|
||||
d3d12Format,
|
||||
desc.Width,
|
||||
desc.Height,
|
||||
mipLevels: mipLevels,
|
||||
arraySize: (ushort)desc.Slice,
|
||||
flags: ConvertTextureUsage(desc.Usage)),
|
||||
//case TextureDimension.TextureCubeArray:
|
||||
// break;
|
||||
_ => throw new ArgumentException($"Unsupported texture dimension: {desc.Dimension}"),
|
||||
};
|
||||
|
||||
var allocationDesc = new AllocationDesc
|
||||
{
|
||||
@@ -131,10 +136,58 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator<ID3D12Resource
|
||||
Allocation allocation = default;
|
||||
ThrowIfFailed(_allocator.CreateResource(&allocationDesc, in resourceDesc, initialState, null, &allocation, IID_NULL, null));
|
||||
|
||||
return new(TrackResource(in allocation, tempResource));
|
||||
var handle = TrackResource(in allocation, initialState, tempResource);
|
||||
|
||||
if (desc.CreationFlags.HasFlag(TextureCreationFlags.Bindless))
|
||||
{
|
||||
var descriptorHandle = _descriptorAllocator.AllocateBindless();
|
||||
var srvDesc = new ShaderResourceViewDescription
|
||||
{
|
||||
Format = d3d12Format,
|
||||
Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING
|
||||
};
|
||||
|
||||
switch (desc.Dimension)
|
||||
{
|
||||
case TextureDimension.Texture2D:
|
||||
srvDesc.ViewDimension = SrvDimension.Texture2D;
|
||||
srvDesc.Texture2D = new Texture2DSrv
|
||||
{
|
||||
MipLevels = mipLevels,
|
||||
};
|
||||
break;
|
||||
case TextureDimension.Texture3D:
|
||||
srvDesc.ViewDimension = SrvDimension.Texture3D;
|
||||
srvDesc.Texture3D = new Texture3DSrv
|
||||
{
|
||||
MipLevels = 0,
|
||||
};
|
||||
break;
|
||||
case TextureDimension.Texture2DArray:
|
||||
srvDesc.ViewDimension = SrvDimension.Texture2DArray;
|
||||
srvDesc.Texture2DArray = new Texture2DArraySrv
|
||||
{
|
||||
MipLevels = mipLevels,
|
||||
ArraySize = desc.Slice,
|
||||
};
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException($"Unsupported texture dimension for SRV: {desc.Dimension}");
|
||||
}
|
||||
|
||||
_device->CreateShaderResourceView(allocation.Resource, &srvDesc, _descriptorAllocator.GetCpuHandle(descriptorHandle));
|
||||
}
|
||||
|
||||
return new(handle);
|
||||
}
|
||||
|
||||
public BufferHandle CreateBufferHandle(ref readonly BufferDesc desc, bool tempResource = false)
|
||||
public TextureHandle CreateRenderTarget(ref readonly RenderTargetDesc desc, bool tempResource = false)
|
||||
{
|
||||
var textureDesc = RenderTargetDesc.ToTextureDescriptor(desc);
|
||||
return CreateTexture(ref textureDesc, tempResource);
|
||||
}
|
||||
|
||||
public BufferHandle CreateBuffer(ref readonly BufferDesc desc, bool tempResource = false)
|
||||
{
|
||||
CheckBufferSize((uint)desc.Size);
|
||||
|
||||
@@ -150,12 +203,11 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator<ID3D12Resource
|
||||
Allocation allocation = default;
|
||||
ThrowIfFailed(_allocator.CreateResource(&allocationDesc, in resourceDescription, initialState, null, &allocation, IID_NULL, null));
|
||||
|
||||
var handle = TrackResource(in allocation, tempResource);
|
||||
var handle = TrackResource(in allocation, initialState, tempResource);
|
||||
|
||||
if (desc.Usage.HasFlag(BufferUsage.ShaderResource) && desc.CreationFlags.HasFlag(BufferCreationFlags.Bindless))
|
||||
{
|
||||
var isRaw = desc.Usage.HasFlag(BufferUsage.Raw);
|
||||
|
||||
var descriptorHandle = _descriptorAllocator.AllocateBindless();
|
||||
|
||||
var srvDesc = new ShaderResourceViewDescription
|
||||
@@ -189,23 +241,75 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator<ID3D12Resource
|
||||
return new(handle);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public IRenderTarget CreateRenderTarget(ref readonly RenderTargetDesc desc, bool tempResource = false)
|
||||
public BufferHandle CreateUploadBuffer(ulong size, bool temp = true)
|
||||
{
|
||||
var textureDesc = RenderTargetDesc.ToTextureDescriptor(desc);
|
||||
return D3D12RenderTarget.Create(CreateTextureHandle(in textureDesc), in desc);
|
||||
var desc = new BufferDesc
|
||||
{
|
||||
Size = size,
|
||||
Usage = BufferUsage.Upload,
|
||||
MemoryType = MemoryType.Upload,
|
||||
};
|
||||
|
||||
return CreateBuffer(in desc, temp);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ITexture CreateTexture(ref readonly TextureDesc desc, bool tempResource = false)
|
||||
public Identifier<Mesh> CreateMesh(ReadOnlySpan<Vertex> vertices, ReadOnlySpan<uint> indices)
|
||||
{
|
||||
return new D3D12Texture(CreateTextureHandle(in desc, tempResource), in desc);
|
||||
var vertexBufferDesc = new BufferDesc
|
||||
{
|
||||
Size = (ulong)(vertices.Length * Unsafe.SizeOf<Vertex>()),
|
||||
Stride = (uint)Unsafe.SizeOf<Vertex>(),
|
||||
Usage = BufferUsage.Vertex | BufferUsage.ShaderResource,
|
||||
MemoryType = MemoryType.Default,
|
||||
CreationFlags = BufferCreationFlags.Bindless
|
||||
};
|
||||
|
||||
var indexBufferDesc = new BufferDesc
|
||||
{
|
||||
Size = (ulong)(indices.Length * sizeof(uint)),
|
||||
Stride = sizeof(uint),
|
||||
Usage = BufferUsage.Index | BufferUsage.ShaderResource,
|
||||
MemoryType = MemoryType.Default,
|
||||
CreationFlags = BufferCreationFlags.Bindless
|
||||
};
|
||||
|
||||
var vertexBuffer = CreateBuffer(ref vertexBufferDesc, true);
|
||||
var indexBuffer = CreateBuffer(ref indexBufferDesc, true);
|
||||
|
||||
var data = new Mesh(vertices, indices, vertexBuffer, indexBuffer);
|
||||
return _resourceDatabase.AddMesh(in data);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public IBuffer CreateBuffer(ref readonly BufferDesc desc, bool tempResource = false)
|
||||
public Identifier<Material> CreateMaterial(Identifier<Shader> shader)
|
||||
{
|
||||
return new D3D12Buffer(CreateBufferHandle(in desc, tempResource), in desc, this);
|
||||
var materialData = new Material
|
||||
{
|
||||
Shader = shader,
|
||||
};
|
||||
|
||||
var shaderResource = _resourceDatabase.GetShader(shader);
|
||||
|
||||
if (shaderResource.ConstantBuffers.Count > 0)
|
||||
{
|
||||
var maxSlot = shaderResource.ConstantBuffers.Max(cb => cb.RegisterSlot);
|
||||
materialData._cBufferCaches = new UnsafeArray<CBufferCache>((int)maxSlot + 1, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
|
||||
|
||||
foreach (var cbufferInfo in shaderResource.ConstantBuffers)
|
||||
{
|
||||
var desc = new BufferDesc
|
||||
{
|
||||
Size = cbufferInfo.Size,
|
||||
Usage = BufferUsage.Constant,
|
||||
MemoryType = MemoryType.Default,
|
||||
};
|
||||
|
||||
var buffer = CreateBuffer(in desc);
|
||||
|
||||
materialData._cBufferCaches[cbufferInfo.RegisterSlot] = new CBufferCache(buffer, cbufferInfo.Size);
|
||||
}
|
||||
}
|
||||
|
||||
return _resourceDatabase.AddMaterial(in materialData);
|
||||
}
|
||||
|
||||
#region Conversion Methods
|
||||
@@ -319,6 +423,42 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator<ID3D12Resource
|
||||
return ResourceStates.Common;
|
||||
}
|
||||
|
||||
private static ResourceState D3D12StatesToRHIState(ResourceStates states)
|
||||
{
|
||||
switch (states)
|
||||
{
|
||||
//case ResourceStates.None:
|
||||
//case ResourceStates.Present:
|
||||
case ResourceStates.Common:
|
||||
return ResourceState.Common;
|
||||
case ResourceStates.VertexAndConstantBuffer:
|
||||
return ResourceState.VertexAndConstantBuffer;
|
||||
case ResourceStates.IndexBuffer:
|
||||
return ResourceState.IndexBuffer;
|
||||
case ResourceStates.RenderTarget:
|
||||
return ResourceState.RenderTarget;
|
||||
case ResourceStates.UnorderedAccess:
|
||||
return ResourceState.UnorderedAccess;
|
||||
case ResourceStates.DepthWrite:
|
||||
return ResourceState.DepthWrite;
|
||||
case ResourceStates.DepthRead:
|
||||
return ResourceState.DepthRead;
|
||||
case ResourceStates.PixelShaderResource:
|
||||
return ResourceState.PixelShaderResource;
|
||||
//case ResourceStates.Predication:
|
||||
case ResourceStates.IndirectArgument:
|
||||
return ResourceState.IndirectArgument;
|
||||
case ResourceStates.CopyDest:
|
||||
return ResourceState.CopyDest;
|
||||
case ResourceStates.CopySource:
|
||||
return ResourceState.CopySource;
|
||||
case ResourceStates.GenericRead:
|
||||
return ResourceState.GenericRead;
|
||||
default:
|
||||
return ResourceState.Common;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void ReleaseTempResource()
|
||||
@@ -326,9 +466,8 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator<ID3D12Resource
|
||||
while (_temResources.Count > 0)
|
||||
{
|
||||
var handle = _temResources.Peek();
|
||||
|
||||
if (_allocations.TryGetElementAt(handle.id, handle.generation, out var info)
|
||||
&& info.Allocated)
|
||||
ref var info = ref _resourceDatabase.GetResourceInfo(handle, out var exist);
|
||||
if (exist && info.Allocated && info.cpuFenceValue > _renderSystem.GPUFenceValue)
|
||||
{
|
||||
break;
|
||||
}
|
||||
@@ -338,22 +477,6 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator<ID3D12Resource
|
||||
}
|
||||
}
|
||||
|
||||
public ID3D12Resource* GetResource(ResourceHandle handle)
|
||||
{
|
||||
if (!handle.IsValid)
|
||||
{
|
||||
throw new InvalidOperationException("Invalid resource handle.");
|
||||
}
|
||||
|
||||
var info = _allocations.GetElementAt(handle.id, handle.generation);
|
||||
if (!info.Allocated)
|
||||
{
|
||||
throw new InvalidOperationException($"Resource with ID {handle.id} and generation {handle.generation} is not allocated or has been released.");
|
||||
}
|
||||
|
||||
return info.allocation.Resource;
|
||||
}
|
||||
|
||||
public void ReleaseResource(ResourceHandle handle)
|
||||
{
|
||||
if (!handle.IsValid)
|
||||
@@ -361,7 +484,7 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator<ID3D12Resource
|
||||
return;
|
||||
}
|
||||
|
||||
ref var info = ref _allocations.GetElementReferenceAt(handle.id, handle.generation, out var exist);
|
||||
ref var info = ref _resourceDatabase.GetResourceInfo(handle, out var exist);
|
||||
|
||||
if (!exist || !info.Allocated)
|
||||
{
|
||||
@@ -369,24 +492,26 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator<ID3D12Resource
|
||||
}
|
||||
|
||||
info.Dispose();
|
||||
_allocations.Remove(handle.id, handle.generation);
|
||||
_resourceDatabase.RemoveResource(handle);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
#if DEBUG
|
||||
if (_allocations.Count > 0)
|
||||
if (_temResources.Count > 0)
|
||||
{
|
||||
throw new InvalidOperationException($"ResourceAllocator is being disposed with {_allocations.Count} allocations still registered. Ensure all resources are released before disposing.");
|
||||
throw new InvalidOperationException($"ResourceAllocator is being disposed with {_temResources.Count} temp allocations still registered. Ensure all resources are released before disposing.");
|
||||
}
|
||||
#endif
|
||||
foreach (var info in _allocations)
|
||||
|
||||
foreach (var handle in _temResources)
|
||||
{
|
||||
info.Dispose();
|
||||
ReleaseResource(handle);
|
||||
}
|
||||
|
||||
_allocations.Dispose();
|
||||
_temResources.Dispose();
|
||||
_allocator.Release();
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,338 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.Data;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.Collections;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Win32.Graphics.D3D12MemoryAllocator;
|
||||
using Win32.Graphics.Direct3D12;
|
||||
|
||||
namespace Ghost.Graphics.D3D12;
|
||||
|
||||
internal class D3D12ResourceDatabase
|
||||
internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
|
||||
{
|
||||
}
|
||||
private struct Slot<T>
|
||||
{
|
||||
public T value;
|
||||
public bool isValid;
|
||||
}
|
||||
|
||||
public struct ResourceInfo : IDisposable
|
||||
{
|
||||
public readonly Allocation allocation;
|
||||
public readonly uint cpuFenceValue;
|
||||
public ResourceState state;
|
||||
|
||||
public readonly bool Allocated => allocation.IsNotNull;
|
||||
|
||||
public ResourceInfo(in Allocation allocation, uint cpuFenceValue, ResourceState state)
|
||||
{
|
||||
this.allocation = allocation;
|
||||
this.cpuFenceValue = cpuFenceValue;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public readonly void Dispose()
|
||||
{
|
||||
if (allocation.IsNull)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
allocation.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private UnsafeSlotMap<ResourceInfo> _resources;
|
||||
|
||||
// NOTE: We use a simple list for shaders since they are not frequently added/removed. This can save 4 bytes for each ecs component.
|
||||
private readonly DynamicArray<Slot<Mesh>> _meshDatas;
|
||||
private readonly DynamicArray<Slot<Shader>> _shaders;
|
||||
|
||||
private readonly D3D12DescriptorAllocator _descriptorAllocator;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
public D3D12ResourceDatabase(D3D12DescriptorAllocator descriptorAllocator)
|
||||
{
|
||||
_resources = new(64, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
|
||||
|
||||
_meshDatas = new(64);
|
||||
_shaders = new(16);
|
||||
|
||||
_descriptorAllocator = descriptorAllocator;
|
||||
}
|
||||
|
||||
~D3D12ResourceDatabase()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public ResourceHandle AddResource(ref readonly Allocation allocation, uint cpuFenceValue, ResourceState initialState)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var id = _resources.Add(new ResourceInfo(allocation, cpuFenceValue, initialState), out var generation);
|
||||
return new ResourceHandle(id, generation);
|
||||
}
|
||||
|
||||
public ref ResourceInfo GetResourceInfo(ResourceHandle handle)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
ref var info = ref _resources.GetElementReferenceAt(handle.id, handle.generation, out var exist);
|
||||
if (!exist)
|
||||
{
|
||||
throw new KeyNotFoundException($"Resource with handle {handle} not found.");
|
||||
}
|
||||
|
||||
return ref info;
|
||||
}
|
||||
|
||||
public ref ResourceInfo GetResourceInfo(ResourceHandle handle, out bool exist)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
return ref _resources.GetElementReferenceAt(handle.id, handle.generation, out exist);
|
||||
}
|
||||
|
||||
public unsafe T* GetResource<T>(ResourceHandle handle)
|
||||
where T : unmanaged
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
if (typeof(T) != typeof(ID3D12Resource))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var info = GetResourceInfo(handle);
|
||||
if (!info.Allocated)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return (T*)info.allocation.Resource;
|
||||
}
|
||||
|
||||
public unsafe ID3D12Resource* GetResource(ResourceHandle handle)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var info = GetResourceInfo(handle);
|
||||
if (!info.Allocated)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return info.allocation.Resource;
|
||||
}
|
||||
|
||||
public ResourceState GetResourceState(ResourceHandle handle)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
return GetResourceInfo(handle).state;
|
||||
}
|
||||
|
||||
public void SetResourceState(ResourceHandle handle, ResourceState state)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
ref var info = ref _resources.GetElementReferenceAt(handle.id, handle.generation, out var exist);
|
||||
if (!exist || info.Allocated == false)
|
||||
{
|
||||
throw new KeyNotFoundException($"Resource with handle {handle} not found.");
|
||||
}
|
||||
|
||||
info.state = state;
|
||||
}
|
||||
|
||||
public void RemoveResource(ResourceHandle handle)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
ref var info = ref _resources.GetElementReferenceAt(handle.id, handle.generation, out var exist);
|
||||
if (!exist || info.Allocated == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
info.Dispose();
|
||||
|
||||
_resources.Remove(handle.id, handle.generation);
|
||||
}
|
||||
|
||||
public Identifier<Mesh> AddMesh(ref readonly Mesh mesh)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var id = new Identifier<Mesh>(_meshDatas.Count);
|
||||
_meshDatas.Add(new()
|
||||
{
|
||||
value = mesh,
|
||||
isValid = true
|
||||
});
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
public bool HasMesh(Identifier<Mesh> id)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
return id.value >= 0 && id.value < _meshDatas.Count && _meshDatas[id.value].isValid;
|
||||
}
|
||||
|
||||
public Mesh GetMesh(Identifier<Mesh> id)
|
||||
{
|
||||
if (!HasMesh(id))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(id), $"Mesh ID {id.value} is out of range.");
|
||||
}
|
||||
|
||||
return _meshDatas[id.value].value;
|
||||
}
|
||||
|
||||
public ref Mesh GetMeshReference(Identifier<Mesh> id)
|
||||
{
|
||||
if (!HasMesh(id))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(id), $"Mesh ID {id.value} is out of range.");
|
||||
}
|
||||
|
||||
return ref _meshDatas[id.value].value;
|
||||
}
|
||||
|
||||
public void RemoveMesh(Identifier<Mesh> id)
|
||||
{
|
||||
if (!HasMesh(id))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ref var meshSlot = ref _meshDatas[id.value];
|
||||
if (!meshSlot.isValid)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ref var mesh = ref meshSlot.value;
|
||||
mesh.ReleaseCpuResources();
|
||||
|
||||
RemoveResource(mesh.vertexBuffer.ResourceHandle);
|
||||
RemoveResource(mesh.indexBuffer.ResourceHandle);
|
||||
|
||||
_descriptorAllocator.Release(mesh.vertexBuffer.BindlessDescriptor);
|
||||
_descriptorAllocator.Release(mesh.indexBuffer.BindlessDescriptor);
|
||||
}
|
||||
|
||||
public Identifier<Shader> AddShader(ref readonly Shader shader)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var id = new Identifier<Shader>(_shaders.Count);
|
||||
_shaders.Add(new()
|
||||
{
|
||||
value = shader,
|
||||
isValid = true
|
||||
});
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
public bool HasShader(Identifier<Shader> id)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
return id.value >= 0 && id.value < _shaders.Count && _shaders[id.value].isValid;
|
||||
}
|
||||
|
||||
public Shader GetShader(Identifier<Shader> id)
|
||||
{
|
||||
if (!HasShader(id))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(id), $"Shader ID {id.value} is out of range.");
|
||||
}
|
||||
|
||||
return _shaders[id.value].value;
|
||||
}
|
||||
|
||||
public ref Shader GetShaderReference(Identifier<Shader> id)
|
||||
{
|
||||
if (!HasShader(id))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(id), $"Shader ID {id.value} is out of range.");
|
||||
}
|
||||
|
||||
return ref _shaders[id.value].value;
|
||||
}
|
||||
|
||||
public void RemoveShader(Identifier<Shader> id)
|
||||
{
|
||||
if (!HasShader(id))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ref var shader = ref _shaders[id.value];
|
||||
|
||||
shader.value.Dispose();
|
||||
shader.value = default;
|
||||
shader.isValid = false;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
if (_resources.Count > 0)
|
||||
{
|
||||
throw new InvalidOperationException($"ResourceAllocator is being disposed with {_resources.Count} allocations still registered. Ensure all resources are released before disposing.");
|
||||
}
|
||||
|
||||
if (_meshDatas.Count > 0)
|
||||
{
|
||||
throw new InvalidOperationException($"ResourceAllocator is being disposed with {_meshDatas.Count} meshes still registered. Ensure all meshes are released before disposing.");
|
||||
}
|
||||
|
||||
if (_shaders.Count > 0)
|
||||
{
|
||||
throw new InvalidOperationException($"ResourceAllocator is being disposed with {_shaders.Count} shaders still registered. Ensure all shaders are released before disposing.");
|
||||
}
|
||||
#endif
|
||||
|
||||
_resources.Dispose();
|
||||
|
||||
foreach (var mesh in _meshDatas)
|
||||
{
|
||||
if (!mesh.isValid)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
mesh.value.ReleaseCpuResources();
|
||||
|
||||
RemoveResource(mesh.value.vertexBuffer.ResourceHandle);
|
||||
RemoveResource(mesh.value.indexBuffer.ResourceHandle);
|
||||
|
||||
_descriptorAllocator.Release(mesh.value.vertexBuffer.BindlessDescriptor);
|
||||
_descriptorAllocator.Release(mesh.value.indexBuffer.BindlessDescriptor);
|
||||
}
|
||||
|
||||
foreach (var shader in _shaders)
|
||||
{
|
||||
if (!shader.isValid)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
shader.value.Dispose();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
@@ -65,32 +65,36 @@ internal unsafe static class D3D12ShaderCompiler
|
||||
};
|
||||
}
|
||||
|
||||
public static CompileResult Compile(Shader shader, ShaderStage stage, CompilerVersion version)
|
||||
public static CompileResult Compile(string shaderPath, ShaderStage stage, CompilerVersion version)
|
||||
{
|
||||
using ComPtr<IDxcCompiler3> compiler = default;
|
||||
using ComPtr<IDxcUtils> utils = default;
|
||||
using ComPtr<IDxcIncludeHandler> includeHandler = default;
|
||||
|
||||
// Create DXC compiler and utils
|
||||
DxcCreateInstance(CLSID_DxcCompiler, __uuidof<IDxcCompiler3>(), compiler.GetVoidAddressOf());
|
||||
DxcCreateInstance(CLSID_DxcUtils, __uuidof<IDxcUtils>(), utils.GetVoidAddressOf());
|
||||
DxcCreateInstance(in CLSID_DxcCompiler, __uuidof<IDxcCompiler3>(), compiler.GetVoidAddressOf());
|
||||
DxcCreateInstance(in CLSID_DxcUtils, __uuidof<IDxcUtils>(), utils.GetVoidAddressOf());
|
||||
utils.Get()->CreateDefaultIncludeHandler(includeHandler.GetAddressOf());
|
||||
|
||||
// Create source blob
|
||||
using ComPtr<IDxcBlobEncoding> sourceBlob = default;
|
||||
var sourceBytes = System.Text.Encoding.UTF8.GetBytes(shader.Source);
|
||||
fixed (byte* sourceBytesPtr = sourceBytes)
|
||||
//var sourceBytes = System.Text.Encoding.UTF8.GetBytes(shaderPath);
|
||||
|
||||
fixed (char* pShaderPath = shaderPath.AsSpan())
|
||||
{
|
||||
utils.Get()->CreateBlob(sourceBytesPtr, (uint)sourceBytes.Length, DXC_CP_UTF8, sourceBlob.GetAddressOf());
|
||||
utils.Get()->LoadFile(pShaderPath, null, sourceBlob.GetAddressOf());
|
||||
//utils.Get()->CreateBlob(sourceBytesPtr, (uint)sourceBytes.Length, DXC_CP_UTF8, sourceBlob.GetAddressOf());
|
||||
}
|
||||
|
||||
// Prepare compilation arguments - NOTE: NO -Qstrip_reflect to keep reflection data
|
||||
var argsArray = new string[]
|
||||
{
|
||||
"-T", GetProfileString(stage, version), // Target profile (vs_6_6, ps_6_6)
|
||||
"-E", GetEntryPoint(stage), // Entry point
|
||||
"-HV", "2021", // HLSL version 2021 (required for SM 6.6)
|
||||
"-enable-16bit-types", // Enable 16-bit types
|
||||
"-O3", // Optimization level
|
||||
"-Qstrip_debug" // Strip debug info but KEEP reflection
|
||||
"-T", GetProfileString(stage, version), // Target profile (vs_6_6, ps_6_6)
|
||||
"-E", GetEntryPoint(stage), // Entry point
|
||||
"-HV", "2021", // HLSL version 2021 (required for SM 6.6)
|
||||
"-enable-16bit-types", // Enable 16-bit types
|
||||
"-O3", // Optimization level
|
||||
"-Qstrip_debug" // Strip debug info but KEEP reflection
|
||||
};
|
||||
|
||||
// Convert to wide strings (DXC expects LPCWSTR)
|
||||
@@ -116,7 +120,7 @@ internal unsafe static class D3D12ShaderCompiler
|
||||
Encoding = DXC_CP_UTF8
|
||||
};
|
||||
|
||||
compiler.Get()->Compile(&buffer, (char**)argsPtr, (uint)argsArray.Length, null, __uuidof<IDxcResult>(), result.GetVoidAddressOf());
|
||||
compiler.Get()->Compile(&buffer, (char**)argsPtr, (uint)argsArray.Length, includeHandler.Get(), __uuidof<IDxcResult>(), result.GetVoidAddressOf());
|
||||
}
|
||||
|
||||
// Check compilation result
|
||||
@@ -203,85 +207,92 @@ internal unsafe static class D3D12ShaderCompiler
|
||||
ShaderDescription shaderDesc;
|
||||
reflection.Get()->GetDesc(&shaderDesc);
|
||||
|
||||
var cbufferRegistry = shader.ConstantBuffers.ToDictionary(cb => cb.Name);
|
||||
var textureRegistry = shader.RegularTextures.ToDictionary(t => t.Name);
|
||||
var cbufferRegistry = new Dictionary<string, CBufferInfo>();
|
||||
var textureRegistry = new Dictionary<string, TextureInfo>();
|
||||
|
||||
for (uint i = 0; i < shaderDesc.BoundResources; i++)
|
||||
{
|
||||
ShaderInputBindDescription bindDesc;
|
||||
reflection.Get()->GetResourceBindingDesc(i, &bindDesc);
|
||||
|
||||
if (bindDesc.Type == ShaderInputType.ConstantBuffer)
|
||||
switch (bindDesc.Type)
|
||||
{
|
||||
var cbufferName = Marshal.PtrToStringAnsi((IntPtr)bindDesc.Name);
|
||||
if (cbufferName == null || cbufferRegistry.ContainsKey(cbufferName))
|
||||
case ShaderInputType.ConstantBuffer:
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var cbuffer = reflection.Get()->GetConstantBufferByName(bindDesc.Name);
|
||||
ShaderBufferDescription cbufferDesc;
|
||||
cbuffer->GetDesc(&cbufferDesc);
|
||||
|
||||
var cbufferInfo = new CBufferInfo
|
||||
{
|
||||
Name = cbufferName,
|
||||
Size = cbufferDesc.Size,
|
||||
RegisterSlot = bindDesc.BindPoint
|
||||
};
|
||||
cbufferRegistry.Add(cbufferName, cbufferInfo);
|
||||
|
||||
for (uint j = 0; j < cbufferDesc.Variables; j++)
|
||||
{
|
||||
var variable = cbuffer->GetVariableByIndex(j);
|
||||
ShaderVariableDescription varDesc;
|
||||
variable->GetDesc(&varDesc);
|
||||
|
||||
var variableName = Marshal.PtrToStringAnsi((IntPtr)varDesc.Name);
|
||||
if (variableName == null || shader.PropertyNameToIdMap.ContainsKey(variableName))
|
||||
var cbufferName = Marshal.PtrToStringAnsi((IntPtr)bindDesc.Name);
|
||||
if (cbufferName == null || cbufferRegistry.ContainsKey(cbufferName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var propInfo = new PropertyInfo
|
||||
var cbuffer = reflection.Get()->GetConstantBufferByName(bindDesc.Name);
|
||||
ShaderBufferDescription cbufferDesc;
|
||||
cbuffer->GetDesc(&cbufferDesc);
|
||||
|
||||
var cbufferInfo = new CBufferInfo
|
||||
{
|
||||
Name = variableName,
|
||||
CBufferIndex = cbufferInfo.RegisterSlot,
|
||||
ByteOffset = varDesc.StartOffset,
|
||||
Size = varDesc.Size
|
||||
Size = cbufferDesc.Size,
|
||||
RegisterSlot = bindDesc.BindPoint
|
||||
};
|
||||
cbufferRegistry.Add(cbufferName, cbufferInfo);
|
||||
|
||||
for (uint j = 0; j < cbufferDesc.Variables; j++)
|
||||
{
|
||||
var variable = cbuffer->GetVariableByIndex(j);
|
||||
ShaderVariableDescription varDesc;
|
||||
variable->GetDesc(&varDesc);
|
||||
|
||||
var variableName = Marshal.PtrToStringAnsi((IntPtr)varDesc.Name);
|
||||
if (variableName == null || shader.PropertyNameToIdMap.ContainsKey(variableName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var propInfo = new PropertyInfo
|
||||
{
|
||||
CBufferIndex = cbufferInfo.RegisterSlot,
|
||||
ByteOffset = varDesc.StartOffset,
|
||||
Size = varDesc.Size
|
||||
};
|
||||
|
||||
shader.AddProperty(variableName, propInfo);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ShaderInputType.Texture:
|
||||
{
|
||||
var textureName = Marshal.PtrToStringAnsi((IntPtr)bindDesc.Name);
|
||||
if (textureName == null || textureRegistry.ContainsKey(textureName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// ALL texture input slots are regular textures!
|
||||
// Bindless textures don't use explicit texture inputs - they use ResourceDescriptorHeap[index]
|
||||
var textureInfo = new TextureInfo
|
||||
{
|
||||
RegisterSlot = bindDesc.BindPoint,
|
||||
RootParameterIndex = (uint)shader.ConstantBuffers.Count // Descriptor table comes after CBVs
|
||||
};
|
||||
|
||||
// Add to the list and create the name-to-ID mapping
|
||||
var newId = shader.Properties.Count;
|
||||
shader.Properties.Add(propInfo);
|
||||
shader.PropertyNameToIdMap.Add(variableName, newId);
|
||||
textureRegistry.Add(textureName, textureInfo);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (bindDesc.Type == ShaderInputType.Texture)
|
||||
{
|
||||
var textureName = Marshal.PtrToStringAnsi((IntPtr)bindDesc.Name);
|
||||
if (textureName == null || textureRegistry.ContainsKey(textureName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// ALL texture input slots are regular textures!
|
||||
// Bindless textures don't use explicit texture inputs - they use ResourceDescriptorHeap[index]
|
||||
var textureInfo = new TextureInfo
|
||||
{
|
||||
Name = textureName,
|
||||
RegisterSlot = bindDesc.BindPoint,
|
||||
RootParameterIndex = (uint)shader.ConstantBuffers.Count // Descriptor table comes after CBVs
|
||||
};
|
||||
|
||||
textureRegistry.Add(textureName, textureInfo);
|
||||
}
|
||||
}
|
||||
|
||||
shader.ConstantBuffers.Clear();
|
||||
shader.ConstantBuffers.AddRange(cbufferRegistry.Values);
|
||||
foreach (var cbuf in cbufferRegistry.Values)
|
||||
{
|
||||
shader.ConstantBuffers.Add(cbuf);
|
||||
}
|
||||
|
||||
shader.RegularTextures.Clear();
|
||||
shader.RegularTextures.AddRange(textureRegistry.Values);
|
||||
foreach (var tex in textureRegistry.Values)
|
||||
{
|
||||
shader.RegularTextures.Add(tex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,11 +10,11 @@ internal unsafe static class D3D12PipelineResource
|
||||
public const int BACK_BUFFER_COUNT = 2;
|
||||
|
||||
private readonly static InputElementDescription[] s_inputElementDescs = [
|
||||
new InputElementDescription{ SemanticName = Vertex.Semantic.pPositionName, SemanticIndex = 0u, Format = Format.R32G32B32A32Float, InputSlot = 0u, AlignedByteOffset = 0u, InputSlotClass = InputClassification.PerVertexData, InstanceDataStepRate = 0 },
|
||||
new InputElementDescription{ SemanticName = Vertex.Semantic.pNormalName, SemanticIndex = 0u, Format = Format.R32G32B32A32Float, InputSlot = 0u, AlignedByteOffset = 16u, InputSlotClass = InputClassification.PerVertexData, InstanceDataStepRate = 0 },
|
||||
new InputElementDescription{ SemanticName = Vertex.Semantic.pTangentName, SemanticIndex = 0u, Format = Format.R32G32B32A32Float, InputSlot = 0u, AlignedByteOffset = 32u, InputSlotClass = InputClassification.PerVertexData, InstanceDataStepRate = 0 },
|
||||
new InputElementDescription{ SemanticName = Vertex.Semantic.pColorName, SemanticIndex = 0u, Format = Format.R32G32B32A32Float, InputSlot = 0u, AlignedByteOffset = 48u, InputSlotClass = InputClassification.PerVertexData, InstanceDataStepRate = 0 },
|
||||
new InputElementDescription{ SemanticName = Vertex.Semantic.pUVName, SemanticIndex = 0u, Format = Format.R32G32B32A32Float, InputSlot = 0u, AlignedByteOffset = 64u, InputSlotClass = InputClassification.PerVertexData, InstanceDataStepRate = 0 }
|
||||
new InputElementDescription{ SemanticName = Vertex.Semantic.position.GetUnsafePointer(), SemanticIndex = 0u, Format = Format.R32G32B32A32Float, InputSlot = 0u, AlignedByteOffset = 0u, InputSlotClass = InputClassification.PerVertexData, InstanceDataStepRate = 0 },
|
||||
new InputElementDescription{ SemanticName = Vertex.Semantic.normal.GetUnsafePointer(), SemanticIndex = 0u, Format = Format.R32G32B32A32Float, InputSlot = 0u, AlignedByteOffset = 16u, InputSlotClass = InputClassification.PerVertexData, InstanceDataStepRate = 0 },
|
||||
new InputElementDescription{ SemanticName = Vertex.Semantic.tangent.GetUnsafePointer(), SemanticIndex = 0u, Format = Format.R32G32B32A32Float, InputSlot = 0u, AlignedByteOffset = 32u, InputSlotClass = InputClassification.PerVertexData, InstanceDataStepRate = 0 },
|
||||
new InputElementDescription{ SemanticName = Vertex.Semantic.uv.GetUnsafePointer(), SemanticIndex = 0u, Format = Format.R32G32B32A32Float, InputSlot = 0u, AlignedByteOffset = 48u, InputSlotClass = InputClassification.PerVertexData, InstanceDataStepRate = 0 },
|
||||
new InputElementDescription{ SemanticName = Vertex.Semantic.color.GetUnsafePointer(), SemanticIndex = 0u, Format = Format.R32G32B32A32Float, InputSlot = 0u, AlignedByteOffset = 64u, InputSlotClass = InputClassification.PerVertexData, InstanceDataStepRate = 0 },
|
||||
];
|
||||
|
||||
public const Format SWAP_CHAIN_BACK_BUFFER_FORMAT = Format.B8G8R8A8Unorm;
|
||||
|
||||
Reference in New Issue
Block a user