Refactoring Rendering backend

This commit is contained in:
2025-10-05 16:26:37 +09:00
parent a39f377533
commit 01a850ff94
99 changed files with 5056 additions and 5136 deletions

View File

@@ -1,17 +0,0 @@
namespace Ghost.Core;
public unsafe readonly struct ConstPtr<T>
where T : unmanaged
{
private readonly T* _ptr;
public ConstPtr(T* ptr)
{
_ptr = ptr;
}
public readonly T* Ptr => _ptr;
public static implicit operator T*(ConstPtr<T> constPtr) => constPtr._ptr;
public static implicit operator ConstPtr<T>(T* pointer) => new(pointer);
}

View File

@@ -13,4 +13,8 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Folder Include="Contracts\" />
</ItemGroup>
</Project>

View File

@@ -1,6 +1,10 @@
namespace Ghost.Core;
public interface IHandleType;
public interface IIdentifierType;
public readonly struct Handle<T>
where T : IHandleType
{
public readonly int id;
@@ -14,6 +18,8 @@ public readonly struct Handle<T>
public static Handle<T> Invalid => new(-1, -1);
public bool IsValid => this != Invalid;
public readonly override int GetHashCode()
{
return id.GetHashCode();
@@ -46,6 +52,7 @@ public readonly struct Handle<T>
}
public readonly struct Identifier<T>
where T : IIdentifierType
{
public readonly int value;
@@ -56,6 +63,8 @@ public readonly struct Identifier<T>
public static Identifier<T> Invalid => new(-1);
public bool IsValid => this != Invalid;
public readonly override int GetHashCode()
{
return value.GetHashCode();

152
Ghost.Core/Logging.cs Normal file
View File

@@ -0,0 +1,152 @@
using System.Collections.ObjectModel;
namespace Ghost.Core;
public enum LogLevel
{
Info,
Warning,
Error
}
internal struct LogMessage
{
public LogLevel Level
{
get;
}
public string Message
{
get;
}
public string? StackTrace
{
get;
}
public DateTime Timestamp
{
get;
}
public LogMessage(LogLevel level, string message, string? stackTrace = null)
{
Level = level;
Message = message;
StackTrace = stackTrace;
Timestamp = DateTime.Now;
}
public override string ToString()
{
if (StackTrace != null)
{
return $"{Timestamp:HH:mm:ss} [{Level}] {Message}\n{StackTrace}";
}
return $"{Timestamp:HH:mm:ss} [{Level}] {Message}";
}
}
internal interface ILogger
{
public ReadOnlyObservableCollection<LogMessage> Logs
{
get;
}
public void Log(string message, LogLevel level);
public void Log(Exception exception);
public void Assert(bool condition, string message);
}
// TODO: Add file logging.
internal class LoggerImplementation : ILogger
{
private readonly ObservableCollection<LogMessage> _logs = new();
private readonly Lock _lock = new();
public ReadOnlyObservableCollection<LogMessage> Logs => new(_logs);
public void Log(string message, LogLevel level)
{
lock (_lock)
{
_logs.Add(new LogMessage(level, message));
}
}
public void Log(Exception exception)
{
lock (_lock)
{
_logs.Add(new LogMessage(LogLevel.Error, exception.Message, exception.StackTrace));
}
}
public void Assert(bool condition, string message)
{
lock (_lock)
{
if (!condition)
{
Log(message, LogLevel.Error);
}
}
}
}
public static class Logger
{
private static readonly ILogger s_logger = new LoggerImplementation();
internal static ReadOnlyObservableCollection<LogMessage> Logs => s_logger.Logs;
public static void Log(LogLevel level, object? message)
{
s_logger.Log(message?.ToString() ?? "null", level);
}
public static void Log(LogLevel level, string message)
{
s_logger.Log(message, level);
}
public static void LogInfo(object? message)
{
s_logger.Log(message?.ToString() ?? "null", LogLevel.Info);
}
public static void LogInfo(string message)
{
s_logger.Log(message, LogLevel.Info);
}
public static void LogWarning(object? message)
{
s_logger.Log(message?.ToString() ?? "null", LogLevel.Warning);
}
public static void LogWarning(string message)
{
s_logger.Log(message, LogLevel.Warning);
}
public static void LogError(object? message)
{
s_logger.Log(message?.ToString() ?? "null", LogLevel.Error);
}
public static void LogError(string message)
{
s_logger.Log(message, LogLevel.Error);
}
public static void LogError(Exception ex)
{
s_logger.Log(ex);
}
}

View File

@@ -22,7 +22,7 @@ public readonly struct Result
return new Result(false, message);
}
public void EnsureSuccess()
public void ThrowIfFailed()
{
if (!success)
{
@@ -57,7 +57,7 @@ public readonly struct Result<T>
return new Result<T>(false, default!, message);
}
public void EnsureSuccess()
public void ThrowIfFailed()
{
if (!success)
{

View File

@@ -5,6 +5,7 @@ using System.Reflection;
using System.Text.Json;
namespace Ghost.Editor.Core.AssetHandle;
public static partial class AssetDatabase
{
private static readonly Dictionary<string, Type> _importerTypeLookup = new();

View File

@@ -1,6 +1,4 @@
using Microsoft.UI.Xaml.Data;
using System;
using System.IO;
namespace Ghost.Editor.Utilities.Converters;

View File

@@ -1,5 +1,4 @@
using Microsoft.UI.Xaml.Data;
using System;
namespace Ghost.Editor.Utilities.Converters;

View File

@@ -1,7 +1,6 @@
using Ghost.Editor.Controls.Internal;
using Ghost.Graphics;
using Ghost.Graphics.Contracts;
using Ghost.Graphics.D3D12;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using WinRT;

View File

@@ -1,4 +1,5 @@
namespace Ghost.Entities.Components;
public interface IComponentData
{
}

File diff suppressed because it is too large Load Diff

View File

@@ -26,7 +26,7 @@ public readonly struct QueryItem<T0>
{
entity = _entity;
c0 = new (ref _pool0.GetRef(_entity));
c0 = new(ref _pool0.GetRef(_entity));
}
}
@@ -54,8 +54,8 @@ public readonly struct QueryItem<T0, T1>
{
entity = _entity;
c0 = new (ref _pool0.GetRef(_entity));
c1 = new (ref _pool1.GetRef(_entity));
c0 = new(ref _pool0.GetRef(_entity));
c1 = new(ref _pool1.GetRef(_entity));
}
}
@@ -86,9 +86,9 @@ public readonly struct QueryItem<T0, T1, T2>
{
entity = _entity;
c0 = new (ref _pool0.GetRef(_entity));
c1 = new (ref _pool1.GetRef(_entity));
c2 = new (ref _pool2.GetRef(_entity));
c0 = new(ref _pool0.GetRef(_entity));
c1 = new(ref _pool1.GetRef(_entity));
c2 = new(ref _pool2.GetRef(_entity));
}
}
@@ -122,10 +122,10 @@ public readonly struct QueryItem<T0, T1, T2, T3>
{
entity = _entity;
c0 = new (ref _pool0.GetRef(_entity));
c1 = new (ref _pool1.GetRef(_entity));
c2 = new (ref _pool2.GetRef(_entity));
c3 = new (ref _pool3.GetRef(_entity));
c0 = new(ref _pool0.GetRef(_entity));
c1 = new(ref _pool1.GetRef(_entity));
c2 = new(ref _pool2.GetRef(_entity));
c3 = new(ref _pool3.GetRef(_entity));
}
}
@@ -162,11 +162,11 @@ public readonly struct QueryItem<T0, T1, T2, T3, T4>
{
entity = _entity;
c0 = new (ref _pool0.GetRef(_entity));
c1 = new (ref _pool1.GetRef(_entity));
c2 = new (ref _pool2.GetRef(_entity));
c3 = new (ref _pool3.GetRef(_entity));
c4 = new (ref _pool4.GetRef(_entity));
c0 = new(ref _pool0.GetRef(_entity));
c1 = new(ref _pool1.GetRef(_entity));
c2 = new(ref _pool2.GetRef(_entity));
c3 = new(ref _pool3.GetRef(_entity));
c4 = new(ref _pool4.GetRef(_entity));
}
}
@@ -206,12 +206,12 @@ public readonly struct QueryItem<T0, T1, T2, T3, T4, T5>
{
entity = _entity;
c0 = new (ref _pool0.GetRef(_entity));
c1 = new (ref _pool1.GetRef(_entity));
c2 = new (ref _pool2.GetRef(_entity));
c3 = new (ref _pool3.GetRef(_entity));
c4 = new (ref _pool4.GetRef(_entity));
c5 = new (ref _pool5.GetRef(_entity));
c0 = new(ref _pool0.GetRef(_entity));
c1 = new(ref _pool1.GetRef(_entity));
c2 = new(ref _pool2.GetRef(_entity));
c3 = new(ref _pool3.GetRef(_entity));
c4 = new(ref _pool4.GetRef(_entity));
c5 = new(ref _pool5.GetRef(_entity));
}
}
@@ -254,13 +254,13 @@ public readonly struct QueryItem<T0, T1, T2, T3, T4, T5, T6>
{
entity = _entity;
c0 = new (ref _pool0.GetRef(_entity));
c1 = new (ref _pool1.GetRef(_entity));
c2 = new (ref _pool2.GetRef(_entity));
c3 = new (ref _pool3.GetRef(_entity));
c4 = new (ref _pool4.GetRef(_entity));
c5 = new (ref _pool5.GetRef(_entity));
c6 = new (ref _pool6.GetRef(_entity));
c0 = new(ref _pool0.GetRef(_entity));
c1 = new(ref _pool1.GetRef(_entity));
c2 = new(ref _pool2.GetRef(_entity));
c3 = new(ref _pool3.GetRef(_entity));
c4 = new(ref _pool4.GetRef(_entity));
c5 = new(ref _pool5.GetRef(_entity));
c6 = new(ref _pool6.GetRef(_entity));
}
}
@@ -306,14 +306,14 @@ public readonly struct QueryItem<T0, T1, T2, T3, T4, T5, T6, T7>
{
entity = _entity;
c0 = new (ref _pool0.GetRef(_entity));
c1 = new (ref _pool1.GetRef(_entity));
c2 = new (ref _pool2.GetRef(_entity));
c3 = new (ref _pool3.GetRef(_entity));
c4 = new (ref _pool4.GetRef(_entity));
c5 = new (ref _pool5.GetRef(_entity));
c6 = new (ref _pool6.GetRef(_entity));
c7 = new (ref _pool7.GetRef(_entity));
c0 = new(ref _pool0.GetRef(_entity));
c1 = new(ref _pool1.GetRef(_entity));
c2 = new(ref _pool2.GetRef(_entity));
c3 = new(ref _pool3.GetRef(_entity));
c4 = new(ref _pool4.GetRef(_entity));
c5 = new(ref _pool5.GetRef(_entity));
c6 = new(ref _pool6.GetRef(_entity));
c7 = new(ref _pool7.GetRef(_entity));
}
}

View File

@@ -6,17 +6,17 @@ namespace Ghost.Entities;
public delegate void QueryRefComponent<T0>(Entity entity, ref T0 t0Component)
where T0 : unmanaged, IComponentData;
public delegate void QueryRefComponent<T0, T1>(Entity entity, ref T0 t0Component,ref T1 t1Component)
public delegate void QueryRefComponent<T0, T1>(Entity entity, ref T0 t0Component, ref T1 t1Component)
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData;
public delegate void QueryRefComponent<T0, T1, T2>(Entity entity, ref T0 t0Component,ref T1 t1Component,ref T2 t2Component)
public delegate void QueryRefComponent<T0, T1, T2>(Entity entity, ref T0 t0Component, ref T1 t1Component, ref T2 t2Component)
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData;
public delegate void QueryRefComponent<T0, T1, T2, T3>(Entity entity, ref T0 t0Component,ref T1 t1Component,ref T2 t2Component,ref T3 t3Component)
public delegate void QueryRefComponent<T0, T1, T2, T3>(Entity entity, ref T0 t0Component, ref T1 t1Component, ref T2 t2Component, ref T3 t3Component)
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData;
public delegate void QueryRefComponent<T0, T1, T2, T3, T4>(Entity entity, ref T0 t0Component,ref T1 t1Component,ref T2 t2Component,ref T3 t3Component,ref T4 t4Component)
public delegate void QueryRefComponent<T0, T1, T2, T3, T4>(Entity entity, ref T0 t0Component, ref T1 t1Component, ref T2 t2Component, ref T3 t3Component, ref T4 t4Component)
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData;
public delegate void QueryRefComponent<T0, T1, T2, T3, T4, T5>(Entity entity, ref T0 t0Component,ref T1 t1Component,ref T2 t2Component,ref T3 t3Component,ref T4 t4Component,ref T5 t5Component)
public delegate void QueryRefComponent<T0, T1, T2, T3, T4, T5>(Entity entity, ref T0 t0Component, ref T1 t1Component, ref T2 t2Component, ref T3 t3Component, ref T4 t4Component, ref T5 t5Component)
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData where T5 : unmanaged, IComponentData;
public delegate void QueryRefComponent<T0, T1, T2, T3, T4, T5, T6>(Entity entity, ref T0 t0Component,ref T1 t1Component,ref T2 t2Component,ref T3 t3Component,ref T4 t4Component,ref T5 t5Component,ref T6 t6Component)
public delegate void QueryRefComponent<T0, T1, T2, T3, T4, T5, T6>(Entity entity, ref T0 t0Component, ref T1 t1Component, ref T2 t2Component, ref T3 t3Component, ref T4 t4Component, ref T5 t5Component, ref T6 t6Component)
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData where T5 : unmanaged, IComponentData where T6 : unmanaged, IComponentData;
public delegate void QueryRefComponent<T0, T1, T2, T3, T4, T5, T6, T7>(Entity entity, ref T0 t0Component,ref T1 t1Component,ref T2 t2Component,ref T3 t3Component,ref T4 t4Component,ref T5 t5Component,ref T6 t6Component,ref T7 t7Component)
public delegate void QueryRefComponent<T0, T1, T2, T3, T4, T5, T6, T7>(Entity entity, ref T0 t0Component, ref T1 t1Component, ref T2 t2Component, ref T3 t3Component, ref T4 t4Component, ref T5 t5Component, ref T6 t6Component, ref T7 t7Component)
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData where T5 : unmanaged, IComponentData where T6 : unmanaged, IComponentData where T7 : unmanaged, IComponentData;

View File

@@ -1,9 +1,13 @@
using Ghost.Core.Attributes;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using static TerraFX.Interop.Windows.Windows;
[assembly: InternalsVisibleTo("Ghost.Engine")]
[assembly: InternalsVisibleTo("Ghost.Editor")]
[assembly: InternalsVisibleTo("Ghost.Editor.Core")]
[assembly: InternalsVisibleTo("Ghost.UnitTest")]
[assembly: SupportedOSPlatform("windows10.0.22621.0")]
[assembly: EngineAssembly]

View File

@@ -2,8 +2,9 @@
namespace Ghost.Graphics.Contracts;
public interface IRenderPass : IDisposable
public interface IRenderPass
{
void Initialize(ICommandBuffer cmd);
void Execute(ICommandBuffer cmd);
public void Initialize(ICommandBuffer cmd);
public void Execute(ICommandBuffer cmd);
public void Cleanup(IResourceDatabase resourceDatabase);
}

View File

@@ -1,79 +0,0 @@
using Ghost.Core;
using Ghost.Graphics.Data;
using Win32.Graphics.Direct3D;
using Win32.Graphics.Direct3D12;
namespace Ghost.Graphics.D3D12;
public unsafe class CommandList
{
private readonly ConstPtr<ID3D12GraphicsCommandList10> _commandList;
internal ConstPtr<ID3D12GraphicsCommandList10> NativeCommandList => _commandList;
public CommandList(ID3D12GraphicsCommandList10* commandList)
{
_commandList = commandList;
}
internal void BarrierTransition(GraphicsResource resource, ResourceStates beforeState, ResourceStates afterState)
{
_commandList.Ptr->ResourceBarrierTransition(resource.NativeResource.Ptr, beforeState, afterState);
}
internal void SetGraphicsRootConstantBufferView(uint slot, ulong gpuAddress)
{
_commandList.Ptr->SetGraphicsRootConstantBufferView(slot, gpuAddress);
}
/// <summary>
/// Draws a mesh using fully bindless rendering with SM 6.6 support.
/// This method does not use the Input Assembler stage and instead relies on
/// vertex and index buffer access through bindless descriptors in the shader.
/// </summary>
/// <param name="mesh">The mesh to draw</param>
/// <param name="material">The bindless material to use</param>
public void DrawMesh(MeshClass mesh, MaterialClass material)
{
// Bind the bindless material (sets up root signature, pipeline state, and descriptor heaps)
material.Bind(this);
// For fully bindless rendering, we don't use the Input Assembler stage
// Instead, we use instanced drawing where each "instance" represents a triangle
// The shader will use SV_InstanceID to index into the index buffer and then into the vertex buffer
_commandList.Ptr->IASetPrimitiveTopology(PrimitiveTopology.TriangleList);
// Draw without vertex/index buffers - use instanced drawing
// Each instance represents a triangle (3 vertices)
var triangleCount = mesh.IndexCount / 3;
_commandList.Ptr->DrawInstanced(3, triangleCount, 0, 0);
}
public void SetRenderTarget(RenderTexture? color, RenderTexture? depth)
{
var rtvHandle = color?.RenderTargetView?.CpuHandle;
var rtvHandleValue = rtvHandle ?? default;
var pRtvHandle = rtvHandle.HasValue ? &rtvHandleValue : null;
var dsvHandle = depth?.RenderTargetView?.CpuHandle;
var dsvHandleValue = dsvHandle ?? default;
var pDsvHandle = dsvHandle.HasValue ? &dsvHandleValue : null;
_commandList.Ptr->OMSetRenderTargets(1, pRtvHandle, false, pDsvHandle);
}
public void ClearRenderTarget(RenderTexture renderTarget, Color128 color)
{
renderTarget.ClearColor(this, color);
}
public void ClearDepthStencil(RenderTexture depthStencil, ClearFlags flags, float depth = 1.0f, byte stencil = 0)
{
depthStencil.ClearDepthStencil(this, flags, depth, stencil);
}
public void CopyResource(GraphicsResource dstResource, uint dstOffset, GraphicsResource srcResource, uint srcOffset, uint size)
{
_commandList.Ptr->CopyBufferRegion(dstResource.NativeResource, dstOffset, srcResource.NativeResource, srcOffset, size);
}
}

View File

@@ -1,139 +0,0 @@
using Ghost.Graphics.Data;
using Ghost.Graphics.RHI;
using Win32;
using Win32.Graphics.Direct3D12;
namespace Ghost.Graphics.D3D12;
/// <summary>
/// D3D12 implementation of buffer interface using resource handles
/// </summary>
internal unsafe class D3D12Buffer : IBuffer
{
private readonly BufferHandle _handle;
private readonly D3D12ResourceAllocator? _allocator;
private readonly ComPtr<ID3D12Resource> _externalResource; // For externally managed resources
private ResourceState _currentState;
private void* _mappedPtr;
private bool _disposed;
public BufferUsage Usage
{
get;
}
public MemoryType MemoryType
{
get;
}
public string Name
{
get => field;
set
{
field = value;
NativeResource->SetName(field);
}
} = string.Empty;
public ulong Size
{
get;
}
public BufferHandle Handle => _handle;
public ResourceState CurrentState => _currentState;
public ID3D12Resource* NativeResource => _externalResource.Get() == null ? _allocator!.GetResource(_handle.ResourceHandle) : _externalResource.Get();
/// <summary>
/// Constructor for wrapping existing D3D12 resources
/// </summary>
public D3D12Buffer(ComPtr<ID3D12Resource> resource, ulong size, BufferUsage usage, MemoryType memoryType)
{
_handle = BufferHandle.Invalid;
_allocator = null;
_externalResource = resource.Move();
Size = size;
Usage = usage;
MemoryType = memoryType;
_currentState = ResourceState.Common;
}
/// <summary>
/// Constructor for allocator-managed buffers
/// </summary>
public D3D12Buffer(BufferHandle handle, ref readonly BufferDesc desc, D3D12ResourceAllocator allocator)
{
_handle = handle;
_allocator = allocator;
_externalResource = default;
Size = desc.Size;
Usage = desc.Usage;
MemoryType = desc.MemoryType;
_currentState = ResourceState.Common;
}
public void* Map()
{
if (_mappedPtr != null)
{
return _mappedPtr;
}
if (MemoryType != MemoryType.Upload && MemoryType != MemoryType.Readback)
{
throw new InvalidOperationException("Only upload and readback buffers can be mapped");
}
var range = new Win32.Graphics.Direct3D12.Range { Begin = 0, End = 0 };
fixed (void** ptr = &_mappedPtr)
{
NativeResource->Map(0, &range, ptr);
}
return _mappedPtr;
}
public void Unmap()
{
if (_mappedPtr != null)
{
NativeResource->Unmap(0, null);
_mappedPtr = null;
}
}
public void SetCurrentState(ResourceState state)
{
_currentState = state;
}
public void Dispose()
{
if (_disposed)
{
return;
}
Unmap();
if (_handle.IsValid)
{
// Release resource via allocator
_allocator?.ReleaseResource(_handle.ResourceHandle);
}
else
{
// Release external resource
_externalResource.Dispose();
}
_disposed = true;
}
}

View File

@@ -1,9 +1,13 @@
using Ghost.Core;
using Ghost.Graphics.Data;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Utilities;
using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows;
using Win32;
using Win32.Graphics.Direct3D;
using Win32.Graphics.Direct3D12;
using Win32.Graphics.Dxgi.Common;
using Win32.Numerics;
namespace Ghost.Graphics.D3D12;
@@ -16,12 +20,13 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
private ComPtr<ID3D12CommandAllocator> _allocator;
private ComPtr<ID3D12GraphicsCommandList10> _commandList;
private readonly D3D12PipelineStateController _stateController;
private readonly D3D12PipelineLibrary _stateController;
private readonly D3D12ResourceDatabase _resourceDatabase;
private readonly D3D12ResourceAllocator _resourceAllocator;
private readonly D3D12DescriptorAllocator _descriptorAllocator;
private readonly CommandBufferType _type;
private ushort _commandCount;
private bool _isRecording;
private bool _disposed;
@@ -29,9 +34,11 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
public ID3D12GraphicsCommandList10* NativeCommandList => _commandList.Get();
public bool IsEmpty => _commandCount == 0;
public D3D12CommandBuffer(
D3D12RenderDevice device,
D3D12PipelineStateController stateController,
D3D12PipelineLibrary stateController,
D3D12ResourceDatabase resourceDatabase,
D3D12ResourceAllocator resourceAllocator,
D3D12DescriptorAllocator descriptorAllocator,
@@ -71,6 +78,11 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
}
}
private void IncrementCommandCount()
{
_commandCount++;
}
public void Begin()
{
ThrowIfDisposed();
@@ -82,6 +94,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
_allocator.Get()->Reset();
_commandList.Get()->Reset(_allocator.Get(), null);
_commandCount = 0;
_isRecording = true;
}
@@ -94,22 +107,53 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
_isRecording = false;
}
public void BeginRenderPass(IRenderTarget renderTarget, Color128 clearColor)
public void SetRenderTargets(ReadOnlySpan<Handle<Texture>> renderTargets, Handle<Texture> depthTarget)
{
ThrowIfDisposed();
ThrowIfNotRecording();
IncrementCommandCount();
var rtvHandles = stackalloc CpuDescriptorHandle[renderTargets.Length];
for (var i = 0; i < renderTargets.Length; i++)
{
var handle = renderTargets[i];
if (!handle.IsValid)
{
throw new ArgumentException($"Render target at index {i} is not a valid texture handle");
}
var descriptor = _resourceDatabase.GetResourceInfo(handle.AsResource()).descriptor;
rtvHandles[i] = _descriptorAllocator.GetCpuHandle(descriptor.rtv);
}
var dsvHandle = stackalloc CpuDescriptorHandle[depthTarget.IsValid ? 1 : 0];
if (dsvHandle != null)
{
*dsvHandle = _descriptorAllocator.GetCpuHandle(_resourceDatabase.GetResourceInfo(depthTarget.AsResource()).descriptor.dsv);
}
_commandList.Get()->OMSetRenderTargets((uint)renderTargets.Length, rtvHandles, Bool32.False, dsvHandle);
}
public void BeginRenderPass(Handle<Texture> renderTarget, Handle<Texture> depthTarget, Color128 clearColor)
{
// TODO: Implement render pass begin
throw new NotImplementedException();
}
public void EndRenderPass()
{
// TODO: Implement render pass end
throw new NotImplementedException();
ThrowIfDisposed();
ThrowIfNotRecording();
IncrementCommandCount();
_commandList.Get()->EndRenderPass();
}
public void SetViewport(ViewportDesc viewport)
{
ThrowIfDisposed();
ThrowIfNotRecording();
IncrementCommandCount();
var d3d12Viewport = new Viewport(viewport.width, viewport.height, viewport.x, viewport.y, viewport.minDepth, viewport.maxDepth);
_commandList.Get()->RSSetViewports(1, &d3d12Viewport);
@@ -119,81 +163,125 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
{
ThrowIfDisposed();
ThrowIfNotRecording();
IncrementCommandCount();
var d3d12Rect = new Rect(rect.Left, rect.Top, rect.Right, rect.Bottom);
var d3d12Rect = new Rect((int)rect.left, (int)rect.top, (int)rect.right, (int)rect.bottom);
_commandList.Get()->RSSetScissorRects(1, &d3d12Rect);
}
public void ResourceBarrier(IResource resource, ResourceState before, ResourceState after)
public void ResourceBarrier(Handle<GPUResource> resource, ResourceState before, ResourceState after)
{
ThrowIfDisposed();
ThrowIfNotRecording();
IncrementCommandCount();
if (resource is D3D12Texture d3d12Texture)
{
_commandList.Get()->ResourceBarrierTransition(d3d12Texture.NativeResource,
ConvertResourceState(before), ConvertResourceState(after));
}
else if (resource is D3D12Buffer d3d12Buffer)
{
_commandList.Get()->ResourceBarrierTransition(d3d12Buffer.NativeResource,
ConvertResourceState(before), ConvertResourceState(after));
}
else
{
throw new ArgumentException("Resource must be a D3D12 resource", nameof(resource));
}
var d3d12Resource = _resourceDatabase.GetResource(resource);
_commandList.Get()->ResourceBarrierTransition(d3d12Resource,
before.ToD3D12States(), after.ToD3D12States());
}
public void SetGraphicsRootSignature(IRootSignature rootSignature)
public void SetRootSignature(IRootSignature rootSignature)
{
// TODO: Implement root signature setting
throw new NotImplementedException();
}
public void SetPipelineState(IPipelineStateController pipelineState)
public void SetPipelineState(IShaderPipeline pipelineState)
{
// TODO: Implement pipeline state setting
throw new NotImplementedException();
}
// TODO: Batch draw calls by material to minimize state changes
public void DrawMesh(MeshClass mesh, MaterialClass material)
public void SetVertexBuffer(uint slot, Handle<GraphicsBuffer> buffer, ulong offset = 0)
{
ThrowIfDisposed();
ThrowIfNotRecording();
IncrementCommandCount();
// Bind the bindless material (sets up root signature, pipeline state, and descriptor heaps)
var shaderPipeline = _stateController.GetShaderPipeline(material.Shader);
var pResource = _resourceDatabase.GetResource(buffer.AsResource());
var vbView = new VertexBufferView
{
BufferLocation = pResource->GetGPUVirtualAddress() + offset,
SizeInBytes = (uint)(pResource->GetDesc().Width - offset),
StrideInBytes = _resourceDatabase.GetResourceDescription(buffer.AsResource()).bufferDescription.Stride
};
_commandList.Get()->IASetVertexBuffers(slot, 1, &vbView);
}
public void SetIndexBuffer(Handle<GraphicsBuffer> buffer, IndexType type, ulong offset = 0)
{
ThrowIfDisposed();
ThrowIfNotRecording();
IncrementCommandCount();
var pResource = _resourceDatabase.GetResource(buffer.AsResource());
var ibView = new IndexBufferView
{
BufferLocation = pResource->GetGPUVirtualAddress() + offset,
SizeInBytes = (uint)(pResource->GetDesc().Width - offset),
Format = type == IndexType.UInt16 ? Format.R16Uint : Format.R32Uint
};
_commandList.Get()->IASetIndexBuffer(&ibView);
}
public void Draw(uint vertexCount, uint instanceCount = 1, uint startVertex = 0, uint startInstance = 0)
{
ThrowIfDisposed();
ThrowIfNotRecording();
IncrementCommandCount();
_commandList.Get()->DrawInstanced(vertexCount, instanceCount, startVertex, startInstance);
}
public void DrawIndexed(uint indexCount, uint instanceCount = 1, uint startIndex = 0, int baseVertex = 0, uint startInstance = 0)
{
ThrowIfDisposed();
ThrowIfNotRecording();
IncrementCommandCount();
_commandList.Get()->DrawIndexedInstanced(indexCount, instanceCount, startIndex, baseVertex, startInstance);
}
// TODO: Batch draw calls by material to minimize state changes
public void DrawMesh(Handle<Mesh> mesh, Handle<Material> material)
{
ThrowIfDisposed();
ThrowIfNotRecording();
IncrementCommandCount();
ref var meshRef = ref _resourceDatabase.GetMeshReference(mesh);
ref var materialRef = ref _resourceDatabase.GetMaterialReference(material);
ref var shaderRef = ref _resourceDatabase.GetShaderReference(materialRef.Shader);
var shaderPipeline = _stateController.GetShaderPipeline(materialRef.Shader);
if (shaderPipeline is not D3D12ShaderPipeline d3d12Pipeline)
{
throw new InvalidOperationException("Shader pipeline is not compiled or invalid");
}
// Set root signature and pipeline state
_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];
heaps[0] = _descriptorAllocator.GetBindlessHeap(); // Specialized bindless heap
heaps[0] = _descriptorAllocator.GetCbvSrvUavHeap(); // Specialized bindless heap
heaps[1] = d3d12Pipeline.samplerHeap.Get(); // Sampler heap from shader
_commandList.Get()->SetDescriptorHeaps(2, heaps);
// Bind constant buffers
var rootParamIndex = 0u;
foreach (var cbufferInfo in material.Shader.ConstantBuffers)
foreach (var cbufferInfo in shaderRef.ConstantBuffers)
{
var cache = material.CBufferCaches[(int)cbufferInfo.RegisterSlot];
var resource = _resourceDatabase.GetResource(cache.GpuResource.ResourceHandle);
ref var cache = ref materialRef._cBufferCaches[(int)cbufferInfo.RegisterSlot];
var resource = _resourceDatabase.GetResource(cache.GpuResource.AsResource());
_commandList.Get()->SetGraphicsRootConstantBufferView(rootParamIndex++, resource->GetGPUVirtualAddress());
}
// Bind sampler descriptor table (last root parameter)
var samplerGpuHandle = d3d12Pipeline.samplerHeap.Get()->GetGPUDescriptorHandleForHeapStart();
_commandList.Get()->SetGraphicsRootDescriptorTable(rootParamIndex, samplerGpuHandle);
// For fully bindless rendering, we don't use the Input Assembler stage
// Instead, we use instanced drawing where each "instance" represents a triangle
// The shader will use SV_InstanceID to index into the index buffer and then into the vertex buffer
@@ -201,7 +289,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
// Draw without vertex/index buffers - use instanced drawing
// Each instance represents a triangle (3 vertices)
var triangleCount = mesh.IndexCount / 3;
var triangleCount = (uint)meshRef.indices.Count / 3;
_commandList.Get()->DrawInstanced(3, triangleCount, 0, 0);
}
@@ -209,63 +297,92 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
{
ThrowIfDisposed();
ThrowIfNotRecording();
IncrementCommandCount();
_commandList.Get()->Dispatch(threadGroupCountX, threadGroupCountY, threadGroupCountZ);
}
public void Upload<T>(BufferHandle buffer, ReadOnlySpan<T> data)
public void Upload<T>(Handle<GraphicsBuffer> buffer, ReadOnlySpan<T> data)
where T : unmanaged
{
ThrowIfDisposed();
ThrowIfNotRecording();
IncrementCommandCount();
var sizeInBytes = (uint)(data.Length * sizeof(T));
var uploadHandle = _resourceAllocator.CreateUploadBuffer(sizeInBytes);
var uploadResource = _resourceDatabase.GetResource(uploadHandle.ResourceHandle);
var pUploadResource = _resourceDatabase.GetResource(uploadHandle.AsResource());
void* mappedData;
uploadResource->Map(0, null, &mappedData);
fixed (T* dataPtr = data)
void* pMappedData;
pUploadResource->Map(0, null, &pMappedData);
fixed (T* pData = data)
{
MemoryUtilities.MemCpy(mappedData, dataPtr, sizeInBytes);
MemoryUtilities.MemCpy(pMappedData, pData, sizeInBytes);
}
uploadResource->Unmap(0, null);
pUploadResource->Unmap(0, null);
var resource = _resourceDatabase.GetResource(buffer.ResourceHandle);
var pResource = _resourceDatabase.GetResource(buffer.AsResource());
// Copy from upload buffer to destination
_commandList.Get()->CopyBufferRegion(resource, 0, uploadResource, 0, sizeInBytes);
_commandList.Get()->CopyBufferRegion(pResource, 0, pUploadResource, 0, sizeInBytes);
}
public void Upload(TextureHandle texture, uint firstSubresource, ref SubResourceData subresources, uint numSubresources)
public void Upload(Handle<Texture> texture, params ReadOnlySpan<SubResourceData> subresources)
{
ThrowIfDisposed();
ThrowIfNotRecording();
IncrementCommandCount();
var textureResource = _resourceDatabase.GetResource(texture.ResourceHandle);
var pResource = _resourceDatabase.GetResource(texture.AsResource());
var resourceDesc = textureResource->GetDesc();
var requiredSize = GetRequiredIntermediateSize(textureResource, firstSubresource, numSubresources);
var resourceDesc = pResource->GetDesc();
var requiredSize = GetRequiredIntermediateSize(pResource, 0, (uint)subresources.Length);
var uploadHandle = _resourceAllocator.CreateUploadBuffer(requiredSize);
var uploadResource = _resourceDatabase.GetResource(uploadHandle.ResourceHandle);
var pUploadResource = _resourceDatabase.GetResource(uploadHandle.AsResource());
var d3d12Subresources = new SubresourceData
var d3d12Subresources = stackalloc SubresourceData[subresources.Length];
for (var i = 0; i < subresources.Length; i++)
{
pData = subresources.pData,
RowPitch = subresources.rowPitch,
SlicePitch = subresources.slicePitch
};
d3d12Subresources[i] = new SubresourceData
{
pData = subresources[i].pData,
RowPitch = subresources[i].rowPitch,
SlicePitch = subresources[i].slicePitch
};
}
UpdateSubresources(
(ID3D12GraphicsCommandList*)_commandList.Get(),
textureResource,
uploadResource,
pResource,
pUploadResource,
0,
firstSubresource,
numSubresources,
&d3d12Subresources);
0,
(uint)subresources.Length,
d3d12Subresources);
}
public void CopyBuffer(Handle<GraphicsBuffer> dest, Handle<GraphicsBuffer> src, ulong destOffset = 0, ulong srcOffset = 0, ulong numBytes = 0)
{
ThrowIfDisposed();
ThrowIfNotRecording();
IncrementCommandCount();
var pDestResource = _resourceDatabase.GetResource(dest.AsResource());
var pSrcResource = _resourceDatabase.GetResource(src.AsResource());
if (pSrcResource == null || pDestResource == null)
{
throw new ArgumentException("Source or destination buffer is not valid");
}
if (numBytes == 0)
{
_commandList.Get()->CopyResource(pDestResource, pSrcResource);
}
else
{
_commandList.Get()->CopyBufferRegion(pDestResource, destOffset, pSrcResource, srcOffset, numBytes);
}
}
private static CommandListType ConvertCommandBufferType(CommandBufferType type)
@@ -279,24 +396,6 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
};
}
private static ResourceStates ConvertResourceState(ResourceState state)
{
return state switch
{
ResourceState.Common or ResourceState.Present => ResourceStates.Common,
ResourceState.VertexAndConstantBuffer => ResourceStates.VertexAndConstantBuffer,
ResourceState.IndexBuffer => ResourceStates.IndexBuffer,
ResourceState.RenderTarget => ResourceStates.RenderTarget,
ResourceState.UnorderedAccess => ResourceStates.UnorderedAccess,
ResourceState.DepthWrite => ResourceStates.DepthWrite,
ResourceState.DepthRead => ResourceStates.DepthRead,
ResourceState.PixelShaderResource => ResourceStates.PixelShaderResource,
ResourceState.CopyDest => ResourceStates.CopyDest,
ResourceState.CopySource => ResourceStates.CopySource,
_ => throw new ArgumentException($"Unknown resource state: {state}")
};
}
public void Dispose()
{
if (_isRecording)
@@ -311,6 +410,8 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
_commandList.Dispose();
_allocator.Dispose();
_isRecording = false;
_commandCount = 0;
_disposed = true;
GC.SuppressFinalize(this);

View File

@@ -1,4 +1,6 @@
using Ghost.Graphics.RHI;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Win32;
using Win32.Graphics.Direct3D12;
@@ -49,6 +51,11 @@ internal unsafe class D3D12CommandQueue : ICommandQueue
public void Submit(ICommandBuffer commandBuffer)
{
if (commandBuffer.IsEmpty)
{
return;
}
if (commandBuffer is D3D12CommandBuffer d3d12CommandBuffer)
{
var commandList = d3d12CommandBuffer.NativeCommandList;
@@ -63,21 +70,43 @@ internal unsafe class D3D12CommandQueue : ICommandQueue
public void Submit(params ReadOnlySpan<ICommandBuffer> commandBuffers)
{
var commandLists = stackalloc ID3D12CommandList*[commandBuffers.Length];
Span<int> executableIndices = stackalloc int[commandBuffers.Length];
executableIndices.Fill(-1);
var currentIndex = 0;
for (var i = 0; i < commandBuffers.Length; i++)
{
if (commandBuffers[i] is D3D12CommandBuffer d3d12CommandBuffer)
if (!commandBuffers[i].IsEmpty)
{
commandLists[i] = (ID3D12CommandList*)d3d12CommandBuffer.NativeCommandList;
}
else
{
throw new ArgumentException($"Command buffer at index {i} must be a D3D12CommandBuffer", nameof(commandBuffers));
executableIndices[currentIndex] = i;
currentIndex++;
}
}
_queue.Get()->ExecuteCommandLists((uint)commandBuffers.Length, commandLists);
var ppCommandLists = stackalloc ID3D12CommandList*[commandBuffers.Length];
currentIndex = 0;
while (currentIndex < commandBuffers.Length)
{
var cmdIndex = executableIndices[currentIndex];
if (cmdIndex == -1)
{
break;
}
if (commandBuffers[cmdIndex] is D3D12CommandBuffer d3d12CommandBuffer)
{
ppCommandLists[currentIndex] = (ID3D12CommandList*)d3d12CommandBuffer.NativeCommandList;
}
else
{
throw new ArgumentException("Command buffer must be a D3D12CommandBuffer", nameof(commandBuffers));
}
currentIndex++;
}
_queue.Get()->ExecuteCommandLists((uint)currentIndex, ppCommandLists);
}
public ulong Signal(ulong value)
@@ -104,6 +133,12 @@ internal unsafe class D3D12CommandQueue : ICommandQueue
return _fence.Get()->GetCompletedValue();
}
public void WaitIdle()
{
var fenceValue = Signal(Interlocked.Increment(ref _fenceValue));
WaitForValue(fenceValue);
}
private static CommandListType ConvertCommandQueueType(CommandQueueType type)
{
return type switch

View File

@@ -1,7 +1,5 @@
using Ghost.Core;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.Data;
using Ghost.Graphics.RHI;
using System.Runtime.CompilerServices;
using Win32.Graphics.Direct3D12;
@@ -10,25 +8,21 @@ namespace Ghost.Graphics.D3D12;
/// <summary>
/// D3D12 implementation of descriptor allocator that manages different types of descriptor heaps.
/// </summary>
internal unsafe class D3D12DescriptorAllocator : IDescriptorAllocator, IDisposable
internal unsafe class D3D12DescriptorAllocator : IDisposable
{
private readonly D3D12DescriptorHeap _rtvHeap;
private readonly D3D12DescriptorHeap _dsvHeap;
private readonly D3D12DescriptorHeap _srvHeap;
private readonly D3D12DescriptorHeap _cbvSrvUavHeap;
private readonly D3D12DescriptorHeap _samplerHeap;
private readonly BindlessDescriptorHeap _bindlessHeap;
private bool _disposed;
public unsafe D3D12DescriptorAllocator(D3D12RenderDevice device, uint initialRtvCount = 256, uint initialDsvCount = 256, uint initialSrvCount = 1024, uint initialSamplerCount = 256, uint initialBindlessCount = 10000)
public unsafe D3D12DescriptorAllocator(D3D12RenderDevice device, int initialRtvCount = 256, int initialDsvCount = 256, int initialSrvCount = 200_000, int initialSamplerCount = 256)
{
var pDevice = device.NativeDevice;
_rtvHeap = new D3D12DescriptorHeap("rtv", pDevice, DescriptorHeapType.Rtv, initialRtvCount);
_dsvHeap = new D3D12DescriptorHeap("dsv", pDevice, DescriptorHeapType.Dsv, initialDsvCount);
_srvHeap = new D3D12DescriptorHeap("srv", pDevice, DescriptorHeapType.CbvSrvUav, initialSrvCount);
_samplerHeap = new D3D12DescriptorHeap("sampler", pDevice, DescriptorHeapType.Sampler, initialSamplerCount);
_bindlessHeap = new BindlessDescriptorHeap(pDevice, initialBindlessCount);
_rtvHeap = new D3D12DescriptorHeap("rtv", device, DescriptorHeapType.Rtv, initialRtvCount, initialRtvCount / 2);
_dsvHeap = new D3D12DescriptorHeap("dsv", device, DescriptorHeapType.Dsv, initialDsvCount, initialDsvCount / 2);
_cbvSrvUavHeap = new D3D12DescriptorHeap("srv", device, DescriptorHeapType.CbvSrvUav, initialSrvCount, initialSrvCount /2);
_samplerHeap = new D3D12DescriptorHeap("sampler", device, DescriptorHeapType.Sampler, initialSamplerCount, initialSamplerCount);
}
~D3D12DescriptorAllocator()
@@ -38,54 +32,55 @@ internal unsafe class D3D12DescriptorAllocator : IDescriptorAllocator, IDisposab
#region RTV Methods
public RenderTargetDescriptor AllocateRTV()
public Identifier<RTVDesc> AllocateRTV(bool dynamic = false)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var index = _rtvHeap.AllocateDescriptor();
if (index == uint.MaxValue)
var index = dynamic ? _rtvHeap.AllocateDescriptorDynamic() : _rtvHeap.AllocateDescriptor();
if (index == -1)
{
throw new InvalidOperationException("Failed to allocate RTV descriptor");
}
return new RenderTargetDescriptor { Index = index };
return new Identifier<RTVDesc>(index);
}
public RenderTargetDescriptor[] AllocateRTVs(uint count)
public Identifier<RTVDesc>[] AllocateRTVs(int count, bool dynamic = false)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var baseIndex = _rtvHeap.AllocateDescriptors(count);
if (baseIndex == uint.MaxValue)
var baseIndex = dynamic ? _rtvHeap.AllocateDescriptorsDynamic(count) : _rtvHeap.AllocateDescriptors(count);
if (baseIndex == -1)
{
throw new InvalidOperationException($"Failed to allocate {count} RTV descriptors");
}
var descriptors = new RenderTargetDescriptor[count];
for (uint i = 0; i < count; i++)
var descriptors = new Identifier<RTVDesc>[count];
for (var i = 0; i < count; i++)
{
var index = baseIndex + i;
descriptors[i] = new RenderTargetDescriptor { Index = index };
descriptors[i] = new Identifier<RTVDesc>(index);
}
return descriptors;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CpuDescriptorHandle GetCpuHandle(RenderTargetDescriptor descriptor)
public CpuDescriptorHandle GetCpuHandle(Identifier<RTVDesc> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _rtvHeap.GetCpuHandle(descriptor.Index);
return _rtvHeap.GetCpuHandle(descriptor.value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Release(RenderTargetDescriptor descriptor)
public void Release(Identifier<RTVDesc> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
_rtvHeap.ReleaseDescriptor(descriptor.Index);
_rtvHeap.ReleaseDescriptor(descriptor.value);
}
public void Release(ReadOnlySpan<RenderTargetDescriptor> descriptors)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Release(ReadOnlySpan<Identifier<RTVDesc>> descriptors)
{
ObjectDisposedException.ThrowIf(_disposed, this);
@@ -95,56 +90,80 @@ internal unsafe class D3D12DescriptorAllocator : IDescriptorAllocator, IDisposab
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void MakePersistent(Identifier<RTVDesc> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
_rtvHeap.CopyToPersistentHeap(descriptor.value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void MakePersistent(ReadOnlySpan<Identifier<RTVDesc>> descriptors)
{
ObjectDisposedException.ThrowIf(_disposed, this);
foreach (var descriptor in descriptors)
{
MakePersistent(descriptor);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ResetRTVDynamicHeap()
{
ObjectDisposedException.ThrowIf(_disposed, this);
_rtvHeap.ResetDynamicHeap();
}
#endregion
#region DSV Methods
public DepthStencilDescriptor AllocateDSV()
public Identifier<DSVDesc> AllocateDSV(bool dynamic = false)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var index = _dsvHeap.AllocateDescriptor();
if (index == uint.MaxValue)
var index = dynamic ? _dsvHeap.AllocateDescriptorDynamic() : _dsvHeap.AllocateDescriptor();
if (index == -1)
{
throw new InvalidOperationException("Failed to allocate DSV descriptor");
}
return new DepthStencilDescriptor { Index = index };
return new Identifier<DSVDesc>(index);
}
public DepthStencilDescriptor[] AllocateDSVs(uint count)
public Identifier<DSVDesc>[] AllocateDSVs(int count, bool dynamic = false)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var baseIndex = _dsvHeap.AllocateDescriptors(count);
if (baseIndex == uint.MaxValue)
var baseIndex = dynamic ? _dsvHeap.AllocateDescriptorsDynamic(count) : _dsvHeap.AllocateDescriptors(count);
if (baseIndex == -1)
{
throw new InvalidOperationException($"Failed to allocate {count} DSV descriptors");
}
var descriptors = new DepthStencilDescriptor[count];
for (uint i = 0; i < count; i++)
var descriptors = new Identifier<DSVDesc>[count];
for (var i = 0; i < count; i++)
{
var index = baseIndex + i;
descriptors[i] = new DepthStencilDescriptor { Index = index };
descriptors[i] = new Identifier<DSVDesc>(index);
}
return descriptors;
}
public CpuDescriptorHandle GetCpuHandle(DepthStencilDescriptor descriptor)
public CpuDescriptorHandle GetCpuHandle(Identifier<DSVDesc> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _dsvHeap.GetCpuHandle(descriptor.Index);
return _dsvHeap.GetCpuHandle(descriptor.value);
}
public void Release(DepthStencilDescriptor descriptor)
public void Release(Identifier<DSVDesc> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
_dsvHeap.ReleaseDescriptor(descriptor.Index);
_dsvHeap.ReleaseDescriptor(descriptor.value);
}
public void Release(ReadOnlySpan<DepthStencilDescriptor> descriptors)
public void Release(ReadOnlySpan<Identifier<DSVDesc>> descriptors)
{
ObjectDisposedException.ThrowIf(_disposed, this);
@@ -154,64 +173,94 @@ internal unsafe class D3D12DescriptorAllocator : IDescriptorAllocator, IDisposab
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void MakePersistent(Identifier<DSVDesc> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
_dsvHeap.CopyToPersistentHeap(descriptor.value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void MakePersistent(ReadOnlySpan<Identifier<DSVDesc>> descriptors)
{
ObjectDisposedException.ThrowIf(_disposed, this);
foreach (var descriptor in descriptors)
{
MakePersistent(descriptor);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ResetDSVDynamicHeap()
{
ObjectDisposedException.ThrowIf(_disposed, this);
_dsvHeap.ResetDynamicHeap();
}
#endregion
#region SRV Methods
#region CBV_SRV_UAV Methods
public ShaderResourceDescriptor AllocateSRV()
public Identifier<CbvSrvUavDesc> AllocateCbvSrvUav(bool dynamic = false)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var index = _srvHeap.AllocateDescriptor();
if (index == uint.MaxValue)
var index = dynamic ? _cbvSrvUavHeap.AllocateDescriptorDynamic() : _cbvSrvUavHeap.AllocateDescriptor();
if (index == -1)
{
throw new InvalidOperationException("Failed to allocate SRV descriptor");
throw new InvalidOperationException("Failed to allocate CBV/SRV/UAV descriptor");
}
_srvHeap.CopyToShaderVisibleHeap(index);
return new ShaderResourceDescriptor { Index = index };
_cbvSrvUavHeap.CopyToShaderVisibleHeap(index);
return new Identifier<CbvSrvUavDesc>(index);
}
public ShaderResourceDescriptor[] AllocateSRVs(uint count)
public Identifier<CbvSrvUavDesc>[] AllocateSRVs(int count, bool dynamic = false)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var baseIndex = _srvHeap.AllocateDescriptors(count);
if (baseIndex == uint.MaxValue)
var baseIndex = dynamic ? _cbvSrvUavHeap.AllocateDescriptorsDynamic(count) : _cbvSrvUavHeap.AllocateDescriptors(count);
if (baseIndex == -1)
{
throw new InvalidOperationException($"Failed to allocate {count} SRV descriptors");
throw new InvalidOperationException($"Failed to allocate {count} CBV/SRV/UAV descriptors");
}
var descriptors = new ShaderResourceDescriptor[count];
for (uint i = 0; i < count; i++)
var descriptors = new Identifier<CbvSrvUavDesc>[count];
for (var i = 0; i < count; i++)
{
var index = baseIndex + i;
descriptors[i] = new ShaderResourceDescriptor { Index = index };
descriptors[i] = new Identifier<CbvSrvUavDesc>(index);
}
_srvHeap.CopyToShaderVisibleHeap(baseIndex, count);
_cbvSrvUavHeap.CopyToShaderVisibleHeap(baseIndex, count);
return descriptors;
}
public CpuDescriptorHandle GetCpuHandle(ShaderResourceDescriptor descriptor)
public CpuDescriptorHandle GetCpuHandle(Identifier<CbvSrvUavDesc> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _srvHeap.GetCpuHandle(descriptor.Index);
return _cbvSrvUavHeap.GetCpuHandle(descriptor.value);
}
public GpuDescriptorHandle GetGpuHandle(ShaderResourceDescriptor descriptor)
public CpuDescriptorHandle GetCpuHandleShaderVisible(Identifier<CbvSrvUavDesc> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _srvHeap.GetGpuHandle(descriptor.Index);
return _cbvSrvUavHeap.GetCpuHandleShaderVisible(descriptor.value);
}
public void Release(ShaderResourceDescriptor descriptor)
public GpuDescriptorHandle GetGpuHandle(Identifier<CbvSrvUavDesc> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
_srvHeap.ReleaseDescriptor(descriptor.Index);
return _cbvSrvUavHeap.GetGpuHandle(descriptor.value);
}
public void Release(ReadOnlySpan<ShaderResourceDescriptor> descriptors)
public void Release(Identifier<CbvSrvUavDesc> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
_cbvSrvUavHeap.ReleaseDescriptor(descriptor.value);
}
public void Release(ReadOnlySpan<Identifier<CbvSrvUavDesc>> descriptors)
{
ObjectDisposedException.ThrowIf(_disposed, this);
@@ -221,64 +270,94 @@ internal unsafe class D3D12DescriptorAllocator : IDescriptorAllocator, IDisposab
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void MakePersistent(Identifier<CbvSrvUavDesc> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
_cbvSrvUavHeap.CopyToPersistentHeap(descriptor.value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void MakePersistent(ReadOnlySpan<Identifier<CbvSrvUavDesc>> descriptors)
{
ObjectDisposedException.ThrowIf(_disposed, this);
foreach (var descriptor in descriptors)
{
MakePersistent(descriptor);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ResetCbvSrvUavDynamicHeap()
{
ObjectDisposedException.ThrowIf(_disposed, this);
_cbvSrvUavHeap.ResetDynamicHeap();
}
#endregion
#region Sampler Methods
public SamplerDescriptor AllocateSampler()
public Identifier<SamplerDesc> AllocateSampler()
{
ObjectDisposedException.ThrowIf(_disposed, this);
var index = _samplerHeap.AllocateDescriptor();
if (index == uint.MaxValue)
if (index == -1)
{
throw new InvalidOperationException("Failed to allocate Sampler descriptor");
}
_samplerHeap.CopyToShaderVisibleHeap(index);
return new SamplerDescriptor { Index = index };
return new Identifier<SamplerDesc>(index);
}
public SamplerDescriptor[] AllocateSamplers(uint count)
public Identifier<SamplerDesc>[] AllocateSamplers(int count)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var baseIndex = _samplerHeap.AllocateDescriptors(count);
if (baseIndex == uint.MaxValue)
if (baseIndex == -1)
{
throw new InvalidOperationException($"Failed to allocate {count} Sampler descriptors");
}
var descriptors = new SamplerDescriptor[count];
for (uint i = 0; i < count; i++)
var descriptors = new Identifier<SamplerDesc>[count];
for (var i = 0; i < count; i++)
{
var index = baseIndex + i;
descriptors[i] = new SamplerDescriptor { Index = index };
descriptors[i] = new Identifier<SamplerDesc>(index);
}
_samplerHeap.CopyToShaderVisibleHeap(baseIndex, count);
return descriptors;
}
public CpuDescriptorHandle GetCpuHandle(SamplerDescriptor descriptor)
public CpuDescriptorHandle GetCpuHandle(Identifier<SamplerDesc> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _samplerHeap.GetCpuHandle(descriptor.Index);
return _samplerHeap.GetCpuHandle(descriptor.value);
}
public GpuDescriptorHandle GetGpuHandle(SamplerDescriptor descriptor)
public CpuDescriptorHandle GetCpuHandleShaderVisible(Identifier<SamplerDesc> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _samplerHeap.GetGpuHandle(descriptor.Index);
return _samplerHeap.GetCpuHandleShaderVisible(descriptor.value);
}
public void Release(SamplerDescriptor descriptor)
public GpuDescriptorHandle GetGpuHandle(Identifier<SamplerDesc> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
_samplerHeap.ReleaseDescriptor(descriptor.Index);
return _samplerHeap.GetGpuHandle(descriptor.value);
}
public void Release(ReadOnlySpan<SamplerDescriptor> descriptors)
public void Release(Identifier<SamplerDesc> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
_samplerHeap.ReleaseDescriptor(descriptor.value);
}
public void Release(ReadOnlySpan<Identifier<SamplerDesc>> descriptors)
{
ObjectDisposedException.ThrowIf(_disposed, this);
@@ -290,84 +369,19 @@ internal unsafe class D3D12DescriptorAllocator : IDescriptorAllocator, IDisposab
#endregion
#region Bindless Methods
/// <summary>
/// Allocates a bindless descriptor for SM 6.6 rendering.
/// The returned descriptor maintains a 1:1 relationship between allocation index and shader index.
/// </summary>
public BindlessDescriptor AllocateBindless()
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Release(D3D12ResourceDescriptor descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var index = _bindlessHeap.AllocateDescriptor();
if (index == uint.MaxValue)
{
throw new InvalidOperationException("Failed to allocate bindless descriptor");
}
return new BindlessDescriptor { Index = index };
Release(descriptor.rtv);
Release(descriptor.dsv);
Release(descriptor.srv);
Release(descriptor.cbv);
Release(descriptor.uav);
Release(descriptor.sampler);
}
/// <summary>
/// Allocates multiple bindless descriptors for SM 6.6 rendering.
/// </summary>
public BindlessDescriptor[] AllocateBindless(uint count)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var baseIndex = _bindlessHeap.AllocateDescriptors(count);
if (baseIndex == uint.MaxValue)
{
throw new InvalidOperationException($"Failed to allocate {count} bindless descriptors");
}
var descriptors = new BindlessDescriptor[count];
for (uint i = 0; i < count; i++)
{
var index = baseIndex + i;
descriptors[i] = new BindlessDescriptor { Index = index };
}
return descriptors;
}
public CpuDescriptorHandle GetCpuHandle(BindlessDescriptor descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _bindlessHeap.GetCpuHandle(descriptor.Index);
}
public GpuDescriptorHandle GetGpuHandle(BindlessDescriptor descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _bindlessHeap.GetGpuHandle(descriptor.Index);
}
/// <summary>
/// Releases a bindless descriptor.
/// </summary>
public void Release(BindlessDescriptor descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
_bindlessHeap.ReleaseDescriptor(descriptor.Index);
}
/// <summary>
/// Releases multiple bindless descriptors.
/// </summary>
public void Release(ReadOnlySpan<BindlessDescriptor> descriptors)
{
ObjectDisposedException.ThrowIf(_disposed, this);
foreach (var descriptor in descriptors)
{
Release(descriptor);
}
}
#endregion
#region Utility Methods
/// <summary>
@@ -381,34 +395,30 @@ internal unsafe class D3D12DescriptorAllocator : IDescriptorAllocator, IDisposab
public ID3D12DescriptorHeap* GetDSVHeap() => _dsvHeap.Heap;
/// <summary>
/// Gets the SRV heap for binding to the command list.
/// Gets the CBV/SRV/UAV heap for binding to the command list.
/// </summary>
public ID3D12DescriptorHeap* GetSRVHeap() => _srvHeap.ShaderVisibleHeap;
public ID3D12DescriptorHeap* GetCbvSrvUavHeap() => _cbvSrvUavHeap.ShaderVisibleHeap;
/// <summary>
/// Gets the sampler heap for binding to the command list.
/// </summary>
public ID3D12DescriptorHeap* GetSamplerHeap() => _samplerHeap.ShaderVisibleHeap;
/// <summary>
/// Gets the bindless heap for SM 6.6 bindless rendering.
/// </summary>
public ID3D12DescriptorHeap* GetBindlessHeap() => _bindlessHeap.BindlessHeap;
/// <summary>
/// Gets the shader visible heaps that need to be bound to the command list.
/// </summary>
public ID3D12DescriptorHeap*[] GetShaderVisibleHeaps()
/// <param name="ppHeap">An array of two ID3D12DescriptorHeap pointers to receive the CBV/SRV/UAV and Sampler heaps.</param>
public void GetShaderVisibleHeaps(ID3D12DescriptorHeap** ppHeap)
{
return [_srvHeap.ShaderVisibleHeap, _samplerHeap.ShaderVisibleHeap];
}
ObjectDisposedException.ThrowIf(_disposed, this);
/// <summary>
/// Gets the shader visible heaps including bindless heap for SM 6.6 rendering.
/// </summary>
public ConstPtr<ID3D12DescriptorHeap>[] GetShaderVisibleHeapsWithBindless()
{
return [_bindlessHeap.BindlessHeap, _srvHeap.ShaderVisibleHeap, _samplerHeap.ShaderVisibleHeap];
if (ppHeap == null)
{
throw new ArgumentNullException(nameof(ppHeap));
}
ppHeap[0] = _cbvSrvUavHeap.ShaderVisibleHeap;
ppHeap[1] = _samplerHeap.ShaderVisibleHeap;
}
#endregion
@@ -422,9 +432,8 @@ internal unsafe class D3D12DescriptorAllocator : IDescriptorAllocator, IDisposab
_rtvHeap.Dispose();
_dsvHeap.Dispose();
_srvHeap.Dispose();
_cbvSrvUavHeap.Dispose();
_samplerHeap.Dispose();
_bindlessHeap.Dispose();
_disposed = true;

View File

@@ -9,7 +9,7 @@ internal unsafe class D3D12GraphicsEngine : IGraphicsEngine
#endif
private readonly D3D12RenderDevice _device;
private readonly D3D12PipelineStateController _stateController;
private readonly D3D12PipelineLibrary _stateController;
private readonly D3D12DescriptorAllocator _descriptorAllocator;
private readonly D3D12ResourceDatabase _resourceDatabase;
@@ -22,7 +22,7 @@ internal unsafe class D3D12GraphicsEngine : IGraphicsEngine
public IResourceDatabase ResourceDatabase => _resourceDatabase;
public IResourceAllocator ResourceAllocator => _resourceAllocator;
public IPipelineStateController PipelineStateController => _stateController;
public IPipelineLibrary PipelineStateController => _stateController;
public D3D12GraphicsEngine(RenderSystem renderSystem)
{
@@ -52,7 +52,7 @@ internal unsafe class D3D12GraphicsEngine : IGraphicsEngine
public IRenderer CreateRenderer()
{
return new D3D12Renderer(this, _resourceAllocator);
return new D3D12Renderer(this, _resourceAllocator, _resourceDatabase);
}
public ICommandBuffer CreateCommandBuffer(CommandBufferType type = CommandBufferType.Graphics)
@@ -68,7 +68,7 @@ internal unsafe class D3D12GraphicsEngine : IGraphicsEngine
public ISwapChain CreateSwapChain(SwapChainDesc desc)
{
return new D3D12SwapChain(_device.DXGIFactory, ((D3D12CommandQueue)_device.ComputeQueue).NativeQueue, desc);
return new D3D12SwapChain(_resourceDatabase, _device.DXGIFactory, ((D3D12CommandQueue)_device.ComputeQueue).NativeQueue, desc);
}
public void BeginFrame()

View File

@@ -0,0 +1,324 @@
#undef USE_TRANDITIONAL_BINDLESS
using Ghost.Core;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.Data;
using Ghost.Graphics.RHI;
using Ghost.Graphics.Utilities;
using System.Runtime.InteropServices;
using Win32;
using Win32.Graphics.Direct3D;
using Win32.Graphics.Direct3D12;
using Win32.Graphics.Dxgi.Common;
namespace Ghost.Graphics.D3D12;
// TODO: Fixed root signature and use bindless samplers and textures.
// This can dramatically reduce the number of root parameters needed and improve performance.
internal class D3D12ShaderPipeline : IShaderPipeline, IDisposable
{
public ComPtr<ID3D12PipelineState> pipelineState;
public D3D12ShaderCompiler.CompileResult vsResult;
public D3D12ShaderCompiler.CompileResult psResult;
public D3D12ShaderCompiler.CompileResult csResult;
public PipelineType Type
{
get; init;
}
public void Dispose()
{
pipelineState.Dispose();
vsResult.Dispose();
psResult.Dispose();
csResult.Dispose();
}
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal unsafe struct D3D12GraphicsPipelineStream
{
public PipelineStateSubObjectType rootSignatureType;
public ID3D12RootSignature* pRootSignature;
public PipelineStateSubObjectType vsType;
public ShaderBytecode vs;
public PipelineStateSubObjectType psType;
public ShaderBytecode ps;
public PipelineStateSubObjectType rasterizerType;
public RasterizerDescription rasterizer;
public PipelineStateSubObjectType blendType;
public BlendDescription blend;
public PipelineStateSubObjectType depthStencilType;
public DepthStencilDescription depthStencil;
public PipelineStateSubObjectType topologyType;
public PrimitiveTopologyType primitiveTopology;
public PipelineStateSubObjectType rtvFormatType;
public RtFormatArray rtvFormats;
public PipelineStateSubObjectType dsvFormatType;
public Format dsvFormat;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal unsafe struct ComputePipelineStream
{
public PipelineStateSubObjectType rootSignatureType;
public ID3D12RootSignature* pRootSignature;
public PipelineStateSubObjectType csType;
public ShaderBytecode cs;
}
internal unsafe class D3D12PipelineLibrary : IPipelineLibrary, IDisposable
{
private readonly D3D12RenderDevice _device;
private readonly D3D12ResourceDatabase _resourceDatabase;
private ComPtr<ID3D12PipelineLibrary1> _library;
private ComPtr<ID3D12RootSignature> _defaultRootSignature;
private readonly Dictionary<Identifier<Shader>, D3D12ShaderPipeline> _shaderPipelines;
public D3D12PipelineLibrary(D3D12RenderDevice device, D3D12ResourceDatabase resourceDatabase, string? cachePath)
{
_device = device;
_resourceDatabase = resourceDatabase;
_shaderPipelines = new();
InitializePipelineLibrary(cachePath);
CreateDefaultRootSignature();
}
private void InitializePipelineLibrary(string? cachePath)
{
if (!File.Exists(cachePath))
{
_device.NativeDevice->CreatePipelineLibrary(null, 0, __uuidof<ID3D12PipelineLibrary1>(), _library.GetVoidAddressOf()).ThrowIfFailed();
}
var fileBytes = File.ReadAllBytes(cachePath!);
fixed (byte* pFileBytes = fileBytes)
{
_device.NativeDevice->CreatePipelineLibrary(pFileBytes, (nuint)fileBytes.Length, __uuidof<ID3D12PipelineLibrary1>(), _library.GetVoidAddressOf()).ThrowIfFailed();
}
}
private void CreateDefaultRootSignature()
{
const int rootParamCount =
#if USE_TRANDITIONAL_BINDLESS
6
#else
4
#endif
;
_defaultRootSignature = default;
// The layout of the root signature is:
// - Global buffer (b0)
// - Per-view buffer (b1)
// - Per-object buffer (b2)
// - Per-instance buffer (b3)
// - Descriptor table for bindless textures (t0)
// - Descriptor table for bindless samplers (s0)
// NOTE: Since we are targeting SM 6.6, we can use ResourceDescriptorHeap and SamplerDescriptorHeap directly without needing to set up descriptor tables.
var rootParameters = stackalloc RootParameter1[rootParamCount];
rootParameters[0] = new RootParameter1
{
ParameterType = RootParameterType.Cbv,
ShaderVisibility = ShaderVisibility.All,
Descriptor = new RootDescriptor1(0, 0), // b0
};
rootParameters[1] = new RootParameter1
{
ParameterType = RootParameterType.Cbv,
ShaderVisibility = ShaderVisibility.All,
Descriptor = new RootDescriptor1(1, 0), // b1
};
rootParameters[2] = new RootParameter1
{
ParameterType = RootParameterType.Cbv,
ShaderVisibility = ShaderVisibility.All,
Descriptor = new RootDescriptor1(2, 0), // b2
};
rootParameters[3] = new RootParameter1
{
ParameterType = RootParameterType.Cbv,
ShaderVisibility = ShaderVisibility.All,
Descriptor = new RootDescriptor1(3, 0), // b3
};
#if USE_TRANDITIONAL_BINDLESS
// Descriptor table for bindless textures
var srvRange = new DescriptorRange1(
DescriptorRangeType.Srv,
~0u,
0,
0,
DescriptorRangeFlags.DataVolatile);
rootParameters[4] = new RootParameter1
{
ParameterType = RootParameterType.DescriptorTable,
ShaderVisibility = ShaderVisibility.All,
DescriptorTable = new RootDescriptorTable1(1, &srvRange)
};
// Descriptor table for bindless samplers
var sampRange = new DescriptorRange1(
DescriptorRangeType.Sampler,
~0u,
0,
0,
DescriptorRangeFlags.DataVolatile);
rootParameters[5] = new RootParameter1
{
ParameterType = RootParameterType.DescriptorTable,
ShaderVisibility = ShaderVisibility.All,
DescriptorTable = new RootDescriptorTable1(1, &sampRange)
};
#endif
var rootSignatureDesc = new RootSignatureDescription1
{
NumParameters = rootParamCount,
pParameters = rootParameters,
NumStaticSamplers = 0,
pStaticSamplers = null,
Flags = RootSignatureFlags.AllowInputAssemblerInputLayout
#if !USE_TRANDITIONAL_BINDLESS
| RootSignatureFlags.CbvSrvUavHeapDirectlyIndexed
| RootSignatureFlags.SamplerHeapDirectlyIndexed
#endif
};
var versionedDesc = new VersionedRootSignatureDescription
{
Version = RootSignatureVersion.V1_1,
Desc_1_1 = rootSignatureDesc
};
using ComPtr<ID3DBlob> signature = default;
using ComPtr<ID3DBlob> error = default;
var serializeResult = D3D12SerializeVersionedRootSignature(&versionedDesc, signature.GetAddressOf(), error.GetAddressOf());
if (serializeResult.Failure)
{
var errorMsg = error.Get() != null ? Marshal.PtrToStringAnsi((nint)error.Get()->GetBufferPointer()) : "Unknown error";
throw new InvalidOperationException($"Failed to serialize default root signature: {errorMsg}");
}
_device.NativeDevice->CreateRootSignature(0, signature.Get()->GetBufferPointer(), signature.Get()->GetBufferSize(),
__uuidof<ID3D12RootSignature>(), _defaultRootSignature.GetVoidAddressOf()).ThrowIfFailed();
}
public void StorePipeline(string psoIdentifier, ID3D12PipelineState* pso)
{
_library.Get()->StorePipeline(psoIdentifier.AsSpan().GetPointer(), pso);
}
public void* LoadGraphicsPipeline(string psoIdentifier)
{
if (_library.Get()->LoadGraphicsPipeline(psoIdentifier.AsSpan().GetPointer(), __uuidof<ID3D12PipelineState>(), out var pso).Failure)
{
return null;
}
return pso;
}
public void CompileShader(Identifier<Shader> id, string shaderPath)
{
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);
ref var shader = ref _resourceDatabase.GetShaderReference(id);
D3D12ShaderCompiler.PerformDXCReflection(ref shader, vsResult.reflection.Get());
D3D12ShaderCompiler.PerformDXCReflection(ref shader, psResult.reflection.Get());
var shaderPipeline = new D3D12ShaderPipeline
{
Type = PipelineType.Graphics,
vsResult = vsResult,
psResult = psResult
};
_shaderPipelines[id] = shaderPipeline;
}
// Create PSO from SDL (Shader Definition Language) file
private void CreatePipelineStateObject(D3D12ShaderPipeline shaderPipeline)
{
var psoDesc = new GraphicsPipelineStateDescription
{
pRootSignature = _defaultRootSignature.Get(),
VS = new ShaderBytecode(shaderPipeline.vsResult.bytecode.GetUnsafePtr(), (nuint)shaderPipeline.vsResult.bytecode.Count),
PS = new ShaderBytecode(shaderPipeline.psResult.bytecode.GetUnsafePtr(), (nuint)shaderPipeline.vsResult.bytecode.Count),
InputLayout = D3D12PipelineResource.InputLayoutDescription,
RasterizerState = RasterizerDescription.CullNone,
BlendState = BlendDescription.Opaque,
DepthStencilState = DepthStencilDescription.Default,
SampleMask = uint.MaxValue,
PrimitiveTopologyType = PrimitiveTopologyType.Triangle,
NumRenderTargets = 1,
SampleDesc = new SampleDescription(1, 0),
DSVFormat = Format.Unknown,
};
psoDesc.RTVFormats[0] = D3D12PipelineResource.SWAP_CHAIN_BACK_BUFFER_FORMAT;
_device.NativeDevice->CreateGraphicsPipelineState(&psoDesc, __uuidof<ID3D12PipelineState>(), shaderPipeline.pipelineState.GetVoidAddressOf());
}
// TODO: Pipeline variants (keywords)
// TODO: Disk caching
// TODO: Async compilation
public void PreCookPipelineState()
{
foreach (var kvp in _shaderPipelines)
{
ref var shader = ref _resourceDatabase.GetShaderReference(kvp.Key);
CreatePipelineStateObject(kvp.Value);
kvp.Value.vsResult.Dispose();
kvp.Value.psResult.Dispose();
kvp.Value.csResult.Dispose();
}
}
public IShaderPipeline GetShaderPipeline(Identifier<Shader> id)
{
if (_shaderPipelines.TryGetValue(id, out var pipeline))
{
return pipeline;
}
throw new KeyNotFoundException($"Shader pipeline not found for shader ID: {id}");
}
public void Dispose()
{
foreach (var kvp in _shaderPipelines)
{
kvp.Value.Dispose();
}
}
}

View File

@@ -1,257 +0,0 @@
using Ghost.Core;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.Data;
using Ghost.Graphics.RHI;
using Win32;
using Win32.Graphics.Direct3D;
using Win32.Graphics.Direct3D12;
using Win32.Graphics.Dxgi.Common;
namespace Ghost.Graphics.D3D12;
internal class D3D12ShaderPipeline : IShaderPipeline, IDisposable
{
public ComPtr<ID3D12RootSignature> rootSignature;
public ComPtr<ID3D12PipelineState> pipelineState;
public ComPtr<ID3D12DescriptorHeap> samplerHeap;
public D3D12ShaderCompiler.CompileResult vsResult;
public D3D12ShaderCompiler.CompileResult psResult;
public D3D12ShaderCompiler.CompileResult csResult;
public PipelineType Type
{
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<Identifier<Shader>, D3D12ShaderPipeline> _shaderPipelines;
public D3D12PipelineStateController(D3D12RenderDevice device, D3D12ResourceDatabase resourceDatabase)
{
_device = device.NativeDevice;
_resourceDatabase = resourceDatabase;
_shaderPipelines = new();
}
public void CompileShader(Identifier<Shader> id, string shaderPath)
{
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);
var shader = _resourceDatabase.GetShader(id);
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)
{
// Calculate total root parameters: CBVs + Regular texture descriptor table + Sampler table
var totalRootParams = shader.ConstantBuffers.Count + (shader.RegularTextures.Count > 0 ? 1 : 0) + 1; // +1 for sampler
var rootParameters = new RootParameter1[totalRootParams];
var parameterIndex = 0;
// Add CBV root parameters
foreach (var cbufferInfo in shader.ConstantBuffers)
{
rootParameters[parameterIndex++] = new RootParameter1
{
ParameterType = RootParameterType.Cbv,
ShaderVisibility = ShaderVisibility.All,
Descriptor = new RootDescriptor1(cbufferInfo.RegisterSlot, 0),
};
}
// Add regular texture descriptor table if we have regular textures
if (shader.RegularTextures.Count > 0)
{
var textureRanges = new DescriptorRange1[1];
textureRanges[0] = new DescriptorRange1
{
RangeType = DescriptorRangeType.Srv,
NumDescriptors = (uint)shader.RegularTextures.Count,
BaseShaderRegister = 0, // Start from t0
RegisterSpace = 0,
Flags = DescriptorRangeFlags.None,
OffsetInDescriptorsFromTableStart = 0
};
fixed (DescriptorRange1* textureRangesPtr = textureRanges)
{
rootParameters[parameterIndex++] = new RootParameter1
{
ParameterType = RootParameterType.DescriptorTable,
ShaderVisibility = ShaderVisibility.All,
DescriptorTable = new RootDescriptorTable1(1, textureRangesPtr)
};
}
}
// Sampler descriptor table (still needed for samplers)
var samplerRanges = new DescriptorRange1[1];
samplerRanges[0] = new DescriptorRange1
{
RangeType = DescriptorRangeType.Sampler,
NumDescriptors = 1,
BaseShaderRegister = 0, // s0
RegisterSpace = 0,
Flags = DescriptorRangeFlags.None,
OffsetInDescriptorsFromTableStart = 0
};
fixed (DescriptorRange1* samplerRangesPtr = samplerRanges)
{
rootParameters[parameterIndex] = new RootParameter1
{
ParameterType = RootParameterType.DescriptorTable,
ShaderVisibility = ShaderVisibility.All,
DescriptorTable = new RootDescriptorTable1(1, samplerRangesPtr)
};
}
// Create root signature with the modern flag
fixed (RootParameter1* rootParamsPtr = rootParameters)
{
var rootSignatureDesc = new RootSignatureDescription1
{
NumParameters = (uint)rootParameters.Length,
pParameters = rootParamsPtr,
NumStaticSamplers = 0,
pStaticSamplers = null,
// Key difference: Use the modern flag for direct heap indexing
Flags = RootSignatureFlags.AllowInputAssemblerInputLayout |
RootSignatureFlags.CbvSrvUavHeapDirectlyIndexed
};
var versionedDesc = new VersionedRootSignatureDescription
{
Version = RootSignatureVersion.V1_1,
Desc_1_1 = rootSignatureDesc
};
using ComPtr<ID3DBlob> signature = default;
using ComPtr<ID3DBlob> error = default;
D3D12SerializeVersionedRootSignature(&versionedDesc, signature.GetAddressOf(), error.GetAddressOf());
_device->CreateRootSignature(0, signature.Get()->GetBufferPointer(), signature.Get()->GetBufferSize(), __uuidof<ID3D12RootSignature>(), shaderPipeline.rootSignature.GetVoidAddressOf());
}
}
private void CreatePipelineStateObject(D3D12ShaderPipeline shaderPipeline)
{
var psoDesc = new GraphicsPipelineStateDescription
{
pRootSignature = shaderPipeline.rootSignature.Get(),
VS = new ShaderBytecode(shaderPipeline.vsResult.bytecode.GetUnsafePtr(), (nuint)shaderPipeline.vsResult.bytecode.Count),
PS = new ShaderBytecode(shaderPipeline.psResult.bytecode.GetUnsafePtr(), (nuint)shaderPipeline.vsResult.bytecode.Count),
InputLayout = D3D12PipelineResource.InputLayoutDescription,
RasterizerState = RasterizerDescription.CullNone,
BlendState = BlendDescription.Opaque,
DepthStencilState = DepthStencilDescription.Default,
SampleMask = uint.MaxValue,
PrimitiveTopologyType = PrimitiveTopologyType.Triangle,
NumRenderTargets = 1,
SampleDesc = new SampleDescription(1, 0),
DSVFormat = Format.Unknown,
};
psoDesc.RTVFormats[0] = D3D12PipelineResource.SWAP_CHAIN_BACK_BUFFER_FORMAT;
_device->CreateGraphicsPipelineState(&psoDesc, __uuidof<ID3D12PipelineState>(), shaderPipeline.pipelineState.GetVoidAddressOf());
}
private void CreateSamplerHeap(D3D12ShaderPipeline shaderPipeline)
{
// Create sampler heap
var samplerHeapDesc = new DescriptorHeapDescription
{
Type = DescriptorHeapType.Sampler,
NumDescriptors = 1,
Flags = DescriptorHeapFlags.ShaderVisible
};
_device->CreateDescriptorHeap(&samplerHeapDesc, __uuidof<ID3D12DescriptorHeap>(), shaderPipeline.samplerHeap.GetVoidAddressOf());
// Create default sampler
var samplerDesc = new SamplerDescription
{
Filter = Filter.MinMagMipLinear,
AddressU = TextureAddressMode.Wrap,
AddressV = TextureAddressMode.Wrap,
AddressW = TextureAddressMode.Wrap,
MipLODBias = 0,
MaxAnisotropy = 1,
MinLOD = 0,
MaxLOD = float.MaxValue
};
// Set border color manually
samplerDesc.BorderColor[0] = 0;
samplerDesc.BorderColor[1] = 0;
samplerDesc.BorderColor[2] = 0;
samplerDesc.BorderColor[3] = 0;
var samplerHandle = shaderPipeline.samplerHeap.Get()->GetCPUDescriptorHandleForHeapStart();
_device->CreateSampler(&samplerDesc, samplerHandle);
}
// TODO: Pipeline variants (keywords)
// TODO: Disk caching
// TODO: Async compilation
public void PreCookPipelineState()
{
foreach (var kvp in _shaderPipelines)
{
var shader = _resourceDatabase.GetShader(kvp.Key);
CreateRootSignature(shader, kvp.Value);
CreatePipelineStateObject(kvp.Value);
CreateSamplerHeap(kvp.Value);
}
}
public IShaderPipeline GetShaderPipeline(Identifier<Shader> id)
{
if (_shaderPipelines.TryGetValue(id, out var pipeline))
{
return pipeline;
}
throw new KeyNotFoundException($"Shader pipeline not found for shader ID: {id}");
}
public void Dispose()
{
foreach (var kvp in _shaderPipelines)
{
kvp.Value.Dispose();
}
}
}

View File

@@ -1,4 +1,6 @@
using Ghost.Graphics.RHI;
using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows;
using Win32;
using Win32.Graphics.Direct3D;
using Win32.Graphics.Direct3D12;

View File

@@ -1,60 +0,0 @@
using Ghost.Graphics.Data;
using Ghost.Graphics.RHI;
using System.Runtime.CompilerServices;
using Win32;
using Win32.Graphics.Direct3D12;
namespace Ghost.Graphics.D3D12;
/// <summary>
/// D3D12 implementation of render target interface
/// Supports either color OR depth rendering, not both
/// </summary>
internal unsafe class D3D12RenderTarget : D3D12Texture, IRenderTarget
{
public RenderTargetType Type
{
get;
}
private D3D12RenderTarget(ComPtr<ID3D12Resource> resource, uint width, uint height, uint slice, TextureFormat format, RenderTargetType type, uint mipLevels = 1)
: base(resource, width, height, slice, format, mipLevels)
{
Type = type;
}
private D3D12RenderTarget(TextureHandle handle, ref readonly RenderTargetDesc desc, ref readonly TextureDesc texDesc)
: base(handle, in texDesc)
{
Type = desc.Type;
}
/// <summary>
/// Create a new render target with its own texture
/// </summary>
/// <param name="handle">The handle to the texture resource</param>
/// <param name="desc">The descriptor to describe the render target</param>
/// <returns>New render target instance</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static D3D12RenderTarget Create(TextureHandle handle, ref readonly RenderTargetDesc desc)
{
var texDesc = RenderTargetDesc.ToTextureDescriptor(desc);
return new D3D12RenderTarget(handle, in desc, in texDesc);
}
/// <summary>
/// Create a new render target from an existing D3D12 resource
/// </summary>
/// <param name="resource">The existing D3D12 resource</param>
/// <param name="width">The width of the render target</param>
/// <param name="height">The height of the render target</param>
/// <param name="format">The format of the render target</param>
/// <param name="type">The type of the render target</param>
/// <param name="mipLevels">The number of mip levels</param>
/// <returns>New render target instance</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static D3D12RenderTarget Create(ComPtr<ID3D12Resource> resource, uint width, uint height, uint slice, TextureFormat format, RenderTargetType type, uint mipLevels = 1)
{
return new D3D12RenderTarget(resource, width, height, slice, format, type, mipLevels);
}
}

View File

@@ -1,13 +1,15 @@
using Ghost.Core;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.Data;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.Mathematics;
namespace Ghost.Graphics.D3D12;
/// <summary>
/// D3D12 implementation of the renderer interface using RHI abstractions
/// </summary>
public unsafe class D3D12Renderer : IRenderer
internal unsafe class D3D12Renderer : IRenderer
{
private struct FrameResource : IDisposable
{
@@ -31,24 +33,30 @@ public unsafe class D3D12Renderer : IRenderer
private uint _frameIndex;
private readonly IResourceAllocator _resourceAllocator;
private readonly D3D12ResourceDatabase _resourceDatabase;
private IRenderTarget? _customRenderTarget; // User-provided render target
private IRenderTarget? _offScreenRenderTarget; // Off-screen target for swap chain
private Handle<Texture> _customRenderTarget; // User-provided render target
private Handle<Texture> _offScreenRenderTarget; // Off-screen target for swap chain
private ISwapChain? _swapChain;
private readonly Lock _lock = new();
private uint2 _currentSize;
private uint _pendingWidth;
private uint _pendingHeight;
private bool _resizeRequested;
private bool _disposed;
public uint2 Size => _currentSize;
// TODO: Add render passes support
// private ImmutableArray<IRenderPass> _renderPasses;
public D3D12Renderer(IGraphicsEngine graphicsEngine, IResourceAllocator resourceAllocator)
public D3D12Renderer(IGraphicsEngine graphicsEngine, IResourceAllocator resourceAllocator, D3D12ResourceDatabase resourceDatabase)
{
_resourceAllocator = resourceAllocator;
_commandQueue = graphicsEngine.Device.GraphicsQueue;
_resourceAllocator = resourceAllocator;
_resourceDatabase = resourceDatabase;
// Create frame resources for double buffering
_frameResources = new FrameResource[D3D12PipelineResource.BACK_BUFFER_COUNT];
@@ -56,6 +64,9 @@ public unsafe class D3D12Renderer : IRenderer
{
_frameResources[i] = new FrameResource(graphicsEngine);
}
_customRenderTarget = Handle<Texture>.Invalid;
_offScreenRenderTarget = Handle<Texture>.Invalid;
}
~D3D12Renderer()
@@ -63,20 +74,20 @@ public unsafe class D3D12Renderer : IRenderer
Dispose();
}
public void SetRenderTarget(IRenderTarget? renderTarget)
public void SetRenderTarget(Handle<Texture> renderTarget)
{
_customRenderTarget = renderTarget;
_swapChain = null;
// Clean up off-screen target when switching to render target mode
_offScreenRenderTarget?.Dispose();
_offScreenRenderTarget = null;
_resourceDatabase.ReleaseResource(_offScreenRenderTarget.AsResource());
_offScreenRenderTarget = Handle<Texture>.Invalid;
}
public void SetSwapChain(ISwapChain? swapChain)
{
_swapChain = swapChain;
_customRenderTarget = null;
_customRenderTarget = Handle<Texture>.Invalid;
if (_swapChain != null)
{
@@ -84,8 +95,8 @@ public unsafe class D3D12Renderer : IRenderer
}
else
{
_offScreenRenderTarget?.Dispose();
_offScreenRenderTarget = null;
_resourceDatabase.ReleaseResource(_offScreenRenderTarget.AsResource());
_offScreenRenderTarget = Handle<Texture>.Invalid;
}
}
@@ -122,6 +133,7 @@ public unsafe class D3D12Renderer : IRenderer
// Resize swap chain if present
_swapChain?.Resize(newWidth, newHeight);
_currentSize = new uint2(newWidth, newHeight);
// Update off-screen render target size
if (_swapChain != null)
@@ -144,12 +156,12 @@ public unsafe class D3D12Renderer : IRenderer
frame.commandBuffer.Begin();
if (_customRenderTarget != null)
if (_customRenderTarget.IsValid)
{
// Render target mode: render directly to custom target
RenderScene(_customRenderTarget, frame.commandBuffer);
}
else if (_swapChain != null && _offScreenRenderTarget != null)
else if (_swapChain != null && _offScreenRenderTarget.IsValid)
{
// Swap chain mode: render to off-screen, then blit to back buffer
var backBufferRT = _swapChain.GetCurrentBackBuffer();
@@ -164,17 +176,18 @@ public unsafe class D3D12Renderer : IRenderer
_commandQueue.Submit(frame.commandBuffer);
_swapChain?.Present();
frame.fenceValue = _commandQueue.Signal(++_frameIndex);
frame.fenceValue = _commandQueue.Signal(_frameIndex);
_frameIndex++;
}
private void RenderScene(IRenderTarget target, ICommandBuffer cmd)
private void RenderScene(Handle<Texture> target, ICommandBuffer cmd)
{
var clearColor = new Color128 { r = 1.0f, g = 0.0f, b = 1.0f, a = 1.0f };
cmd.BeginRenderPass(target, clearColor);
cmd.BeginRenderPass(target, Handle<Texture>.Invalid, clearColor);
var viewport = new ViewportDesc(target.Width, target.Height);
var scissor = new RectDesc(0, 0, (int)target.Width, (int)target.Height);
var viewport = new ViewportDesc { width = _currentSize.x, height = _currentSize.y, minDepth = 0, maxDepth = 1 };
var scissor = new RectDesc { right = _currentSize.x, bottom = _currentSize.y };
cmd.SetViewport(viewport);
cmd.SetScissorRect(scissor);
@@ -188,13 +201,13 @@ public unsafe class D3D12Renderer : IRenderer
cmd.EndRenderPass();
}
private void BlitToDestination(IRenderTarget source, IRenderTarget destination, ICommandBuffer cmd)
private void BlitToDestination(Handle<Texture> source, Handle<Texture> destination, ICommandBuffer cmd)
{
// Handle swap chain back buffer transitions if needed
if (_swapChain != null)
{
// Transition back buffer to render target
cmd.ResourceBarrier(destination, ResourceState.Present, ResourceState.RenderTarget);
cmd.ResourceBarrier(destination.AsResource(), ResourceState.Present, ResourceState.RenderTarget);
}
// For now, we'll do a simple copy operation
@@ -208,29 +221,26 @@ public unsafe class D3D12Renderer : IRenderer
// For now, just clear the destination (this should be replaced with actual blit)
var clearColor = new Color128 { r = 0.0f, g = 0.0f, b = 0.0f, a = 1.0f };
cmd.BeginRenderPass(destination, clearColor);
cmd.BeginRenderPass(destination, Handle<Texture>.Invalid, clearColor);
cmd.EndRenderPass();
// Handle swap chain back buffer transitions if needed
if (_swapChain != null)
{
// Transition back buffer to present
cmd.ResourceBarrier(destination, ResourceState.RenderTarget, ResourceState.Present);
cmd.ResourceBarrier(destination.AsResource(), ResourceState.RenderTarget, ResourceState.Present);
}
}
private void CreateOrUpdateOffScreenRenderTarget(uint width, uint height)
{
// Check if we need to recreate the off-screen render target
if (_offScreenRenderTarget == null ||
_offScreenRenderTarget.Width != width ||
_offScreenRenderTarget.Height != height)
if (_offScreenRenderTarget.IsValid)
{
_offScreenRenderTarget?.Dispose();
var desc = RenderTargetDesc.Color(width, height, 1, TextureFormat.R8G8B8A8_UNorm);
_offScreenRenderTarget = _resourceAllocator.CreateRenderTarget(in desc);
_resourceAllocator.ReleaseResource(_offScreenRenderTarget.AsResource());
}
var desc = RenderTargetDesc.Color(width, height, 1, TextureFormat.R8G8B8A8_UNorm);
_offScreenRenderTarget = _resourceAllocator.CreateRenderTarget(in desc);
}
public void WaitIdle()
@@ -259,7 +269,8 @@ public unsafe class D3D12Renderer : IRenderer
frame.Dispose();
}
_offScreenRenderTarget?.Dispose();
_resourceDatabase.ReleaseResource(_customRenderTarget.AsResource());
_resourceDatabase.ReleaseResource(_offScreenRenderTarget.AsResource());
_disposed = true;

View File

@@ -24,7 +24,7 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
private readonly D3D12DescriptorAllocator _descriptorAllocator;
private readonly D3D12ResourceDatabase _resourceDatabase;
private UnsafeQueue<ResourceHandle> _temResources = new(64, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
private UnsafeQueue<Handle<GPUResource>> _temResources = new(64, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
private Guid* IID_NULL
{
@@ -78,9 +78,9 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private ResourceHandle TrackResource(ref readonly Allocation allocation, ResourceStates state, bool isTemp)
private Handle<GPUResource> TrackResource(ref readonly Allocation allocation, ResourceStates state, D3D12ResourceDescriptor resourceDescriptor, ResourceDesc desc, bool isTemp)
{
var handle = _resourceDatabase.AddResource(in allocation, _renderSystem.CPUFenceValue, D3D12StatesToRHIState(state));
var handle = _resourceDatabase.AddResource(in allocation, _renderSystem.CPUFenceValue, D3D12StatesToRHIState(state), resourceDescriptor, desc);
if (isTemp)
{
@@ -90,13 +90,68 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
return handle;
}
public TextureHandle CreateTexture(ref readonly TextureDesc desc, bool tempResource = false)
private void CreateSRV(ID3D12Resource* pResource, CpuDescriptorHandle descriptorHandle, Format format, TextureDimension dimension, uint mipLevels, uint arraySize)
{
var srvDesc = new ShaderResourceViewDescription
{
Format = format,
Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING
};
switch (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 = arraySize,
};
break;
case TextureDimension.TextureCube:
srvDesc.ViewDimension = SrvDimension.TextureCube;
srvDesc.TextureCube = new TexureCubeSrv
{
MipLevels = mipLevels,
};
break;
case TextureDimension.TextureCubeArray:
srvDesc.ViewDimension = SrvDimension.TextureCubeArray;
srvDesc.TextureCubeArray = new TexureCubeArraySrv
{
MipLevels = mipLevels,
NumCubes = arraySize / 6,
};
break;
default:
throw new ArgumentException($"Unsupported texture dimension for SRV: {dimension}");
}
_device->CreateShaderResourceView(pResource, &srvDesc, descriptorHandle);
}
public Handle<Texture> CreateTexture(ref readonly TextureDesc desc, bool isTemp = false)
{
CheckTexture2DSize(desc.Width, desc.Height);
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 resourceFlags = ConvertTextureUsage(desc.Usage);
var resourceDesc = desc.Dimension switch
{
TextureDimension.Texture2D => ResourceDescription.Tex2D(
@@ -104,24 +159,34 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
desc.Width,
desc.Height,
mipLevels: mipLevels,
flags: ConvertTextureUsage(desc.Usage)),
flags: resourceFlags),
TextureDimension.Texture3D => ResourceDescription.Tex3D(
d3d12Format,
desc.Width,
desc.Height,
(ushort)desc.Slice,
flags: ConvertTextureUsage(desc.Usage)),
//case TextureDimension.TextureCube:
// break;
flags: resourceFlags),
TextureDimension.TextureCube => ResourceDescription.Tex2D(
d3d12Format,
desc.Width,
desc.Height,
mipLevels: mipLevels,
arraySize: 6,
flags: resourceFlags),
TextureDimension.Texture2DArray => ResourceDescription.Tex2D(
d3d12Format,
desc.Width,
desc.Height,
mipLevels: mipLevels,
arraySize: (ushort)desc.Slice,
flags: ConvertTextureUsage(desc.Usage)),
//case TextureDimension.TextureCubeArray:
// break;
flags: resourceFlags),
TextureDimension.TextureCubeArray => ResourceDescription.Tex2D(
d3d12Format,
desc.Width,
desc.Height,
mipLevels: mipLevels,
arraySize: (ushort)(desc.Slice * 6),
flags: resourceFlags),
_ => throw new ArgumentException($"Unsupported texture dimension: {desc.Dimension}"),
};
@@ -136,58 +201,57 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
Allocation allocation = default;
ThrowIfFailed(_allocator.CreateResource(&allocationDesc, in resourceDesc, initialState, null, &allocation, IID_NULL, null));
var handle = TrackResource(in allocation, initialState, tempResource);
if (desc.CreationFlags.HasFlag(TextureCreationFlags.Bindless))
var resourceDescriptor = D3D12ResourceDescriptor.Invalid;
if (desc.Usage.HasFlag(TextureUsage.ShaderResource))
{
var descriptorHandle = _descriptorAllocator.AllocateBindless();
var srvDesc = new ShaderResourceViewDescription
{
Format = d3d12Format,
Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING
};
resourceDescriptor.srv = _descriptorAllocator.AllocateCbvSrvUav(isTemp);
var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.srv);
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));
CreateSRV(allocation.Resource, cpuHandle, d3d12Format, desc.Dimension, mipLevels, desc.Slice);
}
return new(handle);
if (desc.Usage.HasFlag(TextureUsage.RenderTarget))
{
resourceDescriptor.rtv = _descriptorAllocator.AllocateRTV(isTemp);
var rtvDesc = new RenderTargetViewDescription(allocation.Resource);
_device->CreateRenderTargetView(allocation.Resource, &rtvDesc, _descriptorAllocator.GetCpuHandle(resourceDescriptor.rtv));
}
if (desc.Usage.HasFlag(TextureUsage.DepthStencil))
{
resourceDescriptor.dsv = _descriptorAllocator.AllocateDSV(isTemp);
var dsvDesc = new DepthStencilViewDescription(allocation.Resource);
_device->CreateDepthStencilView(allocation.Resource, &dsvDesc, _descriptorAllocator.GetCpuHandle(resourceDescriptor.dsv));
}
if (desc.Usage.HasFlag(TextureUsage.UnorderedAccess))
{
resourceDescriptor.uav = _descriptorAllocator.AllocateCbvSrvUav(isTemp);
var uavDesc = new UnorderedAccessViewDescription
{
ViewDimension = UavDimension.Texture2D,
Format = d3d12Format,
Texture2D = new Texture2DUav
{
MipSlice = 0,
PlaneSlice = 0,
}
};
_device->CreateUnorderedAccessView(allocation.Resource, null, &uavDesc, _descriptorAllocator.GetCpuHandle(resourceDescriptor.uav));
}
var handle = TrackResource(ref allocation, initialState, resourceDescriptor, ResourceDesc.Texture(desc), isTemp);
return handle.AsTexture();
}
public TextureHandle CreateRenderTarget(ref readonly RenderTargetDesc desc, bool tempResource = false)
public Handle<Texture> CreateRenderTarget(ref readonly RenderTargetDesc desc, bool isTemp = false)
{
var textureDesc = RenderTargetDesc.ToTextureDescriptor(desc);
return CreateTexture(ref textureDesc, tempResource);
var textureDesc = RenderTargetDesc.ToTextureDescripton(desc);
return CreateTexture(ref textureDesc, isTemp);
}
public BufferHandle CreateBuffer(ref readonly BufferDesc desc, bool tempResource = false)
public Handle<GraphicsBuffer> CreateBuffer(ref readonly BufferDesc desc, bool isTemp = false)
{
CheckBufferSize((uint)desc.Size);
@@ -203,12 +267,10 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
Allocation allocation = default;
ThrowIfFailed(_allocator.CreateResource(&allocationDesc, in resourceDescription, initialState, null, &allocation, IID_NULL, null));
var handle = TrackResource(in allocation, initialState, tempResource);
var resourceDescriptor = D3D12ResourceDescriptor.Invalid;
if (desc.Usage.HasFlag(BufferUsage.ShaderResource) && desc.CreationFlags.HasFlag(BufferCreationFlags.Bindless))
{
var isRaw = desc.Usage.HasFlag(BufferUsage.Raw);
var descriptorHandle = _descriptorAllocator.AllocateBindless();
resourceDescriptor.srv = _descriptorAllocator.AllocateCbvSrvUav(isTemp);
var srvDesc = new ShaderResourceViewDescription
{
@@ -216,7 +278,7 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING
};
if (isRaw)
if (desc.Usage.HasFlag(BufferUsage.Raw))
{
srvDesc.Format = Format.R32Typeless;
srvDesc.Buffer.FirstElement = 0;
@@ -233,15 +295,14 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
srvDesc.Buffer.Flags = BufferSrvFlags.None;
}
_device->CreateShaderResourceView(allocation.Resource, &srvDesc, _descriptorAllocator.GetCpuHandle(descriptorHandle));
return new(handle, descriptorHandle);
_device->CreateShaderResourceView(allocation.Resource, &srvDesc, _descriptorAllocator.GetCpuHandle(resourceDescriptor.srv));
}
return new(handle);
var handle = TrackResource(ref allocation, initialState, resourceDescriptor, ResourceDesc.Buffer(desc), isTemp);
return handle.AsGraphicsBuffer();
}
public BufferHandle CreateUploadBuffer(ulong size, bool temp = true)
public Handle<GraphicsBuffer> CreateUploadBuffer(ulong size, bool isTemp = true)
{
var desc = new BufferDesc
{
@@ -250,14 +311,14 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
MemoryType = MemoryType.Upload,
};
return CreateBuffer(in desc, temp);
return CreateBuffer(ref desc, isTemp);
}
public Identifier<Mesh> CreateMesh(ReadOnlySpan<Vertex> vertices, ReadOnlySpan<uint> indices)
public Handle<Mesh> CreateMesh(UnsafeList<Vertex> vertices, UnsafeList<uint> indices)
{
var vertexBufferDesc = new BufferDesc
{
Size = (ulong)(vertices.Length * Unsafe.SizeOf<Vertex>()),
Size = (ulong)(vertices.Count * Unsafe.SizeOf<Vertex>()),
Stride = (uint)Unsafe.SizeOf<Vertex>(),
Usage = BufferUsage.Vertex | BufferUsage.ShaderResource,
MemoryType = MemoryType.Default,
@@ -266,35 +327,42 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
var indexBufferDesc = new BufferDesc
{
Size = (ulong)(indices.Length * sizeof(uint)),
Size = (ulong)(indices.Count * 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 vertexBuffer = CreateBuffer(ref vertexBufferDesc);
var indexBuffer = CreateBuffer(ref indexBufferDesc);
var data = new Mesh(vertices, indices, vertexBuffer, indexBuffer);
return _resourceDatabase.AddMesh(in data);
var data = new Mesh
{
vertices = vertices,
indices = indices,
vertexBuffer = vertexBuffer,
indexBuffer = indexBuffer,
};
return _resourceDatabase.AddMesh(ref data);
}
public Identifier<Material> CreateMaterial(Identifier<Shader> shader)
public Handle<Material> CreateMaterial(Identifier<Shader> shader)
{
var materialData = new Material
{
Shader = shader,
};
var shaderResource = _resourceDatabase.GetShader(shader);
ref var shaderRef = ref _resourceDatabase.GetShaderReference(shader);
if (shaderResource.ConstantBuffers.Count > 0)
if (shaderRef.ConstantBuffers.Count > 0)
{
var maxSlot = shaderResource.ConstantBuffers.Max(cb => cb.RegisterSlot);
var maxSlot = shaderRef.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)
foreach (var cbufferInfo in shaderRef.ConstantBuffers)
{
var desc = new BufferDesc
{
@@ -303,13 +371,19 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
MemoryType = MemoryType.Default,
};
var buffer = CreateBuffer(in desc);
var buffer = CreateBuffer(ref desc);
materialData._cBufferCaches[cbufferInfo.RegisterSlot] = new CBufferCache(buffer, cbufferInfo.Size);
}
}
return _resourceDatabase.AddMaterial(in materialData);
return _resourceDatabase.AddMaterial(ref materialData);
}
public Identifier<Shader> CreateShader()
{
var shaderData = new Shader();
return _resourceDatabase.AddShader(ref shaderData);
}
#region Conversion Methods
@@ -461,43 +535,39 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
#endregion
public void ReleaseTempResource()
public void ReleaseTempResources()
{
while (_temResources.Count > 0)
{
var handle = _temResources.Peek();
ref var info = ref _resourceDatabase.GetResourceInfo(handle, out var exist);
if (exist && info.Allocated && info.cpuFenceValue > _renderSystem.GPUFenceValue)
if (!exist || !info.Allocated)
{
// Resource already released or invalid, just dequeue
_temResources.Dequeue();
continue;
}
if (info.cpuFenceValue > _renderSystem.GPUFenceValue)
{
// Resource still in use by GPU, stop processing.
// Since resources are enqueued in order, we can break here.
break;
}
ReleaseResource(handle);
_resourceDatabase.ReleaseResource(handle);
_temResources.Dequeue();
}
}
public void ReleaseResource(ResourceHandle handle)
public void ReleaseResource(Handle<GPUResource> handle)
{
if (!handle.IsValid)
{
return;
}
ref var info = ref _resourceDatabase.GetResourceInfo(handle, out var exist);
if (!exist || !info.Allocated)
{
return;
}
info.Dispose();
_resourceDatabase.RemoveResource(handle);
_resourceDatabase.ReleaseResource(handle);
}
public void Dispose()
{
#if DEBUG
#if DEBUG || GHOST_EDITOR
if (_temResources.Count > 0)
{
throw new InvalidOperationException($"ResourceAllocator is being disposed with {_temResources.Count} temp allocations still registered. Ensure all resources are released before disposing.");

View File

@@ -3,6 +3,8 @@ using Ghost.Graphics.Data;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.Collections;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Runtime.InteropServices;
using Win32;
using Win32.Graphics.D3D12MemoryAllocator;
using Win32.Graphics.Direct3D12;
@@ -10,42 +12,100 @@ namespace Ghost.Graphics.D3D12;
internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
{
internal unsafe struct ResourceInfo
{
[StructLayout(LayoutKind.Explicit)]
private struct ResourceUnion
{
[FieldOffset(0)]
public Allocation allocation;
[FieldOffset(0)]
public ComPtr<ID3D12Resource> resource;
public ResourceUnion(Allocation allocation)
{
this.allocation = allocation;
this.resource = default;
}
public ResourceUnion(ComPtr<ID3D12Resource> resource)
{
this.resource = resource;
this.allocation = default;
}
}
private ResourceUnion _resourceUnion;
private readonly bool _isExternal;
public D3D12ResourceDescriptor descriptor;
public uint cpuFenceValue;
public ResourceState state;
public ResourceDesc desc;
public readonly bool Allocated => _isExternal ? _resourceUnion.resource.Get() != null : _resourceUnion.allocation.IsNotNull;
public readonly ID3D12Resource* ResourcePtr => _isExternal ? _resourceUnion.resource.Get() : _resourceUnion.allocation.Resource;
public ResourceInfo(in Allocation allocation, uint cpuFenceValue, ResourceState state, D3D12ResourceDescriptor resourceDescriptor, ResourceDesc desc)
{
this._resourceUnion = new ResourceUnion(allocation);
this._isExternal = false;
this.descriptor = resourceDescriptor;
this.cpuFenceValue = cpuFenceValue;
this.state = state;
this.desc = desc;
}
public ResourceInfo(ComPtr<ID3D12Resource> resource, ResourceState state)
{
this._resourceUnion = new ResourceUnion(resource);
this._isExternal = true;
this.descriptor = default;
this.cpuFenceValue = ~0u;
this.state = state;
this.desc = ResourceDesc.FromD3D12(resource.Get()->GetDesc());
}
public uint Release()
{
var refCount = 0u;
if (Allocated)
{
if (_isExternal)
{
refCount = _resourceUnion.resource.Get()->Release();
}
else
{
refCount = _resourceUnion.allocation.Release();
}
_resourceUnion = default;
descriptor = default;
}
return refCount;
}
}
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();
}
public bool occupied;
}
private UnsafeSlotMap<ResourceInfo> _resources;
#if DEBUG || GHOST_EDITOR
private readonly Dictionary<ResourceInfo, string> _resourceName;
#endif
// 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 UnsafeSlotMap<Mesh> _meshes;
private readonly UnsafeSlotMap<Material> _materials;
// NOTE: We use a simple list since shader is not frequently added/removed. This can save 4 bytes for each ecs component.
private readonly DynamicArray<Slot<Shader>> _shaders;
private readonly D3D12DescriptorAllocator _descriptorAllocator;
@@ -55,8 +115,12 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
public D3D12ResourceDatabase(D3D12DescriptorAllocator descriptorAllocator)
{
_resources = new(64, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
#if DEBUG || GHOST_EDITOR
_resourceName = new(64);
#endif
_meshDatas = new(64);
_meshes = new(64, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
_materials = new(16, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
_shaders = new(16);
_descriptorAllocator = descriptorAllocator;
@@ -67,15 +131,29 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
Dispose();
}
public ResourceHandle AddResource(ref readonly Allocation allocation, uint cpuFenceValue, ResourceState initialState)
public unsafe Handle<GPUResource> ImportExternalResource<T>(T resource, ResourceState initialState)
where T : unmanaged
{
ObjectDisposedException.ThrowIf(_disposed, this);
var id = _resources.Add(new ResourceInfo(allocation, cpuFenceValue, initialState), out var generation);
return new ResourceHandle(id, generation);
if (resource is not ComPtr<ID3D12Resource> d3d12Resource)
{
throw new InvalidOperationException($"Expect ComPtr<ID3D12Resource> in D3D12ResourceDatabase, but got {typeof(T)}.");
}
var id = _resources.Add(new ResourceInfo(d3d12Resource, initialState), out var generation);
return new Handle<GPUResource>(id, generation);
}
public ref ResourceInfo GetResourceInfo(ResourceHandle handle)
public Handle<GPUResource> AddResource(ref readonly Allocation allocation, uint cpuFenceValue, ResourceState initialState, D3D12ResourceDescriptor resourceDescriptor, ResourceDesc desc)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var id = _resources.Add(new ResourceInfo(allocation, cpuFenceValue, initialState, resourceDescriptor, desc), out var generation);
return new Handle<GPUResource>(id, generation);
}
public ref ResourceInfo GetResourceInfo(Handle<GPUResource> handle)
{
ObjectDisposedException.ThrowIf(_disposed, this);
@@ -88,56 +166,37 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
return ref info;
}
public ref ResourceInfo GetResourceInfo(ResourceHandle handle, out bool exist)
public ref ResourceInfo GetResourceInfo(Handle<GPUResource> 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
public unsafe ID3D12Resource* GetResource(Handle<GPUResource> handle)
{
ObjectDisposedException.ThrowIf(_disposed, this);
if (typeof(T) != typeof(ID3D12Resource))
{
return null;
}
var info = GetResourceInfo(handle);
ref var info = ref GetResourceInfo(handle);
if (!info.Allocated)
{
return null;
}
return (T*)info.allocation.Resource;
return info.ResourcePtr;
}
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)
public ResourceState GetResourceState(Handle<GPUResource> handle)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return GetResourceInfo(handle).state;
}
public void SetResourceState(ResourceHandle handle, ResourceState state)
public void SetResourceState(Handle<GPUResource> 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)
if (!exist || !info.Allocated)
{
throw new KeyNotFoundException($"Resource with handle {handle} not found.");
}
@@ -145,138 +204,170 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
info.state = state;
}
public void RemoveResource(ResourceHandle handle)
public ResourceDesc GetResourceDescription(Handle<GPUResource> handle)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return GetResourceInfo(handle).desc;
}
public int GetBindlessIndex(Handle<GPUResource> handle)
{
ref var info = ref GetResourceInfo(handle, out var exist);
if (!exist || !info.Allocated)
{
return -1;
}
return info.descriptor.srv.value;
}
public unsafe void ReleaseResource(Handle<GPUResource> handle)
{
ObjectDisposedException.ThrowIf(_disposed, this);
ref var info = ref _resources.GetElementReferenceAt(handle.id, handle.generation, out var exist);
if (!exist || info.Allocated == false)
if (!handle.IsValid)
{
return;
}
info.Dispose();
ref var info = ref _resources.GetElementReferenceAt(handle.id, handle.generation, out var exist);
if (!exist || !info.Allocated)
{
return;
}
var refCount = info.Release();
#if DEBUG || GHOST_EDITOR
if (refCount > 0)
{
throw new GPUResourceLeakException(refCount, info.ResourcePtr, _resourceName[info]);
}
#endif
_resources.Remove(handle.id, handle.generation);
}
public Identifier<Mesh> AddMesh(ref readonly Mesh mesh)
public Handle<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;
var id = _meshes.Add(mesh, out var generation);
return new Handle<Mesh>(id, generation);
}
public bool HasMesh(Identifier<Mesh> id)
public bool HasMesh(Handle<Mesh> handle)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return id.value >= 0 && id.value < _meshDatas.Count && _meshDatas[id.value].isValid;
return _meshes.Contain(handle.id, handle.generation);
}
public Mesh GetMesh(Identifier<Mesh> id)
public ref Mesh GetMeshReference(Handle<Mesh> handle)
{
if (!HasMesh(id))
ref var mesh = ref _meshes.GetElementReferenceAt(handle.id, handle.generation, out var exist);
if (!exist)
{
throw new ArgumentOutOfRangeException(nameof(id), $"Mesh ID {id.value} is out of range.");
throw new ArgumentOutOfRangeException(nameof(handle), $"Mesh {handle} is invalid.");
}
return _meshDatas[id.value].value;
return ref mesh;
}
public ref Mesh GetMeshReference(Identifier<Mesh> id)
private void ReleaseMeshResources(ref readonly Mesh mesh)
{
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);
ref var vertexRef = ref GetResourceInfo(mesh.vertexBuffer.AsResource());
ref var indexRef = ref GetResourceInfo(mesh.indexBuffer.AsResource());
_descriptorAllocator.Release(vertexRef.descriptor);
_descriptorAllocator.Release(indexRef.descriptor);
_descriptorAllocator.Release(mesh.vertexBuffer.BindlessDescriptor);
_descriptorAllocator.Release(mesh.indexBuffer.BindlessDescriptor);
ReleaseResource(mesh.vertexBuffer.AsResource());
ReleaseResource(mesh.indexBuffer.AsResource());
}
public void ReleaseMesh(Handle<Mesh> handle)
{
ref var meshSlot = ref _meshes.GetElementReferenceAt(handle.id, handle.generation, out var exist);
if (!exist)
{
return;
}
ReleaseMeshResources(ref meshSlot);
_meshes.Remove(handle.id, handle.generation);
}
public Handle<Material> AddMaterial(ref readonly Material material)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var id = _materials.Add(material, out var generation);
return new Handle<Material>(id, generation);
}
public bool HasMaterial(Handle<Material> handle)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _materials.Contain(handle.id, handle.generation);
}
public ref Material GetMaterialReference(Handle<Material> handle)
{
ref var material = ref _materials.GetElementReferenceAt(handle.id, handle.generation, out var exist);
if (!exist)
{
throw new ArgumentOutOfRangeException(nameof(handle), $"Material handle {handle} is invalid.");
}
return ref material;
}
public void ReleaseMaterial(Handle<Material> handle)
{
ref var material = ref _materials.GetElementReferenceAt(handle.id, handle.generation, out var exist);
if (!exist)
{
return;
}
material.Dispose();
}
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;
var id = _shaders.Count;
_shaders.Add(new Slot<Shader> { value = shader, occupied = true });
return new Identifier<Shader>(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;
return id.value >= 0 && id.value < _shaders.Count && _shaders[id.value].occupied;
}
public ref Shader GetShaderReference(Identifier<Shader> id)
{
if (!HasShader(id))
{
throw new ArgumentOutOfRangeException(nameof(id), $"Shader ID {id.value} is out of range.");
throw new ArgumentOutOfRangeException(nameof(id), $"Shader id {id} is invalid.");
}
return ref _shaders[id.value].value;
ref var shader = ref _shaders[id.value].value;
return ref shader;
}
public void RemoveShader(Identifier<Shader> id)
public void ReleaseShader(Identifier<Shader> handle)
{
if (!HasShader(id))
if (!HasShader(handle))
{
return;
}
ref var shader = ref _shaders[id.value];
shader.value.Dispose();
shader.value = default;
shader.isValid = false;
ref var shader = ref _shaders[handle.value].value;
shader.Dispose();
}
public void Dispose()
@@ -286,15 +377,20 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
return;
}
#if DEBUG
#if DEBUG || GHOST_EDITOR
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)
if (_meshes.Count > 0)
{
throw new InvalidOperationException($"ResourceAllocator is being disposed with {_meshDatas.Count} meshes still registered. Ensure all meshes are released before disposing.");
throw new InvalidOperationException($"ResourceAllocator is being disposed with {_meshes.Count} meshes still registered. Ensure all meshes are released before disposing.");
}
if (_materials.Count > 0)
{
throw new InvalidOperationException($"ResourceAllocator is being disposed with {_materials.Count} materials still registered. Ensure all materials are released before disposing.");
}
if (_shaders.Count > 0)
@@ -302,34 +398,9 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
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();
}
_meshes.Dispose();
_materials.Dispose();
_disposed = true;

View File

@@ -0,0 +1,26 @@
using Ghost.Core;
namespace Ghost.Graphics.D3D12;
internal readonly struct RTVDesc : IIdentifierType;
internal readonly struct DSVDesc : IIdentifierType;
internal readonly struct CbvSrvUavDesc : IIdentifierType;
internal readonly struct SamplerDesc : IIdentifierType;
internal struct D3D12ResourceDescriptor
{
public Identifier<RTVDesc> rtv;
public Identifier<DSVDesc> dsv;
public Identifier<CbvSrvUavDesc> srv;
public Identifier<CbvSrvUavDesc> cbv;
public Identifier<CbvSrvUavDesc> uav;
public Identifier<SamplerDesc> sampler;
public static D3D12ResourceDescriptor Invalid => new()
{
rtv = Identifier<RTVDesc>.Invalid,
dsv = Identifier<DSVDesc>.Invalid,
srv = Identifier<CbvSrvUavDesc>.Invalid,
sampler = Identifier<SamplerDesc>.Invalid,
};
}

View File

@@ -1,7 +1,6 @@
using Ghost.Graphics.Data;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Runtime.InteropServices;
using System.Text;
using Win32;
using Win32.Graphics.Direct3D;
using Win32.Graphics.Direct3D.Dxc;
@@ -182,7 +181,14 @@ internal unsafe static class D3D12ShaderCompiler
}
}
public static void PerformDXCReflection(Shader shader, IDxcBlob* reflectionBlob)
private static void AddProperty(ref Shader shader, string name, PropertyInfo propertyInfo)
{
var id = shader.Properties.Count;
shader.Properties.Add(propertyInfo);
shader.PropertyNameToIdMap[name] = id;
}
public static void PerformDXCReflection(ref Shader shader, IDxcBlob* reflectionBlob)
{
// Create DXC utils to parse reflection data
using ComPtr<IDxcUtils> utils = default;
@@ -255,7 +261,7 @@ internal unsafe static class D3D12ShaderCompiler
Size = varDesc.Size
};
shader.AddProperty(variableName, propInfo);
AddProperty(ref shader, variableName, propInfo);
}
break;

View File

@@ -1,6 +1,10 @@
using Ghost.Core;
using Ghost.Graphics.Contracts;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.Data;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Runtime.CompilerServices;
using Win32;
using Win32.Graphics.Direct3D12;
@@ -14,8 +18,10 @@ namespace Ghost.Graphics.D3D12;
/// </summary>
internal unsafe class D3D12SwapChain : ISwapChain
{
private readonly D3D12ResourceDatabase _resourceDatabase;
private ComPtr<IDXGISwapChain4> _swapChain;
private readonly D3D12RenderTarget[] _backBuffers;
private UnsafeArray<Handle<Texture>> _backBuffers;
private bool _disposed;
public uint Width
@@ -33,9 +39,11 @@ internal unsafe class D3D12SwapChain : ISwapChain
get;
}
public D3D12SwapChain(IDXGIFactory7* pFactory, ID3D12CommandQueue* pCommandQueue, SwapChainDesc desc)
public D3D12SwapChain(D3D12ResourceDatabase resourceDatabase, IDXGIFactory7* pFactory, ID3D12CommandQueue* pCommandQueue, SwapChainDesc desc)
{
_backBuffers = new D3D12RenderTarget[D3D12PipelineResource.BACK_BUFFER_COUNT];
_resourceDatabase = resourceDatabase;
_backBuffers = new UnsafeArray<Handle<Texture>>(D3D12PipelineResource.BACK_BUFFER_COUNT, Allocator.Persistent);
Width = desc.width;
Height = desc.height;
@@ -64,15 +72,15 @@ internal unsafe class D3D12SwapChain : ISwapChain
using ComPtr<IDXGISwapChain1> tempSwapChain = default;
switch (desc.target.Type)
switch (desc.target.type)
{
case SwapChainTargetType.Composition:
pFactory->CreateSwapChainForComposition((IUnknown*)commandQueue, &swapChainDesc, null, tempSwapChain.GetAddressOf());
// Set the composition surface
if (desc.target.CompositionSurface != null)
if (desc.target.compositionSurface != null)
{
using var swapChainPanelNative = ISwapChainPanelNative.FromSwapChainPanel(desc.target.CompositionSurface);
using var swapChainPanelNative = ISwapChainPanelNative.FromSwapChainPanel(desc.target.compositionSurface);
swapChainPanelNative.SetSwapChain((IntPtr)tempSwapChain.Get());
}
break;
@@ -85,7 +93,7 @@ internal unsafe class D3D12SwapChain : ISwapChain
pFactory->CreateSwapChainForHwnd(
(IUnknown*)commandQueue,
desc.target.WindowHandle,
desc.target.windowHandle,
&swapChainDesc,
&swapChainFullscreenDesc,
null,
@@ -110,12 +118,12 @@ internal unsafe class D3D12SwapChain : ISwapChain
_swapChain.Get()->GetBuffer(i, __uuidof<ID3D12Resource>(), backBuffer.GetVoidAddressOf());
backBuffer.Get()->SetName($"SwapChain_BackBuffer_{i}");
_backBuffers[i] = D3D12RenderTarget.Create(backBuffer.Move(), Width, Height, 1, TextureFormat.B8G8R8A8_UNorm, RenderTargetType.Color);
_backBuffers[i] = _resourceDatabase.ImportExternalResource(backBuffer.Move(), ResourceState.Present).AsTexture();
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public IRenderTarget GetCurrentBackBuffer()
public Handle<Texture> GetCurrentBackBuffer()
{
return _backBuffers[_swapChain.Get()->GetCurrentBackBufferIndex()];
}
@@ -134,12 +142,14 @@ internal unsafe class D3D12SwapChain : ISwapChain
public void Resize(uint width, uint height)
{
if (Width == width && Height == height)
{
return;
}
// Release old back buffers and render targets
for (var i = 0; i < _backBuffers.Length; i++)
for (var i = 0; i < _backBuffers.Count; i++)
{
_backBuffers[i]?.Dispose();
_resourceDatabase.ReleaseResource(_backBuffers[i].AsResource());
}
// Resize the swap chain
@@ -174,12 +184,13 @@ internal unsafe class D3D12SwapChain : ISwapChain
return;
}
for (var i = 0; i < _backBuffers.Length; i++)
for (var i = 0; i < _backBuffers.Count; i++)
{
_backBuffers[i]?.Dispose();
_resourceDatabase.ReleaseResource(_backBuffers[i].AsResource());
}
_swapChain.Dispose();
_backBuffers.Dispose();
_disposed = true;
}
}

View File

@@ -1,140 +0,0 @@
using Ghost.Graphics.Data;
using Ghost.Graphics.RHI;
using Win32;
using Win32.Graphics.Direct3D12;
namespace Ghost.Graphics.D3D12;
/// <summary>
/// D3D12 implementation of texture interface using resource handles
/// </summary>
internal unsafe class D3D12Texture : ITexture
{
private readonly TextureHandle _handle;
private readonly ComPtr<ID3D12Resource> _externalResource;
private ResourceState _currentState;
private bool _disposed;
public uint Width
{
get;
}
public uint Height
{
get;
}
public uint Slice
{
get;
}
public TextureFormat Format
{
get;
}
public uint MipLevels
{
get;
}
public string Name
{
get; set;
} = string.Empty;
public ulong Size
{
get;
}
public ResourceState CurrentState => _currentState;
public ID3D12Resource* NativeResource => _handle.IsValid ? _handle.ResourceHandle.GetAllocation().Resource : _externalResource.Get();
public D3D12Texture(ComPtr<ID3D12Resource> resource, uint width, uint height, uint slice, TextureFormat format, uint mipLevels = 1)
{
_handle = TextureHandle.Invalid;
_externalResource = resource.Move();
Width = width;
Height = height;
Slice = slice;
Format = format;
MipLevels = mipLevels;
_currentState = ResourceState.Common;
Size = Width * Height * GetBytesPerPixel(Format);
}
public D3D12Texture(TextureHandle handle, ref readonly TextureDesc desc)
{
_handle = handle;
_externalResource = default;
Width = desc.Width;
Height = desc.Height;
Slice = desc.Slice;
Format = desc.Format;
var mipLevels = desc.MipLevels;
if (mipLevels <= 0)
{
mipLevels = (uint)(Math.Floor(Math.Log2(Math.Max(Width, Height))) + 1);
}
MipLevels = mipLevels;
_currentState = ResourceState.Common;
Size = Width * Height * GetBytesPerPixel(Format);
}
~D3D12Texture()
{
Dispose(false);
}
private static uint GetBytesPerPixel(TextureFormat format)
{
return format switch
{
TextureFormat.R8G8B8A8_UNorm => 4,
TextureFormat.B8G8R8A8_UNorm => 4,
TextureFormat.R16G16B16A16_Float => 8,
TextureFormat.R32G32B32A32_Float => 16,
TextureFormat.D24_UNorm_S8_UInt => 4,
TextureFormat.D32_Float => 4,
_ => 4
};
}
public void SetCurrentState(ResourceState state)
{
_currentState = state;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
if (_handle.IsValid)
{
_handle.Dispose();
}
else
{
_externalResource.Dispose();
}
_disposed = true;
}
}

View File

@@ -1,119 +0,0 @@
using Ghost.Graphics.Data;
using System.Runtime.CompilerServices;
using Win32.Graphics.Direct3D12;
namespace Ghost.Graphics.D3D12;
public unsafe class GraphicsBuffer : GraphicsResource
{
public enum Usage
{
Common,
Vertex,
Index,
CopySource,
CopyDestination,
Structured,
Raw,
Append,
Counter,
Indirect,
Constant,
}
private readonly Usage _usage;
public Usage BufferUsage => _usage;
private GraphicsBuffer(Usage usage, in BufferHandle handle, bool tempResource = false)
: base(handle.ResourceHandle, tempResource)
{
_usage = usage;
}
public static GraphicsBuffer Create(uint sizeInBytes, Usage usage, bool tempResource = false)
{
var heapType = HeapType.Default;
var state = ResourceStates.Common;
switch (usage)
{
case Usage.Vertex:
heapType = HeapType.Default;
state = ResourceStates.VertexAndConstantBuffer;
break;
case Usage.Index:
heapType = HeapType.Default;
state = ResourceStates.IndexBuffer;
break;
case Usage.CopySource:
heapType = HeapType.Readback;
state = ResourceStates.CopySource;
break;
case Usage.CopyDestination:
heapType = HeapType.Default;
state = ResourceStates.CopyDest;
break;
case Usage.Structured:
case Usage.Raw:
case Usage.Append:
case Usage.Counter:
heapType = HeapType.Default;
state = ResourceStates.AllShaderResource | ResourceStates.UnorderedAccess;
break;
case Usage.Indirect:
heapType = HeapType.Default;
state = ResourceStates.IndirectArgument;
break;
case Usage.Constant:
heapType = HeapType.Upload;
state = ResourceStates.GenericRead;
break;
default:
break;
}
var handle = GraphicsPipeline.ResourceAllocator.CreateBuffer(sizeInBytes, heapType, initialState: state, tempResource: tempResource);
return new GraphicsBuffer(usage, in handle, tempResource);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetData<T>(Span<T> data, uint offset)
where T : unmanaged
{
fixed (T* ptr = data)
{
SetData(ptr, offset, (uint)data.Length);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void SetData<T>(T* data, uint offset, uint length)
where T : unmanaged
{
var size = (uint)(length * sizeof(T));
SetData((void*)data, offset, size);
}
public unsafe void SetData(void* data, uint offset, uint size)
{
ThrowIfDisposed();
if (data == null)
{
throw new ArgumentNullException(nameof(data), "Data pointer cannot be null.");
}
if (size > Size)
{
throw new ArgumentException($"Data size {size} exceeds buffer size {Size}.", nameof(size));
}
var range = new Win32.Graphics.Direct3D12.Range(offset, size);
void* mappedPtr;
ThrowIfFailed(NativeResource.Ptr->Map(0, &range, &mappedPtr));
Unsafe.CopyBlock(mappedPtr, data, size);
NativeResource.Ptr->Unmap(0, &range);
}
}

View File

@@ -1,99 +0,0 @@
using Ghost.Core;
using Win32;
using Win32.Graphics.Direct3D;
using Win32.Graphics.Direct3D12;
using Win32.Graphics.Dxgi;
namespace Ghost.Graphics.D3D12;
/// <summary>
/// Legacy D3D12 GraphicsDevice - DEPRECATED
/// Use D3D12RenderDevice instead for new code
/// This class remains for compatibility during migration
/// </summary>
[Obsolete("Use D3D12RenderDevice instead")]
internal unsafe class GraphicsDevice
{
private ComPtr<IDXGIFactory7> _dxgiFactory;
private ComPtr<ID3D12Device14> _device;
private ComPtr<IDXGIAdapter1> _adapter;
private ComPtr<ID3D12CommandQueue> _commandQueue;
private bool _disposed;
public ConstPtr<ID3D12Device14> NativeDevice => new(_device.Get());
public ConstPtr<IDXGIFactory7> DXGIFactory => new(_dxgiFactory.Get());
public ConstPtr<IDXGIAdapter1> Adapter => new(_adapter.Get());
public ConstPtr<ID3D12CommandQueue> CommandQueue => new(_commandQueue.Get());
public GraphicsDevice()
{
InitializeDevice();
InitializeCommandQueue();
}
private void InitializeDevice()
{
#if DEBUG
CreateDXGIFactory2(true, __uuidof<IDXGIFactory7>(), _dxgiFactory.GetVoidAddressOf());
#else
CreateDXGIFactory2(false, __uuidof<IDXGIFactory7>(), _dxgiFactory.GetVoidAddressOf());
#endif
using ComPtr<IDXGIAdapter1> adapter = default;
for (uint adapterIndex = 0;
_dxgiFactory.Get()->EnumAdapterByGpuPreference(adapterIndex, GpuPreference.HighPerformance, __uuidof<IDXGIAdapter1>(), adapter.ReleaseAndGetVoidAddressOf()).Success;
adapterIndex++)
{
AdapterDescription1 desc = default;
adapter.Get()->GetDesc1(&desc);
// Don't select the Basic Render Driver adapter.
if ((desc.Flags & AdapterFlags.Software) != AdapterFlags.None)
{
continue;
}
if (D3D12CreateDevice((IUnknown*)adapter.Get(), FeatureLevel.Level_12_0, __uuidof<ID3D12Device14>(), _device.GetVoidAddressOf()).Success)
{
_adapter = adapter.Move();
break;
}
}
if (_device.Get() == null)
{
throw new PlatformNotSupportedException("Cannot create ID3D12Device with feature level 12.0");
}
}
private void InitializeCommandQueue()
{
var queueDesc = new CommandQueueDescription
{
Type = CommandListType.Direct,
Priority = (int)CommandQueuePriority.High,
Flags = CommandQueueFlags.None,
};
fixed (void* queuePtr = &_commandQueue)
{
_device.Get()->CreateCommandQueue(&queueDesc, __uuidof<ID3D12CommandQueue>(), (void**)queuePtr);
}
}
public void Dispose()
{
if (_disposed)
{
return;
}
_commandQueue.Dispose();
_device.Reset();
_dxgiFactory.Dispose();
_disposed = true;
}
}

View File

@@ -1,75 +0,0 @@
using Ghost.Core;
using Ghost.Graphics.Data;
using System.Runtime.CompilerServices;
using Win32.Graphics.Direct3D12;
namespace Ghost.Graphics.D3D12;
public unsafe class GraphicsResource : IDisposable
{
private readonly ResourceHandle _handle;
private string _name = string.Empty;
internal ConstPtr<ID3D12Resource> NativeResource
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new(_handle.GetAllocation().Resource);
}
internal ulong GPUAddress
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => NativeResource.Ptr->GetGPUVirtualAddress();
}
public string Name
{
get => _name;
set
{
_name = value;
NativeResource.Ptr->SetName(_name);
}
}
public bool TempResource
{
get;
}
public ulong Size => _handle.GetAllocation().Size;
internal GraphicsResource(in ResourceHandle handle, bool tempResource = false)
{
_handle = handle;
TempResource = tempResource;
}
~GraphicsResource()
{
DisposeInternal();
}
/// <summary>
/// Throws an exception if the resource has been disposed.
/// </summary>
protected void ThrowIfDisposed()
{
ObjectDisposedException.ThrowIf(!_handle.IsValid, this);
}
internal void DisposeInternal()
{
_handle.Dispose();
}
public virtual void Dispose()
{
if (!TempResource)
{
DisposeInternal();
}
GC.SuppressFinalize(this);
}
}

View File

@@ -1,361 +0,0 @@
using Ghost.Graphics.Contracts;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.Data;
using System.Collections.Immutable;
using Win32;
using Win32.Graphics.Direct3D12;
using Win32.Graphics.Dxgi;
using Win32.Graphics.Dxgi.Common;
using Win32.Numerics;
namespace Ghost.Graphics.D3D12;
// TODO: We should split the renderer and swap chain into different classes to allow for more flexibility in rendering pipelines.
// Each renderer can have a render target (swap chain or texture).
// When render target is null, skip the render pass execution.
/// <summary>
/// Legacy D3D12 Renderer - DEPRECATED
/// Use D3D12Renderer instead for new code
/// This class remains for compatibility during migration
/// </summary>
[Obsolete("Use D3D12Renderer instead")]
internal unsafe class Renderer
{
private struct FrameResource : IDisposable
{
public ComPtr<ID3D12CommandAllocator> commandAllocator;
public ComPtr<ID3D12GraphicsCommandList10> commandList;
public ComPtr<ID3D12Resource> backBuffer;
public CommandList cmd;
public RenderTargetDescriptor rtvDescriptor;
public ulong fenceValue;
public FrameResource(Renderer renderer, uint index)
{
renderer._graphicsDevice.NativeDevice.Ptr->CreateCommandAllocator(CommandListType.Direct, __uuidof<ID3D12CommandAllocator>(), commandAllocator.GetVoidAddressOf());
renderer._graphicsDevice.NativeDevice.Ptr->CreateCommandList(0u, CommandListType.Direct, commandAllocator.Get(), null, __uuidof<ID3D12GraphicsCommandList10>(), commandList.GetVoidAddressOf());
cmd = new(commandList.Get());
rtvDescriptor = renderer.CreateBackBufferResource(index, backBuffer.GetAddressOf());
}
public readonly void ResetCommandBuffer()
{
commandAllocator.Get()->Reset();
commandList.Get()->Reset(commandAllocator.Get(), null);
}
public readonly void ExecuteCommandBuffer(ID3D12CommandQueue* queue)
{
commandList.Get()->Close();
var commandListPtr = (ID3D12CommandList*)commandList.Get();
queue->ExecuteCommandLists(1, &commandListPtr);
}
public void IncrementFenceValue()
{
fenceValue++;
}
public void Dispose()
{
commandAllocator.Dispose();
commandList.Dispose();
backBuffer.Dispose();
GraphicsPipeline.DescriptorAllocator.Release(rtvDescriptor);
}
}
private readonly GraphicsDevice _graphicsDevice;
private readonly SwapChainPresenter _swapChainPresenter;
private ComPtr<IDXGISwapChain4> _swapChain = default;
private ComPtr<ID3D12Fence1> _fence = default;
private uint _backBufferIndex;
private readonly FrameResource[] _frameResources;
private readonly AutoResetEvent _fenceEvent;
private ImmutableArray<IRenderPass> _renderPasses;
private readonly Lock _lock = new();
private uint _viewPortWidth;
private uint _viewPortHeight;
private uint _pendingWidth;
private uint _pendingHeight;
private bool _resizeRequested;
private bool _disposed;
public ReadOnlySpan<IRenderPass> RenderPasses => _renderPasses.AsSpan();
public Renderer(GraphicsDevice graphicsDevice, in SwapChainPresenter swapChainSurface)
{
_graphicsDevice = graphicsDevice;
_swapChainPresenter = swapChainSurface;
_viewPortWidth = swapChainSurface.Width;
_viewPortHeight = swapChainSurface.Height;
_fenceEvent = new(false);
_renderPasses = [];
InitializeSwapChain();
InitializeFrameResource(out _frameResources);
}
private void InitializeSwapChain()
{
var swapChainDesc = new SwapChainDescription1
{
Width = _swapChainPresenter.Width,
Height = _swapChainPresenter.Height,
Format = D3D12PipelineResource.SWAP_CHAIN_BACK_BUFFER_FORMAT,
SampleDesc = new SampleDescription(1, 0),
BufferUsage = Usage.BackBuffer | Usage.RenderTargetOutput,
BufferCount = GraphicsPipeline._FRAME_COUNT,
Scaling = Scaling.Stretch,
SwapEffect = SwapEffect.FlipDiscard,
AlphaMode = AlphaMode.Ignore,
Flags = SwapChainFlags.AllowTearing,
Stereo = false,
};
using ComPtr<IDXGISwapChain1> tempSwapChain = default;
switch (_swapChainPresenter.Type)
{
case SwapChainPresenter.TargetType.Composition:
_graphicsDevice.DXGIFactory.Ptr->CreateSwapChainForComposition((IUnknown*)_graphicsDevice.CommandQueue.Ptr, &swapChainDesc, null, tempSwapChain.GetAddressOf());
break;
case SwapChainPresenter.TargetType.Hwnd:
var swapChainFullscreenDesc = new SwapChainFullscreenDescription
{
Windowed = true,
};
_graphicsDevice.DXGIFactory.Ptr->CreateSwapChainForHwnd(
(IUnknown*)_graphicsDevice.CommandQueue.Ptr,
_swapChainPresenter.Hwnd,
&swapChainDesc,
&swapChainFullscreenDesc,
null,
tempSwapChain.GetAddressOf());
break;
default:
throw new ArgumentException("Unsupported swap chain surface type.");
}
if (tempSwapChain.Get()->QueryInterface(__uuidof<IDXGISwapChain4>(), _swapChain.GetVoidAddressOf()).Failure)
{
throw new InvalidOperationException("Failed to create IDXGISwapChain4 interface.");
}
if (_swapChainPresenter.Type == SwapChainPresenter.TargetType.Composition)
{
_swapChainPresenter.SwapChainPanelNative.SetSwapChain((IntPtr)_swapChain.Get());
}
_backBufferIndex = _swapChain.Get()->GetCurrentBackBufferIndex();
}
private void InitializeFrameResource(out FrameResource[] frameResources)
{
frameResources = new FrameResource[GraphicsPipeline._FRAME_COUNT];
for (var i = 0u; i < GraphicsPipeline._FRAME_COUNT; i++)
{
frameResources[i] = new FrameResource(this, i);
}
for (var i = 1u; i < GraphicsPipeline._FRAME_COUNT; i++)
{
ref var frameResource = ref frameResources[i];
frameResource.commandList.Get()->Close();
}
_graphicsDevice.NativeDevice.Ptr->CreateFence(0, FenceFlags.None, __uuidof<ID3D12Fence1>(), _fence.GetVoidAddressOf());
frameResources[0].IncrementFenceValue();
}
public void RequestResize(uint width, uint height)
{
lock (_lock)
{
if (_pendingWidth == width && _pendingHeight == height)
{
return;
}
_resizeRequested = true;
_pendingWidth = width;
_pendingHeight = height;
}
}
private RenderTargetDescriptor CreateBackBufferResource(uint i, ID3D12Resource** backBuffer)
{
_swapChain.Get()->GetBuffer(i, __uuidof<ID3D12Resource>(), (void**)backBuffer);
(*backBuffer)->SetName($"BackBuffer_{i}");
var rtvDescriptor = GraphicsPipeline.DescriptorAllocator.AllocateRTV();
var rtvHandle = rtvDescriptor.CpuHandle;
_graphicsDevice.NativeDevice.Ptr->CreateRenderTargetView(*backBuffer, null, rtvHandle);
return rtvDescriptor;
}
public void ExecutePendingResize()
{
if (!_resizeRequested)
{
return;
}
uint newWidth;
uint newHeight;
lock (_lock)
{
newWidth = _pendingWidth;
newHeight = _pendingHeight;
_resizeRequested = false;
}
WaitIdle();
for (var i = 0; i < GraphicsPipeline._FRAME_COUNT; i++)
{
ref var frameResource = ref _frameResources[i];
if (frameResource.backBuffer.Get() is not null)
{
var c = frameResource.backBuffer.Reset();
GraphicsPipeline.DescriptorAllocator.Release(frameResource.rtvDescriptor);
}
frameResource.fenceValue = _frameResources[_backBufferIndex].fenceValue;
}
if (_swapChain.Get()->ResizeBuffers(GraphicsPipeline._FRAME_COUNT, newWidth, newHeight, Format.B8G8R8A8Unorm, SwapChainFlags.AllowTearing).Failure)
{
throw new InvalidOperationException("Failed to resize swap chain buffers.");
}
for (var i = 0u; i < GraphicsPipeline._FRAME_COUNT; i++)
{
var index = CreateBackBufferResource(i, _frameResources[i].backBuffer.GetAddressOf());
_frameResources[i].rtvDescriptor = index;
}
_backBufferIndex = _swapChain.Get()->GetCurrentBackBufferIndex();
_viewPortWidth = newWidth;
_viewPortHeight = newHeight;
}
public void Initialize()
{
ref var frameResource = ref _frameResources[_backBufferIndex];
foreach (var pass in _renderPasses)
{
pass.Initialize(frameResource.cmd);
}
frameResource.ExecuteCommandBuffer(_graphicsDevice.CommandQueue);
WaitIdle();
}
public void Render()
{
_backBufferIndex = _swapChain.Get()->GetCurrentBackBufferIndex();
ref var frameResource = ref _frameResources[_backBufferIndex];
var cpuHandle = frameResource.rtvDescriptor.CpuHandle;
frameResource.ResetCommandBuffer();
frameResource.commandList.Get()->ResourceBarrierTransition(frameResource.backBuffer.Get(), ResourceStates.Present, ResourceStates.RenderTarget);
var clearColor = stackalloc float[4] { 1.0f, 0.0f, 1.0f, 1.0f };
frameResource.commandList.Get()->ClearRenderTargetView(cpuHandle, clearColor, 0, null);
var viewPort = new Viewport(_viewPortWidth, _viewPortHeight);
var rect = new Rect(0, 0, (int)_viewPortWidth, (int)_viewPortHeight);
frameResource.commandList.Get()->RSSetViewports(1, &viewPort);
frameResource.commandList.Get()->RSSetScissorRects(1, &rect);
frameResource.commandList.Get()->OMSetRenderTargets(1, &cpuHandle, false, null);
foreach (var pass in _renderPasses)
{
pass.Execute(frameResource.cmd);
}
frameResource.commandList.Get()->ResourceBarrierTransition(frameResource.backBuffer.Get(), ResourceStates.RenderTarget, ResourceStates.Present);
frameResource.ExecuteCommandBuffer(_graphicsDevice.CommandQueue.Ptr);
if (_swapChain.Get()->Present(1, PresentFlags.None).Failure)
{
throw new InvalidOperationException("Failed to present swap chain.");
}
WaitNextFrame();
}
public void WaitNextFrame()
{
ref var resource = ref _frameResources[_backBufferIndex];
if (_graphicsDevice.CommandQueue.Ptr->Signal((ID3D12Fence*)_fence.Get(), resource.fenceValue).Failure)
{
return;
}
var handle = new Handle((void*)_fenceEvent.SafeWaitHandle.DangerousGetHandle());
if (_fence.Get()->GetCompletedValue() < resource.fenceValue
&& _fence.Get()->SetEventOnCompletion(resource.fenceValue, handle).Success)
{
_fenceEvent.WaitOne();
}
resource.IncrementFenceValue();
}
public void WaitIdle()
{
ref var resource = ref _frameResources[_backBufferIndex];
_graphicsDevice.CommandQueue.Ptr->Signal((ID3D12Fence*)_fence.Get(), resource.fenceValue);
var handle = new Handle((void*)_fenceEvent.SafeWaitHandle.DangerousGetHandle());
if (_fence.Get()->SetEventOnCompletion(resource.fenceValue, handle).Success)
{
_fenceEvent.WaitOne();
resource.IncrementFenceValue();
}
}
public void Dispose()
{
if (_disposed)
{
return;
}
WaitIdle();
_swapChainPresenter.SwapChainPanelNative.SetSwapChain(IntPtr.Zero);
foreach (var pass in _renderPasses)
{
pass.Dispose();
}
foreach (var frameResource in _frameResources)
{
frameResource.Dispose();
}
_swapChain.Dispose();
_fence.Dispose();
_fenceEvent.Dispose();
_backBufferIndex = 0;
_disposed = true;
}
}

View File

@@ -1,309 +0,0 @@
using System.Runtime.CompilerServices;
using Win32;
using Win32.Graphics.Direct3D12;
namespace Ghost.Graphics.D3D12;
/// <summary>
/// Resource upload batch for efficiently uploading resources to GPU memory
/// </summary>
internal unsafe class ResourceUploadBatch : IDisposable
{
private ComPtr<ID3D12CommandAllocator> _commandAllocator;
private ComPtr<ID3D12GraphicsCommandList10> _commandList;
private ComPtr<ID3D12Fence> _fence;
private readonly GraphicsDevice _device = GraphicsPipeline.GraphicsDevice;
private readonly AutoResetEvent _fenceEvent = new(false);
private ulong _fenceValue;
private bool _isRecording;
private bool _disposed;
/// <summary>
/// Gets whether the batch is currently recording commands
/// </summary>
public bool IsRecording => _isRecording;
/// <summary>
/// Creates a new ResourceUploadBatch
/// </summary>
internal ResourceUploadBatch()
{
Initialize();
}
~ResourceUploadBatch()
{
Dispose();
}
private void Initialize()
{
ThrowIfFailed(_device.NativeDevice.Ptr->CreateCommandAllocator(
CommandListType.Direct,
__uuidof<ID3D12CommandAllocator>(),
_commandAllocator.GetVoidAddressOf()));
ThrowIfFailed(_device.NativeDevice.Ptr->CreateCommandList1(
0,
CommandListType.Direct,
CommandListFlags.None,
__uuidof<ID3D12GraphicsCommandList10>(),
_commandList.GetVoidAddressOf()));
ThrowIfFailed(_device.NativeDevice.Ptr->CreateFence(0, FenceFlags.None, __uuidof<ID3D12Fence>(), _fence.GetVoidAddressOf()));
}
/// <summary>
/// Begins recording upload commands
/// </summary>
public void Begin()
{
if (_isRecording)
{
throw new InvalidOperationException("Upload batch is already recording");
}
ThrowIfFailed(_commandAllocator.Get()->Reset());
ThrowIfFailed(_commandList.Get()->Reset(_commandAllocator.Get(), null));
_isRecording = true;
}
/// <summary>
/// Uploads buffer data to a resource
/// </summary>
/// <typeparam name="T">Type of data to upload</typeparam>
/// <param name="resource">Destination resource</param>
/// <param name="data">Source data</param>
public void Upload<T>(ID3D12Resource* resource, ReadOnlySpan<T> data)
where T : unmanaged
{
if (!_isRecording)
{
throw new InvalidOperationException("Upload batch is not recording");
}
var sizeInBytes = (uint)(data.Length * sizeof(T));
var uploadBuffer = GraphicsPipeline.ResourceAllocator.CreateUploadBuffer(sizeInBytes, true);
void* mappedData;
var uploadResource = uploadBuffer.ResourceHandle.GetAllocation().Resource;
uploadResource->Map(0, null, &mappedData);
fixed (T* dataPtr = data)
{
Unsafe.CopyBlock(mappedData, dataPtr, sizeInBytes);
}
uploadResource->Unmap(0, null);
// Copy from upload buffer to destination
_commandList.Get()->CopyBufferRegion(
resource,
0,
uploadResource,
0,
sizeInBytes);
}
/// <summary>
/// Uploads subresource data to a texture
/// </summary>
/// <param name="resource">Destination texture resource</param>
/// <param name="firstSubresource">First subresource index</param>
/// <param name="subresources">Subresource data array</param>
/// <param name="numSubresources">Number of subresources</param>
public void Upload(ID3D12Resource* resource, uint firstSubresource, SubresourceData* subresources, uint numSubresources)
{
if (!_isRecording)
{
throw new InvalidOperationException("Upload batch is not recording");
}
var resourceDesc = resource->GetDesc();
var requiredSize = GetRequiredIntermediateSize(resource, firstSubresource, numSubresources);
var uploadBuffer = GraphicsPipeline.ResourceAllocator.CreateUploadBuffer((uint)requiredSize, true);
UpdateSubresources(
resource,
uploadBuffer.ResourceHandle.GetAllocation().Resource,
0,
firstSubresource,
numSubresources,
subresources);
}
/// <summary>
/// Adds a resource transition barrier
/// </summary>
/// <param name="resource">Resource to transition</param>
/// <param name="stateBefore">State before transition</param>
/// <param name="stateAfter">State after transition</param>
public void Transition(ID3D12Resource* resource, ResourceStates stateBefore, ResourceStates stateAfter)
{
if (!_isRecording)
{
throw new InvalidOperationException("Upload batch is not recording");
}
_commandList.Get()->ResourceBarrierTransition(resource, stateBefore, stateAfter);
}
/// <summary>
/// Generates mipmaps for a texture (if supported)
/// </summary>
/// <param name="resource">Texture resource</param>
public void GenerateMips(GraphicsResource resource)
{
if (!_isRecording)
{
throw new InvalidOperationException("Upload batch is not recording");
}
// This would require compute shader implementation for mipmap generation
// For now, this is a placeholder - DirectXTK12 uses a compute shader approach
throw new NotImplementedException("Mipmap generation not yet implemented");
}
/// <summary>
/// Ends recording and submits the batch for execution
/// </summary>
/// <returns>Future that completes when upload is finished</returns>
public ulong End()
{
if (!_isRecording)
{
throw new InvalidOperationException("Upload batch is not recording");
}
ThrowIfFailed(_commandList.Get()->Close());
_device.CommandQueue.Ptr->ExecuteCommandLists(1, (ID3D12CommandList**)_commandList.GetAddressOf());
ThrowIfFailed(_device.CommandQueue.Ptr->Signal(_fence.Get(), ++_fenceValue));
_isRecording = false;
return _fenceValue;
}
/// <summary>
/// Waits for the upload batch to complete
/// </summary>
/// <param name="fenceValue">Fence value to wait for</param>
public void WaitForCompletion(ulong fenceValue)
{
if (_fence.Get()->GetCompletedValue() < fenceValue)
{
var handle = new Handle((void*)_fenceEvent.SafeWaitHandle.DangerousGetHandle());
ThrowIfFailed(_fence.Get()->SetEventOnCompletion(fenceValue, handle));
_fenceEvent.WaitOne();
}
}
private ulong GetRequiredIntermediateSize(ID3D12Resource* destinationResource, uint firstSubresource, uint numSubresources)
{
var resourceDesc = destinationResource->GetDesc();
ulong requiredSize = 0;
var numRows = stackalloc uint[(int)numSubresources];
var rowSizeInBytes = stackalloc ulong[(int)numSubresources];
_device.NativeDevice.Ptr->GetCopyableFootprints(
&resourceDesc,
firstSubresource,
numSubresources,
0,
null,
numRows,
rowSizeInBytes,
&requiredSize);
return requiredSize;
}
private void UpdateSubresources(ID3D12Resource* destinationResource, ID3D12Resource* intermediate, ulong intermediateOffset, uint firstSubresource, uint numSubresources, SubresourceData* pSubresourceData)
{
var destDesc = destinationResource->GetDesc();
var layouts = stackalloc PlacedSubresourceFootprint[(int)numSubresources];
var numRows = stackalloc uint[(int)numSubresources];
var rowSizeInBytes = stackalloc ulong[(int)numSubresources];
ulong requiredSize = 0;
_device.NativeDevice.Ptr->GetCopyableFootprints(
&destDesc,
firstSubresource,
numSubresources,
intermediateOffset,
layouts,
numRows,
rowSizeInBytes,
&requiredSize);
void* pMappedData;
ThrowIfFailed(intermediate->Map(0, null, &pMappedData));
for (uint i = 0; i < numSubresources; i++)
{
var srcData = pSubresourceData[i];
var destLayout = layouts[i];
var pDestSlice = (byte*)pMappedData + destLayout.Offset;
var pSrcSlice = (byte*)srcData.pData;
for (uint y = 0; y < numRows[i]; y++)
{
var pDestRow = pDestSlice + (y * destLayout.Footprint.RowPitch);
var pSrcRow = pSrcSlice + (y * srcData.RowPitch);
Unsafe.CopyBlockUnaligned(pDestRow, pSrcRow, (uint)rowSizeInBytes[i]);
}
}
intermediate->Unmap(0, null);
if (destDesc.Dimension == ResourceDimension.Buffer)
{
_commandList.Get()->CopyBufferRegion(destinationResource, 0, intermediate, intermediateOffset, requiredSize);
}
else
{
for (uint i = 0; i < numSubresources; i++)
{
var destLocation = new TextureCopyLocation(destinationResource, firstSubresource + i);
var srcLocation = new TextureCopyLocation(intermediate, in layouts[i]);
var box = new Box
{
left = 0,
top = 0,
front = 0,
right = (uint)destDesc.Width,
bottom = destDesc.Height,
back = destDesc.DepthOrArraySize
};
_commandList.Get()->CopyTextureRegion(&destLocation, 0, 0, 0, &srcLocation, &box);
}
}
}
public void Dispose()
{
if (_disposed)
{
return;
}
if (_isRecording)
{
_commandList.Get()->Close();
_isRecording = false;
}
_fence.Dispose();
_commandList.Dispose();
_commandAllocator.Dispose();
_fenceEvent.Dispose();
_disposed = true;
GC.SuppressFinalize(this);
}
}

View File

@@ -1,215 +0,0 @@
using Ghost.Core;
using System.Diagnostics;
using Win32;
using Win32.Graphics.Direct3D12;
using DescriptorIndex = System.UInt32;
namespace Ghost.Graphics.D3D12.Utilities;
/// <summary>
/// Specialized descriptor heap allocator for SM 6.6 bindless rendering with ResourceDescriptorHeap[index].
/// This allocator maintains a 1:1 relationship between allocation indices and shader indices.
/// </summary>
internal unsafe struct BindlessDescriptorHeap : IDisposable
{
private const DescriptorIndex _INVALID_DESCRIPTOR_INDEX = ~0u;
private readonly ComPtr<ID3D12Device14> _device;
private readonly Lock _lock = new();
private ComPtr<ID3D12DescriptorHeap> _bindlessHeap;
private CpuDescriptorHandle _startCpuHandle;
private GpuDescriptorHandle _startGpuHandle;
private Queue<uint> _freeDescriptors;
private uint _stride;
public DescriptorHeapType HeapType
{
get;
}
public uint NumDescriptors
{
get; private set;
}
public uint NumAllocatedDescriptors
{
get; private set;
}
public uint Stride => _stride;
public readonly ConstPtr<ID3D12DescriptorHeap> BindlessHeap => new(_bindlessHeap.Get());
public BindlessDescriptorHeap(ComPtr<ID3D12Device14> device, uint numDescriptors = 10000)
{
_device = device;
device.Get()->AddRef();
HeapType = DescriptorHeapType.CbvSrvUav;
NumDescriptors = numDescriptors;
_stride = device.Get()->GetDescriptorHandleIncrementSize(DescriptorHeapType.CbvSrvUav);
_freeDescriptors = new Queue<uint>();
var success = AllocateResources(numDescriptors);
Debug.Assert(success);
_bindlessHeap.Get()->SetName("bindless");
}
public DescriptorIndex AllocateDescriptor()
{
lock (_lock)
{
if (_freeDescriptors.Count == 0)
{
// Try to grow the heap
if (!Grow(NumDescriptors * 2))
{
return _INVALID_DESCRIPTOR_INDEX;
}
}
var index = _freeDescriptors.Dequeue();
NumAllocatedDescriptors++;
return index;
}
}
public DescriptorIndex AllocateDescriptors(uint count)
{
lock (_lock)
{
if (_freeDescriptors.Count < count)
{
// Try to grow the heap
var newSize = Math.Max(NumDescriptors * 2, NumDescriptors + count);
if (!Grow(newSize))
{
return _INVALID_DESCRIPTOR_INDEX;
}
}
var baseIndex = _freeDescriptors.Dequeue();
for (uint i = 1; i < count; i++)
{
_freeDescriptors.Dequeue();
}
NumAllocatedDescriptors += count;
return baseIndex;
}
}
public void ReleaseDescriptor(DescriptorIndex index)
{
lock (_lock)
{
if (index >= NumDescriptors)
{
return;
}
_freeDescriptors.Enqueue(index);
NumAllocatedDescriptors--;
}
}
public void ReleaseDescriptors(DescriptorIndex baseIndex, uint count = 1)
{
lock (_lock)
{
for (uint i = 0; i < count; i++)
{
var index = baseIndex + i;
if (index >= NumDescriptors)
{
continue;
}
_freeDescriptors.Enqueue(index);
}
NumAllocatedDescriptors -= count;
}
}
public readonly CpuDescriptorHandle GetCpuHandle(DescriptorIndex index)
{
var handle = _startCpuHandle;
return handle.Offset((int)index, _stride);
}
public readonly GpuDescriptorHandle GetGpuHandle(DescriptorIndex index)
{
var handle = _startGpuHandle;
return handle.Offset((int)index, _stride);
}
public readonly GpuDescriptorHandle GetGpuHandleStart()
{
return _startGpuHandle;
}
private bool AllocateResources(uint numDescriptors)
{
NumDescriptors = numDescriptors;
_bindlessHeap.Dispose();
var heapDesc = new DescriptorHeapDescription
{
Type = HeapType,
NumDescriptors = numDescriptors,
Flags = DescriptorHeapFlags.ShaderVisible, // Must be shader visible for SM 6.6
NodeMask = 0
};
fixed (void* heapPtr = &_bindlessHeap)
{
var hr = _device.Get()->CreateDescriptorHeap(&heapDesc, __uuidof<ID3D12DescriptorHeap>(), (void**)heapPtr);
if (hr.Failure)
{
return false;
}
}
_startCpuHandle = _bindlessHeap.Get()->GetCPUDescriptorHandleForHeapStart();
_startGpuHandle = _bindlessHeap.Get()->GetGPUDescriptorHandleForHeapStart();
// Initialize free descriptor queue
_freeDescriptors.Clear();
for (uint i = 0; i < numDescriptors; i++)
{
_freeDescriptors.Enqueue(i);
}
return true;
}
private bool Grow(uint minRequiredSize)
{
var oldSize = NumDescriptors;
var newSize = Math.Max(minRequiredSize, oldSize * 2);
var oldHeap = _bindlessHeap;
if (!AllocateResources(newSize))
{
return false;
}
// Copy old descriptors to new heap
if (oldHeap.Get() is not null)
{
_device.Get()->CopyDescriptorsSimple(oldSize, _startCpuHandle, oldHeap.Get()->GetCPUDescriptorHandleForHeapStart(), HeapType);
}
return true;
}
public void Dispose()
{
_bindlessHeap.Dispose();
}
}

View File

@@ -1,16 +1,17 @@
using System.Diagnostics;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Diagnostics;
using System.Numerics;
using Win32;
using Win32.Graphics.Direct3D12;
using DescriptorIndex = System.UInt32;
using DescriptorIndex = System.Int32;
namespace Ghost.Graphics.D3D12.Utilities;
internal unsafe struct D3D12DescriptorHeap : IDisposable
{
private const DescriptorIndex _INVALID_DESCRIPTOR_INDEX = ~0u;
private const DescriptorIndex _INVALID_DESCRIPTOR_INDEX = -1;
private readonly ID3D12Device14* _pDevice;
private readonly D3D12RenderDevice _device;
private ComPtr<ID3D12DescriptorHeap> _heap;
private ComPtr<ID3D12DescriptorHeap> _shaderVisibleHeap;
@@ -18,7 +19,10 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable
private CpuDescriptorHandle _startCpuHandleShaderVisible;
private GpuDescriptorHandle _startGpuHandleShaderVisible;
private DescriptorIndex _searchStart;
private bool[] _allocatedDescriptors = [];
private UnsafeArray<bool> _allocatedDescriptors;
private readonly DescriptorIndex _dynamicHeapStart;
private DescriptorIndex _currentDynamicOffset;
private readonly Lock _lock = new();
@@ -27,12 +31,12 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable
get;
}
public uint NumDescriptors
public int NumDescriptors
{
get; private set;
}
public uint NumAllocatedDescriptors
public int NumAllocatedDescriptors
{
get; private set;
}
@@ -50,14 +54,19 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable
public readonly ID3D12DescriptorHeap* Heap => _heap.Get();
public readonly ID3D12DescriptorHeap* ShaderVisibleHeap => _shaderVisibleHeap.Get();
public D3D12DescriptorHeap(string name, ID3D12Device14* device, DescriptorHeapType type, uint numDescriptors)
public D3D12DescriptorHeap(string name, D3D12RenderDevice device, DescriptorHeapType type, int numDescriptors, int dynamicHeapStart)
{
_pDevice = device;
numDescriptors = Math.Max(64, numDescriptors);
_device = device;
HeapType = type;
NumDescriptors = numDescriptors;
ShaderVisible = type == DescriptorHeapType.CbvSrvUav || type == DescriptorHeapType.Sampler;
Stride = device->GetDescriptorHandleIncrementSize(type);
Stride = device.NativeDevice->GetDescriptorHandleIncrementSize(type);
_dynamicHeapStart = Math.Clamp(dynamicHeapStart, 0, numDescriptors);
_currentDynamicOffset = 0;
var success = AllocateResources(numDescriptors);
Debug.Assert(success);
@@ -71,11 +80,11 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable
public DescriptorIndex AllocateDescriptor() => AllocateDescriptors(1);
public DescriptorIndex AllocateDescriptors(uint count)
public DescriptorIndex AllocateDescriptors(int count)
{
lock (_lock)
{
DescriptorIndex foundIndex = 0;
var foundIndex = 0;
uint freeCount = 0;
var found = false;
@@ -99,15 +108,10 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable
}
}
if (!found)
if (!found || foundIndex >= _dynamicHeapStart)
{
foundIndex = NumDescriptors;
if (!Grow(NumDescriptors + count))
{
Debug.WriteLine("ERROR: Failed to grow a descriptor heap!");
return _INVALID_DESCRIPTOR_INDEX;
}
Debug.Assert(false, "ERROR: Descriptor heap is full!");
return _INVALID_DESCRIPTOR_INDEX;
}
for (var index = foundIndex; index < foundIndex + count; index++)
@@ -121,20 +125,58 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable
}
}
public DescriptorIndex AllocateDescriptorDynamic() => AllocateDescriptorsDynamic(1);
public DescriptorIndex AllocateDescriptorsDynamic(int count)
{
if (count <= 0)
{
throw new ArgumentOutOfRangeException(nameof(count), "Count must be greater than zero.");
}
// NOTE: In dynamic allocation, we use arena-style allocation without freeing.
// We reset the offset at the beginning of each frame instead.
lock (_lock)
{
var baseIndex = _currentDynamicOffset + _dynamicHeapStart;
_currentDynamicOffset += count;
var requiredSize = baseIndex + count;
if (requiredSize > NumDescriptors)
{
if (!Grow(requiredSize))
{
Debug.Assert(false, "ERROR: Failed to grow a descriptor heap!");
return _INVALID_DESCRIPTOR_INDEX;
}
}
NumAllocatedDescriptors += count;
return baseIndex;
}
}
public void ReleaseDescriptor(DescriptorIndex index) => ReleaseDescriptors(index, 1);
public void ReleaseDescriptors(DescriptorIndex baseIndex, uint count = 1)
public void ReleaseDescriptors(DescriptorIndex baseIndex, int count = 1)
{
if (count == 0)
{
return;
}
if (baseIndex < _dynamicHeapStart)
{
// Dynamic allocations are not released individually.
return;
}
lock (_lock)
{
for (var index = baseIndex; index < baseIndex + count; index++)
{
#if DEBUG
#if DEBUG || GHOST_EDITOR
if (!_allocatedDescriptors[index])
{
Debug.WriteLine("Error: Attempted to release an un-allocated descriptor");
@@ -153,30 +195,73 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable
}
}
public void ResetDynamicHeap()
{
lock (_lock)
{
_currentDynamicOffset = 0;
}
}
public readonly CpuDescriptorHandle GetCpuHandle(DescriptorIndex index)
{
var handle = _startCpuHandle;
return handle.Offset((int)index, Stride);
if (index < 0 || index >= NumDescriptors)
{
throw new ArgumentOutOfRangeException(nameof(index), "Descriptor index is out of range.");
}
return _startCpuHandle.Offset(index, Stride);
}
public readonly CpuDescriptorHandle GetCpuHandleShaderVisible(DescriptorIndex index)
{
var handle = _startCpuHandleShaderVisible;
return handle.Offset((int)index, Stride);
if (index < 0 || index >= NumDescriptors)
{
throw new ArgumentOutOfRangeException(nameof(index), "Descriptor index is out of range.");
}
if (!ShaderVisible)
{
throw new InvalidOperationException("Descriptor heap is not shader visible.");
}
return _startCpuHandleShaderVisible.Offset(index, Stride);
}
public readonly GpuDescriptorHandle GetGpuHandle(DescriptorIndex index)
{
var handle = _startGpuHandleShaderVisible;
return handle.Offset((int)index, Stride);
if (index < 0 || index >= NumDescriptors)
{
throw new ArgumentOutOfRangeException(nameof(index), "Descriptor index is out of range.");
}
if (!ShaderVisible)
{
throw new InvalidOperationException("Descriptor heap is not shader visible.");
}
return _startGpuHandleShaderVisible.Offset(index, Stride);
}
public readonly void CopyToShaderVisibleHeap(DescriptorIndex index, uint count = 1)
public DescriptorIndex CopyToPersistentHeap(DescriptorIndex index, int count = 1)
{
_pDevice->CopyDescriptorsSimple(count, GetCpuHandleShaderVisible(index), GetCpuHandle(index), HeapType);
if (index < _dynamicHeapStart)
{
return index;
}
var newLocation = AllocateDescriptors(count);
_device.NativeDevice->CopyDescriptorsSimple((uint)count, GetCpuHandle(index), GetCpuHandle(newLocation), HeapType);
return newLocation;
}
private bool AllocateResources(uint numDescriptors)
public readonly void CopyToShaderVisibleHeap(DescriptorIndex index, int count = 1)
{
_device.NativeDevice->CopyDescriptorsSimple((uint)count, GetCpuHandleShaderVisible(index), GetCpuHandle(index), HeapType);
}
private bool AllocateResources(int numDescriptors)
{
NumDescriptors = numDescriptors;
_heap.Dispose();
@@ -185,14 +270,14 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable
DescriptorHeapDescription heapDesc = new()
{
Type = HeapType,
NumDescriptors = numDescriptors,
NumDescriptors = (uint)numDescriptors,
Flags = DescriptorHeapFlags.None,
NodeMask = 0
};
fixed (void* heapPtr = &_heap)
{
var hr = _pDevice->CreateDescriptorHeap(&heapDesc, __uuidof<ID3D12DescriptorHeap>(), (void**)heapPtr);
var hr = _device.NativeDevice->CreateDescriptorHeap(&heapDesc, __uuidof<ID3D12DescriptorHeap>(), (void**)heapPtr);
if (hr.Failure)
{
return false;
@@ -200,7 +285,7 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable
}
_startCpuHandle = _heap.Get()->GetCPUDescriptorHandleForHeapStart();
Array.Resize(ref _allocatedDescriptors, (int)numDescriptors);
_allocatedDescriptors.Resize(numDescriptors);
if (ShaderVisible)
{
@@ -208,7 +293,7 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable
fixed (void* heapPtr = &_shaderVisibleHeap)
{
var hr = _pDevice->CreateDescriptorHeap(&heapDesc, __uuidof<ID3D12DescriptorHeap>(), (void**)heapPtr);
var hr = _device.NativeDevice->CreateDescriptorHeap(&heapDesc, __uuidof<ID3D12DescriptorHeap>(), (void**)heapPtr);
if (hr.Failure)
{
return false;
@@ -222,23 +307,23 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable
return true;
}
private bool Grow(uint minRequiredSize)
private bool Grow(int minRequiredSize)
{
var oldSize = NumDescriptors;
var newSize = BitOperations.RoundUpToPowerOf2(minRequiredSize);
var newSize = (int)BitOperations.RoundUpToPowerOf2((uint)minRequiredSize);
var oldHeap = _heap;
using var oldHeap = _heap;
if (!AllocateResources(newSize))
{
return false;
}
_pDevice->CopyDescriptorsSimple(oldSize, _startCpuHandle, oldHeap.Get()->GetCPUDescriptorHandleForHeapStart(), HeapType);
_device.NativeDevice->CopyDescriptorsSimple((uint)oldSize, _startCpuHandle, oldHeap.Get()->GetCPUDescriptorHandleForHeapStart(), HeapType);
if (_shaderVisibleHeap.Get() is not null)
if (_shaderVisibleHeap.Get() != null)
{
_pDevice->CopyDescriptorsSimple(oldSize, _startCpuHandleShaderVisible, oldHeap.Get()->GetCPUDescriptorHandleForHeapStart(), HeapType);
_device.NativeDevice->CopyDescriptorsSimple((uint)oldSize, _startCpuHandleShaderVisible, oldHeap.Get()->GetCPUDescriptorHandleForHeapStart(), HeapType);
}
return true;
@@ -247,7 +332,15 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable
/// <inheritdoc />
public void Dispose()
{
#if DEBUG
if (NumAllocatedDescriptors > 0)
{
Debug.WriteLine($"Warning: Descriptor heap of type {HeapType} is being disposed with {NumAllocatedDescriptors} allocated descriptors.");
}
#endif
_heap.Dispose();
_shaderVisibleHeap.Dispose();
_allocatedDescriptors.Dispose();
}
}

View File

@@ -1,9 +1,10 @@
using Misaki.HighPerformance.LowLevel.Buffer;
using Ghost.Core;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
namespace Ghost.Graphics.Data;
internal struct CBufferCache
internal struct CBufferCache : IDisposable
{
public UnsafeArray<byte> CpuData
{
@@ -11,18 +12,23 @@ internal struct CBufferCache
set;
}
public BufferHandle GpuResource
public Handle<GraphicsBuffer> GpuResource
{
get;
}
private readonly uint _alignedSize;
internal unsafe CBufferCache(BufferHandle buffer, uint bufferSize)
internal unsafe CBufferCache(Handle<GraphicsBuffer> buffer, uint bufferSize)
{
_alignedSize = (bufferSize + 255u) & ~255u;
CpuData = new((int)_alignedSize, Allocator.Persistent);
GpuResource = buffer;
}
public readonly void Dispose()
{
CpuData.Dispose();
}
}

View File

@@ -1,78 +0,0 @@
using Win32.Graphics.Direct3D12;
namespace Ghost.Graphics.Data;
/// <summary>
/// Render target view (RTV) descriptor.
/// </summary>
public readonly struct RenderTargetDescriptor
{
public uint Index
{
get; init;
}
public static RenderTargetDescriptor Invalid => new() { Index = ~0u };
public bool IsValid => Index != ~0u;
}
/// <summary>
/// Depth stencil view (DSV) descriptor.
/// </summary>
public readonly struct DepthStencilDescriptor
{
public uint Index
{
get; init;
}
public static DepthStencilDescriptor Invalid => new() { Index = ~0u };
public bool IsValid => Index != ~0u;
}
/// <summary>
/// Shader resource view (SRV) descriptor.
/// </summary>
public readonly struct ShaderResourceDescriptor
{
public uint Index
{
get; init;
}
public static ShaderResourceDescriptor Invalid => new() { Index = ~0u };
public bool IsValid => Index != ~0u;
}
/// <summary>
/// Sampler descriptor.
/// </summary>
public readonly struct SamplerDescriptor
{
public uint Index
{
get; init;
}
public static SamplerDescriptor Invalid => new() { Index = ~0u };
public bool IsValid => Index != ~0u;
}
/// <summary>
/// Bindless descriptor
/// </summary>
public readonly struct BindlessDescriptor
{
public uint Index
{
get; init;
}
public static BindlessDescriptor Invalid => new() { Index = ~0u };
public bool IsValid => Index != ~0u;
}

View File

@@ -0,0 +1,188 @@
using Ghost.Core;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Numerics;
using System.Runtime.CompilerServices;
namespace Ghost.Graphics.Data;
public struct Material : IHandleType
{
internal UnsafeArray<CBufferCache> _cBufferCaches;
public Identifier<Shader> Shader
{
get; internal set;
}
internal void Dispose()
{
foreach (var cache in _cBufferCaches)
{
cache.Dispose();
}
_cBufferCaches.Dispose();
}
}
public ref struct MaterialAccessor
{
private ref Material _materialData;
private ref Shader _shader;
private readonly IResourceDatabase _resourceDatabase;
internal MaterialAccessor(ref Material materialData, IResourceDatabase resourceDatabase)
{
_resourceDatabase = resourceDatabase;
_materialData = ref materialData;
_shader = ref resourceDatabase.GetShaderReference(materialData.Shader);
}
private readonly unsafe void WriteToCache<T>(int propertyId, in T value)
where T : unmanaged
{
if (propertyId == -1)
{
throw new ArgumentException("Property ID is invalid.");
}
var propInfo = _shader.Properties[propertyId];
if (propInfo.Size != sizeof(T))
{
throw new ArgumentException($"Property '{propertyId}' has a size mismatch. Expected {propInfo.Size} bytes, but got {sizeof(T)} bytes.");
}
var cache = _materialData._cBufferCaches[propInfo.CBufferIndex];
Unsafe.WriteUnaligned(ref cache.CpuData[(int)propInfo.ByteOffset], value);
}
/// <summary>
/// Sets a float property in the material's constant buffer.
/// </summary>
/// <param name="propertyId">The ID of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void SetFloat(int propertyId, in float value)
{
WriteToCache(propertyId, in value);
}
/// <summary>
/// Sets a float property in the material's constant buffer.
/// </summary>
/// <param name="propertyName">The name of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void SetFloat(string propertyName, in float value)
{
SetFloat(_shader.GetPropertyId(propertyName), in value);
}
/// <summary>
/// Sets a uint property in the material's constant buffer (useful for texture indices).
/// </summary>
/// <param name="propertyId">The ID of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void SetUInt(int propertyId, in uint value)
{
WriteToCache(propertyId, in value);
}
/// <summary>
/// Sets a uint property in the material's constant buffer (useful for texture indices).
/// </summary>
/// <param name="propertyName">The name of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void SetUInt(string propertyName, in uint value)
{
SetUInt(_shader.GetPropertyId(propertyName), in value);
}
/// <summary>
/// Sets a Vector property in the material's constant buffer.
/// </summary>
/// <param name="propertyId">The ID of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void SetVector(int propertyId, in Vector4 value)
{
WriteToCache(propertyId, in value);
}
/// <summary>
/// Sets a Vector property in the material's constant buffer.
/// </summary>
/// <param name="propertyName">The name of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void SetVector(string propertyName, in Vector4 value)
{
SetVector(_shader.GetPropertyId(propertyName), in value);
}
/// <summary>
/// Sets a Matrix property in the material's constant buffer.
/// </summary>
/// <param name="propertyId">The ID of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void SetMatrix(int propertyId, in Matrix4x4 value)
{
WriteToCache(propertyId, in value);
}
/// <summary>
/// Sets a Matrix property in the material's constant buffer.
/// </summary>
/// <param name="propertyName">The name of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void SetMatrix(string propertyName, in Matrix4x4 value)
{
SetMatrix(_shader.GetPropertyId(propertyName), in value);
}
/// <summary>
/// Sets a texture index for a shader property (for bindless texture access)
/// </summary>
/// <param name="propertyName">The name of the shader property (e.g., "_TextureIndex1")</param>
/// <param name="texture">The bindless texture to reference</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void SetTextureIndex(string propertyName, Handle<Texture> texture)
{
SetUInt(propertyName, texture.BindlessDescriptor.Index);
}
/// <summary>
/// Sets the mesh buffer indices for bindless vertex and index buffer access
/// </summary>
/// <param name="mesh">The mesh whose buffer indices to set</param>
/// <param name="vertexBufferIndexProperty">The name of the vertex buffer index property</param>
/// <param name="indexBufferIndexProperty">The name of the index buffer index property</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void SetMeshBufferIndex(ref readonly Mesh mesh, string vertexBufferIndexProperty = "_VertexBufferIndex", string indexBufferIndexProperty = "_IndexBufferIndex")
{
SetUInt(vertexBufferIndexProperty, mesh.vertexBuffer.BindlessDescriptor.Index);
SetUInt(indexBufferIndexProperty, mesh.indexBuffer.BindlessDescriptor.Index);
}
/// <summary>
/// Uploads all cached material data to the GPU using the specified command buffer.
/// </summary>
/// <param name="cmb">The command buffer used to perform the upload operations to the GPU. Cannot be null.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void UploadMaterialData(ICommandBuffer cmb)
{
foreach (var cache in _materialData._cBufferCaches)
{
cmb.Upload(cache.GpuResource, cache.CpuData.AsSpan());
}
}
}

View File

@@ -1,376 +0,0 @@
using Ghost.Core;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Numerics;
using System.Runtime.CompilerServices;
namespace Ghost.Graphics.Data;
public struct Material
{
internal UnsafeArray<CBufferCache> _cBufferCaches;
public Identifier<Shader> Shader
{
get; internal set;
}
}
public ref struct MaterialAccessor
{
private ref Material _materialData;
private ref Shader _shader;
public MaterialAccessor(ref Material materialData, IResourceDatabase resourceDatabase)
{
_materialData = ref materialData;
_shader = ref resourceDatabase.GetShaderReference(materialData.Shader);
}
private readonly unsafe void WriteToCache<T>(int propertyId, in T value)
where T : unmanaged
{
if (propertyId == -1)
{
throw new ArgumentException("Property ID is invalid.");
}
var propInfo = _shader.Properties[propertyId];
if (propInfo.Size != sizeof(T))
{
throw new ArgumentException($"Property '{propertyId}' has a size mismatch. Expected {propInfo.Size} bytes, but got {sizeof(T)} bytes.");
}
var cache = _materialData._cBufferCaches[propInfo.CBufferIndex];
Unsafe.WriteUnaligned(ref cache.CpuData[(int)propInfo.ByteOffset], value);
}
/// <summary>
/// Sets a float property in the material's constant buffer.
/// </summary>
/// <param name="propertyId">The ID of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void SetFloat(int propertyId, in float value)
{
WriteToCache(propertyId, in value);
}
/// <summary>
/// Sets a float property in the material's constant buffer.
/// </summary>
/// <param name="propertyName">The name of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetFloat(string propertyName, in float value)
{
SetFloat(_shader.GetPropertyId(propertyName), in value);
}
/// <summary>
/// Sets a uint property in the material's constant buffer (useful for texture indices).
/// </summary>
/// <param name="propertyId">The ID of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void SetUInt(int propertyId, in uint value)
{
WriteToCache(propertyId, in value);
}
/// <summary>
/// Sets a uint property in the material's constant buffer (useful for texture indices).
/// </summary>
/// <param name="propertyName">The name of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetUInt(string propertyName, in uint value)
{
SetUInt(_shader.GetPropertyId(propertyName), in value);
}
/// <summary>
/// Sets a Vector property in the material's constant buffer.
/// </summary>
/// <param name="propertyId">The ID of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void SetVector(int propertyId, in Vector4 value)
{
WriteToCache(propertyId, in value);
}
/// <summary>
/// Sets a Vector property in the material's constant buffer.
/// </summary>
/// <param name="propertyName">The name of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetVector(string propertyName, in Vector4 value)
{
SetVector(_shader.GetPropertyId(propertyName), in value);
}
/// <summary>
/// Sets a Matrix property in the material's constant buffer.
/// </summary>
/// <param name="propertyId">The ID of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void SetMatrix(int propertyId, in Matrix4x4 value)
{
WriteToCache(propertyId, in value);
}
/// <summary>
/// Sets a Matrix property in the material's constant buffer.
/// </summary>
/// <param name="propertyName">The name of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetMatrix(string propertyName, in Matrix4x4 value)
{
SetMatrix(_shader.GetPropertyId(propertyName), in value);
}
/// <summary>
/// Sets a texture index for a shader property (for bindless texture access)
/// </summary>
/// <param name="propertyName">The name of the shader property (e.g., "_TextureIndex1")</param>
/// <param name="texture">The bindless texture to reference</param>
public void SetTexture(string propertyName, Texture2D texture)
{
SetUInt(propertyName, texture.DescriptorIndex);
}
/// <summary>
/// Sets the mesh buffer indices for bindless vertex and index buffer access
/// </summary>
/// <param name="mesh">The mesh whose buffer indices to set</param>
/// <param name="vertexBufferIndexProperty">The name of the vertex buffer index property (e.g., "_VertexBufferIndex")</param>
/// <param name="indexBufferIndexProperty">The name of the index buffer index property (e.g., "_IndexBufferIndex")</param>
public void SetMeshBuffer(MeshClass mesh, string vertexBufferIndexProperty = "_VertexBufferIndex", string indexBufferIndexProperty = "_IndexBufferIndex")
{
SetUInt(vertexBufferIndexProperty, mesh.VertexBufferDescriptorIndex);
SetUInt(indexBufferIndexProperty, mesh.IndexBufferDescriptorIndex);
}
/// <summary>
/// Uploads all cached material data to the GPU using the specified command buffer.
/// </summary>
/// <param name="cmb">The command buffer used to perform the upload operations to the GPU. Cannot be null.</param>
public readonly void UploadMaterialData(ICommandBuffer cmb)
{
foreach (var cache in _materialData._cBufferCaches)
{
cmb.Upload(cache.GpuResource, cache.CpuData.AsSpan());
}
}
}
/// <summary>
/// Material implementation for bindless rendering with SM 6.6 support
/// </summary>
public unsafe class MaterialClass : IDisposable
{
private readonly UnsafeArray<CBufferCache> _cbufferCaches;
private bool _disposed;
internal ReadOnlySpan<CBufferCache> CBufferCaches => _cbufferCaches.AsSpan();
public Identifier<Shader> Shader
{
get; set;
}
public MaterialClass(Identifier<Shader> shader, IResourceAllocator resourceAllocator, IResourceDatabase resourceDatabase)
{
Shader = shader;
var shaderResource = resourceDatabase.GetShader(shader);
if (shaderResource.ConstantBuffers.Count > 0)
{
var maxSlot = shaderResource.ConstantBuffers.Max(cb => cb.RegisterSlot);
_cbufferCaches = new UnsafeArray<CBufferCache>((int)maxSlot + 1, Allocator.Persistent);
foreach (var cbufferInfo in shaderResource.ConstantBuffers)
{
var desc = new BufferDesc
{
Size = cbufferInfo.Size,
Usage = BufferUsage.Constant,
MemoryType = MemoryType.Default,
};
var buffer = resourceAllocator.CreateBuffer(in desc);
_cbufferCaches[cbufferInfo.RegisterSlot] = new CBufferCache(buffer, cbufferInfo.Size);
}
}
}
/// <summary>
/// Sets a float property in the material's constant buffer.
/// </summary>
/// <param name="propertyId">The ID of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetFloat(int propertyId, in float value)
{
WriteToCache(propertyId, in value);
}
/// <summary>
/// Sets a float property in the material's constant buffer.
/// </summary>
/// <param name="propertyName">The name of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetFloat(string propertyName, in float value)
{
SetFloat(Shader.GetPropertyId(propertyName), in value);
}
/// <summary>
/// Sets a uint property in the material's constant buffer (useful for texture indices).
/// </summary>
/// <param name="propertyId">The ID of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetUInt(int propertyId, in uint value)
{
WriteToCache(propertyId, in value);
}
/// <summary>
/// Sets a uint property in the material's constant buffer (useful for texture indices).
/// </summary>
/// <param name="propertyName">The name of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetUInt(string propertyName, in uint value)
{
SetUInt(Shader.GetPropertyId(propertyName), in value);
}
/// <summary>
/// Sets a Vector property in the material's constant buffer.
/// </summary>
/// <param name="propertyId">The ID of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetVector(int propertyId, in Vector4 value)
{
WriteToCache(propertyId, in value);
}
/// <summary>
/// Sets a Vector property in the material's constant buffer.
/// </summary>
/// <param name="propertyName">The name of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetVector(string propertyName, in Vector4 value)
{
SetVector(Shader.GetPropertyId(propertyName), in value);
}
/// <summary>
/// Sets a Matrix property in the material's constant buffer.
/// </summary>
/// <param name="propertyId">The ID of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetMatrix(int propertyId, in Matrix4x4 value)
{
WriteToCache(propertyId, in value);
}
/// <summary>
/// Sets a Matrix property in the material's constant buffer.
/// </summary>
/// <param name="propertyName">The name of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetMatrix(string propertyName, in Matrix4x4 value)
{
SetMatrix(Shader.GetPropertyId(propertyName), in value);
}
/// <summary>
/// Sets a texture index for a shader property (for bindless texture access)
/// </summary>
/// <param name="propertyName">The name of the shader property (e.g., "_TextureIndex1")</param>
/// <param name="texture">The bindless texture to reference</param>
public void SetTexture(string propertyName, Texture2D texture)
{
SetUInt(propertyName, texture.DescriptorIndex);
}
/// <summary>
/// Sets the mesh buffer indices for bindless vertex and index buffer access
/// </summary>
/// <param name="mesh">The mesh whose buffer indices to set</param>
/// <param name="vertexBufferIndexProperty">The name of the vertex buffer index property (e.g., "_VertexBufferIndex")</param>
/// <param name="indexBufferIndexProperty">The name of the index buffer index property (e.g., "_IndexBufferIndex")</param>
public void SetMeshBuffer(MeshClass mesh, string vertexBufferIndexProperty = "_VertexBufferIndex", string indexBufferIndexProperty = "_IndexBufferIndex")
{
SetUInt(vertexBufferIndexProperty, mesh.VertexBufferDescriptorIndex);
SetUInt(indexBufferIndexProperty, mesh.IndexBufferDescriptorIndex);
}
private unsafe void WriteToCache<T>(int propertyId, in T value)
where T : unmanaged
{
if (propertyId == -1)
{
throw new ArgumentException("Property ID is invalid.");
}
var propInfo = Shader.Properties[propertyId];
if (propInfo.Size != sizeof(T))
{
throw new ArgumentException($"Property '{propInfo.Name}' has a size mismatch. Expected {propInfo.Size} bytes, but got {sizeof(T)} bytes.");
}
var cache = _cbufferCaches[propInfo.CBufferIndex];
Unsafe.WriteUnaligned(ref cache.CpuData[(int)propInfo.ByteOffset], value);
}
/// <summary>
/// Uploads the material data to the GPU.
/// </summary>
public void UploadMaterialData()
{
foreach (var cache in _cbufferCaches)
{
cache.UploadToGpu();
}
}
public void Dispose()
{
if (_disposed)
{
return;
}
foreach (var cache in _cbufferCaches)
{
cache.Dispose();
}
// NOTE: We don't dispose the textures here as they might be shared
// The user is responsible for disposing BindlessTexture2D instances
GC.SuppressFinalize(this);
_disposed = true;
}
}

163
Ghost.Graphics/Data/Mesh.cs Normal file
View File

@@ -0,0 +1,163 @@
using Ghost.Core;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Utilities;
using Misaki.HighPerformance.Mathematics;
using Misaki.HighPerformance.Mathematics.Geometry;
namespace Ghost.Graphics.Data;
public struct Mesh : IHandleType
{
public UnsafeList<Vertex> vertices;
public UnsafeList<uint> indices;
public AABB boundingBox;
public Handle<GraphicsBuffer> vertexBuffer;
public Handle<GraphicsBuffer> indexBuffer;
public Mesh()
{
vertexBuffer = Handle<GraphicsBuffer>.Invalid;
indexBuffer = Handle<GraphicsBuffer>.Invalid;
}
internal Mesh(ReadOnlySpan<Vertex> vertices, ReadOnlySpan<uint> indices, Handle<GraphicsBuffer> vertexBuffer, Handle<GraphicsBuffer> indexBuffer)
{
this.vertices = new(vertices.Length, Allocator.Persistent);
this.indices = new(indices.Length, Allocator.Persistent);
this.vertices.CopyFrom(vertices);
this.indices.CopyFrom(indices);
this.vertexBuffer = vertexBuffer;
this.indexBuffer = indexBuffer;
this.ComputeBounds();
}
public void ReleaseCpuResources()
{
vertices.Dispose();
indices.Dispose();
}
}
public static class MeshExtension
{
/// <summary>
/// Computes the bounding box of the mesh based on its vertices.
/// </summary>
public static void ComputeBounds(ref this Mesh mesh)
{
if (mesh.vertices.Count == 0)
{
return;
}
var min = new float3(float.MaxValue);
var max = new float3(float.MinValue);
foreach (var vertex in mesh.vertices)
{
var pos = vertex.position.xyz;
min = math.min(min, pos);
max = math.max(max, pos);
}
mesh.boundingBox = new AABB(min, max);
}
/// <summary>
/// Auto-compute smooth per-vertex normals.
/// </summary>
/// <remarks>
/// Call this method before vertices and indices are valid.
/// </remarks>
public static void ComputeNormal(ref this Mesh mesh)
{
if (!mesh.vertices.IsCreated || mesh.vertices.Count < 3
|| !mesh.indices.IsCreated || mesh.indices.Count < 3)
{
return;
}
for (var i = 0; i < mesh.indices.Count; i += 3)
{
var i0 = mesh.indices[i];
var i1 = mesh.indices[i + 1];
var i2 = mesh.indices[i + 2];
var v0 = mesh.vertices[i0];
var v1 = mesh.vertices[i1];
var v2 = mesh.vertices[i2];
var edge1 = v1.position - v0.position;
var edge2 = v2.position - v0.position;
var faceNormal = math.cross(edge1.xyz, edge2.xyz);
mesh.vertices[i0].normal.xyz += faceNormal;
mesh.vertices[i1].normal.xyz += faceNormal;
mesh.vertices[i2].normal.xyz += faceNormal;
}
for (var i = 0; i < mesh.vertices.Count; i++)
{
mesh.vertices[i].normal = math.normalize(mesh.vertices[i].normal);
}
}
/// <summary>
/// Auto-compute per-vertex tangents.
/// </summary>
/// <remarks>
/// Call this method before vertices, normals, and UVs are valid.
/// </remarks>
public static void ComputeTangents(ref this Mesh mesh)
{
var bitangents = new float4[mesh.vertices.Count];
for (var i = 0; i < mesh.indices.Count; i += 3)
{
var i0 = mesh.indices[i];
var i1 = mesh.indices[i + 1];
var i2 = mesh.indices[i + 2];
var v0 = mesh.vertices[i0];
var v1 = mesh.vertices[i1];
var v2 = mesh.vertices[i2];
var uv0 = mesh.vertices[i0].uv;
var uv1 = mesh.vertices[i1].uv;
var uv2 = mesh.vertices[i2].uv;
var deltaPos1 = v1.position - v0.position;
var deltaPos2 = v2.position - v0.position;
var deltaUV1 = uv1 - uv0;
var deltaUV2 = uv2 - uv0;
var r = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV1.y * deltaUV2.x);
var tangent = (deltaPos1 * deltaUV2.y - deltaPos2 * deltaUV1.y) * r;
var bitangent = (deltaPos2 * deltaUV1.x - deltaPos1 * deltaUV2.x) * r;
for (var j = 0; j < 3; j++)
{
var idx = mesh.indices[i + j];
var t = mesh.vertices[idx].tangent;
mesh.vertices[idx].tangent.xyz = t.xyz + tangent.xyz;
bitangents[idx] += bitangent;
}
}
for (var i = 0; i < mesh.vertices.Count; i++)
{
var n = mesh.vertices[i].normal;
var t = mesh.vertices[i].tangent;
var proj = n * math.dot(n, t);
t = math.normalize(t - proj);
var b = bitangents[i];
var w = math.dot(math.cross(n.xyz, t.xyz), b.xyz) < 0.0f ? -1.0f : 1.0f;
mesh.vertices[i].tangent = new float4(t.x, t.y, t.z, w);
}
}
}

View File

@@ -1,388 +0,0 @@
using Ghost.Graphics.D3D12;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Utilities;
using Misaki.HighPerformance.Mathematics;
using Misaki.HighPerformance.Mathematics.Geometry;
using System.Runtime.CompilerServices;
using Win32.Graphics.Direct3D12;
using Win32.Graphics.Dxgi.Common;
namespace Ghost.Graphics.Data;
public struct Mesh
{
public UnsafeList<Vertex> vertices;
public UnsafeList<uint> indices;
public AABB boundingBox;
public BufferHandle vertexBuffer;
public BufferHandle indexBuffer;
public Mesh()
{
vertexBuffer = BufferHandle.Invalid;
indexBuffer = BufferHandle.Invalid;
}
internal Mesh(ReadOnlySpan<Vertex> vertices, ReadOnlySpan<uint> indices, BufferHandle vertexBuffer, BufferHandle indexBuffer)
{
this.vertices = new(vertices.Length, Allocator.Persistent);
this.indices = new(indices.Length, Allocator.Persistent);
this.vertices.CopyFrom(vertices);
this.indices.CopyFrom(indices);
this.vertexBuffer = vertexBuffer;
this.indexBuffer = indexBuffer;
ComputeBounds();
}
public void ComputeBounds()
{
if (vertices.Count == 0)
{
return;
}
var min = new float3(float.MaxValue);
var max = new float3(float.MinValue);
foreach (var vertex in vertices)
{
var pos = vertex.position.xyz;
min = math.min(min, pos);
max = math.max(max, pos);
}
boundingBox = new AABB(min, max);
}
public void ReleaseCpuResources()
{
vertices.Dispose();
indices.Dispose();
}
}
public unsafe sealed class MeshClass : IDisposable
{
private UnsafeList<Vertex> _vertices;
private UnsafeList<int> _indices;
private AABB _boundingBox;
private IBuffer? _vertexBuffer;
private IBuffer? _indexBuffer;
public Span<Vertex> Vertices => _vertices.AsSpan();
public Span<int> Indices => _indices.AsSpan();
public AABB BoundingBox => _boundingBox;
public uint VertexCount => (uint)_vertices.Count;
public uint IndexCount => (uint)_indices.Count;
public uint VertexBufferDescriptorIndex
{
get
{
if (_vertexBuffer == null || !_vertexBuffer.Handle.IsValid)
{
throw new InvalidOperationException("Vertex buffer is not created.");
}
var bindlessDesc = _vertexBuffer.Handle.BindlessDescriptor;
if (!bindlessDesc.IsValid)
{
throw new InvalidOperationException("Vertex buffer is not created with bindless.");
}
return bindlessDesc.Index;
}
}
public uint IndexBufferDescriptorIndex
{
get
{
if (_indexBuffer == null || !_indexBuffer.Handle.IsValid)
{
throw new InvalidOperationException("Index buffer is not created.");
}
var bindlessDesc = _indexBuffer.Handle.BindlessDescriptor;
if (!bindlessDesc.IsValid)
{
throw new InvalidOperationException("Index buffer is not created with bindless.");
}
return bindlessDesc.Index;
}
}
public MeshClass(int initialVertexCapacity = 256, int initialIndexCapacity = 512)
{
_vertices = new(initialVertexCapacity, Allocator.Persistent);
_indices = new(initialIndexCapacity, Allocator.Persistent);
}
public MeshClass(ReadOnlySpan<Vertex> vertices, ReadOnlySpan<int> indices)
: this(vertices.Length, indices.Length)
{
_vertices = new(vertices.Length, Allocator.Persistent);
_indices = new(indices.Length, Allocator.Persistent);
_vertices.CopyFrom(vertices);
_indices.CopyFrom(indices);
}
~MeshClass()
{
Dispose();
}
/// <summary>
/// Adds a vertex to the mesh with the specified attributes.
/// </summary>
/// <param name="vertex">The vertex data to add</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddVertex(Vertex vertex)
{
_vertices.Add(vertex);
}
/// <summary>
/// Adds a triangle to the mesh by specifying the indices of its three vertices.
/// </summary>
/// <param name="index0">The index of the first vertex in the triangle. Must be within the range of the current vertex count.</param>
/// <param name="index1">The index of the second vertex in the triangle. Must be within the range of the current vertex count.</param>
/// <param name="index2">The index of the third vertex in the triangle. Must be within the range of the current vertex count.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown if any of the specified indices are out of range for the current vertex count.</exception>
public void AddTriangle(int index0, int index1, int index2)
{
if (index0 >= _vertices.Count || index1 >= _vertices.Count || index2 >= _vertices.Count)
{
throw new ArgumentOutOfRangeException("Index out of range for the current vertex count.");
}
_indices.Add(index0);
_indices.Add(index1);
_indices.Add(index2);
}
public void AddTriangles(params ReadOnlySpan<int> indices)
{
if (indices.Length % 3 != 0)
{
throw new ArgumentException("The number of indices must be a multiple of 3 to form triangles.");
}
foreach (var index in indices)
{
if (index < 0 || index >= _vertices.Count)
{
throw new ArgumentOutOfRangeException(nameof(indices), "Index out of range for the current vertex count.");
}
_indices.Add(index);
}
}
/// <summary>
/// Reduces the memory usage of the internal collections by resizing them to match their current element count.
/// </summary>
public void TrimExcess()
{
_vertices.Resize(_vertices.Count);
_indices.Resize(_indices.Count);
}
/// <summary>
/// Auto-compute smooth per-vertex normals.
/// </summary>
/// <remarks>
/// Call this method before vertices and indices are valid.
/// </remarks>
public void ComputeNormal()
{
if (!_vertices.IsCreated || _vertices.Count < 3
|| !_indices.IsCreated || _indices.Count < 3)
{
return;
}
for (var i = 0; i < _indices.Count; i += 3)
{
var i0 = _indices[i];
var i1 = _indices[i + 1];
var i2 = _indices[i + 2];
var v0 = _vertices[i0];
var v1 = _vertices[i1];
var v2 = _vertices[i2];
var edge1 = v1.position - v0.position;
var edge2 = v2.position - v0.position;
var faceNormal = math.cross(edge1.xyz, edge2.xyz);
_vertices[i0].normal.xyz += faceNormal;
_vertices[i1].normal.xyz += faceNormal;
_vertices[i2].normal.xyz += faceNormal;
}
for (var i = 0; i < _vertices.Count; i++)
{
_vertices[i].normal = math.normalize(_vertices[i].normal);
}
}
/// <summary>
/// Auto-compute per-vertex tangents.
/// </summary>
/// <remarks>
/// Call this method before vertices, normals, and UVs are valid.
/// </remarks>
public void ComputeTangents()
{
var bitangents = new float4[_vertices.Count];
for (var i = 0; i < _indices.Count; i += 3)
{
var i0 = _indices[i];
var i1 = _indices[i + 1];
var i2 = _indices[i + 2];
var v0 = _vertices[i0];
var v1 = _vertices[i1];
var v2 = _vertices[i2];
var uv0 = _vertices[i0].uv;
var uv1 = _vertices[i1].uv;
var uv2 = _vertices[i2].uv;
var deltaPos1 = v1.position - v0.position;
var deltaPos2 = v2.position - v0.position;
var deltaUV1 = uv1 - uv0;
var deltaUV2 = uv2 - uv0;
var r = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV1.y * deltaUV2.x);
var tangent = (deltaPos1 * deltaUV2.y - deltaPos2 * deltaUV1.y) * r;
var bitangent = (deltaPos2 * deltaUV1.x - deltaPos1 * deltaUV2.x) * r;
for (var j = 0; j < 3; j++)
{
var idx = _indices[i + j];
var t = _vertices[idx].tangent;
_vertices[idx].tangent.xyz = t.xyz + tangent.xyz;
bitangents[idx] += bitangent;
}
}
for (var i = 0; i < _vertices.Count; i++)
{
var n = _vertices[i].normal;
var t = _vertices[i].tangent;
var proj = n * math.dot(n, t);
t = math.normalize(t - proj);
var b = bitangents[i];
var w = math.dot(math.cross(n.xyz, t.xyz), b.xyz) < 0.0f ? -1.0f : 1.0f;
_vertices[i].tangent = new float4(t.x, t.y, t.z, w);
}
}
/// <summary>
/// Computes the bounding box of the mesh based on its vertices.
/// </summary>
public void ComputeBounds()
{
if (_vertices.Count == 0)
{
_boundingBox = AABB.Zero;
return;
}
var min = new float3(float.MaxValue);
var max = new float3(float.MinValue);
foreach (var vertex in _vertices)
{
var pos = vertex.position.xyz;
min = math.min(min, pos);
max = math.max(max, pos);
}
_boundingBox = new AABB(min, max);
}
/// <summary>
/// Uploads the mesh data to GPU resources.
/// </summary>
public unsafe void UploadMeshData()
{
if (VertexCount == 0 || IndexCount == 0)
{
return;
}
DisposeGpuResources();
var vertexBufferSize = (uint)(VertexCount * sizeof(Vertex));
var indexBufferSize = IndexCount * sizeof(int);
_vertexBuffer = GraphicsBuffer.Create(vertexBufferSize, GraphicsBuffer.Usage.CopyDestination);
_indexBuffer = GraphicsBuffer.Create(indexBufferSize, GraphicsBuffer.Usage.CopyDestination);
var uploadBatch = GraphicsPipeline.UploadBatch;
uploadBatch.Upload(_vertexBuffer.NativeResource, _vertices.AsSpan());
uploadBatch.Upload(_indexBuffer.NativeResource, _indices.AsSpan());
uploadBatch.Transition(_vertexBuffer.NativeResource, ResourceStates.CopyDest, ResourceStates.VertexAndConstantBuffer);
uploadBatch.Transition(_indexBuffer.NativeResource, ResourceStates.CopyDest, ResourceStates.IndexBuffer);
// Create bindless descriptors for vertex and index buffers
CreateBindlessDescriptors();
}
internal void MarkNoLongerReadable()
{
_vertices.Dispose();
_indices.Dispose();
}
/// <summary>
/// Clears all vertex and index data and releases associated GPU resources.
/// </summar>
public void Clear()
{
_vertices.Clear();
_indices.Clear();
DisposeGpuResources();
}
private void DisposeGpuResources()
{
_vertexBuffer?.Dispose();
_vertexBuffer = null;
_indexBuffer?.Dispose();
_indexBuffer = null;
if (_vertexBufferDescriptor.IsValid)
{
RenderSystem.GraphicsEngine.DescriptorAllocator.Release(_vertexBufferDescriptor);
}
if (_indexBufferDescriptor.IsValid)
{
RenderSystem.GraphicsEngine.DescriptorAllocator.Release(_indexBufferDescriptor);
}
}
public void Dispose()
{
_vertices.Dispose();
_indices.Dispose();
DisposeGpuResources();
GC.SuppressFinalize(this);
}
}

View File

@@ -1,305 +0,0 @@
using Ghost.Graphics.D3D12;
using Win32.Graphics.Direct3D12;
using Win32.Graphics.Dxgi.Common;
namespace Ghost.Graphics.Data;
/// <summary>
/// Defines the type of render texture.
/// </summary>
public enum RenderTextureType
{
/// <summary>
/// Render target view - used for color output.
/// </summary>
ColorTarget,
/// <summary>
/// Depth stencil view - used for depth/stencil testing.
/// </summary>
DepthStencil
}
/// <summary>
/// Render texture class that encapsulates GPU resources for rendering.
/// </summary>
public unsafe class RenderTexture : Texture
{
private readonly RenderTextureType _renderTextureType;
private readonly RenderTargetDescriptor? _rtvDescriptor;
private readonly DepthStencilDescriptor? _dsvDescriptor;
private RenderTexture(uint width, uint height, Format format, RenderTextureType renderTextureType, in TextureHandle handle, BindlessDescriptor bindlessDescriptor, RenderTargetDescriptor? rtvDescriptor, DepthStencilDescriptor? dsvDescriptor)
: base(width, height, format, in handle, bindlessDescriptor)
{
_renderTextureType = renderTextureType;
_rtvDescriptor = rtvDescriptor;
_dsvDescriptor = dsvDescriptor;
}
/// <summary>
/// Gets the type of this render texture.
/// </summary>
public RenderTextureType RenderTextureType => _renderTextureType;
/// <summary>
/// Gets the render target view descriptor. Only valid for color render textures.
/// </summary>
internal RenderTargetDescriptor? RenderTargetView => _rtvDescriptor;
/// <summary>
/// Gets the depth stencil view descriptor. Only valid for depth render textures.
/// </summary>
internal DepthStencilDescriptor? DepthStencilView => _dsvDescriptor;
/// <summary>
/// Creates a color render texture.
/// </summary>
/// <param name="width">Width of the render texture</param>
/// <param name="height">Height of the render texture</param>
/// <param name="format">Color format (e.g., Format.R8G8B8A8Unorm)</param>
/// <returns>A new color render texture</returns>
public static RenderTexture CreateColorTarget(uint width, uint height, Format format = Format.R8G8B8A8Unorm, bool tempResource = false)
{
ValidateColorFormat(format);
var handle = GraphicsPipeline.ResourceAllocator.CreateTexture2D(width, height, 1, format, resFlags: ResourceFlags.AllowRenderTarget | ResourceFlags.AllowUnorderedAccess, tempResource: tempResource);
var resource = handle.ResourceHandle.GetAllocation().Resource;
var bindlessDescriptor = CreateBindlessDescriptorForRenderTexture(resource, format);
var rtvDescriptor = CreateRenderTargetView(resource, format);
return new RenderTexture(width, height, format, RenderTextureType.ColorTarget, in handle, bindlessDescriptor, rtvDescriptor, null);
}
/// <summary>
/// Creates a depth stencil render texture.
/// </summary>
/// <param name="width">Width of the render texture</param>
/// <param name="height">Height of the render texture</param>
/// <param name="format">Depth format (e.g., Format.D24UnormS8Uint, Format.D32Float)</param>
/// <returns>A new depth stencil render texture</returns>
public static RenderTexture CreateDepthStencil(uint width, uint height, Format format = Format.D24UnormS8Uint, bool tempResource = false)
{
ValidateDepthFormat(format);
var handle = GraphicsPipeline.ResourceAllocator.CreateTexture2D(width, height, 1, format, resFlags: ResourceFlags.AllowDepthStencil, tempResource: tempResource);
var resource = handle.ResourceHandle.GetAllocation().Resource;
var bindlessDescriptor = CreateBindlessDescriptorForRenderTexture(resource, GetShaderResourceFormat(format));
var dsvDescriptor = CreateDepthStencilView(resource, format);
return new RenderTexture(width, height, format, RenderTextureType.DepthStencil, in handle, bindlessDescriptor, null, dsvDescriptor);
}
/// <summary>
/// Validates that the format is suitable for color render targets.
/// </summary>
private static void ValidateColorFormat(Format format)
{
switch (format)
{
case Format.R8G8B8A8Unorm:
case Format.R8G8B8A8UnormSrgb:
case Format.B8G8R8A8Unorm:
case Format.B8G8R8A8UnormSrgb:
case Format.R16G16B16A16Float:
case Format.R32G32B32A32Float:
case Format.R16G16Float:
case Format.R32Float:
break;
default:
throw new ArgumentException($"Format {format} is not supported for color render targets.");
}
}
/// <summary>
/// Validates that the format is suitable for depth stencil targets.
/// </summary>
private static void ValidateDepthFormat(Format format)
{
switch (format)
{
case Format.D32Float:
case Format.D24UnormS8Uint:
case Format.D16Unorm:
break;
default:
throw new ArgumentException($"Format {format} is not supported for depth stencil targets.");
}
}
/// <summary>
/// Gets the shader resource format for depth textures (for sampling depth in shaders).
/// </summary>
private static Format GetShaderResourceFormat(Format depthFormat)
{
return depthFormat switch
{
Format.D32Float => Format.R32Float,
Format.D24UnormS8Uint => Format.R24UnormX8Typeless,
Format.D16Unorm => Format.R16Unorm,
_ => throw new ArgumentException($"Cannot determine shader resource format for depth format {depthFormat}")
};
}
/// <summary>
/// Creates a bindless descriptor for render texture shader resource access.
/// </summary>
private static BindlessDescriptor CreateBindlessDescriptorForRenderTexture(ID3D12Resource* resource, Format srvFormat)
{
var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr;
var bindlessDescriptor = GraphicsPipeline.DescriptorAllocator.AllocateBindless();
var srvDesc = new ShaderResourceViewDescription
{
Format = srvFormat,
ViewDimension = SrvDimension.Texture2D,
Texture2D = new Texture2DSrv { MipLevels = 1 },
Shader4ComponentMapping = 0x1688 // D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING
};
device->CreateShaderResourceView(resource, &srvDesc, bindlessDescriptor.CpuHandle);
return bindlessDescriptor;
}
/// <summary>
/// Creates a render target view for color render textures.
/// </summary>
private static RenderTargetDescriptor CreateRenderTargetView(ID3D12Resource* resource, Format format)
{
var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr;
var rtvDescriptor = GraphicsPipeline.DescriptorAllocator.AllocateRTV();
var rtvDesc = new RenderTargetViewDescription
{
Format = format,
ViewDimension = RtvDimension.Texture2D,
Texture2D = new Texture2DRtv { MipSlice = 0 }
};
device->CreateRenderTargetView(resource, &rtvDesc, rtvDescriptor.CpuHandle);
return rtvDescriptor;
}
/// <summary>
/// Creates a depth stencil view for depth render textures.
/// </summary>
private static DepthStencilDescriptor CreateDepthStencilView(ID3D12Resource* resource, Format format)
{
var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr;
var dsvDescriptor = GraphicsPipeline.DescriptorAllocator.AllocateDSV();
var dsvDesc = new DepthStencilViewDescription
{
Format = format,
ViewDimension = DsvDimension.Texture2D,
Texture2D = new Texture2DDsv { MipSlice = 0 }
};
device->CreateDepthStencilView(resource, &dsvDesc, dsvDescriptor.CpuHandle);
return dsvDescriptor;
}
/// <summary>
/// Clears the render texture with the specified color (for color targets only).
/// </summary>
/// <param name="commandList">Command list to record clear commands</param>
/// <param name="clearColor">Color to clear to</param>
public void ClearColor(CommandList commandList, Color128 clearColor)
{
ThrowIfDisposed();
if (_renderTextureType != RenderTextureType.ColorTarget || _rtvDescriptor == null)
{
throw new InvalidOperationException("ClearColor can only be called on color render textures.");
}
commandList.NativeCommandList.Ptr->ClearRenderTargetView(_rtvDescriptor.CpuHandle, (float*)&clearColor, 0, null);
}
/// <summary>
/// Clears the depth stencil render texture.
/// </summary>
/// <param name="commandList">Command list to record clear commands</param>
/// <param name="clearDepth">Depth value to clear to (0.0 to 1.0)</param>
/// <param name="clearStencil">Stencil value to clear to</param>
public void ClearDepthStencil(CommandList commandList, ClearFlags flags, float clearDepth = 1.0f, byte clearStencil = 0)
{
ThrowIfDisposed();
if (_renderTextureType != RenderTextureType.DepthStencil || _dsvDescriptor == null)
{
throw new InvalidOperationException("ClearDepthStencil can only be called on depth stencil render textures.");
}
commandList.NativeCommandList.Ptr->ClearDepthStencilView(_dsvDescriptor.CpuHandle, flags, clearDepth, clearStencil, 0, null);
}
/// <summary>
/// Transitions the render texture to the specified resource state.
/// </summary>
/// <param name="commandList">Command list to record transition</param>
/// <param name="newState">New resource state</param>
internal void TransitionTo(CommandList commandList, ResourceStates newState)
{
ThrowIfDisposed();
commandList.BarrierTransition(this, ResourceStates.Common, newState);
}
/// <summary>
/// Convenience method to transition to render target state (for color targets).
/// </summary>
/// <param name="commandList">Command list to record transition</param>
internal void TransitionToRenderTarget(CommandList commandList)
{
if (_renderTextureType != RenderTextureType.ColorTarget)
{
throw new InvalidOperationException("TransitionToRenderTarget can only be called on color render textures.");
}
TransitionTo(commandList, ResourceStates.RenderTarget);
}
/// <summary>
/// Convenience method to transition to depth write state (for depth targets).
/// </summary>
/// <param name="commandList">Command list to record transition</param>
internal void TransitionToDepthWrite(CommandList commandList)
{
if (_renderTextureType != RenderTextureType.DepthStencil)
{
throw new InvalidOperationException("TransitionToDepthWrite can only be called on depth stencil render textures.");
}
TransitionTo(commandList, ResourceStates.DepthWrite);
}
/// <summary>
/// Convenience method to transition to shader resource state (for reading in shaders).
/// </summary>
/// <param name="commandList">Command list to record transition</param>
public void TransitionToShaderResource(CommandList commandList)
{
TransitionTo(commandList, ResourceStates.PixelShaderResource);
}
/// <summary>
/// Disposes the render texture and releases associated descriptors.
/// </summary>
public override void Dispose()
{
base.Dispose();
if (_rtvDescriptor != null)
{
GraphicsPipeline.DescriptorAllocator.ReleaseRTV(_rtvDescriptor);
}
if (_dsvDescriptor != null)
{
GraphicsPipeline.DescriptorAllocator.ReleaseDSV(_dsvDescriptor);
}
}
}

View File

@@ -0,0 +1,146 @@
using Ghost.Core;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Utilities;
using Win32;
namespace Ghost.Graphics.Data;
public unsafe readonly ref struct RenderingContext
{
private readonly IRenderDevice _device;
private readonly ICommandBuffer _cmd;
private readonly ICommandBuffer _copyCmd;
private readonly ICommandBuffer _computeCmd;
private readonly IResourceAllocator _resourceAllocator;
private readonly IResourceDatabase _resourceDatabase;
private readonly IPipelineLibrary _stateController;
internal RenderingContext(
IRenderDevice device,
ICommandBuffer cmd,
ICommandBuffer copyCmd,
ICommandBuffer computeCmd,
IResourceAllocator resourceAllocator,
IResourceDatabase resourceDatabase,
IPipelineLibrary stateController)
{
_device = device;
_cmd = cmd;
_copyCmd = copyCmd;
_computeCmd = computeCmd;
_resourceAllocator = resourceAllocator;
_resourceDatabase = resourceDatabase;
_stateController = stateController;
}
public Handle<Mesh> CreateMesh(UnsafeList<Vertex> vertices, UnsafeList<uint> indices)
{
var mesh = _resourceAllocator.CreateMesh(vertices, indices);
return mesh;
}
public Handle<Mesh> CreateMesh(ReadOnlySpan<Vertex> vertices, ReadOnlySpan<uint> indices)
{
var vertexList = new UnsafeList<Vertex>(vertices.Length, Allocator.Persistent);
var indexList = new UnsafeList<uint>(indices.Length, Allocator.Persistent);
vertexList.CopyFrom(vertices);
indexList.CopyFrom(indices);
return CreateMesh(vertexList, indexList);
}
/// <summary>
/// Uploads the mesh data to the GPU.
/// </summary>
/// <param name="mesh">The handle point to the mesh buffer</param>
/// <param name="markMeshStatic">Whether to mark the mesh as static. If it's true, the cpu buffer of the mesh will not be avaliable any more</param>
public void UploadMesh(Handle<Mesh> mesh, bool markMeshStatic)
{
ref var meshData = ref _resourceDatabase.GetMeshReference(mesh);
var vertexState = _resourceDatabase.GetResourceState(meshData.vertexBuffer.AsResource());
var indexState = _resourceDatabase.GetResourceState(meshData.indexBuffer.AsResource());
var needVertexTransition = vertexState != ResourceState.CopyDest;
var needIndexTransition = indexState != ResourceState.CopyDest;
if (needVertexTransition)
{
_copyCmd.ResourceBarrier(meshData.vertexBuffer.AsResource(), vertexState, ResourceState.CopyDest);
_resourceDatabase.SetResourceState(meshData.vertexBuffer.AsResource(), ResourceState.CopyDest);
}
if (needIndexTransition)
{
_copyCmd.ResourceBarrier(meshData.indexBuffer.AsResource(), indexState, ResourceState.CopyDest);
_resourceDatabase.SetResourceState(meshData.indexBuffer.AsResource(), ResourceState.CopyDest);
}
_copyCmd.Upload(meshData.vertexBuffer, meshData.vertices.AsSpan());
_copyCmd.Upload(meshData.indexBuffer, meshData.indices.AsSpan());
if (needVertexTransition)
{
_copyCmd.ResourceBarrier(meshData.vertexBuffer.AsResource(), ResourceState.CopyDest, vertexState);
_resourceDatabase.SetResourceState(meshData.vertexBuffer.AsResource(), vertexState);
}
if (needIndexTransition)
{
_resourceDatabase.SetResourceState(meshData.indexBuffer.AsResource(), indexState);
_copyCmd.ResourceBarrier(meshData.indexBuffer.AsResource(), ResourceState.CopyDest, indexState);
}
if (markMeshStatic)
{
meshData.ReleaseCpuResources();
}
}
public Handle<Texture> CreateTexture(ref readonly TextureDesc desc, bool tempResource = false)
{
return _resourceAllocator.CreateTexture(in desc, tempResource);
}
public void UploadTexture(Handle<Texture> texture, ReadOnlySpan<byte> data)
{
var desc = _resourceDatabase.GetResourceDescription(texture.AsResource());
desc.textureDescription.Format.GetSurfaceInfo((int)desc.textureDescription.Width, (int)desc.textureDescription.Height, out var rowPitch, out var slicePitch, out _);
var subresourceData = new SubResourceData
{
pData = data.GetPointer(),
rowPitch = rowPitch,
slicePitch = slicePitch
};
var sateBefore = _resourceDatabase.GetResourceState(texture.AsResource());
var needTransition = sateBefore != ResourceState.CopyDest;
if (needTransition)
{
_copyCmd.ResourceBarrier(texture.AsResource(), sateBefore, ResourceState.CopyDest);
_resourceDatabase.SetResourceState(texture.AsResource(), ResourceState.CopyDest);
}
_copyCmd.Upload(texture, subresourceData);
if (needTransition)
{
_copyCmd.ResourceBarrier(texture.AsResource(), ResourceState.CopyDest, sateBefore);
_resourceDatabase.SetResourceState(texture.AsResource(), sateBefore);
}
}
public void RenderMesh(Handle<Mesh> mesh, Handle<Material> material)
{
_cmd.DrawMesh(mesh, material);
}
public void ExecuteCopyCommands()
{
_device.CopyQueue.Submit(_copyCmd);
_device.CopyQueue.WaitIdle();
}
}

View File

@@ -1,148 +1,30 @@
using System.Runtime.CompilerServices;
using Win32.Graphics.D3D12MemoryAllocator;
using Ghost.Core;
namespace Ghost.Graphics.Data;
public readonly struct ResourceHandle : IEquatable<ResourceHandle>
public readonly struct GPUResource : IHandleType;
public readonly struct Texture : IHandleType;
public readonly struct GraphicsBuffer : IHandleType;
public static class ResourceHandleExtensions
{
private const int _INVALID_ID = -1;
public readonly int id;
public readonly int generation;
public static ResourceHandle Invalid => new(_INVALID_ID, _INVALID_ID);
internal ResourceHandle(int id, int generation)
public static Handle<GPUResource> AsResource(this Handle<Texture> texture)
{
this.id = id;
this.generation = generation;
return new Handle<GPUResource>(texture.id, texture.generation);
}
public bool IsValid => id != _INVALID_ID && generation != _INVALID_ID;
public bool Equals(ResourceHandle other)
public static Handle<GPUResource> AsResource(this Handle<GraphicsBuffer> buffer)
{
return id == other.id && generation == other.generation;
return new Handle<GPUResource>(buffer.id, buffer.generation);
}
public override int GetHashCode()
internal static Handle<Texture> AsTexture(this Handle<GPUResource> resource)
{
unchecked
{
return (id * 397) ^ generation;
}
return new Handle<Texture>(resource.id, resource.generation);
}
public override bool Equals(object? obj)
internal static Handle<GraphicsBuffer> AsGraphicsBuffer(this Handle<GPUResource> resource)
{
return obj is ResourceHandle handle && Equals(handle);
}
public static bool operator ==(ResourceHandle left, ResourceHandle right)
{
return left.Equals(right);
}
public static bool operator !=(ResourceHandle left, ResourceHandle right)
{
return !(left == right);
}
}
public readonly struct TextureHandle : IEquatable<TextureHandle>
{
private readonly ResourceHandle _resourceHandle;
private readonly BindlessDescriptor _bindlessDescriptor;
public ResourceHandle ResourceHandle => _resourceHandle;
public static TextureHandle Invalid => new(ResourceHandle.Invalid);
internal TextureHandle(ResourceHandle resourceHandle)
{
_resourceHandle = resourceHandle;
_bindlessDescriptor = BindlessDescriptor.Invalid;
}
internal TextureHandle(ResourceHandle resourceHandle, BindlessDescriptor descriptor)
{
_resourceHandle = resourceHandle;
_bindlessDescriptor = descriptor;
}
public bool IsValid => _resourceHandle.IsValid;
public bool Equals(TextureHandle other)
{
return _resourceHandle.Equals(other._resourceHandle) && _bindlessDescriptor.Equals(other._bindlessDescriptor);
}
public override bool Equals(object? obj)
{
return obj is TextureHandle other && Equals(other);
}
public override int GetHashCode()
{
return HashCode.Combine(_resourceHandle, _bindlessDescriptor);
}
public static bool operator ==(TextureHandle left, TextureHandle right)
{
return left.Equals(right);
}
public static bool operator !=(TextureHandle left, TextureHandle right)
{
return !(left == right);
}
}
public readonly struct BufferHandle : IEquatable<BufferHandle>
{
private readonly ResourceHandle _resourceHandle;
private readonly BindlessDescriptor _bindlessDescriptor;
public static BufferHandle Invalid => new(ResourceHandle.Invalid);
public ResourceHandle ResourceHandle => _resourceHandle;
public BindlessDescriptor BindlessDescriptor => _bindlessDescriptor;
internal BufferHandle(ResourceHandle resourceHandle)
{
_resourceHandle = resourceHandle;
_bindlessDescriptor = BindlessDescriptor.Invalid;
}
internal BufferHandle(ResourceHandle resourceHandle, BindlessDescriptor descriptor)
{
_resourceHandle = resourceHandle;
_bindlessDescriptor = descriptor;
}
public bool IsValid => _resourceHandle.IsValid;
public bool Equals(BufferHandle other)
{
return _resourceHandle.Equals(other._resourceHandle) && _bindlessDescriptor.Equals(other._bindlessDescriptor);
}
public override bool Equals(object? obj)
{
return obj is BufferHandle other && Equals(other);
}
public override int GetHashCode()
{
return HashCode.Combine(_resourceHandle, _bindlessDescriptor);
}
public static bool operator ==(BufferHandle left, BufferHandle right)
{
return left.Equals(right);
}
public static bool operator !=(BufferHandle left, BufferHandle right)
{
return !(left == right);
return new Handle<GraphicsBuffer>(resource.id, resource.generation);
}
}

View File

@@ -1,3 +1,4 @@
using Ghost.Core;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
@@ -47,16 +48,19 @@ internal readonly struct CBufferInfo
}
}
internal struct ShaderPass
{
}
/// <summary>
/// A representation of a GPU shader, including its metadata about its resources.
/// </summary>
// TODO: Multi pass and keyword support
public struct Shader : IDisposable
public struct Shader : IIdentifierType
{
private UnsafeList<CBufferInfo> _constantBuffers;
private UnsafeList<PropertyInfo> _properties;
private UnsafeList<TextureInfo> _regularTextures; // Add regular texture support
// TODO: Possible to move this to unmanaged heap?
private Dictionary<string, int> _propertyNameToIdMap;
@@ -64,26 +68,16 @@ public struct Shader : IDisposable
internal readonly UnsafeList<CBufferInfo> ConstantBuffers => _constantBuffers;
internal readonly UnsafeList<PropertyInfo> Properties => _properties;
internal readonly UnsafeList<TextureInfo> RegularTextures => _regularTextures;
internal readonly Dictionary<string, int> PropertyNameToIdMap => _propertyNameToIdMap;
public Shader()
{
_constantBuffers = new(8, Allocator.Persistent);
_properties = new(8, Allocator.Persistent);
_regularTextures = new(8, Allocator.Persistent);
_propertyNameToIdMap = new(8);
_disposed = false;
}
internal void AddProperty(string name, PropertyInfo propertyInfo)
{
var id = _properties.Count;
_properties.Add(propertyInfo);
_propertyNameToIdMap[name] = id;
}
/// <summary>
/// Gets a unique, stable ID for a shader property.
/// </summary>
@@ -94,7 +88,7 @@ public struct Shader : IDisposable
return _propertyNameToIdMap.TryGetValue(propertyName, out var id) ? id : -1;
}
public void Dispose()
internal void Dispose()
{
if (_disposed)
{
@@ -103,7 +97,6 @@ public struct Shader : IDisposable
_constantBuffers.Dispose();
_properties.Dispose();
_regularTextures.Dispose();
_propertyNameToIdMap.Clear();
_propertyNameToIdMap = null!;

View File

@@ -1,147 +0,0 @@
using Ghost.Graphics.D3D12;
using Win32.Graphics.Direct3D12;
using Win32.Graphics.Dxgi.Common;
namespace Ghost.Graphics.Data;
/// <summary>
/// Base class for all texture types in the graphics pipeline.
/// Provides common functionality for texture dimensions, format, and GPU resource management.
/// </summary>
public abstract unsafe class Texture : GraphicsResource
{
private readonly BindlessDescriptor _bindlessDescriptor;
/// <summary>
/// Width of the texture in pixels.
/// </summary>
public uint Width
{
get;
}
/// <summary>
/// Height of the texture in pixels.
/// </summary>
public uint Height
{
get;
}
/// <summary>
/// Number of bytes per pixel for the texture format.
/// </summary>
public uint BytesPerPixel
{
get;
}
/// <summary>
/// Format of the texture.
/// </summary>
public Format Format
{
get;
}
/// <summary>
/// The index of this texture in the global bindless descriptor heap.
/// </summary>
public uint DescriptorIndex => _bindlessDescriptor.Index;
internal Texture(uint width, uint height, Format format, in TextureHandle handle, BindlessDescriptor bindlessDescriptor)
: base(handle.ResourceHandle)
{
Width = width;
Height = height;
BytesPerPixel = GetBytesPerPixel(format);
Format = format;
_bindlessDescriptor = bindlessDescriptor;
}
/// <summary>
/// Creates a bindless shader resource view descriptor for the texture.
/// </summary>
protected static BindlessDescriptor CreateBindlessShaderResourceView(ID3D12Resource* resource, Format format)
{
var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr;
var bindlessDescriptor = GraphicsPipeline.DescriptorAllocator.AllocateBindless();
var srvDesc = new ShaderResourceViewDescription
{
Format = format,
ViewDimension = SrvDimension.Texture2D,
Texture2D = new Texture2DSrv { MipLevels = 1 },
Shader4ComponentMapping = 0x1688 // D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING
};
device->CreateShaderResourceView(resource, &srvDesc, bindlessDescriptor.CpuHandle);
return bindlessDescriptor;
}
/// <summary>
/// Gets the bytes per pixel for the specified format.
/// </summary>
private static uint GetBytesPerPixel(Format format)
{
return format switch
{
Format.R8G8B8A8Unorm => 4,
Format.R8G8B8A8UnormSrgb => 4,
Format.B8G8R8A8Unorm => 4,
Format.B8G8R8A8UnormSrgb => 4,
Format.R8G8B8A8Uint => 4,
Format.R8G8B8A8Sint => 4,
Format.R8G8B8A8Snorm => 4,
Format.R8G8Unorm => 2,
Format.R8G8Uint => 2,
Format.R8G8Sint => 2,
Format.R8G8Snorm => 2,
Format.R8Unorm => 1,
Format.R8Uint => 1,
Format.R8Sint => 1,
Format.R8Snorm => 1,
Format.A8Unorm => 1,
Format.R16G16B16A16Float => 8,
Format.R16G16B16A16Unorm => 8,
Format.R16G16B16A16Uint => 8,
Format.R16G16B16A16Sint => 8,
Format.R16G16B16A16Snorm => 8,
Format.R32G32B32A32Float => 16,
Format.R32G32B32A32Uint => 16,
Format.R32G32B32A32Sint => 16,
Format.R32G32B32Float => 12,
Format.R32G32B32Uint => 12,
Format.R32G32B32Sint => 12,
Format.R32G32Float => 8,
Format.R32G32Uint => 8,
Format.R32G32Sint => 8,
Format.R32Float => 4,
Format.R32Uint => 4,
Format.R32Sint => 4,
Format.R16G16Float => 4,
Format.R16G16Unorm => 4,
Format.R16G16Uint => 4,
Format.R16G16Sint => 4,
Format.R16G16Snorm => 4,
Format.R16Float => 2,
Format.R16Unorm => 2,
Format.R16Uint => 2,
Format.R16Sint => 2,
Format.R16Snorm => 2,
Format.D32Float => 4,
Format.D24UnormS8Uint => 4,
Format.D16Unorm => 2,
_ => throw new NotSupportedException($"Format {format} is not supported.")
};
}
/// <summary>
/// Disposes the texture resources.
/// </summary>
public override void Dispose()
{
base.Dispose();
GraphicsPipeline.DescriptorAllocator.Release(_bindlessDescriptor);
}
}

View File

@@ -1,193 +0,0 @@
using Misaki.HighPerformance.Image;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Helpers;
using Win32.Graphics.Direct3D12;
using Win32.Graphics.Dxgi.Common;
namespace Ghost.Graphics.Data;
/// <summary>
/// Unified texture implementation that supports both bindless and regular descriptor table binding
/// for use with SM 6.6 ResourceDescriptorHeap access and traditional frame buffer textures
/// </summary>
public unsafe class Texture2D : Texture
{
private UnTypedArray _cpuData;
public uint Pitch
{
get;
}
private Texture2D(uint width, uint height, Format format, in TextureHandle handle, in ReadOnlySpan<byte> cpuData, BindlessDescriptor bindlessDescriptor)
: base(width, height, format, in handle, bindlessDescriptor)
{
Pitch = width * BytesPerPixel;
uint alignment;
if (BytesPerPixel > 4)
{
alignment = (uint)MemoryUtilities.AlignOf<float>();
}
else
{
alignment = (uint)MemoryUtilities.AlignOf<byte>();
}
_cpuData = new UnTypedArray((uint)Size, alignment, ref AllocationManager.PersistentHandle);
if (!cpuData.IsEmpty)
{
if ((uint)cpuData.Length > Size)
{
throw new ArgumentException($"Data size mismatch. Expected {Size} bytes, got {cpuData.Length} bytes.");
}
_cpuData.CopyFrom(cpuData);
}
}
public static Texture2D Create(uint width, uint height, in ReadOnlySpan<byte> data, Format format)
{
var handle = GraphicsPipeline.ResourceAllocator.CreateTexture2D(width, height, 0, format);
var bindlessDescriptor = CreateBindlessShaderResourceView(handle.ResourceHandle.GetAllocation().Resource, format);
return new Texture2D(width, height, format, in handle, in data, bindlessDescriptor);
}
public static Texture2D FromFile(string filePath)
{
using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
using var image = ImageResult.FromStream(stream, ColorComponents.RGBA);
return Create(image.Width, image.Height, image.AsSpan(), Format.R8G8B8A8Unorm);
}
/// <summary>
/// Sets the entire texture data on the CPU side.
/// </summary>
/// <param name="data">The texture data to set</param>
public void SetData<T>(ReadOnlySpan<T> data)
where T : unmanaged
{
ThrowIfDisposed();
if ((uint)data.Length > Size)
{
throw new ArgumentException($"Data size mismatch. Expected {Size} bytes, got {data.Length} bytes.");
}
_cpuData.CopyFrom(data);
}
/// <summary>
/// Sets a single pixel value using generic color types.
/// </summary>
/// <typeparam name="T">The color type (e.g., uint for RGBA32)</typeparam>
/// <param name="x">X coordinate of the pixel</param>
/// <param name="y">Y coordinate of the pixel</param>
/// <param name="color">The color value</param>
public void SetPixel<T>(uint x, uint y, T color)
where T : unmanaged
{
if (sizeof(T) != BytesPerPixel)
{
throw new ArgumentException($"Color type size mismatch. Expected {BytesPerPixel} bytes, got {sizeof(T)} bytes.");
}
var colorSpan = new ReadOnlySpan<byte>(&color, sizeof(T));
SetPixel(x, y, colorSpan);
}
/// <summary>
/// Sets a single pixel value on the CPU side.
/// </summary>
/// <param name="x">X coordinate of the pixel</param>
/// <param name="y">Y coordinate of the pixel</param>
/// <param name="color">The color data for the pixel</param>
public void SetPixel(uint x, uint y, ReadOnlySpan<byte> color)
{
ThrowIfDisposed();
if (x >= Width || y >= Height)
{
throw new ArgumentException("Pixel coordinates are outside texture bounds.");
}
if (color.Length != BytesPerPixel)
{
throw new ArgumentException($"Color data size mismatch. Expected {BytesPerPixel} bytes, got {color.Length} bytes.");
}
var offset = y * Pitch + x * BytesPerPixel;
_cpuData.CopyFrom(color, 0u, offset, BytesPerPixel);
}
/// <summary>
/// Gets a single pixel value as a generic color type.
/// </summary>
/// <typeparam name="T">The color type (e.g., uint for RGBA32)</typeparam>
/// <param name="x">X coordinate of the pixel</param>
/// <param name="y">Y coordinate of the pixel</param>
/// <returns>The pixel color value</returns>
public T GetPixel<T>(uint x, uint y)
where T : unmanaged
{
if (sizeof(T) != BytesPerPixel)
{
throw new ArgumentException($"Color type size mismatch. Expected {BytesPerPixel} bytes, got {sizeof(T)} bytes.");
}
var pixelData = GetPixel(x, y);
fixed (byte* pPixel = pixelData)
{
return *(T*)pPixel;
}
}
/// <summary>
/// Gets a single pixel value from the CPU side data.
/// </summary>
/// <param name="x">X coordinate of the pixel</param>
/// <param name="y">Y coordinate of the pixel</param>
/// <returns>The pixel color data</returns>
public ReadOnlySpan<byte> GetPixel(uint x, uint y)
{
ThrowIfDisposed();
if (x >= Width || y >= Height)
{
throw new ArgumentException("Pixel coordinates are outside texture bounds.");
}
var offset = (int)(y * Pitch + x * BytesPerPixel);
return _cpuData.AsSpan().Slice(offset, (int)BytesPerPixel);
}
/// <summary>
/// Uploads the CPU-side texture data to the GPU resource.
/// </summary>
public void UploadTextureData()
{
ThrowIfDisposed();
Format.GetSurfaceInfo((int)Width, (int)Height, out var rowPitch, out var slicePitch);
var initData = new SubresourceData()
{
pData = _cpuData.GetUnsafePtr(),
RowPitch = rowPitch,
SlicePitch = slicePitch
};
var uploadBatch = GraphicsPipeline.UploadBatch;
uploadBatch.Transition(NativeResource, ResourceStates.Common, ResourceStates.CopyDest);
uploadBatch.Upload(NativeResource, 0, &initData, 1);
uploadBatch.Transition(NativeResource, ResourceStates.CopyDest, ResourceStates.PixelShaderResource);
}
public override void Dispose()
{
base.Dispose();
_cpuData.Dispose();
}
}

View File

@@ -1,3 +1,4 @@
using Ghost.Core;
using Ghost.Graphics.Data;
using Ghost.Graphics.RenderGraphModule;
using System.Numerics;
@@ -47,14 +48,14 @@ public static class RenderGraphExample
/// </summary>
public class CameraViewState
{
public TextureHandle? PreviousFrameColorBuffer;
public TextureHandle? PreviousFrameDepthBuffer;
public TextureHandle? VelocityBuffer;
public Handle<Texture>? PreviousFrameColorBuffer;
public Handle<Texture>? PreviousFrameDepthBuffer;
public Handle<Texture>? VelocityBuffer;
public Matrix4x4 PreviousViewProjectionMatrix;
}
public static void ExampleRenderGraph(CameraViewState viewState, TextureHandle backBuffer)
public static void ExampleRenderGraph(CameraViewState viewState, Handle<Texture> backBuffer)
{
// Create render graph with optional descriptor
var renderGraphDesc = new RenderGraphDesc(initialResourceCapacity: 512, initialPassCapacity: 128);
@@ -189,7 +190,7 @@ public static class RenderGraphExample
/// <summary>
/// Example with multiple cameras rendering to different targets
/// </summary>
public static void MultiCameraExample(List<CameraViewState> cameras, List<TextureHandle> renderTargets)
public static void MultiCameraExample(List<CameraViewState> cameras, List<Handle<Texture>> renderTargets)
{
using var renderGraph = new RenderGraph("MultiCameraRenderGraph");
renderGraph.BeginRecord();

View File

@@ -0,0 +1,9 @@
namespace Ghost.Graphics;
internal unsafe class GPUResourceLeakException : Exception
{
public GPUResourceLeakException(uint refCount, void* address, string name)
: base($"GPU resource leak detected! Resource '{name}' at address {(UIntPtr)address} has a reference count of {refCount} when it should be 0. This indicates that the resource was not properly released before being destroyed, which can lead to memory leaks and other issues. Please ensure that all references to this resource are released appropriately.")
{
}
}

View File

@@ -19,10 +19,12 @@
<ItemGroup>
<!--<PackageReference Include="StbImageSharp" Version="2.30.15" />-->
<PackageReference Include="Misaki.HighPerformance.Mathematics" Version="1.1.0" />
<PackageReference Include="Vortice.Dxc.Native" Version="1.0.3" />
<PackageReference Include="Vortice.Win32.Graphics.D3D12MemoryAllocator" Version="2.2.7" />
<PackageReference Include="Vortice.Win32.Graphics.Direct3D.Dxc" Version="2.2.7" />
<PackageReference Include="Vortice.Win32.Graphics.Direct3D.Fxc" Version="2.2.7" />
<PackageReference Include="TerraFX.Interop.D3D12MemoryAllocator" Version="2.0.1.5" />
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.26100.2" />
<!--<PackageReference Include="Vortice.Dxc.Native" Version="1.0.4" />
<PackageReference Include="Vortice.Win32.Graphics.D3D12MemoryAllocator" Version="2.3.0" />
<PackageReference Include="Vortice.Win32.Graphics.Direct3D.Dxc" Version="2.3.0" />
<PackageReference Include="Vortice.Win32.Graphics.Direct3D.Fxc" Version="2.3.0" />-->
</ItemGroup>
<ItemGroup>
@@ -41,7 +43,7 @@
</Reference>
</ItemGroup>
<ItemGroup>
<!--<ItemGroup>
<Using Include="Win32.Apis">
<Static>True</Static>
</Using>
@@ -57,6 +59,6 @@
<Using Include="Win32.Graphics.Direct3D.Dxc.Apis">
<Static>True</Static>
</Using>
</ItemGroup>
</ItemGroup>-->
</Project>

View File

@@ -1,129 +0,0 @@
using Ghost.Graphics.D3D12;
using Ghost.Graphics.RHI;
using Win32.Graphics.Direct3D12;
using Win32.Graphics.Dxgi;
namespace Ghost.Graphics;
/// <summary>
/// Legacy graphics pipeline - DEPRECATED
/// Use RenderSystem and D3D12RenderDevice for new code
/// This class remains for compatibility during migration
/// </summary>
[Obsolete("Use RenderSystem and D3D12RenderDevice instead")]
public static class GraphicsPipeline
{
internal const uint _FRAME_COUNT = 2;
#if DEBUG
private static D3D12DebugLayer? s_debugLayer;
#endif
private static GraphicsDevice? s_graphicsDevice;
private static D3D12DescriptorAllocator? s_descriptorAllocator;
private static D3D12DescriptorAllocator? s_resourceAllocator;
private static ResourceUploadBatch? s_uploadBatch;
// New RHI-based device for modern usage
private static IRenderDevice? s_renderDevice;
private static RenderSystem? s_renderSystem;
private static bool s_initialized;
internal static GraphicsDevice GraphicsDevice => s_graphicsDevice ?? throw new InvalidOperationException("Graphics device is not initialized.");
internal static D3D12DescriptorAllocator ResourceAllocator => s_resourceAllocator ?? throw new InvalidOperationException("Resource allocator is not initialized.");
internal static D3D12DescriptorAllocator DescriptorAllocator => s_descriptorAllocator ?? throw new InvalidOperationException("Descriptor allocator is not initialized.");
/// <summary>
/// Gets the modern RHI render device - prefer this over legacy GraphicsDevice
/// </summary>
public static IRenderDevice RenderDevice => s_renderDevice ?? throw new InvalidOperationException("Render device is not initialized.");
/// <summary>
/// Gets the render system for managing renderers and frame synchronization
/// </summary>
public static RenderSystem RenderSystem => s_renderSystem ?? throw new InvalidOperationException("Render system is not initialized.");
internal static ResourceUploadBatch UploadBatch
{
get
{
if (s_uploadBatch == null)
{
s_uploadBatch = new();
s_uploadBatch.Begin();
}
return s_uploadBatch;
}
}
internal static unsafe void Initialize()
{
#if DEBUG
s_debugLayer = new D3D12DebugLayer();
#endif
// Initialize legacy components for compatibility
s_graphicsDevice = new GraphicsDevice();
s_descriptorAllocator = new DescriptorAllocator();
s_resourceAllocator = new D3D12ResourceAllocator((IDXGIAdapter*)s_graphicsDevice.Adapter.Ptr, (ID3D12Device*)s_graphicsDevice.NativeDevice.Ptr);
// Initialize modern RHI components
s_renderDevice = new D3D12.D3D12RenderDevice();
s_renderSystem = new RenderSystem(s_renderDevice);
s_initialized = true;
}
/// <summary>
/// Legacy method - use RenderSystem.Start() instead
/// </summary>
[Obsolete("Use RenderSystem.Start() instead")]
internal static void Start()
{
s_renderSystem?.Start();
}
/// <summary>
/// Legacy method - use RenderSystem.Stop() instead
/// </summary>
[Obsolete("Use RenderSystem.Stop() instead")]
internal static void Stop()
{
s_renderSystem?.Stop();
}
/// <summary>
/// Legacy method - use RenderSystem.WaitForGPUReady() instead
/// </summary>
[Obsolete("Use RenderSystem.WaitForGPUReady() instead")]
internal static bool WaitForGPUReady(int timeOut = -1)
{
return s_renderSystem?.WaitForGPUReady(timeOut) ?? false;
}
/// <summary>
/// Legacy method - use RenderSystem.SignalCPUReady() instead
/// </summary>
[Obsolete("Use RenderSystem.SignalCPUReady() instead")]
internal static void SignalCPUReady()
{
s_renderSystem?.SignalCPUReady();
}
internal static void Shutdown()
{
s_renderSystem?.Dispose();
s_renderDevice?.Dispose();
s_resourceAllocator?.Dispose();
s_descriptorAllocator?.Dispose();
s_graphicsDevice?.Dispose();
#if DEBUG
s_debugLayer?.Dispose();
#endif
s_initialized = false;
}
}

View File

@@ -1,7 +1,6 @@
using Ghost.Graphics.D3D12;
using Ghost.Core;
using Ghost.Graphics.Data;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Drawing;
using Win32.Graphics.Direct3D12;
namespace Ghost.Graphics.RHI;
@@ -15,6 +14,11 @@ public interface ICommandBuffer : IDisposable
get;
}
public bool IsEmpty
{
get;
}
/// <summary>
/// Begins recording commands into this command buffer
/// </summary>
@@ -25,12 +29,25 @@ public interface ICommandBuffer : IDisposable
/// </summary>
public void End();
/// <summary>
/// Sets the optional render targets and optional depth target for subsequent rendering operations.
/// </summary>
/// <remarks>
/// To specify no render targets, provide an empty span for <paramref name="renderTargets"/>.
/// Use <see cref="Handle{Texture}.Invalid"/> for <paramref name="depthTarget"/> if no depth target is required.
/// </remarks>
/// <param name="renderTargets">A read-only span of handles to textures that will be used as render targets.
/// The order of handles determines the order in which render targets are bound.</param>
/// <param name="depthTarget">A handle to the texture to be used as the depth target. Specify a invalid handle if no depth target is required.</param>
public void SetRenderTargets(ReadOnlySpan<Handle<Texture>> renderTargets, Handle<Texture> depthTarget);
/// <summary>
/// Begins a render pass with the specified render target
/// </summary>
/// <param name="renderTarget">Render target to render into</param>
/// <param name="renderTarget">Render target to render into (can be invalid)</param>
/// <param name="depthTarget">Depth target to use (can be invalid)</param>
/// <param name="clearColor">Color to clear the render target with</param>
public void BeginRenderPass(IRenderTarget renderTarget, Color128 clearColor);
public void BeginRenderPass(Handle<Texture> renderTarget, Handle<Texture> depthTarget, Color128 clearColor);
/// <summary>
/// Ends the current render pass
@@ -55,26 +72,25 @@ public interface ICommandBuffer : IDisposable
/// <param name="resource">Resource to transition</param>
/// <param name="before">Current resource state</param>
/// <param name="after">Target resource state</param>
public void ResourceBarrier(IResource resource, ResourceState before, ResourceState after);
public void ResourceBarrier(Handle<GPUResource> resource, ResourceState before, ResourceState after);
/// <summary>
/// Sets the graphics root signature
/// </summary>
/// <param name="rootSignature">Root signature to set</param>
public void SetGraphicsRootSignature(IRootSignature rootSignature);
public void SetRootSignature(IRootSignature rootSignature);
/// <summary>
/// Sets the pipeline state object
/// </summary>
/// <param name="pipelineState">Pipeline state to set</param>
public void SetPipelineState(IPipelineStateController pipelineState);
public void SetPipelineState(IShaderPipeline pipelineState);
/// <summary>
/// Renders the specified mesh using the provided material.
/// </summary>
/// <param name="mesh">The mesh to be drawn. Must not be null.</param>
/// <param name="material">The material to use for rendering the mesh. Must not be null.</param>
public void DrawMesh(MeshClass mesh, MaterialClass material);
public void SetVertexBuffer(uint slot, Handle<GraphicsBuffer> buffer, ulong offset = 0);
public void SetIndexBuffer(Handle<GraphicsBuffer> buffer, IndexType type, ulong offset = 0);
public void Draw(uint vertexCount, uint instanceCount = 1, uint startVertex = 0, uint startInstance = 0);
public void DrawIndexed(uint indexCount, uint instanceCount = 1, uint startIndex = 0, int baseVertex = 0, uint startInstance = 0);
/// <summary>
/// Dispatches compute threads
@@ -91,7 +107,7 @@ public interface ICommandBuffer : IDisposable
/// <param name="buffer">A handle to the buffer that will receive the uploaded data.</param>
/// <param name="data">A read-only span containing the data to upload to the buffer. The span must contain elements of type
/// <typeparamref name="T"/>.</param>
public void Upload<T>(BufferHandle buffer, ReadOnlySpan<T> data)
public void Upload<T>(Handle<GraphicsBuffer> buffer, ReadOnlySpan<T> data)
where T : unmanaged;
/// <summary>
@@ -102,7 +118,17 @@ public interface ICommandBuffer : IDisposable
/// <param name="subresources">A reference to the structure containing the subresource data to upload. The data must match the format and layout expected by the texture.</param>
/// <param name="numSubresources">The number of subresources to upload, starting from <paramref name="firstSubresource"/>.
/// Must be greater than zero and not exceed the remaining subresources in the texture.</param>
public void Upload(TextureHandle texture, uint firstSubresource, ref SubResourceData subresources, uint numSubresources);
public void Upload(Handle<Texture> texture, params ReadOnlySpan<SubResourceData> subresources);
/// <summary>
/// Copies a specified number of bytes from the source graphics buffer to the destination graphics buffer.
/// </summary>
/// <param name="dest">The handle to the destination graphics buffer where data will be written. Cannot be null.</param>
/// <param name="src">The handle to the source graphics buffer from which data will be read. Cannot be null.</param>
/// <param name="destOffset">The byte offset in the destination buffer at which to begin writing. Must be zero or greater.</param>
/// <param name="srcOffset">The byte offset in the source buffer at which to begin reading. Must be zero or greater.</param>
/// <param name="numBytes">The number of bytes to copy. If zero, copies the remaining bytes from the source buffer starting at <paramref name="srcOffset"/>.</param>
public void CopyBuffer(Handle<GraphicsBuffer> dest, Handle<GraphicsBuffer> src, ulong destOffset = 0, ulong srcOffset = 0, ulong numBytes = 0);
}
/// <summary>
@@ -116,16 +142,6 @@ public struct ViewportDesc
public float height;
public float minDepth;
public float maxDepth;
public ViewportDesc(float width, float height)
{
x = 0;
y = 0;
this.width = width;
this.height = height;
minDepth = 0.0f;
maxDepth = 1.0f;
}
}
/// <summary>
@@ -133,18 +149,10 @@ public struct ViewportDesc
/// </summary>
public struct RectDesc
{
public int Left;
public int Top;
public int Right;
public int Bottom;
public RectDesc(int left, int top, int right, int bottom)
{
Left = left;
Top = top;
Right = right;
Bottom = bottom;
}
public uint left;
public uint top;
public uint right;
public uint bottom;
}
/// <summary>
@@ -168,6 +176,27 @@ public enum ResourceState
Present = 0,
}
internal static class ResourceStateExtensions
{
public static ResourceStates ToD3D12States(this ResourceState state)
{
return state switch
{
ResourceState.Common or ResourceState.Present => ResourceStates.Common,
ResourceState.VertexAndConstantBuffer => ResourceStates.VertexAndConstantBuffer,
ResourceState.IndexBuffer => ResourceStates.IndexBuffer,
ResourceState.RenderTarget => ResourceStates.RenderTarget,
ResourceState.UnorderedAccess => ResourceStates.UnorderedAccess,
ResourceState.DepthWrite => ResourceStates.DepthWrite,
ResourceState.DepthRead => ResourceStates.DepthRead,
ResourceState.PixelShaderResource => ResourceStates.PixelShaderResource,
ResourceState.CopyDest => ResourceStates.CopyDest,
ResourceState.CopySource => ResourceStates.CopySource,
_ => throw new ArgumentException($"Unknown resource state: {state}")
};
}
}
public struct SubResourceData
{

View File

@@ -8,7 +8,7 @@ public interface ICommandQueue : IDisposable
/// <summary>
/// Type of commands this queue can execute
/// </summary>
CommandQueueType Type
public CommandQueueType Type
{
get;
}
@@ -17,32 +17,37 @@ public interface ICommandQueue : IDisposable
/// Submits a single command buffer for execution
/// </summary>
/// <param name="commandBuffer">Command buffer to submit</param>
void Submit(ICommandBuffer commandBuffer);
public void Submit(ICommandBuffer commandBuffer);
/// <summary>
/// Submits multiple command buffers for execution
/// </summary>
/// <param name="commandBuffers">Command buffers to submit</param>
void Submit(params ReadOnlySpan<ICommandBuffer> commandBuffers);
public void Submit(params ReadOnlySpan<ICommandBuffer> commandBuffers);
/// <summary>
/// Signals a fence with the specified value
/// </summary>
/// <param name="value">Value to signal</param>
/// <returns>The fence value that was signaled</returns>
ulong Signal(ulong value);
public ulong Signal(ulong value);
/// <summary>
/// Waits for the fence to reach the specified value
/// </summary>
/// <param name="value">Value to wait for</param>
void WaitForValue(ulong value);
public void WaitForValue(ulong value);
/// <summary>
/// Gets the last completed fence value
/// </summary>
/// <returns>Last completed fence value</returns>
ulong GetCompletedValue();
public ulong GetCompletedValue();
/// <summary>
/// Waits until all submitted commands have finished executing
/// </summary>
public void WaitIdle();
}
/// <summary>

View File

@@ -1,64 +0,0 @@
using Ghost.Graphics.Data;
using System.Runtime.CompilerServices;
using Win32.Graphics.Direct3D12;
namespace Ghost.Graphics.RHI;
public interface IDescriptorAllocator
{
public RenderTargetDescriptor AllocateRTV();
public RenderTargetDescriptor[] AllocateRTVs(uint count);
public DepthStencilDescriptor AllocateDSV();
public DepthStencilDescriptor[] AllocateDSVs(uint count);
public ShaderResourceDescriptor AllocateSRV();
public ShaderResourceDescriptor[] AllocateSRVs(uint count);
public SamplerDescriptor AllocateSampler();
public SamplerDescriptor[] AllocateSamplers(uint count);
public BindlessDescriptor AllocateBindless();
public BindlessDescriptor[] AllocateBindless(uint count);
public CpuDescriptorHandle GetCpuHandle(RenderTargetDescriptor descriptor);
public CpuDescriptorHandle GetCpuHandle(DepthStencilDescriptor descriptor);
public CpuDescriptorHandle GetCpuHandle(ShaderResourceDescriptor descriptor);
public GpuDescriptorHandle GetGpuHandle(ShaderResourceDescriptor descriptor);
public CpuDescriptorHandle GetCpuHandle(SamplerDescriptor descriptor);
public GpuDescriptorHandle GetGpuHandle(SamplerDescriptor descriptor);
public CpuDescriptorHandle GetCpuHandle(BindlessDescriptor descriptor);
public GpuDescriptorHandle GetGpuHandle(BindlessDescriptor descriptor);
public void Release(RenderTargetDescriptor descriptor);
public void Release(ReadOnlySpan<RenderTargetDescriptor> descriptors);
public void Release(DepthStencilDescriptor descriptor);
public void Release(ReadOnlySpan<DepthStencilDescriptor> descriptors);
public void Release(ShaderResourceDescriptor descriptor);
public void Release(ReadOnlySpan<ShaderResourceDescriptor> descriptors);
public void Release(SamplerDescriptor descriptor);
public void Release(ReadOnlySpan<SamplerDescriptor> descriptors);
public void Release(BindlessDescriptor descriptor);
public void Release(ReadOnlySpan<BindlessDescriptor> descriptors);
}

View File

@@ -14,7 +14,7 @@ public interface IShaderPipeline
}
}
public interface IPipelineStateController
public interface IPipelineLibrary
{
public void CompileShader(Identifier<Shader> id, string shaderPath);

View File

@@ -1,4 +1,6 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Win32.Graphics.Direct3D12;
namespace Ghost.Graphics.RHI;
@@ -25,6 +27,61 @@ public enum PipelineType
Compute
}
[StructLayout(LayoutKind.Explicit)]
public struct ResourceDesc
{
[FieldOffset(0)]
public TextureDesc textureDescription;
[FieldOffset(0)]
public BufferDesc bufferDescription;
public static ResourceDesc Buffer(BufferDesc desc)
{
return new ResourceDesc
{
bufferDescription = desc
};
}
public static ResourceDesc Texture(TextureDesc desc)
{
return new ResourceDesc
{
textureDescription = desc
};
}
internal static ResourceDesc FromD3D12(ResourceDescription desc)
{
if (desc.Dimension == ResourceDimension.Buffer)
{
return Buffer(new BufferDesc
{
Size = desc.Width,
Stride = 0,
Usage = BufferUsage.None,
CreationFlags = BufferCreationFlags.None,
MemoryType = MemoryType.Default
});
}
else
{
return Texture(new TextureDesc
{
Width = (uint)desc.Width,
Height = desc.Height,
Slice = desc.DepthOrArraySize,
Format = desc.Format.ToTextureFormat(),
Dimension = desc.Dimension.ToTextureDimension(),
MipLevels = desc.MipLevels,
Usage = TextureUsage.None,
CreationFlags = TextureCreationFlags.None
});
}
}
}
/// <summary>
/// Render target description
/// Supports either color OR depth rendering, not both
@@ -157,7 +214,7 @@ public struct RenderTargetDesc
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TextureDesc ToTextureDescriptor(RenderTargetDesc desc)
public static TextureDesc ToTextureDescripton(RenderTargetDesc desc)
{
var usage = desc.Type == RenderTargetType.Color ? TextureUsage.RenderTarget : TextureUsage.DepthStencil;
if (desc.CreationFlags.HasFlag(RenderTargetCreationFlags.AllowUAV))
@@ -329,6 +386,20 @@ public enum TextureDimension
TextureCubeArray = 5
}
public static class TextureDimensionExtension
{
public static TextureDimension ToTextureDimension(this ResourceDimension dimension)
{
return dimension switch
{
ResourceDimension.Texture1D => TextureDimension.Texture2D,
ResourceDimension.Texture2D => TextureDimension.Texture2D,
ResourceDimension.Texture3D => TextureDimension.Texture3D,
_ => TextureDimension.Unknown,
};
}
}
/// <summary>
/// Render target creation flags
/// </summary>

View File

@@ -1,4 +1,6 @@
using Ghost.Graphics.RHI;
using Ghost.Core;
using Ghost.Graphics.Data;
using Misaki.HighPerformance.Mathematics;
namespace Ghost.Graphics.RHI;
@@ -7,37 +9,42 @@ namespace Ghost.Graphics.RHI;
/// </summary>
public interface IRenderer : IDisposable
{
public uint2 Size
{
get;
}
/// <summary>
/// Sets the render target for this renderer
/// </summary>
/// <param name="renderTarget">Render target to render into</param>
void SetRenderTarget(IRenderTarget? renderTarget);
public void SetRenderTarget(Handle<Texture> renderTarget);
/// <summary>
/// Sets the swap chain for this renderer
/// </summary>
/// <param name="swapChain">Swap chain for presentation</param>
void SetSwapChain(ISwapChain? swapChain);
public void SetSwapChain(ISwapChain? swapChain);
/// <summary>
/// Executes any pending resize operations
/// </summary>
void ExecutePendingResize();
public void ExecutePendingResize();
/// <summary>
/// Renders a frame
/// </summary>
void Render();
public void Render();
/// <summary>
/// Requests a resize operation
/// </summary>
/// <param name="width">New width</param>
/// <param name="height">New height</param>
void RequestResize(uint width, uint height);
public void RequestResize(uint width, uint height);
/// <summary>
/// Waits for the GPU to complete all work
/// </summary>
void WaitIdle();
public void WaitIdle();
}

View File

@@ -1,120 +1,7 @@
using Ghost.Graphics.Data;
using Win32.Graphics.Dxgi.Common;
namespace Ghost.Graphics.RHI;
/// <summary>
/// Base interface for all graphics resources
/// </summary>
public interface IResource : IDisposable
{
/// <summary>
/// Current resource state
/// </summary>
ResourceState CurrentState
{
get;
}
/// <summary>
/// Resource name for debugging
/// </summary>
string Name
{
get; set;
}
/// <summary>
/// Size of the resource in bytes
/// </summary>
ulong Size
{
get;
}
}
/// <summary>
/// Texture resource interface
/// </summary>
public interface ITexture : IResource
{
/// <summary>
/// Width of the texture in pixels
/// </summary>
uint Width
{
get;
}
/// <summary>
/// Height of the texture in pixels
/// </summary>
uint Height
{
get;
}
/// <summary>
/// Texture format
/// </summary>
TextureFormat Format
{
get;
}
/// <summary>
/// Number of mip levels
/// </summary>
uint MipLevels
{
get;
}
}
/// <summary>
/// Buffer resource interface
/// </summary>
public interface IBuffer : IResource
{
/// <summary>
/// Buffer usage type
/// </summary>
public BufferUsage Usage
{
get;
}
public BufferHandle Handle
{
get;
}
/// <summary>
/// Maps the buffer for CPU access
/// </summary>
/// <returns>Pointer to mapped memory</returns>
public unsafe void* Map();
/// <summary>
/// Unmaps the buffer from CPU access
/// </summary>
public void Unmap();
}
/// <summary>
/// Render target interface for rendering operations
/// Supports either color OR depth rendering, not both
/// </summary>
public interface IRenderTarget : ITexture
{
/// <summary>
/// Type of render target (color or depth)
/// </summary>
RenderTargetType Type
{
get;
}
}
/// <summary>
/// Type of render target
/// </summary>
@@ -127,6 +14,8 @@ public enum RenderTargetType
/// <summary>
/// Texture format enumeration
/// </summary>
// TODO: Support compressed formats (BCn, ASTC, ETC2, etc)
public enum TextureFormat
{
Unknown,
@@ -162,4 +51,160 @@ public enum BufferCreationFlags
{
None = 0,
Bindless = 1 << 0
}
public enum IndexType
{
UInt16,
UInt32
}
internal static class TextureFormatExtensions
{
public static Format ToD3D12Format(this TextureFormat format)
{
return format switch
{
TextureFormat.R8G8B8A8_UNorm => Format.R8G8B8A8Unorm,
TextureFormat.B8G8R8A8_UNorm => Format.B8G8R8A8Unorm,
TextureFormat.R16G16B16A16_Float => Format.R16G16B16A16Float,
TextureFormat.R32G32B32A32_Float => Format.R32G32B32A32Float,
TextureFormat.D24_UNorm_S8_UInt => Format.D24UnormS8Uint,
TextureFormat.D32_Float => Format.D32Float,
_ => throw new NotSupportedException($"Texture format {format} is not supported."),
};
}
public static TextureFormat ToTextureFormat(this Format format)
{
return format switch
{
Format.R8G8B8A8Unorm => TextureFormat.R8G8B8A8_UNorm,
Format.B8G8R8A8Unorm => TextureFormat.B8G8R8A8_UNorm,
Format.R16G16B16A16Float => TextureFormat.R16G16B16A16_Float,
Format.R32G32B32A32Float => TextureFormat.R32G32B32A32_Float,
Format.D24UnormS8Uint => TextureFormat.D24_UNorm_S8_UInt,
Format.D32Float => TextureFormat.D32_Float,
_ => TextureFormat.Unknown,
};
}
public static int GetBytesPerPixel(this TextureFormat format)
{
return format switch
{
TextureFormat.R8G8B8A8_UNorm => 4,
TextureFormat.B8G8R8A8_UNorm => 4,
TextureFormat.R16G16B16A16_Float => 8,
TextureFormat.R32G32B32A32_Float => 16,
TextureFormat.D24_UNorm_S8_UInt => 4,
TextureFormat.D32_Float => 4,
_ => throw new NotSupportedException($"Texture format {format} is not supported."),
};
}
public static void GetSurfaceInfo(this TextureFormat format, int width, int height, out int rowPitch, out int slicePitch, out int rowCount)
{
var bc = false;
var packed = false;
var planar = false;
var bpe = 0;
//switch (format)
//{
// case Format.BC1Typeless:
// case Format.BC1Unorm:
// case Format.BC1UnormSrgb:
// case Format.BC4Typeless:
// case Format.BC4Unorm:
// case Format.BC4Snorm:
// bc = true;
// bpe = 8;
// break;
// case Format.BC2Typeless:
// case Format.BC2Unorm:
// case Format.BC2UnormSrgb:
// case Format.BC3Typeless:
// case Format.BC3Unorm:
// case Format.BC3UnormSrgb:
// case Format.BC5Typeless:
// case Format.BC5Unorm:
// case Format.BC5Snorm:
// case Format.BC6HTypeless:
// case Format.BC6HUF16:
// case Format.BC6HSF16:
// case Format.BC7Typeless:
// case Format.BC7Unorm:
// case Format.BC7UnormSrgb:
// bc = true;
// bpe = 16;
// break;
// case Format.R8G8_B8G8Unorm:
// case Format.G8R8_G8B8Unorm:
// case Format.YUY2:
// packed = true;
// bpe = 4;
// break;
// case Format.Y210:
// case Format.Y216:
// packed = true;
// bpe = 8;
// break;
// case Format.NV12:
// case Format.Opaque420:
// case Format.P208:
// planar = true;
// bpe = 2;
// break;
// case Format.P010:
// case Format.P016:
// planar = true;
// bpe = 4;
// break;
// default:
// break;
//}
if (bc)
{
var numBlocksWide = 0;
if (width > 0)
{
numBlocksWide = Math.Max(1, (width + 3) / 4);
}
var numBlocksHigh = 0;
if (height > 0)
{
numBlocksHigh = Math.Max(1, (height + 3) / 4);
}
rowPitch = numBlocksWide * bpe;
rowCount = numBlocksHigh;
slicePitch = rowPitch * numBlocksHigh;
}
else if (packed)
{
rowPitch = ((width + 1) >> 1) * bpe;
rowCount = height;
slicePitch = rowPitch * height;
}
else if (planar)
{
rowPitch = ((width + 1) >> 1) * bpe;
slicePitch = (rowPitch * height) + ((rowPitch * height + 1) >> 1);
rowCount = (int)(height + ((height + 1u) >> 1));
}
else
{
var bpp = GetBytesPerPixel(format) * 8;
rowPitch = (width * bpp + 7) / 8; // round up to nearest byte
rowCount = height;
slicePitch = rowPitch * height;
}
}
}

View File

@@ -1,4 +1,6 @@
using Ghost.Graphics.Data;
using Ghost.Core;
using Ghost.Graphics.Data;
using Misaki.HighPerformance.LowLevel.Collections;
namespace Ghost.Graphics.RHI;
@@ -9,25 +11,46 @@ public interface IResourceAllocator
/// </summary>
/// <param name="desc">Texture description</param>
/// <returns>A new texture handle point to the resource</returns>
public TextureHandle CreateTexture(ref readonly TextureDesc desc, bool tempResource = false);
public Handle<Texture> CreateTexture(ref readonly TextureDesc desc, bool tempResource = false);
/// <summary>
/// Creates a render target for off-screen rendering
/// </summary>
/// <param name="desc">Render target description</param>
/// <returns>A new render target instance</returns>
public TextureHandle CreateRenderTarget(ref readonly RenderTargetDesc desc, bool tempResource = false);
/// <returns>A new texture handle point to the resource</returns>
public Handle<Texture> CreateRenderTarget(ref readonly RenderTargetDesc desc, bool tempResource = false);
/// <summary>
/// Creates a buffer resource
/// </summary>
/// <param name="desc">Buffer description</param>
/// <returns>A new buffer handle point to the resource</returns>
public BufferHandle CreateBuffer(ref readonly BufferDesc desc, bool tempResource = false);
public Handle<GraphicsBuffer> CreateBuffer(ref readonly BufferDesc desc, bool tempResource = false);
/// <summary>
/// Creates a new mesh from the specified vertex and index data.
/// </summary>
/// <param name="vertices">A UnsafeList containing the vertices that define the geometry of the mesh. Must contain at least one vertex.</param>
/// <param name="indices">A UnsafeList containing the indices that specify how vertices are connected to form primitives. Must contain at least one index.</param>
/// <returns>An <see cref="Identifier{Mesh}"/> representing the newly created mesh.</returns>
public Handle<Mesh> CreateMesh(UnsafeList<Vertex> vertices, UnsafeList<uint> indices);
/// <summary>
/// Creates a new material instance using the specified shader.
/// </summary>
/// <param name="shader">The identifier of the shader to associate with the new material. Cannot be null.</param>
/// <returns>An <see cref="Identifier{Material}"/> representing the newly created material.</returns>
public Handle<Material> CreateMaterial(Identifier<Shader> shader);
/// <summary>
/// Creates a new shader and returns its unique identifier.
/// </summary>
/// <returns>An <see cref="Identifier{Shader}"/> representing the newly created shader.</returns>
public Identifier<Shader> CreateShader();
/// <summary>
/// Release a resource given its handle
/// </summary>
/// <param name="handle">Resource handle</param>
public void ReleaseResource(ResourceHandle handle);
public void ReleaseResource(Handle<GPUResource> handle);
}

View File

@@ -6,51 +6,127 @@ namespace Ghost.Graphics.RHI;
public interface IResourceDatabase
{
/// <summary>
/// Get the raw gpu resource pointer from a resource handle
/// Imports an external unmanaged resource and returns a handle for use within the resource management system.
/// </summary>
/// <typeparam name="T">The type of the resource.</typeparam>
/// <param name="handle">Resource handle</param>
/// <returns>Pointer to the resource</returns>
public unsafe T* GetResource<T>(ResourceHandle handle)
where T: unmanaged;
/// <typeparam name="T">The type of the unmanaged resource pointer to import.</typeparam>
/// <param name="resourcePtr">A pointer to the external unmanaged resource to be imported. Must remain valid for the duration of the resource's usage.</param>
/// <param name="initialState">The initial state to assign to the imported resource.</param>
/// <returns>A handle representing the imported resource, which can be used for subsequent operations.</returns>
public unsafe Handle<GPUResource> ImportExternalResource<T>(T resourcePtr, ResourceState initialState)
where T : unmanaged;
/// <summary>
/// Retrieves the current state of the specified resource.
/// </summary>
/// <param name="handle">The handle that uniquely identifies the resource whose state is to be retrieved. Must not be null.</param>
/// <returns>A ResourceState value representing the current state of the resource associated with the specified handle.</returns>
public ResourceState GetResourceState(ResourceHandle handle);
public ResourceState GetResourceState(Handle<GPUResource> handle);
/// <summary>
/// Sets the state of the specified resource handle to the given value.
/// </summary>
/// <param name="handle">The handle that identifies the resource whose state will be updated. Cannot be null.</param>
/// <param name="state">The new state to assign to the resource represented by <paramref name="handle"/>.</param>
public void SetResourceState(ResourceHandle handle, ResourceState state);
public void SetResourceState(Handle<GPUResource> handle, ResourceState state);
/// <summary>
/// Retrieves the description of a GPU resource associated with the specified handle.
/// </summary>
/// <param name="handle">A handle that identifies the GPU resource for which to obtain the description. Must reference a valid resource.</param>
/// <returns>A ResourceDesc structure containing details about the specified GPU resource.</returns>
public ResourceDesc GetResourceDescription(Handle<GPUResource> handle);
/// <summary>
/// Retrieves the bindless index associated with the specified GPU resource handle.
/// </summary>
/// <param name="handle">A handle to the GPU resource for which to obtain the bindless index. Must reference a valid, currently registered resource.</param>
/// <returns>The bindless index corresponding to the specified GPU resource handle. -1 if the resource does not support bindless access or is not found.</returns>
public int GetBindlessIndex(Handle<GPUResource> handle);
/// <summary>
/// Removes a resource from the database using its handle.
/// </summary>
/// <param name="handle">The handle of the resource to be removed.</param>
public void RemoveResource(ResourceHandle handle);
public void ReleaseResource(Handle<GPUResource> handle);
public Identifier<Mesh> AddMesh(ref readonly Mesh mesh);
/// <summary>
/// Adds a mesh to the resource database and returns its handle.
/// </summary>
/// <param name="mesh">The mesh data to be added to the database.</param>
/// <returns>The <see cref="Handle{Mesh}"/> representing the newly added mesh.</returns>"/>
public Handle<Mesh> AddMesh(ref readonly Mesh mesh);
public bool HasMesh(Identifier<Mesh> id);
/// <summary>
/// Determines whether a mesh with the specified Handle exists.
/// </summary>
/// <param name="handle">The handle of the mesh to check for existence. Cannot be null.</param>
/// <returns>true if a mesh with the specified Handle exists; otherwise, false.</returns>
public bool HasMesh(Handle<Mesh> handle);
public Mesh GetMesh(Identifier<Mesh> id);
/// <summary>
/// Returns a reference to the mesh associated with the specified handle.
/// </summary>
/// <param name="handle">The handle of the mesh to retrieve. Must refer to a valid mesh; otherwise, the behavior is undefined.</param>
/// <returns>A reference to the mesh corresponding to the specified handle.</returns>
public ref Mesh GetMeshReference(Handle<Mesh> handle);
public ref Mesh GetMeshReference(Identifier<Mesh> id);
/// <summary>
/// Releases the mesh resource associated with the specified handle, freeing any resources held by it. Includes both CPU and GPU resources.
/// </summary>
/// <param name="handle">The handle of the mesh to release. Must refer to a mesh that was previously created and not already released.</param>
public void ReleaseMesh(Handle<Mesh> handle);
public void RemoveMesh(Identifier<Mesh> id);
/// <summary>
/// Adds a new material to the collection and returns its unique handle.
/// </summary>
/// <param name="material">The material to add. The material must be fully initialized before calling this method.</param>
/// <returns>The <see cref="Handle{Material}"/> representing the newly added material.</returns>
public Handle<Material> AddMaterial(ref readonly Material material);
/// <summary>
/// Determines whether a material with the specified handle exists in the collection.
/// </summary>
/// <param name="handle">The handle of the material to check for existence.</param>
/// <returns>true if a material with the specified handle exists; otherwise, false.</returns>
public bool HasMaterial(Handle<Material> handle);
/// <summary>
/// Gets a reference to the material associated with the specified handle.
/// </summary>
/// <param name="handle">The handle of the material to retrieve. Must refer to a valid material.</param>
/// <returns>A reference to the material corresponding to the specified handle.</returns>
public ref Material GetMaterialReference(Handle<Material> handle);
/// <summary>
/// Releases the material associated with the specified handle, making it available for reuse or disposal.
/// </summary>
/// <param name="handle">The handle of the material to release. Must refer to a material that has been previously acquired.</param>
public void ReleaseMaterial(Handle<Material> handle);
/// <summary>
/// Adds the specified shader to the collection and returns its unique identifier.
/// </summary>
/// <param name="shader">The shader to add. The shader is passed by read-only reference and will not be modified.</param>
/// <returns>The <see cref="Identifier{Shader}"/> representing the newly added shader.</returns>
public Identifier<Shader> AddShader(ref readonly Shader shader);
/// <summary>
/// Determines whether a shader with the specified identifier exists in the collection.
/// </summary>
/// <param name="id">The identifier of the shader to check for existence.</param>
/// <returns>true if a shader with the specified identifier exists; otherwise, false.</returns>
public bool HasShader(Identifier<Shader> id);
public Shader GetShader(Identifier<Shader> id);
/// <summary>
/// Returns a reference to the shader associated with the specified identifier.
/// </summary>
/// <param name="id">The identifier of the shader to retrieve. Must refer to a valid shader.</param>
/// <returns>A reference to the shader corresponding to the specified identifier.</returns>
public ref Shader GetShaderReference(Identifier<Shader> id);
public void RemoveShader(Identifier<Shader> id);
/// <summary>
/// Releases the shader associated with the specified identifier, freeing any resources allocated to it.
/// </summary>
/// <param name="id">The identifier of the shader to release. Must refer to a valid, previously created shader.</param>
public void ReleaseShader(Identifier<Shader> id);
}

View File

@@ -1,4 +1,5 @@
using Ghost.Graphics.Contracts;
using Ghost.Core;
using Ghost.Graphics.Data;
namespace Ghost.Graphics.RHI;
@@ -10,36 +11,45 @@ public interface ISwapChain : IDisposable
/// <summary>
/// Width of the swap chain back buffers
/// </summary>
uint Width { get; }
public uint Width
{
get;
}
/// <summary>
/// Height of the swap chain back buffers
/// </summary>
uint Height { get; }
public uint Height
{
get;
}
/// <summary>
/// Number of back buffers
/// </summary>
uint BufferCount { get; }
public uint BufferCount
{
get;
}
/// <summary>
/// Gets the current back buffer texture
/// </summary>
/// <returns>Current back buffer texture</returns>
IRenderTarget GetCurrentBackBuffer();
public Handle<Texture> GetCurrentBackBuffer();
/// <summary>
/// Presents the rendered frame
/// </summary>
/// <param name="vsync">Enable vertical synchronization</param>
void Present(bool vsync = true);
public void Present(bool vsync = true);
/// <summary>
/// Resizes the swap chain back buffers
/// </summary>
/// <param name="width">New width</param>
/// <param name="height">New height</param>
void Resize(uint width, uint height);
public void Resize(uint width, uint height);
}
/// <summary>
@@ -84,25 +94,25 @@ public struct SwapChainTarget
/// <summary>
/// Target type
/// </summary>
public SwapChainTargetType Type;
public SwapChainTargetType type;
/// <summary>
/// Window handle for HWND targets
/// </summary>
public nint WindowHandle;
public nint windowHandle;
/// <summary>
/// Composition surface for UWP/WinUI targets
/// </summary>
public object? CompositionSurface;
public object? compositionSurface;
public static SwapChainTarget FromWindowHandle(nint hwnd)
{
return new SwapChainTarget
{
Type = SwapChainTargetType.WindowHandle,
WindowHandle = hwnd,
CompositionSurface = null
type = SwapChainTargetType.WindowHandle,
windowHandle = hwnd,
compositionSurface = null
};
}
@@ -110,9 +120,9 @@ public struct SwapChainTarget
{
return new SwapChainTarget
{
Type = SwapChainTargetType.Composition,
WindowHandle = nint.Zero,
CompositionSurface = surface
type = SwapChainTargetType.Composition,
windowHandle = nint.Zero,
compositionSurface = surface
};
}
}

View File

@@ -1,3 +1,4 @@
using Ghost.Core;
using Ghost.Graphics.Data;
namespace Ghost.Graphics.RenderGraphModule;
@@ -122,7 +123,7 @@ public sealed class RenderGraph : IDisposable
/// <summary>
/// Import an external texture (e.g., from previous frame, swap chain)
/// </summary>
public RGTextureHandle ImportTexture(string name, TextureHandle externalHandle, TextureDescription description)
public RGTextureHandle ImportTexture(string name, Handle<Texture> externalHandle, TextureDescription description)
{
var texture = new RenderGraphTexture(_resources.Count, name, externalHandle, description);
@@ -133,7 +134,7 @@ public sealed class RenderGraph : IDisposable
/// <summary>
/// Import an external buffer (e.g., from previous frame)
/// </summary>
public RGBufferHandle ImportBuffer(string name, BufferHandle externalHandle, BufferDescription description)
public RGBufferHandle ImportBuffer(string name, Handle<GraphicsBuffer> externalHandle, BufferDescription description)
{
var buffer = new RenderGraphBuffer(_resources.Count, name, externalHandle, description);
@@ -144,7 +145,7 @@ public sealed class RenderGraph : IDisposable
/// <summary>
/// Export a resource for use in the next frame (for history buffers)
/// </summary>
public TextureHandle ExportTexture(RGTextureHandle handle)
public Handle<Texture> ExportTexture(RGTextureHandle handle)
{
if (!handle.IsValid || handle._resourceId >= _resources.Count)
throw new ArgumentException("Invalid texture handle", nameof(handle));
@@ -157,7 +158,7 @@ public sealed class RenderGraph : IDisposable
/// <summary>
/// Export a buffer for use in the next frame
/// </summary>
public BufferHandle ExportBuffer(RGBufferHandle handle)
public Handle<GraphicsBuffer> ExportBuffer(RGBufferHandle handle)
{
if (!handle.IsValid || handle._resourceId >= _resources.Count)
throw new ArgumentException("Invalid buffer handle", nameof(handle));

View File

@@ -1,3 +1,4 @@
using Ghost.Core;
using Ghost.Graphics.Data;
using Win32.Graphics.Direct3D12;
using Win32.Graphics.Dxgi.Common;
@@ -91,7 +92,7 @@ public abstract class RenderGraphResource
/// </summary>
public sealed class RenderGraphTexture : RenderGraphResource
{
internal TextureHandle Handle
internal Handle<Texture> Handle
{
get; set;
}
@@ -107,7 +108,7 @@ public sealed class RenderGraphTexture : RenderGraphResource
Description = description;
}
public RenderGraphTexture(int id, string name, TextureHandle handle, TextureDescription description)
public RenderGraphTexture(int id, string name, Handle<Texture> handle, TextureDescription description)
: base(id, name, ResourceLifetime.External)
{
Handle = handle;
@@ -152,7 +153,7 @@ public sealed class RenderGraphTexture : RenderGraphResource
/// </summary>
public sealed class RenderGraphBuffer : RenderGraphResource
{
internal BufferHandle Handle
internal Handle<GraphicsBuffer> Handle
{
get; set;
}
@@ -167,7 +168,7 @@ public sealed class RenderGraphBuffer : RenderGraphResource
{
Description = description;
}
public RenderGraphBuffer(int id, string name, BufferHandle handle, BufferDescription description)
public RenderGraphBuffer(int id, string name, Handle<GraphicsBuffer> handle, BufferDescription description)
: base(id, name, ResourceLifetime.External)
{
Handle = handle;

View File

@@ -1,6 +1,3 @@
using Ghost.Graphics.Data;
using Win32.Graphics.Direct3D12;
namespace Ghost.Graphics.RenderGraphModule;
/// <summary>

View File

@@ -1,9 +1,11 @@
using Ghost.Core;
using Ghost.Graphics.Contracts;
using Ghost.Graphics.D3D12;
using Ghost.Graphics.Data;
using Ghost.Graphics.RHI;
using Ghost.Graphics.Utilities;
using System.Numerics;
using Misaki.HighPerformance.Image;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Utilities;
namespace Ghost.Graphics.RenderPasses;
@@ -12,98 +14,10 @@ namespace Ghost.Graphics.RenderPasses;
/// </summary>
internal unsafe class MeshRenderPass : IRenderPass
{
private const string _HLSL_SOURCE = @"
cbuffer ConstantBuffer : register(b0)
{
float4 _Color;
uint _TextureIndex1;
uint _TextureIndex2;
uint _TextureIndex3;
uint _TextureIndex4;
uint _VertexBufferIndex;
uint _IndexBufferIndex;
};
// SM 6.6 approach - direct access to global descriptor heap
SamplerState _MainSampler : register(s0);
struct Vertex
{
float4 position;
float4 normal;
float4 tangent;
float4 color;
float4 uv;
};
struct PixelInput
{
float4 position : SV_POSITION;
float4 color : COLOR;
float4 uv : TEXCOORD0;
};
// Bindless vertex shader that fetches vertex data from bindless buffers
PixelInput VSMain(uint vertexId : SV_VertexID, uint instanceId : SV_InstanceID)
{
// Get bindless buffers
ByteAddressBuffer vertexBuffer = ResourceDescriptorHeap[_VertexBufferIndex];
ByteAddressBuffer indexBuffer = ResourceDescriptorHeap[_IndexBufferIndex];
// For fully bindless rendering, we use instanced drawing where:
// - Each instance represents a triangle (instanceId = triangle index)
// - vertexId goes from 0 to 2 (the 3 vertices of the triangle)
// Calculate the index into the index buffer
uint indexOffset = (instanceId * 3 + vertexId) * 4; // 4 bytes per index (uint32)
uint vertexIndex = indexBuffer.Load(indexOffset);
// Calculate the offset into the vertex buffer
uint vertexOffset = vertexIndex * 80; // 80 bytes per vertex (5 * float4)
// Load vertex data from bindless vertex buffer
Vertex vertex;
vertex.position = asfloat(vertexBuffer.Load4(vertexOffset + 0));
vertex.normal = asfloat(vertexBuffer.Load4(vertexOffset + 16));
vertex.tangent = asfloat(vertexBuffer.Load4(vertexOffset + 32));
vertex.color = asfloat(vertexBuffer.Load4(vertexOffset + 48));
vertex.uv = asfloat(vertexBuffer.Load4(vertexOffset + 64));
// Output transformed vertex
PixelInput output;
output.position = vertex.position;
output.color = vertex.color;
output.uv = vertex.uv;
return output;
}
float4 PSMain(PixelInput input) : SV_TARGET
{
// SM 6.6 Modern Bindless Approach:
// ResourceDescriptorHeap[index] directly accesses any texture in the heap
Texture2D tex1 = ResourceDescriptorHeap[_TextureIndex1];
Texture2D tex2 = ResourceDescriptorHeap[_TextureIndex2];
Texture2D tex3 = ResourceDescriptorHeap[_TextureIndex3];
Texture2D tex4 = ResourceDescriptorHeap[_TextureIndex4];
// Sample the textures
float4 color1 = tex1.Sample(_MainSampler, input.uv.xy);
float4 color2 = tex2.Sample(_MainSampler, input.uv.xy);
float4 color3 = tex3.Sample(_MainSampler, input.uv.xy);
float4 color4 = tex4.Sample(_MainSampler, input.uv.xy);
// Blend all textures together (simple average)
float4 blendedColor = (color1 + color2 + color3 + color4) * 0.25f;
return blendedColor * _Color;
}
";
// High-level bindless objects
private MeshClass? _mesh;
private Shader? _shader;
private MaterialClass? _material;
private Texture2D[]? _textures;
private Identifier<Mesh> _mesh;
private Identifier<Shader> _shader;
private Identifier<Material> _material;
private Handle<Texture>[]? _textures;
// Texture file paths for this demo
private readonly string[] _textureFiles = [
@@ -113,48 +27,62 @@ float4 PSMain(PixelInput input) : SV_TARGET
"C:/Users/Misaki/Downloads/Im/yande.re 1134666 blue_archive nakamasa_ichika sugarhigh.jpg"
];
public void Initialize(ICommandBuffer cmd)
public void Initialize(ref readonly RenderingContext ctx, IResourceAllocator resourceAllocator, IPipelineLibrary stateController)
{
_mesh = MeshBuilder.CreateCube(0.75f);
_mesh.UploadMeshData();
MeshBuilder.CreateCube(0.75f, default, out var vertices, out var indices);
_shader = new ShaderData(_HLSL_SOURCE);
_material = new Material(_shader);
_mesh = ctx.CreateMesh(vertices, indices);
ctx.UploadMesh(_mesh, true);
_textures = new Texture2D[_textureFiles.Length];
_shader = resourceAllocator.CreateShader();
_material = resourceAllocator.CreateMaterial(_shader);
var imageResults = new ImageResult[_textureFiles.Length];
_textures = new Handle<Texture>[_textureFiles.Length];
for (var i = 0; i < _textureFiles.Length; i++)
{
_textures[i] = Texture2D.FromFile(_textureFiles[i]);
_textures[i].UploadTextureData();
using var stream = File.OpenRead(_textureFiles[i]);
using var imageData = ImageResult.FromStream(stream);
imageResults[i] = imageData;
var desc = new TextureDesc
{
Width = imageData.Width,
Height = imageData.Height,
Dimension = TextureDimension.Texture2D,
CreationFlags = TextureCreationFlags.Bindless,
Format = TextureFormat.R8G8B8A8_UNorm,
MipLevels = 1,
Slice = 1,
Usage = TextureUsage.ShaderResource,
};
_textures[i] = ctx.CreateTexture(ref desc);
ctx.UploadTexture(_textures[i], new Span<byte>(imageData.Data, (int)imageData.Size));
}
_material.SetVector("_Color", new Vector4(1.0f, 1.0f, 1.0f, 1.0f));
for (var i = 0; i < _textures.Length; i++)
{
var texture = _textures[i];
_material.SetTexture($"_TextureIndex{i + 1}", texture);
}
_material.SetMeshBuffer(_mesh);
_material.UploadMaterialData();
stateController.CompileShader(_shader, "F:\\csharp\\GhostEngine\\Ghost.Graphics\\RenderPasses\\ShaderCode.hlsl");
stateController.PreCookPipelineState();
}
public void Execute(ICommandBuffer cmd)
public void Execute(ref readonly RenderingContext ctx)
{
cmd.DrawMesh(_mesh!, _material!);
ctx.RenderMesh(_mesh, _material);
}
public void Dispose()
public void Cleanup(IResourceDatabase resourceDatabase)
{
_mesh?.Dispose();
_shader?.Dispose();
_material?.Dispose();
resourceDatabase.ReleaseMaterial(_material);
resourceDatabase.ReleaseShader(_shader);
resourceDatabase.ReleaseMesh(_mesh);
if (_textures != null)
{
foreach (var texture in _textures)
{
texture?.Dispose();
resourceDatabase.ReleaseResource(texture.AsResource());
}
}
}

View File

@@ -0,0 +1,84 @@
cbuffer ConstantBuffer : register(b0)
{
float4 _Color;
uint _TextureIndex1;
uint _TextureIndex2;
uint _TextureIndex3;
uint _TextureIndex4;
uint _VertexBufferIndex;
uint _IndexBufferIndex;
};
// SM 6.6 approach - direct access to global descriptor heap
SamplerState _MainSampler : register(s0);
struct Vertex
{
float4 position;
float4 normal;
float4 tangent;
float4 color;
float4 uv;
};
struct PixelInput
{
float4 position : SV_POSITION;
float4 color : COLOR;
float4 uv : TEXCOORD0;
};
// Bindless vertex shader that fetches vertex data from bindless buffers
PixelInput VSMain(uint vertexId : SV_VertexID, uint instanceId : SV_InstanceID)
{
// Get bindless buffers
ByteAddressBuffer vertexBuffer = ResourceDescriptorHeap[_VertexBufferIndex];
ByteAddressBuffer indexBuffer = ResourceDescriptorHeap[_IndexBufferIndex];
// For fully bindless rendering, we use instanced drawing where:
// - Each instance represents a triangle (instanceId = triangle index)
// - vertexId goes from 0 to 2 (the 3 vertices of the triangle)
// Calculate the index into the index buffer
uint indexOffset = (instanceId * 3 + vertexId) * 4; // 4 bytes per index (uint32)
uint vertexIndex = indexBuffer.Load(indexOffset);
// Calculate the offset into the vertex buffer
uint vertexOffset = vertexIndex * 80; // 80 bytes per vertex (5 * float4)
// Load vertex data from bindless vertex buffer
Vertex vertex;
vertex.position = asfloat(vertexBuffer.Load4(vertexOffset + 0));
vertex.normal = asfloat(vertexBuffer.Load4(vertexOffset + 16));
vertex.tangent = asfloat(vertexBuffer.Load4(vertexOffset + 32));
vertex.color = asfloat(vertexBuffer.Load4(vertexOffset + 48));
vertex.uv = asfloat(vertexBuffer.Load4(vertexOffset + 64));
// Output transformed vertex
PixelInput output;
output.position = vertex.position;
output.color = vertex.color;
output.uv = vertex.uv;
return output;
}
float4 PSMain(PixelInput input) : SV_TARGET
{
// SM 6.6 Modern Bindless Approach:
// ResourceDescriptorHeap[index] directly accesses any texture in the heap
Texture2D tex1 = ResourceDescriptorHeap[_TextureIndex1];
Texture2D tex2 = ResourceDescriptorHeap[_TextureIndex2];
Texture2D tex3 = ResourceDescriptorHeap[_TextureIndex3];
Texture2D tex4 = ResourceDescriptorHeap[_TextureIndex4];
// Sample the textures
float4 color1 = tex1.Sample(_MainSampler, input.uv.xy);
float4 color2 = tex2.Sample(_MainSampler, input.uv.xy);
float4 color3 = tex3.Sample(_MainSampler, input.uv.xy);
float4 color4 = tex4.Sample(_MainSampler, input.uv.xy);
// Blend all textures together (simple average)
float4 blendedColor = (color1 + color2 + color3 + color4) * 0.25f;
return blendedColor * _Color;
}

View File

@@ -1,19 +1,23 @@
using Ghost.Graphics.Data;
using System.Numerics;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.Mathematics;
namespace Ghost.Graphics.Utilities;
public static class MeshBuilder
public unsafe static class MeshBuilder
{
/// <summary>
/// Creates a unit cube centered at the origin with size 1.
/// </summary>
public static MeshClass CreateCube(float size = 1.0f, Color128 color = default)
public static void CreateCube(float size, Color128 color, out UnsafeList<Vertex> vertices, out UnsafeList<uint> indices)
{
var half = size * 0.5f;
var mesh = new MeshClass(24, 36);
var corners = new Vector4[]
vertices = new UnsafeList<Vertex>(24, Allocator.Persistent);
indices = new UnsafeList<uint>(36, Allocator.Persistent);
var corners = new float4[]
{
new (-half, -half, -half, 1.0f), new (half, -half, -half, 1.0f),
new (half, half, -half, 1.0f), new (-half, half, -half, 1.0f),
@@ -21,71 +25,114 @@ public static class MeshBuilder
new (half, half, half, 1.0f), new (-half, half, half, 1.0f)
};
int[][] faces =
[
[0,1,2,3],
[5,4,7,6],
[4,0,3,7],
[1,5,6,2],
[3,2,6,7],
[4,5,1,0]
];
var uvs = new Vector2[] { new(0, 0), new(1, 0), new(1, 1), new(0, 1) };
foreach (var face in faces)
var faces = stackalloc int[]
{
var baseIndex = mesh.VertexCount;
0,1,2,3,
5,4,7,6,
4,0,3,7,
1,5,6,2,
3,2,6,7,
4,5,1,0
};
var uvs = stackalloc float2[] { new(0, 0), new(1, 0), new(1, 1), new(0, 1) };
for (var f = 0; f < 6; f++)
{
var face = &faces[f * 4];
var baseIndex = (uint)vertices.Count;
for (var i = 0; i < 4; i++)
{
var vertex = new Vertex
{
position = corners[face[i]],
normal = Vector4.Zero,
tangent = Vector4.Zero,
normal = float4.zero,
tangent = float4.zero,
color = color,
uv = uvs[i].AsVector4()
uv = new(uvs[i], 0.0f, 0.0f)
};
mesh.AddVertex(vertex);
vertices.Add(vertex);
}
mesh.AddTriangle((int)baseIndex + 0, (int)baseIndex + 1, (int)baseIndex + 2);
mesh.AddTriangle((int)baseIndex + 0, (int)baseIndex + 2, (int)baseIndex + 3);
indices.Add(baseIndex + 0);
indices.Add(baseIndex + 1);
indices.Add(baseIndex + 2);
indices.Add(baseIndex + 0);
indices.Add(baseIndex + 2);
indices.Add(baseIndex + 3);
}
mesh.ComputeNormal();
mesh.ComputeTangents();
return mesh;
ComputeNormal(vertices, indices);
ComputeTangents(vertices, indices);
}
/// <summary>
/// Creates a plane on the XZ axis centered at the origin.
/// </summary>
public static MeshClass CreatePlane(float width = 1.0f, float depth = 1.0f, Color128 color = default)
public static void CreatePlane(float width, float depth, Color128 color, out UnsafeList<Vertex> vertices, out UnsafeList<uint> indices)
{
var hw = width * 0.5f;
var hd = depth * 0.5f;
var mesh = new MeshClass(4, 6);
mesh.AddVertex(new(new(-hw, 0.0f, -hd, 0.0f), Vector4.Zero, Vector4.Zero, color, new(0.0f)));
mesh.AddVertex(new(new(hw, 0.0f, -hd, 0.0f), Vector4.Zero, Vector4.Zero, color, new(1.0f, 0.0f, 0.0f, 0.0f)));
mesh.AddVertex(new(new(hw, 0.0f, hd, 0.0f), Vector4.Zero, Vector4.Zero, color, new(1.0f, 1.0f, 0.0f, 0.0f)));
mesh.AddVertex(new(new(-hw, 0.0f, hd, 0.0f), Vector4.Zero, Vector4.Zero, color, new(0.0f, 1.0f, 0.0f, 0.0f)));
vertices = new UnsafeList<Vertex>(4, Allocator.Persistent);
indices = new UnsafeList<uint>(6, Allocator.Persistent);
mesh.AddTriangle(0, 1, 2);
mesh.AddTriangle(0, 2, 3);
vertices.Add(new Vertex()
{
position = new(-hw, 0.0f, -hd, 0.0f),
normal = float4.zero,
tangent = float4.zero,
color = color,
uv = new(0.0f)
});
mesh.ComputeNormal();
mesh.ComputeTangents();
return mesh;
vertices.Add(new Vertex()
{
position = new(hw, 0.0f, -hd, 0.0f),
normal = float4.zero,
tangent = float4.zero,
color = color,
uv = new(1.0f, 0.0f, 0.0f, 0.0f)
});
vertices.Add(new Vertex()
{
position = new(hw, 0.0f, hd, 0.0f),
normal = float4.zero,
tangent = float4.zero,
color = color,
uv = new(1.0f, 1.0f, 0.0f, 0.0f)
});
vertices.Add(new Vertex()
{
position = new(-hw, 0.0f, hd, 0.0f),
normal = float4.zero,
tangent = float4.zero,
color = color,
uv = new(0.0f, 1.0f, 0.0f, 0.0f)
});
indices.Add(0);
indices.Add(1);
indices.Add(2);
indices.Add(0);
indices.Add(2);
indices.Add(3);
ComputeNormal(vertices, indices);
ComputeTangents(vertices, indices);
}
/// <summary>
/// Creates a UV sphere centered at the origin.
/// </summary>
public static MeshClass CreateSphere(int latitudeSegments = 16, int longitudeSegments = 24, float radius = 0.5f, Color128 color = default)
public static void CreateSphere(int latitudeSegments, int longitudeSegments, float radius, Color128 color, out UnsafeList<Vertex> vertices, out UnsafeList<uint> indices)
{
var mesh = new MeshClass((latitudeSegments + 1) * (longitudeSegments + 1), latitudeSegments * longitudeSegments * 6);
vertices = new UnsafeList<Vertex>((latitudeSegments + 1) * (longitudeSegments + 1), Allocator.Persistent);
indices = new UnsafeList<uint>(latitudeSegments * longitudeSegments * 6, Allocator.Persistent);
// Vertices
for (var lat = 0; lat <= latitudeSegments; lat++)
@@ -104,15 +151,14 @@ public static class MeshBuilder
var y = cosTheta;
var z = sinPhi * sinTheta;
mesh.AddVertex(
new()
{
position = new Vector4(x, y, z, 0.0f) * radius,
normal = Vector4.Zero,
tangent = Vector4.Zero,
color = color,
uv = new Vector4((float)lon / longitudeSegments, (float)lat / latitudeSegments, 0.0f, 0.0f)
});
vertices.Add(new Vertex
{
position = new float4(x, y, z, 0.0f) * radius,
normal = float4.zero,
tangent = float4.zero,
color = color,
uv = new float4((float)lon / longitudeSegments, (float)lat / latitudeSegments, 0.0f, 0.0f)
});
}
}
@@ -126,13 +172,112 @@ public static class MeshBuilder
var i2 = i1 + 1;
var i3 = i0 + 1;
mesh.AddTriangle(i0, i1, i2);
mesh.AddTriangle(i0, i2, i3);
indices.Add((uint)i0);
indices.Add((uint)i1);
indices.Add((uint)i2);
indices.Add((uint)i0);
indices.Add((uint)i2);
indices.Add((uint)i3);
}
}
mesh.ComputeNormal();
mesh.ComputeTangents();
return mesh;
ComputeNormal(vertices, indices);
ComputeTangents(vertices, indices);
}
/// <summary>
/// Auto-compute smooth per-vertex normals.
/// </summary>
/// <param name="vertices">The vertex list.</param>
/// <param name="indices">The index list.</param>
public static void ComputeNormal(UnsafeList<Vertex> vertices, UnsafeList<uint> indices)
{
if (!vertices.IsCreated || vertices.Count < 3
|| !indices.IsCreated || indices.Count < 3)
{
return;
}
for (var i = 0; i < indices.Count; i += 3)
{
var i0 = indices[i];
var i1 = indices[i + 1];
var i2 = indices[i + 2];
var v0 = vertices[i0];
var v1 = vertices[i1];
var v2 = vertices[i2];
var edge1 = v1.position - v0.position;
var edge2 = v2.position - v0.position;
var faceNormal = math.cross(edge1.xyz, edge2.xyz);
vertices[i0].normal.xyz += faceNormal;
vertices[i1].normal.xyz += faceNormal;
vertices[i2].normal.xyz += faceNormal;
}
for (var i = 0; i < vertices.Count; i++)
{
vertices[i].normal = math.normalize(vertices[i].normal);
}
}
/// <summary>
/// Auto-compute per-vertex tangents.
/// </summary>
/// <param name="vertices">The vertex list.</param>
/// <param name="indices">The index list.</param>
public static void ComputeTangents(UnsafeList<Vertex> vertices, UnsafeList<uint> indices)
{
using var scope = AllocationManager.CreateStackScope();
var bitangents = new UnsafeArray<float4>(vertices.Count, Allocator.Stack);
for (var i = 0; i < indices.Count; i += 3)
{
var i0 = indices[i];
var i1 = indices[i + 1];
var i2 = indices[i + 2];
var v0 = vertices[i0];
var v1 = vertices[i1];
var v2 = vertices[i2];
var uv0 = vertices[i0].uv;
var uv1 = vertices[i1].uv;
var uv2 = vertices[i2].uv;
var deltaPos1 = v1.position - v0.position;
var deltaPos2 = v2.position - v0.position;
var deltaUV1 = uv1 - uv0;
var deltaUV2 = uv2 - uv0;
var r = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV1.y * deltaUV2.x);
var tangent = (deltaPos1 * deltaUV2.y - deltaPos2 * deltaUV1.y) * r;
var bitangent = (deltaPos2 * deltaUV1.x - deltaPos1 * deltaUV2.x) * r;
for (var j = 0; j < 3; j++)
{
var idx = indices[i + j];
var t = vertices[idx].tangent;
vertices[idx].tangent.xyz = t.xyz + tangent.xyz;
bitangents[idx] += bitangent;
}
}
for (var i = 0; i < vertices.Count; i++)
{
var n = vertices[i].normal;
var t = vertices[i].tangent;
var proj = n * math.dot(n, t);
t = math.normalize(t - proj);
var b = bitangents[i];
var w = math.dot(math.cross(n.xyz, t.xyz), b.xyz) < 0.0f ? -1.0f : 1.0f;
vertices[i].tangent = new float4(t.x, t.y, t.z, w);
}
}
}

View File

@@ -1,4 +1,5 @@
namespace Ghost.Graphics.Utilities;
public class TextureUtility
{
public static uint CountMips(uint width, uint height)

View File

@@ -0,0 +1,14 @@
using Win32;
namespace Ghost.Graphics.Utilities;
internal static class Win32Utility
{
public static void ThrowIfFailed(this HResult hr)
{
if (hr.Failure)
{
throw new InvalidOperationException($"Operation failed with HRESULT: 0x{hr.Value:X8}");
}
}
}

View File

@@ -0,0 +1,50 @@
shader "MyShader/Standard"
{
properties
{
float4 color = float4(1, 1, 1, 1);
texture2d albedo;
}
pipeline
{
ztest = less_equal;
zwrite = on;
cull = back;
blend = opaque;
}
pass "Forward"
{
vs("ForwardVS.hlsl", "main");
ps("ForwardPS.hlsl", "main");
defines
{
USE_FOG;
}
keywords
{
dynamic(SHADOWS_ON, SHADOWS_OFF);
static(_ALBEDO_ON, _ALBEDO_OFF);
}
includes
{
"Common.hlsl";
}
}
pass "DepthOnly"
{
vs("DepthOnly.hlsl", "vsMain");
ps("DepthOnly.hlsl", "psMain");
pipeline
{
zwrite = on;
color_mask = 0;
}
}
}

View File

@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishAot>True</PublishAot>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Ghost.Shader\Ghost.Shader.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,18 @@
using Ghost.Shader;
var source = File.ReadAllText("F:\\csharp\\GhostEngine\\Ghost.Graphics\\test.ghostshader");
var lexer = new Lexer(source);
//foreach (var token in lexer.Tokenize())
//{
// Console.WriteLine($"{token.type} : '{token.lexeme}' at line {token.line}");
//}
var stream = new TokenStream(lexer.Tokenize().ToArray());
var shaderInfo = ShaderCompiler.ParseShaders(stream);
var model = ShaderCompiler.SemanticAnalysis(shaderInfo[0], out var errors);
foreach (var error in errors)
{
Console.WriteLine(error);
}

View File

@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Ghost.Shader.Test")]

View File

@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Combinators\**" />
<EmbeddedResource Remove="Combinators\**" />
<None Remove="Combinators\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Misaki.HighPerformance.Mathematics" Version="1.1.0" />
</ItemGroup>
</Project>

148
Ghost.Shader/Lexer.cs Normal file
View File

@@ -0,0 +1,148 @@
namespace Ghost.Shader;
public class Lexer
{
private readonly string _source;
private int _pos = 0;
private int _line = 0;
private int _column = 0;
public Lexer(string source) => _source = source;
public IEnumerable<Token> Tokenize()
{
while (_pos < _source.Length)
{
var c = _source[_pos];
// Skip whitespace
if (char.IsWhiteSpace(c))
{
if (c == '\n')
{
_line++;
_column = 0;
}
else
{
_column++;
}
_pos++;
continue;
}
// Punctuation
switch (c)
{
case '=':
yield return MakeToken(TokenType.Equals, "=");
break;
case ';':
yield return MakeToken(TokenType.Semicolon, ";");
break;
case ',':
yield return MakeToken(TokenType.Comma, ",");
break;
case '{':
yield return MakeToken(TokenType.LBrace, "{");
break;
case '}':
yield return MakeToken(TokenType.RBrace, "}");
break;
case '(':
yield return MakeToken(TokenType.LParen, "(");
break;
case ')':
yield return MakeToken(TokenType.RParen, ")");
break;
case '"':
yield return ReadString();
break;
default:
if (char.IsLetter(c) || c == '_')
{
yield return ReadIdentifierOrKeyword();
}
else if (char.IsDigit(c) || c == '.')
{
yield return ReadNumber();
}
else
{
_pos++; // skip unknown
}
break;
}
}
yield return new Token(TokenType.EndOfFile, "", _line, _column);
}
private Token MakeToken(TokenType type, string lexeme)
{
var token = new Token(type, lexeme, _line, _column);
_pos++;
_column++;
return token;
}
private Token ReadString()
{
var startCol = _column;
_pos++;
_column++; // skip "
var start = _pos;
while (_pos < _source.Length && _source[_pos] != '"')
{
_pos++;
_column++;
}
var text = _source.Substring(start, _pos - start);
_pos++;
_column++; // skip closing "
return new Token(TokenType.StringLiteral, text, _line, startCol);
}
private Token ReadIdentifierOrKeyword()
{
var startCol = _column;
var start = _pos;
while (_pos < _source.Length && (char.IsLetterOrDigit(_source[_pos]) || _source[_pos] == '_'))
{
_pos++;
_column++;
}
var text = _source.Substring(start, _pos - start);
// Optional: detect keywords
if (TokenLexicon.IsKeyword(text))
{
return new Token(TokenType.Keyword, text, _line, startCol);
}
return new Token(TokenType.Identifier, text, _line, startCol);
}
private Token ReadNumber()
{
var startCol = _column;
var start = _pos;
while (_pos < _source.Length && (char.IsDigit(_source[_pos]) || _source[_pos] == '.'))
{
_pos++;
_column++;
}
var num = _source.Substring(start, _pos - start);
return new Token(TokenType.Number, num, _line, startCol);
}
}

View File

@@ -0,0 +1,58 @@
namespace Ghost.Shader;
internal static class ParseUtility
{
public static List<Token> ParseFunctionArguments(ref TokenStreamSlice stream, TokenType tokenType)
{
var args = new List<Token>();
stream.Expect(TokenType.LParen);
while (!stream.Peek().type.Equals(TokenType.RParen))
{
var argToken = stream.Expect(tokenType);
args.Add(argToken);
if (stream.Peek().type == TokenType.Comma)
{
stream.Consume();
}
else
{
break;
}
}
stream.Expect(TokenType.RParen);
return args;
}
public static bool TrySliceLine(ref TokenStreamSlice stream, out TokenStreamSlice lineStream)
{
var length = 0;
if (!stream.TryPeek(out var nextToken))
{
lineStream = default;
return false;
}
while (!nextToken.Match(TokenType.Semicolon) && !nextToken.Match(TokenType.RBrace))
{
length++;
if (!stream.TryPeek(length, out nextToken))
{
break;
}
}
if (length > 0)
{
lineStream = stream.Slice(length);
stream.Consume(); // Consume the semicolon
return true;
}
lineStream = default;
return false;
}
}

View File

@@ -0,0 +1,29 @@
namespace Ghost.Shader.ParserBlock;
internal class DefinesBlock : IBlockParser<List<string>>
{
public static bool ShouldEnter(Token token)
{
return token.Match(TokenType.Keyword, TokenLexicon.KnownKeywords.DEFINES);
}
public static List<string> Parse(TokenStreamSlice stream)
{
stream.Expect(TokenType.Keyword);
stream.Expect(TokenType.LBrace);
var defines = new List<string>();
var bodyStream = stream.Slice(stream.Remaining - 1);
while (bodyStream.HasMore)
{
var defineToken = bodyStream.Expect(TokenType.Identifier);
defines.Add(defineToken.lexeme);
bodyStream.Expect(TokenType.Semicolon);
}
stream.Expect(TokenType.RBrace);
return defines;
}
}

View File

@@ -0,0 +1,13 @@
namespace Ghost.Shader.ParserBlock;
internal interface IBlockParser<T>
{
public static abstract bool ShouldEnter(Token token);
public static abstract T Parse(TokenStreamSlice ts);
}
internal interface IBlockParser<T, U> : IBlockParser<T>
{
public U SemanticAnalysis(T syntax, List<ShaderError> errors);
}

View File

@@ -0,0 +1,29 @@
namespace Ghost.Shader.ParserBlock;
internal class IncludesBlock : IBlockParser<List<string>>
{
public static bool ShouldEnter(Token token)
{
return token.Match(TokenType.Keyword, TokenLexicon.KnownKeywords.INCLUDES);
}
public static List<string> Parse(TokenStreamSlice stream)
{
stream.Expect(TokenType.Keyword);
stream.Expect(TokenType.LBrace);
var includes = new List<string>();
var bodyStream = stream.Slice(stream.Remaining - 1);
while (bodyStream.HasMore)
{
var includeToken = bodyStream.Expect(TokenType.StringLiteral);
includes.Add(includeToken.lexeme);
bodyStream.Expect(TokenType.Semicolon);
}
stream.Expect(TokenType.RBrace);
return includes;
}
}

View File

@@ -0,0 +1,30 @@
namespace Ghost.Shader.ParserBlock;
internal class KeywordsBlock : IBlockParser<List<FunctionCall>>
{
public static bool ShouldEnter(Token token)
{
return token.Match(TokenType.Keyword, TokenLexicon.KnownKeywords.KEYWORDS);
}
public static List<FunctionCall> Parse(TokenStreamSlice stream)
{
stream.Expect(TokenType.Keyword);
stream.Expect(TokenType.LBrace);
var keywords = new List<FunctionCall>();
var bodyStream = stream.Slice(stream.Remaining - 1);
while (bodyStream.HasMore)
{
var keywordToken = bodyStream.Expect(TokenType.Identifier);
var args = ParseUtility.ParseFunctionArguments(ref bodyStream, TokenType.Identifier);
keywords.Add(new FunctionCall { name = keywordToken, arguments = args });
bodyStream.Expect(TokenType.Semicolon);
}
stream.Expect(TokenType.RBrace);
return keywords;
}
}

View File

@@ -0,0 +1,63 @@
namespace Ghost.Shader.ParserBlock;
internal class PassBlock : IBlockParser<ShaderPassSyntax>
{
public static bool ShouldEnter(Token token)
{
return token.Match(TokenType.Keyword, TokenLexicon.KnownKeywords.PASS);
}
public static ShaderPassSyntax Parse(TokenStreamSlice stream)
{
var pass = new ShaderPassSyntax();
stream.Expect(TokenType.Keyword);
pass.name = stream.Expect(TokenType.StringLiteral);
stream.Expect(TokenType.LBrace);
var bodyStream = stream.Slice(stream.Remaining - 1);
while (bodyStream.TryPeek(out var nextToken))
{
if (DefinesBlock.ShouldEnter(nextToken))
{
pass.defines = DefinesBlock.Parse(bodyStream.SliceNextBlock());
}
else if (IncludesBlock.ShouldEnter(nextToken))
{
pass.includes = IncludesBlock.Parse(bodyStream.SliceNextBlock());
}
else if (KeywordsBlock.ShouldEnter(nextToken))
{
pass.keywords = KeywordsBlock.Parse(bodyStream.SliceNextBlock());
}
else if (PipelineBlock.ShouldEnter(nextToken))
{
pass.overridePipeline = PipelineBlock.Parse(bodyStream.SliceNextBlock());
}
else if (nextToken.Match(TokenType.Identifier, "vs"))
{
bodyStream.Expect(TokenType.Identifier, "vs");
var vsArgs = ParseUtility.ParseFunctionArguments(ref bodyStream, TokenType.StringLiteral);
pass.vertexShader = vsArgs[0];
pass.vertexEntry = vsArgs[1];
bodyStream.Expect(TokenType.Semicolon);
}
else if (nextToken.Match(TokenType.Identifier, "ps"))
{
bodyStream.Expect(TokenType.Identifier, "ps");
var psArgs = ParseUtility.ParseFunctionArguments(ref bodyStream, TokenType.StringLiteral);
pass.pixelShader = psArgs[0];
pass.pixelEntry = psArgs[1];
bodyStream.Expect(TokenType.Semicolon);
}
else
{
throw new Exception($"Unexpected token '{nextToken.lexeme}' in pass body.");
}
}
stream.Expect(TokenType.RBrace);
return pass;
}
}

View File

@@ -0,0 +1,217 @@
using static Ghost.Shader.TokenLexicon;
namespace Ghost.Shader.ParserBlock;
internal class PipelineBlock : IBlockParser<PipelineStateSyntax, PipelineStateModel>
{
public static bool ShouldEnter(Token token)
{
return token.Match(TokenType.Keyword, TokenLexicon.KnownKeywords.PIPELINE);
}
public static PipelineStateSyntax Parse(TokenStreamSlice stream)
{
stream.Expect(TokenType.Keyword);
stream.Expect(TokenType.LBrace);
var pipeline = new PipelineStateSyntax();
var bodyStream = stream.Slice(stream.Remaining - 1);
while (bodyStream.HasMore)
{
var stateToken = bodyStream.Expect(TokenType.Identifier);
bodyStream.Expect(TokenType.Equals);
var valueToken = bodyStream.Expect(TokenType.Identifier | TokenType.Number);
switch (stateToken.lexeme)
{
case KnownPipelineProperties.ZTEST:
pipeline.zTest = valueToken;
break;
case KnownPipelineProperties.ZWRITE:
pipeline.zWrite = valueToken;
break;
case KnownPipelineProperties.CULL:
pipeline.cull = valueToken;
break;
case KnownPipelineProperties.BLEND:
pipeline.blend = valueToken;
break;
case KnownPipelineProperties.COLORMASK:
pipeline.colorMask = valueToken;
break;
default:
throw new InvalidDataException($"Unknown pipeline state: {stateToken.lexeme}");
}
bodyStream.Expect(TokenType.Semicolon);
}
stream.Expect(TokenType.RBrace);
return pipeline;
}
public PipelineStateModel SemanticAnalysis(PipelineStateSyntax syntax, List<ShaderError> errors)
{
var model = new PipelineStateModel();
// ZTest
if (!syntax.zTest.Match(TokenType.None))
{
switch (syntax.zTest.lexeme)
{
case "disable":
model.zTest = ZTestOptions.Disabled;
break;
case "less":
model.zTest = ZTestOptions.Less;
break;
case "less_equal":
model.zTest = ZTestOptions.LessEqual;
break;
case "equal":
model.zTest = ZTestOptions.Equal;
break;
case "greater_equal":
model.zTest = ZTestOptions.GreaterEqual;
break;
case "greater":
model.zTest = ZTestOptions.Greater;
break;
case "not_equal":
model.zTest = ZTestOptions.NotEqual;
break;
case "always":
model.zTest = ZTestOptions.Always;
break;
default:
errors.Add(new ShaderError
{
message = $"Invalid ZTest option: {syntax.zTest.lexeme}",
line = syntax.zTest.line,
column = syntax.zTest.column
});
break;
}
}
else
{
model.zTest = ZTestOptions.LessEqual;
}
// ZWrite
if (!syntax.zWrite.Match(TokenType.None))
{
switch (syntax.zWrite.lexeme)
{
case "on":
model.zWrite = ZWriteOptions.On;
break;
case "off":
model.zWrite = ZWriteOptions.Off;
break;
default:
errors.Add(new ShaderError
{
message = $"Invalid ZWrite option: {syntax.zWrite.lexeme}",
line = syntax.zWrite.line,
column = syntax.zWrite.column
});
break;
}
}
else
{
model.zWrite = ZWriteOptions.On;
}
// Cull
if (!syntax.cull.Match(TokenType.None))
{
switch (syntax.cull.lexeme)
{
case "off":
model.cull = CullOptions.Off;
break;
case "front":
model.cull = CullOptions.Front;
break;
case "back":
model.cull = CullOptions.Back;
break;
default:
errors.Add(new ShaderError
{
message = $"Invalid Cull option: {syntax.cull.lexeme}",
line = syntax.cull.line,
column = syntax.cull.column
});
break;
}
}
else
{
model.cull = CullOptions.Back;
}
// Blend
if (!syntax.blend.Match(TokenType.None))
{
switch (syntax.blend.lexeme)
{
case "opaque":
model.blend = BlendOptions.Opaque;
break;
case "alpha":
model.blend = BlendOptions.Alpha;
break;
case "additive":
model.blend = BlendOptions.Additive;
break;
case "multiply":
model.blend = BlendOptions.Multiply;
break;
case "premultiplied":
model.blend = BlendOptions.PremultipliedAlpha;
break;
default:
errors.Add(new ShaderError
{
message = $"Invalid Blend option: {syntax.blend.lexeme}",
line = syntax.blend.line,
column = syntax.blend.column
});
break;
}
}
else
{
model.blend = BlendOptions.Opaque;
}
// Color Mask
if (!syntax.colorMask.Match(TokenType.None))
{
if (uint.TryParse(syntax.colorMask.lexeme, out var colorMask))
{
model.colorMask = colorMask;
}
else
{
errors.Add(new ShaderError
{
message = $"Invalid Color Mask value: {syntax.colorMask.lexeme}",
line = syntax.colorMask.line,
column = syntax.colorMask.column
});
}
}
else
{
model.colorMask = 0xF; // Default to RGBA
}
return model;
}
}

View File

@@ -0,0 +1,337 @@
using Misaki.HighPerformance.Mathematics;
using System.Globalization;
namespace Ghost.Shader.ParserBlock;
internal class PropertiesBlock : IBlockParser<List<PropertySyntax>, List<PropertyModel>>
{
private sealed record PropTypeInfo(int ArgCount, TokenType ArgTokenType, Func<List<Token>, object?>? Builder);
private static readonly Dictionary<ShaderPropertyType, PropTypeInfo> s_propTypeInfo = new()
{
// Floats
[ShaderPropertyType.Float] = new(1, TokenType.Number, a => float.Parse(a[0].lexeme, CultureInfo.InvariantCulture)),
[ShaderPropertyType.Float2] = new(2, TokenType.Number, a => new float2(
float.Parse(a[0].lexeme, CultureInfo.InvariantCulture),
float.Parse(a[1].lexeme, CultureInfo.InvariantCulture))),
[ShaderPropertyType.Float3] = new(3, TokenType.Number, a => new float3(
float.Parse(a[0].lexeme, CultureInfo.InvariantCulture),
float.Parse(a[1].lexeme, CultureInfo.InvariantCulture),
float.Parse(a[2].lexeme, CultureInfo.InvariantCulture))),
[ShaderPropertyType.Float4] = new(4, TokenType.Number, a => new float4(
float.Parse(a[0].lexeme, CultureInfo.InvariantCulture),
float.Parse(a[1].lexeme, CultureInfo.InvariantCulture),
float.Parse(a[2].lexeme, CultureInfo.InvariantCulture),
float.Parse(a[3].lexeme, CultureInfo.InvariantCulture))),
// Ints
[ShaderPropertyType.Int] = new(1, TokenType.Number, a => int.Parse(a[0].lexeme, CultureInfo.InvariantCulture)),
[ShaderPropertyType.Int2] = new(2, TokenType.Number, a => new int2(
int.Parse(a[0].lexeme, CultureInfo.InvariantCulture),
int.Parse(a[1].lexeme, CultureInfo.InvariantCulture))),
[ShaderPropertyType.Int3] = new(3, TokenType.Number, a => new int3(
int.Parse(a[0].lexeme, CultureInfo.InvariantCulture),
int.Parse(a[1].lexeme, CultureInfo.InvariantCulture),
int.Parse(a[2].lexeme, CultureInfo.InvariantCulture))),
[ShaderPropertyType.Int4] = new(4, TokenType.Number, a => new int4(
int.Parse(a[0].lexeme, CultureInfo.InvariantCulture),
int.Parse(a[1].lexeme, CultureInfo.InvariantCulture),
int.Parse(a[2].lexeme, CultureInfo.InvariantCulture),
int.Parse(a[3].lexeme, CultureInfo.InvariantCulture))),
// UInts
[ShaderPropertyType.UInt] = new(1, TokenType.Number, a => uint.Parse(a[0].lexeme, CultureInfo.InvariantCulture)),
[ShaderPropertyType.UInt2] = new(2, TokenType.Number, a => new uint2(
uint.Parse(a[0].lexeme, CultureInfo.InvariantCulture),
uint.Parse(a[1].lexeme, CultureInfo.InvariantCulture))),
[ShaderPropertyType.UInt3] = new(3, TokenType.Number, a => new uint3(
uint.Parse(a[0].lexeme, CultureInfo.InvariantCulture),
uint.Parse(a[1].lexeme, CultureInfo.InvariantCulture),
uint.Parse(a[2].lexeme, CultureInfo.InvariantCulture))),
[ShaderPropertyType.UInt4] = new(4, TokenType.Number, a => new uint4(
uint.Parse(a[0].lexeme, CultureInfo.InvariantCulture),
uint.Parse(a[1].lexeme, CultureInfo.InvariantCulture),
uint.Parse(a[2].lexeme, CultureInfo.InvariantCulture),
uint.Parse(a[3].lexeme, CultureInfo.InvariantCulture))),
// Bools (numbers 0/1)
[ShaderPropertyType.Bool] = new(1, TokenType.Number, a => a[0].lexeme != "0"),
[ShaderPropertyType.Bool2] = new(2, TokenType.Number, a => new bool2(a[0].lexeme != "0", a[1].lexeme != "0")),
[ShaderPropertyType.Bool3] = new(3, TokenType.Number, a => new bool3(a[0].lexeme != "0", a[1].lexeme != "0", a[2].lexeme != "0")),
[ShaderPropertyType.Bool4] = new(4, TokenType.Number, a => new bool4(a[0].lexeme != "0", a[1].lexeme != "0", a[2].lexeme != "0", a[3].lexeme != "0")),
// Textures (single identifier argument currently no default object built)
[ShaderPropertyType.Texture2D] = new(1, TokenType.Identifier, null),
[ShaderPropertyType.Texture3D] = new(1, TokenType.Identifier, null),
[ShaderPropertyType.TextureCube] = new(1, TokenType.Identifier, null),
};
public static bool ShouldEnter(Token token)
{
return token.Match(TokenType.Keyword, TokenLexicon.KnownKeywords.PROPERTIES);
}
private static ShaderPropertyType FromString(string type)
{
return type.ToLower() switch
{
"float" => ShaderPropertyType.Float,
"float2" => ShaderPropertyType.Float2,
"float3" => ShaderPropertyType.Float3,
"float4" => ShaderPropertyType.Float4,
"int" => ShaderPropertyType.Int,
"int2" => ShaderPropertyType.Int2,
"int3" => ShaderPropertyType.Int3,
"int4" => ShaderPropertyType.Int4,
"uint" => ShaderPropertyType.UInt,
"uint2" => ShaderPropertyType.UInt2,
"uint3" => ShaderPropertyType.UInt3,
"uint4" => ShaderPropertyType.UInt4,
"bool" => ShaderPropertyType.Bool,
"bool2" => ShaderPropertyType.Bool2,
"bool3" => ShaderPropertyType.Bool3,
"bool4" => ShaderPropertyType.Bool4,
"texture2d" => ShaderPropertyType.Texture2D,
"texture3d" => ShaderPropertyType.Texture3D,
"texturecube" => ShaderPropertyType.TextureCube,
_ => ShaderPropertyType.None,
};
}
public static List<PropertySyntax> Parse(TokenStreamSlice stream)
{
stream.Expect(TokenType.Keyword);
stream.Expect(TokenType.LBrace);
var properties = new List<PropertySyntax>();
var bodyStream = stream.Slice(stream.Remaining - 1);
while (bodyStream.HasMore)
{
var shaderProperty = new PropertySyntax();
var typeToken = bodyStream.Expect(TokenType.Identifier);
var nameToken = bodyStream.Expect(TokenType.Identifier);
shaderProperty.type = typeToken;
shaderProperty.name = nameToken;
var nextToken = bodyStream.Consume();
switch (nextToken.type)
{
case TokenType.Equals:
{
var constructorTypeToken = bodyStream.Expect(TokenType.Identifier);
var args = ParseUtility.ParseFunctionArguments(ref bodyStream, TokenType.Identifier | TokenType.Number);
shaderProperty.propertyConstructor = new FunctionCall
{
name = constructorTypeToken,
arguments = args
};
bodyStream.Expect(TokenType.Semicolon);
break;
}
case TokenType.Semicolon:
break;
default:
throw new Exception($"Unexpected token '{nextToken.lexeme}' in property declaration.");
}
properties.Add(shaderProperty);
}
stream.Expect(TokenType.RBrace);
return properties;
}
public List<PropertyModel> SemanticAnalysis(List<PropertySyntax> syntax, List<ShaderError> errors)
{
var models = new List<PropertyModel>();
var usedPropertyNames = new HashSet<string>();
foreach (var property in syntax)
{
var model = new PropertyModel();
var flowControl = ValidatePropertyType(errors, property, model);
if (!flowControl)
{
continue;
}
flowControl = ValidatePropertyName(errors, usedPropertyNames, property, model);
if (!flowControl)
{
continue;
}
if (property.propertyConstructor != null)
{
flowControl = ValidatePropertyConstructor(errors, property, model);
if (!flowControl)
{
continue;
}
}
usedPropertyNames.Add(property.name.lexeme);
models.Add(model);
}
return models;
}
private static bool ValidatePropertyType(List<ShaderError> errors, PropertySyntax property, PropertyModel model)
{
if (!TokenLexicon.IsType(property.type.lexeme))
{
errors.Add(new ShaderError
{
message = $"Shader property type '{property.type.lexeme}' is not a valid type.",
line = property.type.line,
column = property.type.column
});
return false;
}
model.type = FromString(property.type.lexeme);
return true;
}
private static bool ValidatePropertyName(List<ShaderError> errors, HashSet<string> usedPropertyNames, PropertySyntax property, PropertyModel model)
{
if (string.IsNullOrWhiteSpace(property.name.lexeme))
{
errors.Add(new ShaderError
{
message = "Shader property has an empty name.",
line = property.name.line,
column = property.name.column
});
return false;
}
else if (usedPropertyNames.Contains(property.name.lexeme))
{
errors.Add(new ShaderError
{
message = $"Shader property name '{property.name.lexeme}' is duplicated.",
line = property.name.line,
column = property.name.column
});
return false;
}
model.name = property.name.lexeme;
return true;
}
private static bool ValidatePropertyConstructor(List<ShaderError> errors, PropertySyntax property, PropertyModel model)
{
var constructor = property.propertyConstructor;
if (constructor == null)
{
errors.Add(new ShaderError
{
message = "Shader property constructor is null.",
line = property.name.line,
column = property.name.column
});
return false;
}
if (string.IsNullOrWhiteSpace(constructor.name.lexeme))
{
errors.Add(new ShaderError
{
message = "Shader property constructor has an empty name.",
line = constructor.name.line,
column = constructor.name.column
});
return false;
}
if (constructor.name.lexeme != property.type.lexeme)
{
errors.Add(new ShaderError
{
message = $"Shader property constructor name '{constructor.name.lexeme}' does not match property type '{property.type.lexeme}'.",
line = constructor.name.line,
column = constructor.name.column
});
return false;
}
if (!s_propTypeInfo.TryGetValue(model.type, out var info))
{
errors.Add(new ShaderError
{
message = $"No constructor metadata registered for property type '{model.type}'.",
line = constructor.name.line,
column = constructor.name.column
});
return false;
}
// Count check
if (constructor.arguments.Count != info.ArgCount)
{
errors.Add(new ShaderError
{
message = $"Shader property constructor for type '{property.type.lexeme}' expects {info.ArgCount} argument(s), but got {constructor.arguments.Count}.",
line = constructor.name.line,
column = constructor.name.column
});
return false;
}
// Type check (uniform requirement for all args)
var hasError = false;
for (var i = 0; i < constructor.arguments.Count; i++)
{
var arg = constructor.arguments[i];
if (!arg.Match(info.ArgTokenType))
{
errors.Add(new ShaderError
{
message = $"Shader property constructor argument {i} expects token kind '{info.ArgTokenType}', but got '{arg.type}'.",
line = arg.line,
column = arg.column
});
hasError = true;
}
}
if (hasError)
{
return false;
}
// Build default value if we have a builder (textures currently null / TODO)
if (info.Builder != null)
{
try
{
model.defaultValue = info.Builder(constructor.arguments);
}
catch (Exception ex)
{
errors.Add(new ShaderError
{
message = $"Failed to construct default value for property '{property.name.lexeme}': {ex.Message}",
line = constructor.name.line,
column = constructor.name.column
});
return false;
}
}
return true;
}
}

View File

@@ -0,0 +1,43 @@
namespace Ghost.Shader.ParserBlock;
internal class ShaderBlock : IBlockParser<ShaderSyntax>
{
public static bool ShouldEnter(Token token)
{
return token.Match(TokenType.Keyword, TokenLexicon.KnownKeywords.SHADER);
}
public static ShaderSyntax Parse(TokenStreamSlice stream)
{
var shader = new ShaderSyntax();
stream.Expect(TokenType.Keyword);
shader.name = stream.Expect(TokenType.StringLiteral);
stream.Expect(TokenType.LBrace);
var bodyStream = stream.Slice(stream.Remaining - 1);
while (bodyStream.TryPeek(out var nextToken))
{
if (PropertiesBlock.ShouldEnter(nextToken))
{
shader.properties = PropertiesBlock.Parse(bodyStream.SliceNextBlock());
}
else if (PipelineBlock.ShouldEnter(nextToken))
{
shader.pipeline = PipelineBlock.Parse(bodyStream.SliceNextBlock());
}
else if (PassBlock.ShouldEnter(nextToken))
{
shader.passes.Add(PassBlock.Parse(bodyStream.SliceNextBlock()));
}
else
{
throw new Exception($"Unexpected token '{nextToken.lexeme}' in shader body.");
}
}
stream.Expect(TokenType.RBrace);
return shader;
}
}

View File

@@ -0,0 +1,35 @@
namespace Ghost.Shader;
public enum ZTestOptions
{
Disabled,
Less,
LessEqual,
Equal,
GreaterEqual,
Greater,
NotEqual,
Always
}
public enum ZWriteOptions
{
Off,
On
}
public enum CullOptions
{
Off,
Front,
Back
}
public enum BlendOptions
{
Opaque,
Alpha,
Additive,
Multiply,
PremultipliedAlpha
}

View File

@@ -0,0 +1,58 @@
using Ghost.Shader.ParserBlock;
namespace Ghost.Shader;
public struct ShaderError
{
public string message;
public int line;
public int column;
public readonly override string ToString()
{
return $"Error at {line}:{column} - {message}";
}
}
internal static class ShaderCompiler
{
public static List<ShaderSyntax> ParseShaders(TokenStream stream)
{
var shaders = new List<ShaderSyntax>();
while (stream.TryPeek(out var nextToken))
{
if (ShaderBlock.ShouldEnter(nextToken))
{
var shader = ShaderBlock.Parse(stream.SliceNextBlock());
shaders.Add(shader);
}
else if (nextToken.Match(TokenType.EndOfFile))
{
stream.Consume();
}
else
{
throw new Exception($"Unexpected token '{nextToken.lexeme}' at top level. Expected 'shader' declaration.");
}
}
return shaders;
}
public static ShaderModel SemanticAnalysis(ShaderSyntax syntax, out List<ShaderError> errors)
{
var shaderModel = new ShaderModel();
errors = new List<ShaderError>();
shaderModel.name = syntax.name.lexeme;
var propertiesBlock = new PropertiesBlock();
shaderModel.properties = propertiesBlock.SemanticAnalysis(syntax.properties, errors);
var pipelineBlock = new PipelineBlock();
shaderModel.pipeline = pipelineBlock.SemanticAnalysis(syntax.pipeline, errors);
return shaderModel;
}
}

View File

@@ -0,0 +1,34 @@
namespace Ghost.Shader;
public enum ShaderPropertyType
{
None,
Float, Float2, Float3, Float4,
Int, Int2, Int3, Int4,
UInt, UInt2, UInt3, UInt4,
Bool, Bool2, Bool3, Bool4,
Texture2D, Texture3D, TextureCube,
}
internal class PropertyModel
{
public ShaderPropertyType type;
public string name = string.Empty;
public object? defaultValue;
}
internal class PipelineStateModel
{
public ZTestOptions zTest;
public ZWriteOptions zWrite;
public CullOptions cull;
public BlendOptions blend;
public uint colorMask;
}
internal class ShaderModel
{
public string name = string.Empty;
public List<PropertyModel> properties = new();
public PipelineStateModel pipeline = new();
}

View File

@@ -0,0 +1,44 @@
namespace Ghost.Shader;
public class FunctionCall
{
public Token name;
public List<Token> arguments = new();
}
public class PropertySyntax
{
public Token type;
public Token name;
public FunctionCall? propertyConstructor;
}
public class PipelineStateSyntax
{
public Token zTest;
public Token zWrite;
public Token cull;
public Token blend;
public Token colorMask;
}
public class ShaderPassSyntax
{
public Token name;
public Token vertexShader;
public Token vertexEntry;
public Token pixelShader;
public Token pixelEntry;
public List<string>? defines;
public List<string>? includes;
public List<FunctionCall>? keywords;
public PipelineStateSyntax? overridePipeline;
}
public class ShaderSyntax
{
public Token name;
public List<PropertySyntax> properties = new();
public PipelineStateSyntax pipeline = new();
public List<ShaderPassSyntax> passes = new();
}

138
Ghost.Shader/Token.cs Normal file
View File

@@ -0,0 +1,138 @@
namespace Ghost.Shader;
[Flags]
public enum TokenType
{
None = 0,
Identifier = 1 << 0, // variable names, types, etc.
Keyword = 1 << 1, // shader, properties, pipeline, pass, etc.
Number = 1 << 2, // numeric literals
StringLiteral = 1 << 3, // "ForwardVS.hlsl"
Equals = 1 << 4, // =
Semicolon = 1 << 5, // ;
Comma = 1 << 6, // ,
LBrace = 1 << 7, // {
RBrace = 1 << 8, // }
LParen = 1 << 9, // (
RParen = 1 << 10, // )
EndOfFile = 1 << 11 // EOF
}
public readonly struct Token
{
public readonly TokenType type;
public readonly string lexeme;
public readonly int line;
public readonly int column;
public Token(TokenType type, string lexeme, int line, int column)
{
this.type = type;
this.lexeme = lexeme;
this.line = line;
this.column = column;
}
public override readonly string ToString()
{
return $"{type}('{lexeme}') at {line}:{column}";
}
}
public static class TokenExtensions
{
public static bool Match(this Token token, TokenType type, string? lexeme = null)
{
if (!type.HasFlag(token.type))
{
return false;
}
if (!string.IsNullOrEmpty(lexeme) && token.lexeme != lexeme)
{
return false;
}
return true;
}
public static void Expect(this Token token, TokenType type, string? lexeme = null)
{
if (!token.Match(type, lexeme))
{
var expected = lexeme != null ? $"{type}('{lexeme}')" : type.ToString();
throw new Exception($"Unexpected token at line {token.line}, column {token.column}. Expected {expected}, got {token.type}('{token.lexeme}').");
}
}
}
public static class TokenLexicon
{
public static class KnownKeywords
{
public const string SHADER = "shader";
public const string PROPERTIES = "properties";
public const string PIPELINE = "pipeline";
public const string PASS = "pass";
public const string DEFINES = "defines";
public const string KEYWORDS = "keywords";
public const string INCLUDES = "includes";
}
public static class KnownPipelineProperties
{
public const string ZTEST = "ztest";
public const string ZWRITE = "zwrite";
public const string CULL = "cull";
public const string BLEND = "blend";
public const string COLORMASK = "color_mask";
}
private static readonly HashSet<string> s_keywords = new()
{
KnownKeywords.SHADER,
KnownKeywords.PROPERTIES,
KnownKeywords.PIPELINE,
KnownKeywords.PASS,
KnownKeywords.DEFINES,
KnownKeywords.KEYWORDS,
KnownKeywords.INCLUDES,
};
private static readonly HashSet<string> s_function = new()
{
"vs",
"ps",
"ms",
"cs",
"dynamic",
"static",
};
private static readonly HashSet<string> s_types = new()
{
"float",
"float2",
"float3",
"float4",
"int",
"int2",
"int3",
"int4",
"uint",
"uint2",
"uint3",
"uint4",
"bool",
"bool2",
"bool3",
"bool4",
"texture2d",
"texture3d",
"texturecube",
};
public static bool IsKeyword(string lexeme) => s_keywords.Contains(lexeme);
public static bool IsFunction(string lexeme) => s_function.Contains(lexeme);
public static bool IsType(string lexeme) => s_types.Contains(lexeme);
}

285
Ghost.Shader/TokenStream.cs Normal file
View File

@@ -0,0 +1,285 @@
namespace Ghost.Shader;
internal static class TokenStreamImple
{
public static Token Peek(ReadOnlySpan<Token> tokens, ref int index, int length)
{
return index + length < tokens.Length ? tokens[index + length] : throw new InvalidOperationException("No more tokens available");
}
public static bool TryPeek(ReadOnlySpan<Token> tokens, ref int index, int length, out Token token)
{
if (index + length < tokens.Length)
{
token = tokens[index + length];
return true;
}
token = default;
return false;
}
public static bool TryConsume(ReadOnlySpan<Token> tokens, ref int index, out Token token)
{
if (index < tokens.Length)
{
token = tokens[index++];
return true;
}
token = default;
return false;
}
public static Token Consume(ReadOnlySpan<Token> tokens, ref int index)
{
return index < tokens.Length ? tokens[index++] : throw new InvalidOperationException("No more tokens available");
}
public static bool Match(ReadOnlySpan<Token> tokens, ref int index, TokenType type, string? lexeme = null)
{
var t = Peek(tokens, ref index, 0);
if (!t.Match(type, lexeme))
{
return false;
}
index++;
return true;
}
public static Token Expect(ReadOnlySpan<Token> tokens, ref int index, TokenType type, string? lexeme = null)
{
if (!TryPeek(tokens, ref index, 0, out var t))
{
throw new InvalidOperationException("Expected token but reached end of stream");
}
if (!t.Match(type, lexeme))
{
throw new InvalidOperationException($"Expected token {type}('{lexeme ?? "*"}') but got {t}");
}
index++;
return t;
}
}
internal class TokenStream
{
private readonly Token[] _tokens;
private int _index = 0;
public int Length => _tokens.Length;
public int Remaining => _tokens.Length - _index;
public bool HasMore => _index < _tokens.Length;
public int Position
{
get => _index;
set
{
if (value < 0 || value > _tokens.Length)
{
throw new ArgumentOutOfRangeException(nameof(value), "Position must be within the bounds of the token stream.");
}
_index = value;
}
}
public TokenStream(Token[] tokens)
{
_tokens = tokens;
}
public Token Peek(int length = 0)
{
return TokenStreamImple.Peek(_tokens, ref _index, length);
}
public bool TryPeek(out Token token)
{
return TokenStreamImple.TryPeek(_tokens, ref _index, 0, out token);
}
public bool TryPeek(int length, out Token token)
{
return TokenStreamImple.TryPeek(_tokens, ref _index, length, out token);
}
public bool TryConsume(out Token token)
{
return TokenStreamImple.TryConsume(_tokens, ref _index, out token);
}
public Token Consume()
{
return TokenStreamImple.Consume(_tokens, ref _index);
}
public bool Match(TokenType type, string? lexeme = null)
{
return TokenStreamImple.Match(_tokens, ref _index, type, lexeme);
}
public Token Expect(TokenType type, string? lexeme = null)
{
return TokenStreamImple.Expect(_tokens, ref _index, type, lexeme);
}
public TokenStreamSlice Slice(int length = -1)
{
if (length <= 0)
{
length = _tokens.Length - _index;
}
var slice = _tokens.AsSpan().Slice(_index, length);
_index += length;
return new TokenStreamSlice(slice);
}
public TokenStreamSlice SliceNextBlock()
{
var length = 0;
var lBraceCount = 0;
var rBraceCount = 0;
Token nextToken;
do
{
nextToken = Peek(length);
if (length > 0 && lBraceCount > 0 && lBraceCount == rBraceCount)
{
break;
}
if (nextToken.Match(TokenType.LBrace))
{
lBraceCount++;
}
else if (nextToken.Match(TokenType.RBrace))
{
rBraceCount++;
}
length++;
}
while (_index + length < _tokens.Length);
return Slice(length);
}
}
internal ref struct TokenStreamSlice
{
private readonly ReadOnlySpan<Token> _tokens;
private int _index;
public readonly int Length => _tokens.Length;
public readonly int Remaining => _tokens.Length - _index;
public readonly bool HasMore => _index < _tokens.Length;
public int Position
{
readonly get => _index;
set
{
if (value < 0 || value > _tokens.Length)
{
throw new ArgumentOutOfRangeException(nameof(value), "Position must be within the bounds of the token stream.");
}
_index = value;
}
}
internal TokenStreamSlice(ReadOnlySpan<Token> tokens)
{
_tokens = tokens;
_index = 0;
}
public Token Peek(int length = 0)
{
return TokenStreamImple.Peek(_tokens, ref _index, length);
}
public bool TryPeek(out Token token)
{
return TokenStreamImple.TryPeek(_tokens, ref _index, 0, out token);
}
public bool TryPeek(int length, out Token token)
{
return TokenStreamImple.TryPeek(_tokens, ref _index, length, out token);
}
public bool TryConsume(out Token token)
{
return TokenStreamImple.TryConsume(_tokens, ref _index, out token);
}
public Token Consume()
{
return TokenStreamImple.Consume(_tokens, ref _index);
}
public bool Match(TokenType type, string? lexeme = null)
{
return TokenStreamImple.Match(_tokens, ref _index, type, lexeme);
}
public Token Expect(TokenType type, string? lexeme = null)
{
return TokenStreamImple.Expect(_tokens, ref _index, type, lexeme);
}
public TokenStreamSlice Slice(int length = -1)
{
if (length <= 0)
{
length = _tokens.Length - _index;
}
var slice = _tokens.Slice(_index, length);
_index += length;
return new TokenStreamSlice(slice);
}
public TokenStreamSlice SliceNextBlock()
{
var length = 0;
var lBraceCount = 0;
var rBraceCount = 0;
do
{
var nextToken = Peek(length);
if (length > 0 && lBraceCount > 0 && lBraceCount == rBraceCount)
{
break;
}
if (nextToken.Match(TokenType.LBrace))
{
lBraceCount++;
}
else if (nextToken.Match(TokenType.RBrace))
{
rBraceCount++;
}
length++;
}
while (_index + length < _tokens.Length);
if (lBraceCount != rBraceCount)
{
throw new InvalidOperationException("Unmatched braces in token stream.");
}
return Slice(length);
}
}

View File

@@ -1,6 +1,4 @@
using System;
namespace Ghost.UnitTest.Models;
namespace Ghost.UnitTest.Models;
public enum LogLevel
{
@@ -12,10 +10,22 @@ public enum LogLevel
internal struct LogItem
{
public LogLevel Level { get; init; }
public string Message { get; init; }
public DateTime Timestamp { get; init; }
public string? StackTrace { get; init; }
public LogLevel Level
{
get; init;
}
public string Message
{
get; init;
}
public DateTime Timestamp
{
get; init;
}
public string? StackTrace
{
get; init;
}
public LogItem(LogLevel level, string message, string? stackTrace = null)
{

View File

@@ -1,9 +1,5 @@
using Ghost.UnitTest.Models;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Threading;
namespace Ghost.UnitTest.Services;
@@ -11,29 +7,31 @@ internal class LoggingService
{
private const int MAX_LOGS = 4096;
private static readonly Lazy<LoggingService> _instance = new(() => new LoggingService());
private readonly List<LogItem> _logs = [];
private readonly object _lockObject = new();
public static LoggingService Instance => _instance.Value;
public IReadOnlyList<LogItem> Logs
{
get
public IReadOnlyList<LogItem> Logs
{
get
{
lock (_lockObject)
{
return _logs.AsReadOnly();
}
}
}
}
public bool CaptureStackTrace { get; set; } = false;
public event Action<LogItem>? LogAdded;
public event Action? LogsCleared;
private LoggingService() { }
private LoggingService()
{
}
private void AddLog(LogItem logItem)
{
@@ -43,18 +41,19 @@ internal class LoggingService
{
_logs.RemoveAt(0);
}
_logs.Add(logItem);
}
// Invoke event outside of lock to prevent deadlock
LogAdded?.Invoke(logItem);
}
private string? CaptureCurrentStackTrace()
{
if (!CaptureStackTrace) return null;
if (!CaptureStackTrace)
return null;
var stackTrace = new StackTrace(skipFrames: 2, fNeedFileInfo: true);
return stackTrace.ToString();
}
@@ -98,7 +97,7 @@ internal class LoggingService
{
_logs.Clear();
}
LogsCleared?.Invoke();
}

View File

@@ -1,11 +1,8 @@
using Ghost.Graphics;
using Ghost.Graphics.Contracts;
using Ghost.Graphics.D3D12;
using Ghost.Graphics.RHI;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
using Misaki.HighPerformance.LowLevel.Buffer;
using WinRT;
namespace Ghost.UnitTest.Windows;
@@ -31,7 +28,7 @@ public sealed partial class GraphicsTestWindow : Window
AllocationManager.EnableDebugLayer();
#endif
_renderSystem = new (GraphicsAPI.Direct3D12);
_renderSystem = new(GraphicsAPI.Direct3D12);
_renderer = _renderSystem.CreateRenderer();
_swapChain = _renderSystem.GraphicsEngine.CreateSwapChain(new SwapChainDesc((uint)AppWindow.Size.Width, (uint)AppWindow.Size.Height, SwapChainTarget.FromCompositionSurface(Panel)));

View File

@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.14.35906.104
# Visual Studio Version 18
VisualStudioVersion = 18.0.11018.127
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ghost.Editor", "Ghost.Editor\Ghost.Editor.csproj", "{15AFE3A1-0CAF-4B36-8835-121C4D683BBF}"
EndProject
@@ -21,6 +21,16 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ghost.Editor.Core", "Ghost.
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ghost.Graphics", "Ghost.Graphics\Ghost.Graphics.csproj", "{F55831B1-2ADE-4CEB-8023-F92C7ABF57FE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ghost.Shader", "Ghost.Shader\Ghost.Shader.csproj", "{145C2867-C0FD-4FB0-B60D-467E52081E14}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ghost.Shader.Test", "Ghost.Shader.Test\Ghost.Shader.Test.csproj", "{C3AB43A4-88D8-49F8-B738-A738C8BCEE3F}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Editor", "Editor", "{4E666310-F835-4A49-A1A3-AE7CB3FB0221}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Runtime", "Runtime", "{6051DF88-C573-4BC4-BF98-3C5E807F4D70}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Test", "Test", "{43E76E46-0E5F-4429-83C8-157689885174}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM64 = Debug|ARM64
@@ -151,10 +161,44 @@ Global
{F55831B1-2ADE-4CEB-8023-F92C7ABF57FE}.Release|x64.Build.0 = Release|Any CPU
{F55831B1-2ADE-4CEB-8023-F92C7ABF57FE}.Release|x86.ActiveCfg = Release|Any CPU
{F55831B1-2ADE-4CEB-8023-F92C7ABF57FE}.Release|x86.Build.0 = Release|Any CPU
{145C2867-C0FD-4FB0-B60D-467E52081E14}.Debug|ARM64.ActiveCfg = Debug|Any CPU
{145C2867-C0FD-4FB0-B60D-467E52081E14}.Debug|ARM64.Build.0 = Debug|Any CPU
{145C2867-C0FD-4FB0-B60D-467E52081E14}.Debug|x64.ActiveCfg = Debug|Any CPU
{145C2867-C0FD-4FB0-B60D-467E52081E14}.Debug|x64.Build.0 = Debug|Any CPU
{145C2867-C0FD-4FB0-B60D-467E52081E14}.Debug|x86.ActiveCfg = Debug|Any CPU
{145C2867-C0FD-4FB0-B60D-467E52081E14}.Debug|x86.Build.0 = Debug|Any CPU
{145C2867-C0FD-4FB0-B60D-467E52081E14}.Release|ARM64.ActiveCfg = Release|Any CPU
{145C2867-C0FD-4FB0-B60D-467E52081E14}.Release|ARM64.Build.0 = Release|Any CPU
{145C2867-C0FD-4FB0-B60D-467E52081E14}.Release|x64.ActiveCfg = Release|Any CPU
{145C2867-C0FD-4FB0-B60D-467E52081E14}.Release|x64.Build.0 = Release|Any CPU
{145C2867-C0FD-4FB0-B60D-467E52081E14}.Release|x86.ActiveCfg = Release|Any CPU
{145C2867-C0FD-4FB0-B60D-467E52081E14}.Release|x86.Build.0 = Release|Any CPU
{C3AB43A4-88D8-49F8-B738-A738C8BCEE3F}.Debug|ARM64.ActiveCfg = Debug|Any CPU
{C3AB43A4-88D8-49F8-B738-A738C8BCEE3F}.Debug|ARM64.Build.0 = Debug|Any CPU
{C3AB43A4-88D8-49F8-B738-A738C8BCEE3F}.Debug|x64.ActiveCfg = Debug|Any CPU
{C3AB43A4-88D8-49F8-B738-A738C8BCEE3F}.Debug|x64.Build.0 = Debug|Any CPU
{C3AB43A4-88D8-49F8-B738-A738C8BCEE3F}.Debug|x86.ActiveCfg = Debug|Any CPU
{C3AB43A4-88D8-49F8-B738-A738C8BCEE3F}.Debug|x86.Build.0 = Debug|Any CPU
{C3AB43A4-88D8-49F8-B738-A738C8BCEE3F}.Release|ARM64.ActiveCfg = Release|Any CPU
{C3AB43A4-88D8-49F8-B738-A738C8BCEE3F}.Release|ARM64.Build.0 = Release|Any CPU
{C3AB43A4-88D8-49F8-B738-A738C8BCEE3F}.Release|x64.ActiveCfg = Release|Any CPU
{C3AB43A4-88D8-49F8-B738-A738C8BCEE3F}.Release|x64.Build.0 = Release|Any CPU
{C3AB43A4-88D8-49F8-B738-A738C8BCEE3F}.Release|x86.ActiveCfg = Release|Any CPU
{C3AB43A4-88D8-49F8-B738-A738C8BCEE3F}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{15AFE3A1-0CAF-4B36-8835-121C4D683BBF} = {4E666310-F835-4A49-A1A3-AE7CB3FB0221}
{1ED62E09-8F36-4671-896B-16C1C1530202} = {6051DF88-C573-4BC4-BF98-3C5E807F4D70}
{0D626DAF-EF18-435C-A85C-EEA1B141E8B5} = {4E666310-F835-4A49-A1A3-AE7CB3FB0221}
{8A1C494B-2888-4D0D-8325-9F5C8D1D1955} = {6051DF88-C573-4BC4-BF98-3C5E807F4D70}
{4179873E-8174-4D17-9584-8C223BA71366} = {43E76E46-0E5F-4429-83C8-157689885174}
{222A4E83-D902-423A-8E99-8321BBFC604C} = {4E666310-F835-4A49-A1A3-AE7CB3FB0221}
{F55831B1-2ADE-4CEB-8023-F92C7ABF57FE} = {6051DF88-C573-4BC4-BF98-3C5E807F4D70}
{C3AB43A4-88D8-49F8-B738-A738C8BCEE3F} = {43E76E46-0E5F-4429-83C8-157689885174}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0C545827-2ED7-4597-BE3C-30E978C85B9E}
EndGlobalSection