Refactor GPU resource management and rendering pipeline

- Introduced `Handle<T>` and `Identifier<T>` for lightweight, strongly-typed resource identifiers.
- Replaced `BitSet` with `UnsafeBitSet` for improved performance and memory safety.
- Refactored `Mesh` and `Material` into `MeshClass` and `MaterialClass` for better GPU resource handling.
- Added `D3D12ResourceDatabase` to centralize GPU resource tracking and lifecycle management.
- Updated `D3D12ShaderCompiler` to load shaders from disk and dynamically populate constant buffers and textures.
- Enhanced `ICommandBuffer` with new upload operations for buffers and textures.
- Refactored `Vertex` struct for simplified memory layout and better performance.
- Updated `MeshBuilder` and rendering logic to align with new resource and shader structures.
- Added `BindlessDescriptor` support to `TextureHandle` and `BufferHandle`.
- Removed unused classes and performed general cleanup.
- Updated unit tests and demos to reflect the new architecture.
This commit is contained in:
2025-09-19 23:20:15 +09:00
parent 6a504cefc8
commit a39f377533
39 changed files with 1669 additions and 826 deletions

88
Ghost.Core/Handle.cs Normal file
View File

@@ -0,0 +1,88 @@
namespace Ghost.Core;
public readonly struct Handle<T>
{
public readonly int id;
public readonly int generation;
public Handle(int id, int generation)
{
this.id = id;
this.generation = generation;
}
public static Handle<T> Invalid => new(-1, -1);
public readonly override int GetHashCode()
{
return id.GetHashCode();
}
public readonly override bool Equals(object? obj)
{
return obj is Handle<T> id && Equals(id);
}
public readonly bool Equals(Handle<T> other)
{
return id == other.id;
}
public readonly int CompareTo(Handle<T> other)
{
return id.CompareTo(other.id);
}
public static bool operator ==(Handle<T> a, Handle<T> b)
{
return a.Equals(b);
}
public static bool operator !=(Handle<T> a, Handle<T> b)
{
return !a.Equals(b);
}
}
public readonly struct Identifier<T>
{
public readonly int value;
public Identifier(int value)
{
this.value = value;
}
public static Identifier<T> Invalid => new(-1);
public readonly override int GetHashCode()
{
return value.GetHashCode();
}
public readonly override bool Equals(object? obj)
{
return obj is Identifier<T> id && Equals(id);
}
public readonly bool Equals(Identifier<T> other)
{
return value == other.value;
}
public readonly int CompareTo(Identifier<T> other)
{
return value.CompareTo(other.value);
}
public static bool operator ==(Identifier<T> a, Identifier<T> b)
{
return a.Equals(b);
}
public static bool operator !=(Identifier<T> a, Identifier<T> b)
{
return !a.Equals(b);
}
}

View File

@@ -472,7 +472,7 @@ internal struct ComponentStorage : IDisposable
private int _currentCapacity = 16;
private IComponentPool?[] _componentPools = new IComponentPool[16];
private BitSet?[] _componentEntityMasks = new BitSet[16];
private UnsafeBitSet?[] _componentEntityMasks = new UnsafeBitSet?[16];
private readonly Dictionary<TypeHandle, int> _typeIDMap = new(16);
private readonly Dictionary<int, TypeHandle> _typeHandleMap = new(16);
@@ -487,7 +487,7 @@ internal struct ComponentStorage : IDisposable
}
internal readonly IReadOnlyList<IComponentPool?> ComponentPools => _componentPools;
internal readonly IReadOnlyList<BitSet?> ComponentEntityMasks => _componentEntityMasks;
internal readonly IReadOnlyList<UnsafeBitSet?> ComponentEntityMasks => _componentEntityMasks;
internal readonly ScriptComponentPool ScriptComponentPool => _scriptComponentPool;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -621,7 +621,7 @@ internal struct ComponentStorage : IDisposable
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool TryGetMask(TypeHandle typeHandle, [NotNullWhen(true)] out BitSet? bitSet)
public readonly bool TryGetMask(TypeHandle typeHandle, [NotNullWhen(true)] out UnsafeBitSet? bitSet)
{
if (!_typeIDMap.TryGetValue(typeHandle, out var id)
|| id >= _currentCapacity)
@@ -631,17 +631,17 @@ internal struct ComponentStorage : IDisposable
}
bitSet = _componentEntityMasks[id];
return bitSet != null;
return bitSet.HasValue;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool TryGetMask<T>([NotNullWhen(true)] out BitSet? bitSet)
public readonly bool TryGetMask<T>([NotNullWhen(true)] out UnsafeBitSet? bitSet)
where T : unmanaged, IComponentData
{
return TryGetMask(TypeHandle.Get<T>(), out bitSet);
}
public BitSet GetOrCreateMask<T>()
public UnsafeBitSet GetOrCreateMask<T>()
where T : unmanaged, IComponentData
{
var typeHandle = TypeHandle.Get<T>();
@@ -658,12 +658,12 @@ internal struct ComponentStorage : IDisposable
}
ref var set = ref _componentEntityMasks[id];
set ??= new BitSet();
set ??= new UnsafeBitSet();
return set;
return set.Value;
}
public BitSet GetOrCreateMask(Type type)
public UnsafeBitSet GetOrCreateMask(Type type)
{
var typeHandle = TypeHandle.Get(type);
if (!_typeIDMap.TryGetValue(typeHandle, out var id))
@@ -679,9 +679,9 @@ internal struct ComponentStorage : IDisposable
}
ref var set = ref _componentEntityMasks[id];
set ??= new BitSet();
set ??= new UnsafeBitSet();
return set;
return set.Value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -704,7 +704,11 @@ internal struct ComponentStorage : IDisposable
pool?.Dispose();
}
Array.Clear(_componentPools);
foreach (var bitSet in _componentEntityMasks)
{
bitSet?.Dispose();
}
_scriptComponentPool.Dispose();
}
}

View File

@@ -186,7 +186,7 @@ public readonly struct EntityManager : IDisposable
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool HasComponent(Entity entity, TypeHandle typeHandle)
{
return _world.ComponentStorage.TryGetMask(typeHandle, out var bitSet) && bitSet.IsSet(entity.ID);
return _world.ComponentStorage.TryGetMask(typeHandle, out var bitSet) && bitSet.Value.IsSet(entity.ID);
}
/// <summary>

View File

@@ -25,25 +25,20 @@ internal struct QueryFilter()
internal List<TypeHandle> _absent = new(6);
internal List<TypeHandle> _disabled = new(6);
public readonly BitSet ComputeFilterBitMask(World world)
public readonly UnsafeBitSet ComputeFilterBitMask(World world)
{
BitSet? allMask = null;
BitSet? anyMask = null;
BitSet? absentMask = null;
var hasAll = false;
var hasAny = false;
var hasAbsent = false;
UnsafeBitSet? allMask = null;
UnsafeBitSet? anyMask = null;
UnsafeBitSet? absentMask = null;
foreach (var typeHandle in _all)
{
var mask = world.ComponentStorage.GetOrCreateMask(typeHandle);
if (!hasAll)
if (!allMask.HasValue)
{
allMask = new BitSet(mask.Length);
allMask.SetAll();
hasAll = true;
allMask = new UnsafeBitSet(mask.Length);
allMask.Value.SetAll();
}
allMask &= mask;
@@ -53,10 +48,9 @@ internal struct QueryFilter()
{
var mask = world.ComponentStorage.GetOrCreateMask(typeHandle);
if (!hasAny)
if (!anyMask.HasValue)
{
anyMask = new BitSet(mask.Length);
hasAny = true;
anyMask = new UnsafeBitSet(mask.Length);
}
anyMask |= mask;
@@ -66,31 +60,30 @@ internal struct QueryFilter()
{
var mask = world.ComponentStorage.GetOrCreateMask(typeHandle);
if (!hasAbsent)
if (!absentMask.HasValue)
{
absentMask = new BitSet(mask.Length);
hasAbsent = true;
absentMask = new UnsafeBitSet(mask.Length);
}
absentMask |= mask;
}
var result = new BitSet(world.EntityManager.EntityCount);
var result = new UnsafeBitSet(world.EntityManager.EntityCount);
result.SetAll();
if (hasAll)
if (allMask.HasValue)
{
result &= allMask!;
result &= allMask.Value;
}
if (hasAny)
if (anyMask.HasValue)
{
result &= anyMask!;
result &= anyMask.Value;
}
if (hasAbsent)
if (absentMask.HasValue)
{
result &= ~absentMask!;
result &= ~absentMask.Value;
}
return result;

View File

@@ -42,7 +42,7 @@ public struct QueryEnumerable<T0>
private int _index;
private readonly int _count;
private BitSet _filterMask;
private UnsafeBitSet _filterMask;
public QueryItem<T0> Current
{
@@ -213,7 +213,7 @@ public struct QueryEnumerable<T0, T1>
private int _index;
private readonly int _count;
private BitSet _filterMask;
private UnsafeBitSet _filterMask;
public QueryItem<T0, T1> Current
{
@@ -389,7 +389,7 @@ public struct QueryEnumerable<T0, T1, T2>
private int _index;
private readonly int _count;
private BitSet _filterMask;
private UnsafeBitSet _filterMask;
public QueryItem<T0, T1, T2> Current
{
@@ -570,7 +570,7 @@ public struct QueryEnumerable<T0, T1, T2, T3>
private int _index;
private readonly int _count;
private BitSet _filterMask;
private UnsafeBitSet _filterMask;
public QueryItem<T0, T1, T2, T3> Current
{
@@ -756,7 +756,7 @@ public struct QueryEnumerable<T0, T1, T2, T3, T4>
private int _index;
private readonly int _count;
private BitSet _filterMask;
private UnsafeBitSet _filterMask;
public QueryItem<T0, T1, T2, T3, T4> Current
{
@@ -947,7 +947,7 @@ public struct QueryEnumerable<T0, T1, T2, T3, T4, T5>
private int _index;
private readonly int _count;
private BitSet _filterMask;
private UnsafeBitSet _filterMask;
public QueryItem<T0, T1, T2, T3, T4, T5> Current
{
@@ -1143,7 +1143,7 @@ public struct QueryEnumerable<T0, T1, T2, T3, T4, T5, T6>
private int _index;
private readonly int _count;
private BitSet _filterMask;
private UnsafeBitSet _filterMask;
public QueryItem<T0, T1, T2, T3, T4, T5, T6> Current
{
@@ -1344,7 +1344,7 @@ public struct QueryEnumerable<T0, T1, T2, T3, T4, T5, T6, T7>
private int _index;
private readonly int _count;
private BitSet _filterMask;
private UnsafeBitSet _filterMask;
public QueryItem<T0, T1, T2, T3, T4, T5, T6, T7> Current
{

View File

@@ -66,7 +66,7 @@ public struct QueryEnumerable<<#= generics #>>
private int _index;
private readonly int _count;
private BitSet _filterMask;
private UnsafeBitSet _filterMask;
public QueryItem<<#= generics #>> Current
{

View File

@@ -33,7 +33,7 @@ public unsafe class CommandList
/// </summary>
/// <param name="mesh">The mesh to draw</param>
/// <param name="material">The bindless material to use</param>
public void DrawMesh(Mesh mesh, Material material)
public void DrawMesh(MeshClass mesh, MaterialClass material)
{
// Bind the bindless material (sets up root signature, pipeline state, and descriptor heaps)
material.Bind(this);
@@ -62,7 +62,7 @@ public unsafe class CommandList
_commandList.Ptr->OMSetRenderTargets(1, pRtvHandle, false, pDsvHandle);
}
public void ClearRenderTarget(RenderTexture renderTarget, Color16 color)
public void ClearRenderTarget(RenderTexture renderTarget, Color128 color)
{
renderTarget.ClearColor(this, color);
}

View File

@@ -1,5 +1,6 @@
using Ghost.Graphics.Data;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Utilities;
using Win32;
using Win32.Graphics.Direct3D;
using Win32.Graphics.Direct3D12;
@@ -16,23 +17,35 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
private ComPtr<ID3D12GraphicsCommandList10> _commandList;
private readonly D3D12PipelineStateController _stateController;
private readonly D3D12ResourceDatabase _resourceDatabase;
private readonly D3D12ResourceAllocator _resourceAllocator;
private readonly D3D12DescriptorAllocator _descriptorAllocator;
private readonly CommandBufferType _type;
private bool _isRecording;
private bool _disposed;
public CommandBufferType Type => _type;
public ID3D12GraphicsCommandList10* NativeCommandList => _commandList.Get();
public D3D12CommandBuffer(D3D12RenderDevice device, D3D12PipelineStateController stateController, D3D12DescriptorAllocator descriptorAllocator, CommandBufferType type)
public D3D12CommandBuffer(
D3D12RenderDevice device,
D3D12PipelineStateController stateController,
D3D12ResourceDatabase resourceDatabase,
D3D12ResourceAllocator resourceAllocator,
D3D12DescriptorAllocator descriptorAllocator,
CommandBufferType type)
{
_type = type;
var commandListType = ConvertCommandBufferType(type);
device.NativeDevice->CreateCommandAllocator(commandListType, __uuidof<ID3D12CommandAllocator>(), _allocator.GetVoidAddressOf());
device.NativeDevice->CreateCommandList(0u, commandListType, _allocator.Get(), null, __uuidof<ID3D12GraphicsCommandList10>(), _commandList.GetVoidAddressOf());
device.NativeDevice->CreateCommandList1(0u, commandListType, CommandListFlags.None, __uuidof<ID3D12GraphicsCommandList10>(), _commandList.GetVoidAddressOf());
_stateController = stateController;
_resourceDatabase = resourceDatabase;
_resourceAllocator = resourceAllocator;
_descriptorAllocator = descriptorAllocator;
// Command lists are created in recording state, so close it
@@ -40,8 +53,28 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
_isRecording = false;
}
~D3D12CommandBuffer()
{
Dispose();
}
private void ThrowIfDisposed()
{
ObjectDisposedException.ThrowIf(_disposed, this);
}
private void ThrowIfNotRecording()
{
if (!_isRecording)
{
throw new InvalidOperationException("Command buffer is not recording");
}
}
public void Begin()
{
ThrowIfDisposed();
if (_isRecording)
{
throw new InvalidOperationException("Command buffer is already recording");
@@ -54,16 +87,14 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
public void End()
{
if (!_isRecording)
{
throw new InvalidOperationException("Command buffer is not recording");
}
ThrowIfDisposed();
ThrowIfNotRecording();
_commandList.Get()->Close();
_isRecording = false;
}
public void BeginRenderPass(IRenderTarget renderTarget, Color16 clearColor)
public void BeginRenderPass(IRenderTarget renderTarget, Color128 clearColor)
{
// TODO: Implement render pass begin
throw new NotImplementedException();
@@ -77,18 +108,27 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
public void SetViewport(ViewportDesc viewport)
{
var d3d12Viewport = new Viewport(viewport.Width, viewport.Height, viewport.X, viewport.Y, viewport.MinDepth, viewport.MaxDepth);
ThrowIfDisposed();
ThrowIfNotRecording();
var d3d12Viewport = new Viewport(viewport.width, viewport.height, viewport.x, viewport.y, viewport.minDepth, viewport.maxDepth);
_commandList.Get()->RSSetViewports(1, &d3d12Viewport);
}
public void SetScissorRect(RectDesc rect)
{
ThrowIfDisposed();
ThrowIfNotRecording();
var d3d12Rect = new Rect(rect.Left, rect.Top, rect.Right, rect.Bottom);
_commandList.Get()->RSSetScissorRects(1, &d3d12Rect);
}
public void ResourceBarrier(IResource resource, ResourceState before, ResourceState after)
{
ThrowIfDisposed();
ThrowIfNotRecording();
if (resource is D3D12Texture d3d12Texture)
{
_commandList.Get()->ResourceBarrierTransition(d3d12Texture.NativeResource,
@@ -118,8 +158,11 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
}
// TODO: Batch draw calls by material to minimize state changes
public void DrawMesh(Mesh mesh, Material material)
public void DrawMesh(MeshClass mesh, MaterialClass material)
{
ThrowIfDisposed();
ThrowIfNotRecording();
// Bind the bindless material (sets up root signature, pipeline state, and descriptor heaps)
var shaderPipeline = _stateController.GetShaderPipeline(material.Shader);
if (shaderPipeline is not D3D12ShaderPipeline d3d12Pipeline)
@@ -128,8 +171,8 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
}
// Set root signature and pipeline state
_commandList.Get()->SetGraphicsRootSignature(d3d12Pipeline.rootSignature.Get());
_commandList.Get()->SetPipelineState(d3d12Pipeline.pipelineState.Get());
_commandList.Get()->SetGraphicsRootSignature(d3d12Pipeline.rootSignature.Get());
// Set descriptor heaps - CRUCIAL: Use the specialized bindless heap for SM 6.6
var heaps = stackalloc ID3D12DescriptorHeap*[2];
@@ -142,7 +185,8 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
foreach (var cbufferInfo in material.Shader.ConstantBuffers)
{
var cache = material.CBufferCaches[(int)cbufferInfo.RegisterSlot];
_commandList.Get()->SetGraphicsRootConstantBufferView(rootParamIndex++, cache.GpuResource.GPUAddress);
var resource = _resourceDatabase.GetResource(cache.GpuResource.ResourceHandle);
_commandList.Get()->SetGraphicsRootConstantBufferView(rootParamIndex++, resource->GetGPUVirtualAddress());
}
// Bind sampler descriptor table (last root parameter)
@@ -163,9 +207,67 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
public void Dispatch(uint threadGroupCountX, uint threadGroupCountY = 1, uint threadGroupCountZ = 1)
{
ThrowIfDisposed();
ThrowIfNotRecording();
_commandList.Get()->Dispatch(threadGroupCountX, threadGroupCountY, threadGroupCountZ);
}
public void Upload<T>(BufferHandle buffer, ReadOnlySpan<T> data)
where T : unmanaged
{
ThrowIfDisposed();
ThrowIfNotRecording();
var sizeInBytes = (uint)(data.Length * sizeof(T));
var uploadHandle = _resourceAllocator.CreateUploadBuffer(sizeInBytes);
var uploadResource = _resourceDatabase.GetResource(uploadHandle.ResourceHandle);
void* mappedData;
uploadResource->Map(0, null, &mappedData);
fixed (T* dataPtr = data)
{
MemoryUtilities.MemCpy(mappedData, dataPtr, sizeInBytes);
}
uploadResource->Unmap(0, null);
var resource = _resourceDatabase.GetResource(buffer.ResourceHandle);
// Copy from upload buffer to destination
_commandList.Get()->CopyBufferRegion(resource, 0, uploadResource, 0, sizeInBytes);
}
public void Upload(TextureHandle texture, uint firstSubresource, ref SubResourceData subresources, uint numSubresources)
{
ThrowIfDisposed();
ThrowIfNotRecording();
var textureResource = _resourceDatabase.GetResource(texture.ResourceHandle);
var resourceDesc = textureResource->GetDesc();
var requiredSize = GetRequiredIntermediateSize(textureResource, firstSubresource, numSubresources);
var uploadHandle = _resourceAllocator.CreateUploadBuffer(requiredSize);
var uploadResource = _resourceDatabase.GetResource(uploadHandle.ResourceHandle);
var d3d12Subresources = new SubresourceData
{
pData = subresources.pData,
RowPitch = subresources.rowPitch,
SlicePitch = subresources.slicePitch
};
UpdateSubresources(
(ID3D12GraphicsCommandList*)_commandList.Get(),
textureResource,
uploadResource,
0,
firstSubresource,
numSubresources,
&d3d12Subresources);
}
private static CommandListType ConvertCommandBufferType(CommandBufferType type)
{
return type switch
@@ -197,11 +299,20 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
public void Dispose()
{
if (_isRecording)
{
throw new InvalidOperationException("Command buffer is still recording");
}
if (_disposed)
{
return;
}
_commandList.Dispose();
_allocator.Dispose();
_disposed = true;
GC.SuppressFinalize(this);
}
}

View File

@@ -42,6 +42,11 @@ internal unsafe class D3D12CommandQueue : ICommandQueue
pDevice->CreateFence(0, FenceFlags.None, __uuidof<ID3D12Fence1>(), _fence.GetVoidAddressOf());
}
~D3D12CommandQueue()
{
Dispose();
}
public void Submit(ICommandBuffer commandBuffer)
{
if (commandBuffer is D3D12CommandBuffer d3d12CommandBuffer)
@@ -113,12 +118,16 @@ internal unsafe class D3D12CommandQueue : ICommandQueue
public void Dispose()
{
if (_disposed)
{
return;
}
_fenceEvent?.Dispose();
_fence.Dispose();
_queue.Dispose();
_disposed = true;
GC.SuppressFinalize(this);
}
}

View File

@@ -9,12 +9,17 @@ internal unsafe class D3D12GraphicsEngine : IGraphicsEngine
#endif
private readonly D3D12RenderDevice _device;
private readonly D3D12PipelineStateController _stateController;
private readonly D3D12DescriptorAllocator _descriptorAllocator;
private readonly D3D12ResourceDatabase _resourceDatabase;
private readonly D3D12ResourceAllocator _resourceAllocator;
private readonly D3D12PipelineStateController _stateController;
private readonly D3D12CommandBuffer _copyCommandBuffer;
public IRenderDevice Device => _device;
public IResourceDatabase ResourceDatabase => _resourceDatabase;
public IResourceAllocator ResourceAllocator => _resourceAllocator;
public IPipelineStateController PipelineStateController => _stateController;
@@ -24,12 +29,25 @@ internal unsafe class D3D12GraphicsEngine : IGraphicsEngine
#if DEBUG
_debugLayer = new();
#endif
_device = new();
_descriptorAllocator = new(_device);
_resourceAllocator = new(renderSystem, _device, _descriptorAllocator);
_stateController = new(_device);
_resourceDatabase = new(_descriptorAllocator);
_resourceAllocator = new(renderSystem, _device, _descriptorAllocator, _resourceDatabase);
_stateController = new(_device, _resourceDatabase);
_copyCommandBuffer = new(
_device,
_stateController,
_resourceDatabase,
_resourceAllocator,
_descriptorAllocator,
CommandBufferType.Copy);
}
~D3D12GraphicsEngine()
{
Dispose();
}
public IRenderer CreateRenderer()
@@ -39,7 +57,13 @@ internal unsafe class D3D12GraphicsEngine : IGraphicsEngine
public ICommandBuffer CreateCommandBuffer(CommandBufferType type = CommandBufferType.Graphics)
{
return new D3D12CommandBuffer(_device, _stateController, _descriptorAllocator, type);
return new D3D12CommandBuffer(
_device,
_stateController,
_resourceDatabase,
_resourceAllocator,
_descriptorAllocator,
type);
}
public ISwapChain CreateSwapChain(SwapChainDesc desc)
@@ -47,15 +71,30 @@ internal unsafe class D3D12GraphicsEngine : IGraphicsEngine
return new D3D12SwapChain(_device.DXGIFactory, ((D3D12CommandQueue)_device.ComputeQueue).NativeQueue, desc);
}
public void BeginFrame()
{
throw new NotImplementedException();
}
public void EndFrame()
{
throw new NotImplementedException();
}
public void Dispose()
{
_copyCommandBuffer.Dispose();
_stateController.Dispose();
_descriptorAllocator.Dispose();
_resourceAllocator.Dispose();
_device.Dispose();
_resourceAllocator.Dispose();
_resourceDatabase.Dispose();
_descriptorAllocator.Dispose();
_device.Dispose();
#if DEBUG
_debugLayer.Dispose();
#endif
GC.SuppressFinalize(this);
}
}

View File

@@ -1,4 +1,5 @@
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Core;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.Data;
using Ghost.Graphics.RHI;
using Win32;
@@ -8,7 +9,7 @@ using Win32.Graphics.Dxgi.Common;
namespace Ghost.Graphics.D3D12;
internal class D3D12ShaderPipeline : IShaderPipeline
internal class D3D12ShaderPipeline : IShaderPipeline, IDisposable
{
public ComPtr<ID3D12RootSignature> rootSignature;
public ComPtr<ID3D12PipelineState> pipelineState;
@@ -21,42 +22,51 @@ internal class D3D12ShaderPipeline : IShaderPipeline
{
get; init;
}
public void Dispose()
{
rootSignature.Dispose();
pipelineState.Dispose();
samplerHeap.Dispose();
vsResult.Dispose();
psResult.Dispose();
csResult.Dispose();
}
}
internal unsafe class D3D12PipelineStateController : IPipelineStateController, IDisposable
{
private readonly ID3D12Device14* _device;
private readonly D3D12ResourceDatabase _resourceDatabase;
private readonly Dictionary<Shader, D3D12ShaderPipeline> _shaderPipelines;
private readonly Dictionary<Identifier<Shader>, D3D12ShaderPipeline> _shaderPipelines;
public D3D12PipelineStateController(D3D12RenderDevice device)
public D3D12PipelineStateController(D3D12RenderDevice device, D3D12ResourceDatabase resourceDatabase)
{
_device = device.NativeDevice;
_resourceDatabase = resourceDatabase;
_shaderPipelines = new();
}
// TODO: Support compute shaders
public void ColectionShader(ReadOnlySpan<Shader> shaders)
public void CompileShader(Identifier<Shader> id, string shaderPath)
{
foreach (var shader in shaders)
{
_shaderPipelines.TryAdd(shader, new()
{
Type = PipelineType.Graphics
});
}
}
var vsResult = D3D12ShaderCompiler.Compile(shaderPath, D3D12ShaderCompiler.ShaderStage.VertexShader, D3D12ShaderCompiler.CompilerVersion.SM_6_6);
var psResult = D3D12ShaderCompiler.Compile(shaderPath, D3D12ShaderCompiler.ShaderStage.PixelShader, D3D12ShaderCompiler.CompilerVersion.SM_6_6);
public void CompileCollected()
{
foreach (var kvp in _shaderPipelines)
{
var vsResult = D3D12ShaderCompiler.Compile(kvp.Key, D3D12ShaderCompiler.ShaderStage.VertexShader, D3D12ShaderCompiler.CompilerVersion.SM_6_6);
var psResult = D3D12ShaderCompiler.Compile(kvp.Key, D3D12ShaderCompiler.ShaderStage.PixelShader, D3D12ShaderCompiler.CompilerVersion.SM_6_6);
var shader = _resourceDatabase.GetShader(id);
kvp.Value.vsResult = vsResult;
kvp.Value.psResult = psResult;
}
D3D12ShaderCompiler.PerformDXCReflection(shader, vsResult.reflection.Get());
D3D12ShaderCompiler.PerformDXCReflection(shader, psResult.reflection.Get());
var shaderPipeline = new D3D12ShaderPipeline
{
Type = PipelineType.Graphics,
vsResult = vsResult,
psResult = psResult
};
_shaderPipelines[id] = shaderPipeline;
}
private void CreateRootSignature(Shader shader, D3D12ShaderPipeline shaderPipeline)
@@ -219,31 +229,29 @@ internal unsafe class D3D12PipelineStateController : IPipelineStateController, I
{
foreach (var kvp in _shaderPipelines)
{
CreateRootSignature(kvp.Key, kvp.Value);
var shader = _resourceDatabase.GetShader(kvp.Key);
CreateRootSignature(shader, kvp.Value);
CreatePipelineStateObject(kvp.Value);
CreateSamplerHeap(kvp.Value);
}
}
public IShaderPipeline GetShaderPipeline(Shader shader)
public IShaderPipeline GetShaderPipeline(Identifier<Shader> id)
{
if (_shaderPipelines.TryGetValue(shader, out var pipeline))
if (_shaderPipelines.TryGetValue(id, out var pipeline))
{
return pipeline;
}
throw new KeyNotFoundException($"Shader pipeline not found for shader: {shader}");
throw new KeyNotFoundException($"Shader pipeline not found for shader ID: {id}");
}
public void Dispose()
{
foreach (var kvp in _shaderPipelines)
{
kvp.Value.rootSignature.Dispose();
kvp.Value.pipelineState.Dispose();
kvp.Value.samplerHeap.Dispose();
kvp.Value.vsResult.Dispose();
kvp.Value.psResult.Dispose();
kvp.Value.Dispose();
}
}
}
}

View File

@@ -169,7 +169,7 @@ public unsafe class D3D12Renderer : IRenderer
private void RenderScene(IRenderTarget target, ICommandBuffer cmd)
{
var clearColor = new Color16 { r = 1.0f, g = 0.0f, b = 1.0f, a = 1.0f };
var clearColor = new Color128 { r = 1.0f, g = 0.0f, b = 1.0f, a = 1.0f };
cmd.BeginRenderPass(target, clearColor);
@@ -207,7 +207,7 @@ public unsafe class D3D12Renderer : IRenderer
// 3. Apply post-processing effects (tone mapping, gamma correction, etc.)
// For now, just clear the destination (this should be replaced with actual blit)
var clearColor = new Color16 { r = 0.0f, g = 0.0f, b = 0.0f, a = 1.0f };
var clearColor = new Color128 { r = 0.0f, g = 0.0f, b = 0.0f, a = 1.0f };
cmd.BeginRenderPass(destination, clearColor);
cmd.EndRenderPass();

View File

@@ -1,4 +1,5 @@
using Ghost.Graphics.Data;
using Ghost.Core;
using Ghost.Graphics.Data;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Runtime.CompilerServices;
@@ -10,32 +11,8 @@ using static Win32.Graphics.D3D12MemoryAllocator.Apis;
namespace Ghost.Graphics.D3D12;
internal unsafe class D3D12ResourceAllocator : IResourceAllocator<ID3D12Resource>, IDisposable
internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
{
private readonly struct AllocationInfo : IDisposable
{
public readonly Allocation allocation;
public readonly uint cpuFenceValue;
public bool Allocated => allocation.IsNotNull;
public AllocationInfo(in Allocation allocation, uint cpuFenceValue)
{
this.allocation = allocation;
this.cpuFenceValue = cpuFenceValue;
}
public void Dispose()
{
if (allocation.IsNull)
{
return;
}
allocation.Release();
}
}
private const uint _MAX_BYTES = D3D12_REQ_RESOURCE_SIZE_IN_MEGABYTES_EXPRESSION_A_TERM * 1024u * 1024u;
private const uint _MAX_TEXTURE2D_DIMENSION = 16384u;
private const uint _MAX_TEXTURE3D_DIMENSION = 2048u;
@@ -45,8 +22,8 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator<ID3D12Resource
private readonly Allocator _allocator;
private readonly RenderSystem _renderSystem;
private readonly D3D12DescriptorAllocator _descriptorAllocator;
private readonly D3D12ResourceDatabase _resourceDatabase;
private UnsafeSlotMap<AllocationInfo> _allocations = new(64, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
private UnsafeQueue<ResourceHandle> _temResources = new(64, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
private Guid* IID_NULL
@@ -60,7 +37,7 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator<ID3D12Resource
}
}
public D3D12ResourceAllocator(RenderSystem renderSystem, D3D12RenderDevice device, D3D12DescriptorAllocator descriptorAllocator)
public D3D12ResourceAllocator(RenderSystem renderSystem, D3D12RenderDevice device, D3D12DescriptorAllocator descriptorAllocator, D3D12ResourceDatabase resourceDatabase)
{
var desc = new AllocatorDesc
{
@@ -74,6 +51,12 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator<ID3D12Resource
_device = device.NativeDevice;
_renderSystem = renderSystem;
_descriptorAllocator = descriptorAllocator;
_resourceDatabase = resourceDatabase;
}
~D3D12ResourceAllocator()
{
Dispose();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -94,11 +77,11 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator<ID3D12Resource
}
}
private ResourceHandle TrackResource(ref readonly Allocation allocation, bool isTemp)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private ResourceHandle TrackResource(ref readonly Allocation allocation, ResourceStates state, bool isTemp)
{
var id = _allocations.Add(new(in allocation, _renderSystem.CPUFenceValue), out var generation);
var handle = _resourceDatabase.AddResource(in allocation, _renderSystem.CPUFenceValue, D3D12StatesToRHIState(state));
var handle = new ResourceHandle(id, generation);
if (isTemp)
{
_temResources.Enqueue(handle);
@@ -107,18 +90,40 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator<ID3D12Resource
return handle;
}
public TextureHandle CreateTextureHandle(ref readonly TextureDesc desc, bool tempResource = false)
public TextureHandle CreateTexture(ref readonly TextureDesc desc, bool tempResource = false)
{
CheckTexture2DSize(desc.Width, desc.Height);
var resourceDesc = ResourceDescription.Tex2D(
ConvertTextureFormat(desc.Format),
desc.Width,
desc.Height,
mipLevels: (ushort)desc.MipLevels,
arraySize: (ushort)desc.Slice,
flags: ConvertTextureUsage(desc.Usage)
);
var d3d12Format = ConvertTextureFormat(desc.Format);
var mipLevels = desc.MipLevels == 0 ? (ushort)(1 + Math.Floor(Math.Log2(Math.Max(desc.Width, desc.Height)))) : (ushort)desc.MipLevels;
var resourceDesc = desc.Dimension switch
{
TextureDimension.Texture2D => ResourceDescription.Tex2D(
d3d12Format,
desc.Width,
desc.Height,
mipLevels: mipLevels,
flags: ConvertTextureUsage(desc.Usage)),
TextureDimension.Texture3D => ResourceDescription.Tex3D(
d3d12Format,
desc.Width,
desc.Height,
(ushort)desc.Slice,
flags: ConvertTextureUsage(desc.Usage)),
//case TextureDimension.TextureCube:
// break;
TextureDimension.Texture2DArray => ResourceDescription.Tex2D(
d3d12Format,
desc.Width,
desc.Height,
mipLevels: mipLevels,
arraySize: (ushort)desc.Slice,
flags: ConvertTextureUsage(desc.Usage)),
//case TextureDimension.TextureCubeArray:
// break;
_ => throw new ArgumentException($"Unsupported texture dimension: {desc.Dimension}"),
};
var allocationDesc = new AllocationDesc
{
@@ -131,10 +136,58 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator<ID3D12Resource
Allocation allocation = default;
ThrowIfFailed(_allocator.CreateResource(&allocationDesc, in resourceDesc, initialState, null, &allocation, IID_NULL, null));
return new(TrackResource(in allocation, tempResource));
var handle = TrackResource(in allocation, initialState, tempResource);
if (desc.CreationFlags.HasFlag(TextureCreationFlags.Bindless))
{
var descriptorHandle = _descriptorAllocator.AllocateBindless();
var srvDesc = new ShaderResourceViewDescription
{
Format = d3d12Format,
Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING
};
switch (desc.Dimension)
{
case TextureDimension.Texture2D:
srvDesc.ViewDimension = SrvDimension.Texture2D;
srvDesc.Texture2D = new Texture2DSrv
{
MipLevels = mipLevels,
};
break;
case TextureDimension.Texture3D:
srvDesc.ViewDimension = SrvDimension.Texture3D;
srvDesc.Texture3D = new Texture3DSrv
{
MipLevels = 0,
};
break;
case TextureDimension.Texture2DArray:
srvDesc.ViewDimension = SrvDimension.Texture2DArray;
srvDesc.Texture2DArray = new Texture2DArraySrv
{
MipLevels = mipLevels,
ArraySize = desc.Slice,
};
break;
default:
throw new ArgumentException($"Unsupported texture dimension for SRV: {desc.Dimension}");
}
_device->CreateShaderResourceView(allocation.Resource, &srvDesc, _descriptorAllocator.GetCpuHandle(descriptorHandle));
}
return new(handle);
}
public BufferHandle CreateBufferHandle(ref readonly BufferDesc desc, bool tempResource = false)
public TextureHandle CreateRenderTarget(ref readonly RenderTargetDesc desc, bool tempResource = false)
{
var textureDesc = RenderTargetDesc.ToTextureDescriptor(desc);
return CreateTexture(ref textureDesc, tempResource);
}
public BufferHandle CreateBuffer(ref readonly BufferDesc desc, bool tempResource = false)
{
CheckBufferSize((uint)desc.Size);
@@ -150,12 +203,11 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator<ID3D12Resource
Allocation allocation = default;
ThrowIfFailed(_allocator.CreateResource(&allocationDesc, in resourceDescription, initialState, null, &allocation, IID_NULL, null));
var handle = TrackResource(in allocation, tempResource);
var handle = TrackResource(in allocation, initialState, tempResource);
if (desc.Usage.HasFlag(BufferUsage.ShaderResource) && desc.CreationFlags.HasFlag(BufferCreationFlags.Bindless))
{
var isRaw = desc.Usage.HasFlag(BufferUsage.Raw);
var descriptorHandle = _descriptorAllocator.AllocateBindless();
var srvDesc = new ShaderResourceViewDescription
@@ -189,23 +241,75 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator<ID3D12Resource
return new(handle);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public IRenderTarget CreateRenderTarget(ref readonly RenderTargetDesc desc, bool tempResource = false)
public BufferHandle CreateUploadBuffer(ulong size, bool temp = true)
{
var textureDesc = RenderTargetDesc.ToTextureDescriptor(desc);
return D3D12RenderTarget.Create(CreateTextureHandle(in textureDesc), in desc);
var desc = new BufferDesc
{
Size = size,
Usage = BufferUsage.Upload,
MemoryType = MemoryType.Upload,
};
return CreateBuffer(in desc, temp);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ITexture CreateTexture(ref readonly TextureDesc desc, bool tempResource = false)
public Identifier<Mesh> CreateMesh(ReadOnlySpan<Vertex> vertices, ReadOnlySpan<uint> indices)
{
return new D3D12Texture(CreateTextureHandle(in desc, tempResource), in desc);
var vertexBufferDesc = new BufferDesc
{
Size = (ulong)(vertices.Length * Unsafe.SizeOf<Vertex>()),
Stride = (uint)Unsafe.SizeOf<Vertex>(),
Usage = BufferUsage.Vertex | BufferUsage.ShaderResource,
MemoryType = MemoryType.Default,
CreationFlags = BufferCreationFlags.Bindless
};
var indexBufferDesc = new BufferDesc
{
Size = (ulong)(indices.Length * sizeof(uint)),
Stride = sizeof(uint),
Usage = BufferUsage.Index | BufferUsage.ShaderResource,
MemoryType = MemoryType.Default,
CreationFlags = BufferCreationFlags.Bindless
};
var vertexBuffer = CreateBuffer(ref vertexBufferDesc, true);
var indexBuffer = CreateBuffer(ref indexBufferDesc, true);
var data = new Mesh(vertices, indices, vertexBuffer, indexBuffer);
return _resourceDatabase.AddMesh(in data);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public IBuffer CreateBuffer(ref readonly BufferDesc desc, bool tempResource = false)
public Identifier<Material> CreateMaterial(Identifier<Shader> shader)
{
return new D3D12Buffer(CreateBufferHandle(in desc, tempResource), in desc, this);
var materialData = new Material
{
Shader = shader,
};
var shaderResource = _resourceDatabase.GetShader(shader);
if (shaderResource.ConstantBuffers.Count > 0)
{
var maxSlot = shaderResource.ConstantBuffers.Max(cb => cb.RegisterSlot);
materialData._cBufferCaches = new UnsafeArray<CBufferCache>((int)maxSlot + 1, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
foreach (var cbufferInfo in shaderResource.ConstantBuffers)
{
var desc = new BufferDesc
{
Size = cbufferInfo.Size,
Usage = BufferUsage.Constant,
MemoryType = MemoryType.Default,
};
var buffer = CreateBuffer(in desc);
materialData._cBufferCaches[cbufferInfo.RegisterSlot] = new CBufferCache(buffer, cbufferInfo.Size);
}
}
return _resourceDatabase.AddMaterial(in materialData);
}
#region Conversion Methods
@@ -319,6 +423,42 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator<ID3D12Resource
return ResourceStates.Common;
}
private static ResourceState D3D12StatesToRHIState(ResourceStates states)
{
switch (states)
{
//case ResourceStates.None:
//case ResourceStates.Present:
case ResourceStates.Common:
return ResourceState.Common;
case ResourceStates.VertexAndConstantBuffer:
return ResourceState.VertexAndConstantBuffer;
case ResourceStates.IndexBuffer:
return ResourceState.IndexBuffer;
case ResourceStates.RenderTarget:
return ResourceState.RenderTarget;
case ResourceStates.UnorderedAccess:
return ResourceState.UnorderedAccess;
case ResourceStates.DepthWrite:
return ResourceState.DepthWrite;
case ResourceStates.DepthRead:
return ResourceState.DepthRead;
case ResourceStates.PixelShaderResource:
return ResourceState.PixelShaderResource;
//case ResourceStates.Predication:
case ResourceStates.IndirectArgument:
return ResourceState.IndirectArgument;
case ResourceStates.CopyDest:
return ResourceState.CopyDest;
case ResourceStates.CopySource:
return ResourceState.CopySource;
case ResourceStates.GenericRead:
return ResourceState.GenericRead;
default:
return ResourceState.Common;
}
}
#endregion
public void ReleaseTempResource()
@@ -326,9 +466,8 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator<ID3D12Resource
while (_temResources.Count > 0)
{
var handle = _temResources.Peek();
if (_allocations.TryGetElementAt(handle.id, handle.generation, out var info)
&& info.Allocated)
ref var info = ref _resourceDatabase.GetResourceInfo(handle, out var exist);
if (exist && info.Allocated && info.cpuFenceValue > _renderSystem.GPUFenceValue)
{
break;
}
@@ -338,22 +477,6 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator<ID3D12Resource
}
}
public ID3D12Resource* GetResource(ResourceHandle handle)
{
if (!handle.IsValid)
{
throw new InvalidOperationException("Invalid resource handle.");
}
var info = _allocations.GetElementAt(handle.id, handle.generation);
if (!info.Allocated)
{
throw new InvalidOperationException($"Resource with ID {handle.id} and generation {handle.generation} is not allocated or has been released.");
}
return info.allocation.Resource;
}
public void ReleaseResource(ResourceHandle handle)
{
if (!handle.IsValid)
@@ -361,7 +484,7 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator<ID3D12Resource
return;
}
ref var info = ref _allocations.GetElementReferenceAt(handle.id, handle.generation, out var exist);
ref var info = ref _resourceDatabase.GetResourceInfo(handle, out var exist);
if (!exist || !info.Allocated)
{
@@ -369,24 +492,26 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator<ID3D12Resource
}
info.Dispose();
_allocations.Remove(handle.id, handle.generation);
_resourceDatabase.RemoveResource(handle);
}
public void Dispose()
{
#if DEBUG
if (_allocations.Count > 0)
if (_temResources.Count > 0)
{
throw new InvalidOperationException($"ResourceAllocator is being disposed with {_allocations.Count} allocations still registered. Ensure all resources are released before disposing.");
throw new InvalidOperationException($"ResourceAllocator is being disposed with {_temResources.Count} temp allocations still registered. Ensure all resources are released before disposing.");
}
#endif
foreach (var info in _allocations)
foreach (var handle in _temResources)
{
info.Dispose();
ReleaseResource(handle);
}
_allocations.Dispose();
_temResources.Dispose();
_allocator.Release();
GC.SuppressFinalize(this);
}
}

View File

@@ -1,11 +1,338 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Ghost.Core;
using Ghost.Graphics.Data;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.Collections;
using Misaki.HighPerformance.LowLevel.Collections;
using Win32.Graphics.D3D12MemoryAllocator;
using Win32.Graphics.Direct3D12;
namespace Ghost.Graphics.D3D12;
internal class D3D12ResourceDatabase
internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
{
}
private struct Slot<T>
{
public T value;
public bool isValid;
}
public struct ResourceInfo : IDisposable
{
public readonly Allocation allocation;
public readonly uint cpuFenceValue;
public ResourceState state;
public readonly bool Allocated => allocation.IsNotNull;
public ResourceInfo(in Allocation allocation, uint cpuFenceValue, ResourceState state)
{
this.allocation = allocation;
this.cpuFenceValue = cpuFenceValue;
this.state = state;
}
public readonly void Dispose()
{
if (allocation.IsNull)
{
return;
}
allocation.Release();
}
}
private UnsafeSlotMap<ResourceInfo> _resources;
// NOTE: We use a simple list for shaders since they are not frequently added/removed. This can save 4 bytes for each ecs component.
private readonly DynamicArray<Slot<Mesh>> _meshDatas;
private readonly DynamicArray<Slot<Shader>> _shaders;
private readonly D3D12DescriptorAllocator _descriptorAllocator;
private bool _disposed;
public D3D12ResourceDatabase(D3D12DescriptorAllocator descriptorAllocator)
{
_resources = new(64, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
_meshDatas = new(64);
_shaders = new(16);
_descriptorAllocator = descriptorAllocator;
}
~D3D12ResourceDatabase()
{
Dispose();
}
public ResourceHandle AddResource(ref readonly Allocation allocation, uint cpuFenceValue, ResourceState initialState)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var id = _resources.Add(new ResourceInfo(allocation, cpuFenceValue, initialState), out var generation);
return new ResourceHandle(id, generation);
}
public ref ResourceInfo GetResourceInfo(ResourceHandle handle)
{
ObjectDisposedException.ThrowIf(_disposed, this);
ref var info = ref _resources.GetElementReferenceAt(handle.id, handle.generation, out var exist);
if (!exist)
{
throw new KeyNotFoundException($"Resource with handle {handle} not found.");
}
return ref info;
}
public ref ResourceInfo GetResourceInfo(ResourceHandle handle, out bool exist)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return ref _resources.GetElementReferenceAt(handle.id, handle.generation, out exist);
}
public unsafe T* GetResource<T>(ResourceHandle handle)
where T : unmanaged
{
ObjectDisposedException.ThrowIf(_disposed, this);
if (typeof(T) != typeof(ID3D12Resource))
{
return null;
}
var info = GetResourceInfo(handle);
if (!info.Allocated)
{
return null;
}
return (T*)info.allocation.Resource;
}
public unsafe ID3D12Resource* GetResource(ResourceHandle handle)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var info = GetResourceInfo(handle);
if (!info.Allocated)
{
return null;
}
return info.allocation.Resource;
}
public ResourceState GetResourceState(ResourceHandle handle)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return GetResourceInfo(handle).state;
}
public void SetResourceState(ResourceHandle handle, ResourceState state)
{
ObjectDisposedException.ThrowIf(_disposed, this);
ref var info = ref _resources.GetElementReferenceAt(handle.id, handle.generation, out var exist);
if (!exist || info.Allocated == false)
{
throw new KeyNotFoundException($"Resource with handle {handle} not found.");
}
info.state = state;
}
public void RemoveResource(ResourceHandle handle)
{
ObjectDisposedException.ThrowIf(_disposed, this);
ref var info = ref _resources.GetElementReferenceAt(handle.id, handle.generation, out var exist);
if (!exist || info.Allocated == false)
{
return;
}
info.Dispose();
_resources.Remove(handle.id, handle.generation);
}
public Identifier<Mesh> AddMesh(ref readonly Mesh mesh)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var id = new Identifier<Mesh>(_meshDatas.Count);
_meshDatas.Add(new()
{
value = mesh,
isValid = true
});
return id;
}
public bool HasMesh(Identifier<Mesh> id)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return id.value >= 0 && id.value < _meshDatas.Count && _meshDatas[id.value].isValid;
}
public Mesh GetMesh(Identifier<Mesh> id)
{
if (!HasMesh(id))
{
throw new ArgumentOutOfRangeException(nameof(id), $"Mesh ID {id.value} is out of range.");
}
return _meshDatas[id.value].value;
}
public ref Mesh GetMeshReference(Identifier<Mesh> id)
{
if (!HasMesh(id))
{
throw new ArgumentOutOfRangeException(nameof(id), $"Mesh ID {id.value} is out of range.");
}
return ref _meshDatas[id.value].value;
}
public void RemoveMesh(Identifier<Mesh> id)
{
if (!HasMesh(id))
{
return;
}
ref var meshSlot = ref _meshDatas[id.value];
if (!meshSlot.isValid)
{
return;
}
ref var mesh = ref meshSlot.value;
mesh.ReleaseCpuResources();
RemoveResource(mesh.vertexBuffer.ResourceHandle);
RemoveResource(mesh.indexBuffer.ResourceHandle);
_descriptorAllocator.Release(mesh.vertexBuffer.BindlessDescriptor);
_descriptorAllocator.Release(mesh.indexBuffer.BindlessDescriptor);
}
public Identifier<Shader> AddShader(ref readonly Shader shader)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var id = new Identifier<Shader>(_shaders.Count);
_shaders.Add(new()
{
value = shader,
isValid = true
});
return id;
}
public bool HasShader(Identifier<Shader> id)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return id.value >= 0 && id.value < _shaders.Count && _shaders[id.value].isValid;
}
public Shader GetShader(Identifier<Shader> id)
{
if (!HasShader(id))
{
throw new ArgumentOutOfRangeException(nameof(id), $"Shader ID {id.value} is out of range.");
}
return _shaders[id.value].value;
}
public ref Shader GetShaderReference(Identifier<Shader> id)
{
if (!HasShader(id))
{
throw new ArgumentOutOfRangeException(nameof(id), $"Shader ID {id.value} is out of range.");
}
return ref _shaders[id.value].value;
}
public void RemoveShader(Identifier<Shader> id)
{
if (!HasShader(id))
{
return;
}
ref var shader = ref _shaders[id.value];
shader.value.Dispose();
shader.value = default;
shader.isValid = false;
}
public void Dispose()
{
if (_disposed)
{
return;
}
#if DEBUG
if (_resources.Count > 0)
{
throw new InvalidOperationException($"ResourceAllocator is being disposed with {_resources.Count} allocations still registered. Ensure all resources are released before disposing.");
}
if (_meshDatas.Count > 0)
{
throw new InvalidOperationException($"ResourceAllocator is being disposed with {_meshDatas.Count} meshes still registered. Ensure all meshes are released before disposing.");
}
if (_shaders.Count > 0)
{
throw new InvalidOperationException($"ResourceAllocator is being disposed with {_shaders.Count} shaders still registered. Ensure all shaders are released before disposing.");
}
#endif
_resources.Dispose();
foreach (var mesh in _meshDatas)
{
if (!mesh.isValid)
{
continue;
}
mesh.value.ReleaseCpuResources();
RemoveResource(mesh.value.vertexBuffer.ResourceHandle);
RemoveResource(mesh.value.indexBuffer.ResourceHandle);
_descriptorAllocator.Release(mesh.value.vertexBuffer.BindlessDescriptor);
_descriptorAllocator.Release(mesh.value.indexBuffer.BindlessDescriptor);
}
foreach (var shader in _shaders)
{
if (!shader.isValid)
{
continue;
}
shader.value.Dispose();
}
_disposed = true;
GC.SuppressFinalize(this);
}
}

View File

@@ -65,32 +65,36 @@ internal unsafe static class D3D12ShaderCompiler
};
}
public static CompileResult Compile(Shader shader, ShaderStage stage, CompilerVersion version)
public static CompileResult Compile(string shaderPath, ShaderStage stage, CompilerVersion version)
{
using ComPtr<IDxcCompiler3> compiler = default;
using ComPtr<IDxcUtils> utils = default;
using ComPtr<IDxcIncludeHandler> includeHandler = default;
// Create DXC compiler and utils
DxcCreateInstance(CLSID_DxcCompiler, __uuidof<IDxcCompiler3>(), compiler.GetVoidAddressOf());
DxcCreateInstance(CLSID_DxcUtils, __uuidof<IDxcUtils>(), utils.GetVoidAddressOf());
DxcCreateInstance(in CLSID_DxcCompiler, __uuidof<IDxcCompiler3>(), compiler.GetVoidAddressOf());
DxcCreateInstance(in CLSID_DxcUtils, __uuidof<IDxcUtils>(), utils.GetVoidAddressOf());
utils.Get()->CreateDefaultIncludeHandler(includeHandler.GetAddressOf());
// Create source blob
using ComPtr<IDxcBlobEncoding> sourceBlob = default;
var sourceBytes = System.Text.Encoding.UTF8.GetBytes(shader.Source);
fixed (byte* sourceBytesPtr = sourceBytes)
//var sourceBytes = System.Text.Encoding.UTF8.GetBytes(shaderPath);
fixed (char* pShaderPath = shaderPath.AsSpan())
{
utils.Get()->CreateBlob(sourceBytesPtr, (uint)sourceBytes.Length, DXC_CP_UTF8, sourceBlob.GetAddressOf());
utils.Get()->LoadFile(pShaderPath, null, sourceBlob.GetAddressOf());
//utils.Get()->CreateBlob(sourceBytesPtr, (uint)sourceBytes.Length, DXC_CP_UTF8, sourceBlob.GetAddressOf());
}
// Prepare compilation arguments - NOTE: NO -Qstrip_reflect to keep reflection data
var argsArray = new string[]
{
"-T", GetProfileString(stage, version), // Target profile (vs_6_6, ps_6_6)
"-E", GetEntryPoint(stage), // Entry point
"-HV", "2021", // HLSL version 2021 (required for SM 6.6)
"-enable-16bit-types", // Enable 16-bit types
"-O3", // Optimization level
"-Qstrip_debug" // Strip debug info but KEEP reflection
"-T", GetProfileString(stage, version), // Target profile (vs_6_6, ps_6_6)
"-E", GetEntryPoint(stage), // Entry point
"-HV", "2021", // HLSL version 2021 (required for SM 6.6)
"-enable-16bit-types", // Enable 16-bit types
"-O3", // Optimization level
"-Qstrip_debug" // Strip debug info but KEEP reflection
};
// Convert to wide strings (DXC expects LPCWSTR)
@@ -116,7 +120,7 @@ internal unsafe static class D3D12ShaderCompiler
Encoding = DXC_CP_UTF8
};
compiler.Get()->Compile(&buffer, (char**)argsPtr, (uint)argsArray.Length, null, __uuidof<IDxcResult>(), result.GetVoidAddressOf());
compiler.Get()->Compile(&buffer, (char**)argsPtr, (uint)argsArray.Length, includeHandler.Get(), __uuidof<IDxcResult>(), result.GetVoidAddressOf());
}
// Check compilation result
@@ -203,85 +207,92 @@ internal unsafe static class D3D12ShaderCompiler
ShaderDescription shaderDesc;
reflection.Get()->GetDesc(&shaderDesc);
var cbufferRegistry = shader.ConstantBuffers.ToDictionary(cb => cb.Name);
var textureRegistry = shader.RegularTextures.ToDictionary(t => t.Name);
var cbufferRegistry = new Dictionary<string, CBufferInfo>();
var textureRegistry = new Dictionary<string, TextureInfo>();
for (uint i = 0; i < shaderDesc.BoundResources; i++)
{
ShaderInputBindDescription bindDesc;
reflection.Get()->GetResourceBindingDesc(i, &bindDesc);
if (bindDesc.Type == ShaderInputType.ConstantBuffer)
switch (bindDesc.Type)
{
var cbufferName = Marshal.PtrToStringAnsi((IntPtr)bindDesc.Name);
if (cbufferName == null || cbufferRegistry.ContainsKey(cbufferName))
case ShaderInputType.ConstantBuffer:
{
continue;
}
var cbuffer = reflection.Get()->GetConstantBufferByName(bindDesc.Name);
ShaderBufferDescription cbufferDesc;
cbuffer->GetDesc(&cbufferDesc);
var cbufferInfo = new CBufferInfo
{
Name = cbufferName,
Size = cbufferDesc.Size,
RegisterSlot = bindDesc.BindPoint
};
cbufferRegistry.Add(cbufferName, cbufferInfo);
for (uint j = 0; j < cbufferDesc.Variables; j++)
{
var variable = cbuffer->GetVariableByIndex(j);
ShaderVariableDescription varDesc;
variable->GetDesc(&varDesc);
var variableName = Marshal.PtrToStringAnsi((IntPtr)varDesc.Name);
if (variableName == null || shader.PropertyNameToIdMap.ContainsKey(variableName))
var cbufferName = Marshal.PtrToStringAnsi((IntPtr)bindDesc.Name);
if (cbufferName == null || cbufferRegistry.ContainsKey(cbufferName))
{
continue;
}
var propInfo = new PropertyInfo
var cbuffer = reflection.Get()->GetConstantBufferByName(bindDesc.Name);
ShaderBufferDescription cbufferDesc;
cbuffer->GetDesc(&cbufferDesc);
var cbufferInfo = new CBufferInfo
{
Name = variableName,
CBufferIndex = cbufferInfo.RegisterSlot,
ByteOffset = varDesc.StartOffset,
Size = varDesc.Size
Size = cbufferDesc.Size,
RegisterSlot = bindDesc.BindPoint
};
cbufferRegistry.Add(cbufferName, cbufferInfo);
for (uint j = 0; j < cbufferDesc.Variables; j++)
{
var variable = cbuffer->GetVariableByIndex(j);
ShaderVariableDescription varDesc;
variable->GetDesc(&varDesc);
var variableName = Marshal.PtrToStringAnsi((IntPtr)varDesc.Name);
if (variableName == null || shader.PropertyNameToIdMap.ContainsKey(variableName))
{
continue;
}
var propInfo = new PropertyInfo
{
CBufferIndex = cbufferInfo.RegisterSlot,
ByteOffset = varDesc.StartOffset,
Size = varDesc.Size
};
shader.AddProperty(variableName, propInfo);
}
break;
}
case ShaderInputType.Texture:
{
var textureName = Marshal.PtrToStringAnsi((IntPtr)bindDesc.Name);
if (textureName == null || textureRegistry.ContainsKey(textureName))
{
continue;
}
// ALL texture input slots are regular textures!
// Bindless textures don't use explicit texture inputs - they use ResourceDescriptorHeap[index]
var textureInfo = new TextureInfo
{
RegisterSlot = bindDesc.BindPoint,
RootParameterIndex = (uint)shader.ConstantBuffers.Count // Descriptor table comes after CBVs
};
// Add to the list and create the name-to-ID mapping
var newId = shader.Properties.Count;
shader.Properties.Add(propInfo);
shader.PropertyNameToIdMap.Add(variableName, newId);
textureRegistry.Add(textureName, textureInfo);
break;
}
}
else if (bindDesc.Type == ShaderInputType.Texture)
{
var textureName = Marshal.PtrToStringAnsi((IntPtr)bindDesc.Name);
if (textureName == null || textureRegistry.ContainsKey(textureName))
{
continue;
}
// ALL texture input slots are regular textures!
// Bindless textures don't use explicit texture inputs - they use ResourceDescriptorHeap[index]
var textureInfo = new TextureInfo
{
Name = textureName,
RegisterSlot = bindDesc.BindPoint,
RootParameterIndex = (uint)shader.ConstantBuffers.Count // Descriptor table comes after CBVs
};
textureRegistry.Add(textureName, textureInfo);
}
}
shader.ConstantBuffers.Clear();
shader.ConstantBuffers.AddRange(cbufferRegistry.Values);
foreach (var cbuf in cbufferRegistry.Values)
{
shader.ConstantBuffers.Add(cbuf);
}
shader.RegularTextures.Clear();
shader.RegularTextures.AddRange(textureRegistry.Values);
foreach (var tex in textureRegistry.Values)
{
shader.RegularTextures.Add(tex);
}
}
}

View File

@@ -10,11 +10,11 @@ internal unsafe static class D3D12PipelineResource
public const int BACK_BUFFER_COUNT = 2;
private readonly static InputElementDescription[] s_inputElementDescs = [
new InputElementDescription{ SemanticName = Vertex.Semantic.pPositionName, SemanticIndex = 0u, Format = Format.R32G32B32A32Float, InputSlot = 0u, AlignedByteOffset = 0u, InputSlotClass = InputClassification.PerVertexData, InstanceDataStepRate = 0 },
new InputElementDescription{ SemanticName = Vertex.Semantic.pNormalName, SemanticIndex = 0u, Format = Format.R32G32B32A32Float, InputSlot = 0u, AlignedByteOffset = 16u, InputSlotClass = InputClassification.PerVertexData, InstanceDataStepRate = 0 },
new InputElementDescription{ SemanticName = Vertex.Semantic.pTangentName, SemanticIndex = 0u, Format = Format.R32G32B32A32Float, InputSlot = 0u, AlignedByteOffset = 32u, InputSlotClass = InputClassification.PerVertexData, InstanceDataStepRate = 0 },
new InputElementDescription{ SemanticName = Vertex.Semantic.pColorName, SemanticIndex = 0u, Format = Format.R32G32B32A32Float, InputSlot = 0u, AlignedByteOffset = 48u, InputSlotClass = InputClassification.PerVertexData, InstanceDataStepRate = 0 },
new InputElementDescription{ SemanticName = Vertex.Semantic.pUVName, SemanticIndex = 0u, Format = Format.R32G32B32A32Float, InputSlot = 0u, AlignedByteOffset = 64u, InputSlotClass = InputClassification.PerVertexData, InstanceDataStepRate = 0 }
new InputElementDescription{ SemanticName = Vertex.Semantic.position.GetUnsafePointer(), SemanticIndex = 0u, Format = Format.R32G32B32A32Float, InputSlot = 0u, AlignedByteOffset = 0u, InputSlotClass = InputClassification.PerVertexData, InstanceDataStepRate = 0 },
new InputElementDescription{ SemanticName = Vertex.Semantic.normal.GetUnsafePointer(), SemanticIndex = 0u, Format = Format.R32G32B32A32Float, InputSlot = 0u, AlignedByteOffset = 16u, InputSlotClass = InputClassification.PerVertexData, InstanceDataStepRate = 0 },
new InputElementDescription{ SemanticName = Vertex.Semantic.tangent.GetUnsafePointer(), SemanticIndex = 0u, Format = Format.R32G32B32A32Float, InputSlot = 0u, AlignedByteOffset = 32u, InputSlotClass = InputClassification.PerVertexData, InstanceDataStepRate = 0 },
new InputElementDescription{ SemanticName = Vertex.Semantic.uv.GetUnsafePointer(), SemanticIndex = 0u, Format = Format.R32G32B32A32Float, InputSlot = 0u, AlignedByteOffset = 48u, InputSlotClass = InputClassification.PerVertexData, InstanceDataStepRate = 0 },
new InputElementDescription{ SemanticName = Vertex.Semantic.color.GetUnsafePointer(), SemanticIndex = 0u, Format = Format.R32G32B32A32Float, InputSlot = 0u, AlignedByteOffset = 64u, InputSlotClass = InputClassification.PerVertexData, InstanceDataStepRate = 0 },
];
public const Format SWAP_CHAIN_BACK_BUFFER_FORMAT = Format.B8G8R8A8Unorm;

View File

@@ -1,12 +1,9 @@
using Ghost.Graphics.D3D12;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Helpers;
using System.Runtime.CompilerServices;
namespace Ghost.Graphics.Data;
internal struct CBufferCache : IDisposable
internal struct CBufferCache
{
public UnsafeArray<byte> CpuData
{
@@ -14,33 +11,18 @@ internal struct CBufferCache : IDisposable
set;
}
public GraphicsBuffer GpuResource
public BufferHandle GpuResource
{
get;
}
private readonly uint _alignedSize;
internal unsafe CBufferCache(uint bufferSize)
internal unsafe CBufferCache(BufferHandle buffer, uint bufferSize)
{
_alignedSize = (bufferSize + 255u) & ~255u;
CpuData = new((int)_alignedSize, Allocator.Persistent);
GpuResource = GraphicsBuffer.Create(_alignedSize, GraphicsBuffer.Usage.Constant);
GpuResource.Name = "Material_CBufferCache";
UploadToGpu();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void UploadToGpu()
{
GpuResource.SetData(CpuData.AsSpan(), 0);
}
public readonly void Dispose()
{
CpuData.Dispose();
GpuResource.Dispose();
GpuResource = buffer;
}
}

View File

@@ -7,14 +7,14 @@ namespace Ghost.Graphics.Data;
/// Represents a color with 4 bytes components.
/// </summary>
[StructLayout(LayoutKind.Sequential, Size = 4)]
public struct Color4 : IEquatable<Color4>
public struct Color32 : IEquatable<Color32>
{
public byte r;
public byte g;
public byte b;
public byte a;
public Color4(byte r, byte g, byte b, byte a)
public Color32(byte r, byte g, byte b, byte a)
{
this.r = r;
this.g = g;
@@ -22,24 +22,24 @@ public struct Color4 : IEquatable<Color4>
this.a = a;
}
public Color4(Color color)
public Color32(Color color)
: this(color.R, color.G, color.B, color.A)
{
}
public Color4(Color16 color128)
public Color32(Color128 color128)
: this((byte)(color128.r * 255.0f), (byte)(color128.g * 255.0f), (byte)(color128.b * 255.0f), (byte)(color128.a * 255.0f))
{
}
public readonly bool Equals(Color4 other)
public readonly bool Equals(Color32 other)
{
return r == other.r && g == other.g && b == other.b && a == other.a;
}
public override readonly bool Equals(object? obj)
{
return obj is Color4 color && Equals(color);
return obj is Color32 color && Equals(color);
}
public override readonly int GetHashCode()
@@ -47,12 +47,12 @@ public struct Color4 : IEquatable<Color4>
return HashCode.Combine(r, g, b, a);
}
public static bool operator ==(Color4 left, Color4 right)
public static bool operator ==(Color32 left, Color32 right)
{
return left.Equals(right);
}
public static bool operator !=(Color4 left, Color4 right)
public static bool operator !=(Color32 left, Color32 right)
{
return !(left == right);
}
@@ -62,14 +62,14 @@ public struct Color4 : IEquatable<Color4>
/// Represents a color with 16 bytes components.
/// </summary>
[StructLayout(LayoutKind.Sequential, Size = 16)]
public struct Color16 : IEquatable<Color16>
public struct Color128 : IEquatable<Color128>
{
public float r;
public float g;
public float b;
public float a;
public Color16(float r, float g, float b, float a)
public Color128(float r, float g, float b, float a)
{
this.r = r;
this.g = g;
@@ -77,24 +77,24 @@ public struct Color16 : IEquatable<Color16>
this.a = a;
}
public Color16(Color color)
public Color128(Color color)
: this(color.R / 255.0f, color.G / 255.0f, color.B / 255.0f, color.A / 255.0f)
{
}
public Color16(Color4 color32)
public Color128(Color32 color32)
: this(color32.r / 255.0f, color32.g / 255.0f, color32.b / 255.0f, color32.a / 255.0f)
{
}
public readonly bool Equals(Color16 other)
public readonly bool Equals(Color128 other)
{
return r.Equals(other.r) && g.Equals(other.g) && b.Equals(other.b) && a.Equals(other.a);
}
public override readonly bool Equals(object? obj)
{
return obj is Color16 color && Equals(color);
return obj is Color128 color && Equals(color);
}
public readonly override int GetHashCode()
@@ -102,12 +102,12 @@ public struct Color16 : IEquatable<Color16>
return HashCode.Combine(r, g, b, a);
}
public static bool operator ==(Color16 left, Color16 right)
public static bool operator ==(Color128 left, Color128 right)
{
return left.Equals(right);
}
public static bool operator !=(Color16 left, Color16 right)
public static bool operator !=(Color128 left, Color128 right)
{
return !(left == right);
}

View File

@@ -1,54 +0,0 @@
namespace Ghost.Graphics.Data;
public struct BatchMaterialID : IEquatable<BatchMaterialID>
{
public uint value;
public static BatchMaterialID Null => new() { value = 0 };
public override int GetHashCode()
{
return value.GetHashCode();
}
public readonly override bool Equals(object? obj)
{
return obj is BatchMaterialID id && Equals(id);
}
public readonly bool Equals(BatchMaterialID other)
{
return value == other.value;
}
public readonly int CompareTo(BatchMaterialID other)
{
return value.CompareTo(other.value);
}
public static bool operator ==(BatchMaterialID a, BatchMaterialID b)
{
return a.Equals(b);
}
public static bool operator !=(BatchMaterialID a, BatchMaterialID b)
{
return !a.Equals(b);
}
}
internal class GraphicsResourceManager
{
private readonly Dictionary<BatchMaterialID, Material> _materials = new();
private readonly Dictionary<BatchMeshID, MeshHandle> _meshes = new();
public Material GetMaterial(BatchMaterialID id)
{
return _materials.TryGetValue(id, out var material) ? material : Material.Null;
}
public MeshHandle GetMesh(BatchMeshID id)
{
return _meshes.TryGetValue(id, out var mesh) ? mesh : MeshHandle.Invalid;
}
}

View File

@@ -1,204 +0,0 @@
using Ghost.Graphics.D3D12;
using Ghost.Graphics.RHI;
using System.Numerics;
using System.Runtime.CompilerServices;
using Win32.Graphics.Direct3D12;
namespace Ghost.Graphics.Data;
/// <summary>
/// Material implementation for bindless rendering with SM 6.6 support
/// </summary>
public unsafe class Material : IDisposable
{
private readonly CBufferCache[] _cbufferCaches;
private bool _disposed;
internal ReadOnlySpan<CBufferCache> CBufferCaches => _cbufferCaches;
public Shader Shader
{
get; set;
}
public Material(Shader shader)
{
Shader = shader;
if (shader.ConstantBuffers.Count > 0)
{
var maxSlot = shader.ConstantBuffers.Max(cb => cb.RegisterSlot);
_cbufferCaches = new CBufferCache[maxSlot + 1];
foreach (var cbufferInfo in shader.ConstantBuffers)
{
_cbufferCaches[cbufferInfo.RegisterSlot] = new CBufferCache(cbufferInfo.Size);
}
}
else
{
_cbufferCaches = Array.Empty<CBufferCache>();
}
}
/// <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(Mesh 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;
}
}

View File

@@ -0,0 +1,376 @@
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;
}
}

View File

@@ -2,82 +2,68 @@
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Helpers;
using Misaki.HighPerformance.LowLevel.Utilities;
using Misaki.HighPerformance.Mathematics;
using Misaki.HighPerformance.Mathematics.Geometry;
using System.Numerics;
using System.Runtime.CompilerServices;
using Win32.Graphics.Direct3D12;
using Win32.Graphics.Dxgi.Common;
namespace Ghost.Graphics.Data;
/// <summary>
/// The CPU-side mesh data structure.
/// </summary>
public struct MeshData
public struct Mesh
{
public UnsafeList<Vertex> vertices;
public UnsafeList<int> indices;
public UnsafeList<uint> indices;
public AABB boundingBox;
public MeshHandle handle;
public MeshData()
{
handle = MeshHandle.Invalid;
}
}
/// <summary>
/// The GPU-side mesh handle containing buffer references.
/// </summary>
public struct MeshHandle
{
public BufferHandle vertexBuffer;
public BufferHandle indexBuffer;
public static MeshHandle Invalid => new() { vertexBuffer = BufferHandle.Invalid, indexBuffer = BufferHandle.Invalid };
public readonly bool IsValid => vertexBuffer.IsValid && indexBuffer.IsValid;
}
public struct BatchMeshID : IEquatable<BatchMeshID>
{
public int value;
public static BatchMeshID Null => new() { value = -1 };
public readonly override int GetHashCode()
public Mesh()
{
return value.GetHashCode();
vertexBuffer = BufferHandle.Invalid;
indexBuffer = BufferHandle.Invalid;
}
public readonly override bool Equals(object? obj)
internal Mesh(ReadOnlySpan<Vertex> vertices, ReadOnlySpan<uint> indices, BufferHandle vertexBuffer, BufferHandle indexBuffer)
{
return obj is BatchMeshID id && Equals(id);
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 readonly bool Equals(BatchMeshID other)
public void ComputeBounds()
{
return value == other.value;
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 readonly int CompareTo(BatchMeshID other)
public void ReleaseCpuResources()
{
return value.CompareTo(other.value);
}
public static bool operator ==(BatchMeshID a, BatchMeshID b)
{
return a.Equals(b);
}
public static bool operator !=(BatchMeshID a, BatchMeshID b)
{
return !a.Equals(b);
vertices.Dispose();
indices.Dispose();
}
}
public unsafe sealed class Mesh : IDisposable
public unsafe sealed class MeshClass : IDisposable
{
private UnsafeList<Vertex> _vertices;
private UnsafeList<int> _indices;
@@ -132,13 +118,13 @@ public unsafe sealed class Mesh : IDisposable
}
}
public Mesh(int initialVertexCapacity = 256, int initialIndexCapacity = 512)
public MeshClass(int initialVertexCapacity = 256, int initialIndexCapacity = 512)
{
_vertices = new(initialVertexCapacity, Allocator.Persistent);
_indices = new(initialIndexCapacity, Allocator.Persistent);
}
public Mesh(ReadOnlySpan<Vertex> vertices, ReadOnlySpan<int> indices)
public MeshClass(ReadOnlySpan<Vertex> vertices, ReadOnlySpan<int> indices)
: this(vertices.Length, indices.Length)
{
_vertices = new(vertices.Length, Allocator.Persistent);
@@ -148,7 +134,7 @@ public unsafe sealed class Mesh : IDisposable
_indices.CopyFrom(indices);
}
~Mesh()
~MeshClass()
{
Dispose();
}
@@ -233,18 +219,18 @@ public unsafe sealed class Mesh : IDisposable
var v1 = _vertices[i1];
var v2 = _vertices[i2];
var edge1 = v1.Position - v0.Position;
var edge2 = v2.Position - v0.Position;
var faceNormal = Vector3.Cross(edge1.AsVector3(), edge2.AsVector3());
var edge1 = v1.position - v0.position;
var edge2 = v2.position - v0.position;
var faceNormal = math.cross(edge1.xyz, edge2.xyz);
_vertices[i0].Normal += faceNormal.AsVector4();
_vertices[i1].Normal += faceNormal.AsVector4();
_vertices[i2].Normal += faceNormal.AsVector4();
_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 = Vector4.Normalize(_vertices[i].Normal);
_vertices[i].normal = math.normalize(_vertices[i].normal);
}
}
@@ -256,7 +242,7 @@ public unsafe sealed class Mesh : IDisposable
/// </remarks>
public void ComputeTangents()
{
var bitangents = new Vector4[_vertices.Count];
var bitangents = new float4[_vertices.Count];
for (var i = 0; i < _indices.Count; i += 3)
{
@@ -268,29 +254,24 @@ public unsafe sealed class Mesh : IDisposable
var v1 = _vertices[i1];
var v2 = _vertices[i2];
var uv0 = _vertices[i0].UV;
var uv1 = _vertices[i1].UV;
var uv2 = _vertices[i2].UV;
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 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;
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 = new Vector4(
t.X + tangent.X,
t.Y + tangent.Y,
t.Z + tangent.Z,
0.0f // well fill w later
);
var t = _vertices[idx].tangent;
_vertices[idx].tangent.xyz = t.xyz + tangent.xyz;
bitangents[idx] += bitangent;
}
@@ -298,18 +279,16 @@ public unsafe sealed class Mesh : IDisposable
for (var i = 0; i < _vertices.Count; i++)
{
var n = _vertices[i].Normal;
var t = _vertices[i].Tangent;
var n3 = n.AsVector3();
var t3 = t.AsVector3();
var n = _vertices[i].normal;
var t = _vertices[i].tangent;
var proj = n3 * Vector3.Dot(n3, t3);
t3 = Vector3.Normalize(t3 - proj);
var proj = n * math.dot(n, t);
t = math.normalize(t - proj);
var b = bitangents[i];
var w = Vector3.Dot(Vector3.Cross(n3, t3), b.AsVector3()) < 0.0f ? -1.0f : 1.0f;
var w = math.dot(math.cross(n.xyz, t.xyz), b.xyz) < 0.0f ? -1.0f : 1.0f;
_vertices[i].Tangent = new Vector4(t3.X, t3.Y, t3.Z, w);
_vertices[i].tangent = new float4(t.x, t.y, t.z, w);
}
}
@@ -320,20 +299,20 @@ public unsafe sealed class Mesh : IDisposable
{
if (_vertices.Count == 0)
{
_boundingBox = Bounds.Zero;
_boundingBox = AABB.Zero;
return;
}
var min = new Vector3(float.MaxValue);
var max = new Vector3(float.MinValue);
var min = new float3(float.MaxValue);
var max = new float3(float.MinValue);
foreach (var vertex in _vertices)
{
var pos = vertex.Position.AsVector3();
min = Vector3.Min(min, pos);
max = Vector3.Max(max, pos);
var pos = vertex.position.xyz;
min = math.min(min, pos);
max = math.max(max, pos);
}
_boundingBox = new Bounds(min, max);
_boundingBox = new AABB(min, max);
}
/// <summary>

View File

@@ -206,7 +206,7 @@ public unsafe class RenderTexture : Texture
/// </summary>
/// <param name="commandList">Command list to record clear commands</param>
/// <param name="clearColor">Color to clear to</param>
public void ClearColor(CommandList commandList, Color16 clearColor)
public void ClearColor(CommandList commandList, Color128 clearColor)
{
ThrowIfDisposed();

View File

@@ -29,7 +29,7 @@ public readonly struct ResourceHandle : IEquatable<ResourceHandle>
{
unchecked
{
return (id * 397) ^ (int)generation;
return (id * 397) ^ generation;
}
}
@@ -52,6 +52,7 @@ public readonly struct ResourceHandle : IEquatable<ResourceHandle>
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);
@@ -59,13 +60,20 @@ public readonly struct TextureHandle : IEquatable<TextureHandle>
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);
return _resourceHandle.Equals(other._resourceHandle) && _bindlessDescriptor.Equals(other._bindlessDescriptor);
}
public override bool Equals(object? obj)
@@ -75,7 +83,7 @@ public readonly struct TextureHandle : IEquatable<TextureHandle>
public override int GetHashCode()
{
return _resourceHandle.GetHashCode();
return HashCode.Combine(_resourceHandle, _bindlessDescriptor);
}
public static bool operator ==(TextureHandle left, TextureHandle right)
@@ -115,7 +123,7 @@ public readonly struct BufferHandle : IEquatable<BufferHandle>
public bool Equals(BufferHandle other)
{
return _resourceHandle.Equals(other._resourceHandle);
return _resourceHandle.Equals(other._resourceHandle) && _bindlessDescriptor.Equals(other._bindlessDescriptor);
}
public override bool Equals(object? obj)
@@ -125,7 +133,7 @@ public readonly struct BufferHandle : IEquatable<BufferHandle>
public override int GetHashCode()
{
return _resourceHandle.GetHashCode();
return HashCode.Combine(_resourceHandle, _bindlessDescriptor);
}
public static bool operator ==(BufferHandle left, BufferHandle right)

View File

@@ -1,12 +1,10 @@
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
namespace Ghost.Graphics.Data;
internal readonly struct TextureInfo
{
public required string Name
{
get; init;
}
public uint RegisterSlot
{
get; init;
@@ -20,11 +18,6 @@ internal readonly struct TextureInfo
internal readonly struct PropertyInfo
{
public required string Name
{
get; init;
}
public uint CBufferIndex
{
get; init;
@@ -43,11 +36,6 @@ internal readonly struct PropertyInfo
internal readonly struct CBufferInfo
{
public required string Name
{
get; init;
}
public uint Size
{
get; init;
@@ -64,36 +52,44 @@ internal readonly struct CBufferInfo
/// </summary>
// TODO: Multi pass and keyword support
public unsafe class Shader : IDisposable
public struct Shader : IDisposable
{
private readonly string _source;
private readonly List<CBufferInfo> _constantBuffers = new();
private readonly List<PropertyInfo> _properties = new();
private readonly List<TextureInfo> _regularTextures = new(); // Add regular texture support
private readonly Dictionary<string, int> _propertyNameToIdMap = new();
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;
private bool _disposed;
internal string Source => _source;
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;
internal List<CBufferInfo> ConstantBuffers => _constantBuffers;
internal List<PropertyInfo> Properties => _properties;
internal List<TextureInfo> RegularTextures => _regularTextures;
internal Dictionary<string, int> PropertyNameToIdMap => _propertyNameToIdMap;
// TODO: In real production, we should not load the shader source code directly.
internal Shader(string shaderCode)
public Shader()
{
_source = shaderCode;
_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>
/// <param name="propertyName">The name of the property (e.g., "_Color").</param>
/// <param name="propertyName">The name of the shader property.</param>
/// <returns>The integer ID of the property, or -1 if not found.</returns>
public int GetPropertyId(string propertyName)
public readonly int GetPropertyId(string propertyName)
{
return _propertyNameToIdMap.TryGetValue(propertyName, out var id) ? id : -1;
}
@@ -105,12 +101,12 @@ public unsafe class Shader : IDisposable
return;
}
_constantBuffers.Clear();
_properties.Clear();
_propertyNameToIdMap.Clear();
_regularTextures.Clear();
_constantBuffers.Dispose();
_properties.Dispose();
_regularTextures.Dispose();
GC.SuppressFinalize(this);
_propertyNameToIdMap.Clear();
_propertyNameToIdMap = null!;
_disposed = true;
}

View File

@@ -1,7 +1,6 @@
using Misaki.HighPerformance.Mathematics;
using System.Runtime.CompilerServices;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.Mathematics;
using System.Runtime.InteropServices;
using System.Text;
using Win32.Graphics.Dxgi.Common;
namespace Ghost.Graphics.Data;
@@ -9,59 +8,21 @@ namespace Ghost.Graphics.Data;
[StructLayout(LayoutKind.Sequential)]
public struct Vertex
{
public unsafe struct Semantic
public unsafe static class Semantic
{
public const Format ALIGNED_FORMAT = Format.R32G32B32A32Float;
public const int COUNT = 5;
private static readonly byte[] s_positionBytes = Encoding.UTF8.GetBytes("POSITION");
private static readonly byte[] s_normalBytes = Encoding.UTF8.GetBytes("NORMAL");
private static readonly byte[] s_tangentBytes = Encoding.UTF8.GetBytes("TANGENT");
private static readonly byte[] s_colorBytes = Encoding.UTF8.GetBytes("COLOR");
private static readonly byte[] s_uvBytes = Encoding.UTF8.GetBytes("TEXCOORD");
public static byte* pPositionName => (byte*)Unsafe.AsPointer(ref s_positionBytes[0]);
public static byte* pNormalName => (byte*)Unsafe.AsPointer(ref s_normalBytes[0]);
public static byte* pTangentName => (byte*)Unsafe.AsPointer(ref s_tangentBytes[0]);
public static byte* pColorName => (byte*)Unsafe.AsPointer(ref s_colorBytes[0]);
public static byte* pUVName => (byte*)Unsafe.AsPointer(ref s_uvBytes[0]);
public static readonly FixedString32 position = new("POSITION");
public static readonly FixedString32 normal = new("NORMAL");
public static readonly FixedString32 tangent = new("TANGENT");
public static readonly FixedString32 uv = new("TEXCOORD");
public static readonly FixedString32 color = new("COLOR");
}
public float4 Position
{
get;
set;
}
public float4 Normal
{
get;
set;
}
public float4 Tangent
{
get;
set;
}
public Color16 Color
{
get;
set;
}
public float4 UV
{
get;
set;
}
public Vertex(float4 position, float4 normal, float4 tangent, Color16 color, float4 uv)
{
Position = position;
Normal = normal;
Tangent = tangent;
Color = color;
UV = uv;
}
public float4 position;
public float4 normal;
public float4 tangent;
public float4 uv;
public Color128 color;
}

View File

@@ -30,6 +30,9 @@
</ItemGroup>
<ItemGroup>
<Reference Include="Misaki.HighPerformance">
<HintPath>..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance\bin\Release\net9.0\Misaki.HighPerformance.dll</HintPath>
</Reference>
<Reference Include="Misaki.HighPerformance.Image">
<HintPath>..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance.Image\bin\Release\net9.0\Misaki.HighPerformance.Image.dll</HintPath>
</Reference>

View File

@@ -1,4 +1,7 @@
using Ghost.Graphics.D3D12;
using Ghost.Graphics.Data;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Drawing;
namespace Ghost.Graphics.RHI;
@@ -7,6 +10,11 @@ namespace Ghost.Graphics.RHI;
/// </summary>
public interface ICommandBuffer : IDisposable
{
public CommandBufferType Type
{
get;
}
/// <summary>
/// Begins recording commands into this command buffer
/// </summary>
@@ -22,7 +30,7 @@ public interface ICommandBuffer : IDisposable
/// </summary>
/// <param name="renderTarget">Render target to render into</param>
/// <param name="clearColor">Color to clear the render target with</param>
public void BeginRenderPass(IRenderTarget renderTarget, Color16 clearColor);
public void BeginRenderPass(IRenderTarget renderTarget, Color128 clearColor);
/// <summary>
/// Ends the current render pass
@@ -66,7 +74,7 @@ public interface ICommandBuffer : IDisposable
/// </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(Mesh mesh, Material material);
public void DrawMesh(MeshClass mesh, MaterialClass material);
/// <summary>
/// Dispatches compute threads
@@ -75,6 +83,26 @@ public interface ICommandBuffer : IDisposable
/// <param name="threadGroupCountY">Thread groups in Y dimension</param>
/// <param name="threadGroupCountZ">Thread groups in Z dimension</param>
public void Dispatch(uint threadGroupCountX, uint threadGroupCountY = 1, uint threadGroupCountZ = 1);
/// <summary>
/// Uploads the specified data to the buffer represented by the given handle.
/// </summary>
/// <typeparam name="T">The unmanaged value type of the elements to upload to the buffer.</typeparam>
/// <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)
where T : unmanaged;
/// <summary>
/// Uploads texture data to the specified texture resource starting at the given subresource index.
/// </summary>
/// <param name="texture">The texture resource to which the subresource data will be uploaded. Must be a valid, initialized texture handle.</param>
/// <param name="firstSubresource">The index of the first subresource in the texture to receive data. Must be less than the total number of subresources in the texture.</param>
/// <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);
}
/// <summary>
@@ -82,21 +110,21 @@ public interface ICommandBuffer : IDisposable
/// </summary>
public struct ViewportDesc
{
public float X;
public float Y;
public float Width;
public float Height;
public float MinDepth;
public float MaxDepth;
public float x;
public float y;
public float width;
public float height;
public float minDepth;
public float maxDepth;
public ViewportDesc(float width, float height)
{
X = 0;
Y = 0;
Width = width;
Height = height;
MinDepth = 0.0f;
MaxDepth = 1.0f;
x = 0;
y = 0;
this.width = width;
this.height = height;
minDepth = 0.0f;
maxDepth = 1.0f;
}
}
@@ -122,17 +150,28 @@ public struct RectDesc
/// <summary>
/// Resource states
/// </summary>
[Flags]
public enum ResourceState
{
Common = 0,
VertexAndConstantBuffer = 0x1,
IndexBuffer = 0x2,
RenderTarget = 0x4,
UnorderedAccess = 0x8,
DepthWrite = 0x10,
DepthRead = 0x20,
PixelShaderResource = 0x80,
CopyDest = 0x400,
CopySource = 0x800,
VertexAndConstantBuffer = 1 << 0,
IndexBuffer = 1 << 1,
RenderTarget = 1 << 2,
UnorderedAccess = 1 << 3,
DepthWrite = 1 << 4,
DepthRead = 1 << 5,
PixelShaderResource = 1 << 6,
CopyDest = 1 << 7,
CopySource = 1 << 8,
GenericRead = 1 << 9,
IndirectArgument = 1 << 10,
Present = 0,
}
public struct SubResourceData
{
public unsafe void* pData;
public nint rowPitch;
public nint slicePitch;
}

View File

@@ -7,6 +7,11 @@ public interface IGraphicsEngine : IDisposable
get;
}
public IResourceDatabase ResourceDatabase
{
get;
}
public IResourceAllocator ResourceAllocator
{
get;
@@ -27,4 +32,14 @@ public interface IGraphicsEngine : IDisposable
/// <param name="desc">Swap chain description</param>
/// <returns>A new swap chain instance</returns>
public ISwapChain CreateSwapChain(SwapChainDesc desc);
/// <summary>
/// Begins a new rendering frame, preparing the graphics context for drawing operations.
/// </summary>
public void BeginFrame();
/// <summary>
/// Completes the current rendering frame and performs any necessary finalization steps.
/// </summary>
public void EndFrame();
}

View File

@@ -1,3 +1,4 @@
using Ghost.Core;
using Ghost.Graphics.Data;
namespace Ghost.Graphics.RHI;
@@ -15,11 +16,9 @@ public interface IShaderPipeline
public interface IPipelineStateController
{
public void ColectionShader(ReadOnlySpan<Shader> shaders);
public void CompileCollected();
public void CompileShader(Identifier<Shader> id, string shaderPath);
public void PreCookPipelineState();
public IShaderPipeline GetShaderPipeline(Shader shader);
public IShaderPipeline GetShaderPipeline(Identifier<Shader> id);
}

View File

@@ -31,7 +31,7 @@ public interface IRenderDevice : IDisposable
}
/// <summary>
/// Command buffer types matching D3D12 command list types
/// Command buffer types
/// </summary>
public enum CommandBufferType
{

View File

@@ -173,7 +173,8 @@ public struct RenderTargetDesc
Format = desc.Format,
Dimension = desc.Dimension,
MipLevels = desc.MipLevels,
Usage = usage
Usage = usage,
CreationFlags = TextureCreationFlags.Bindless
};
}
}
@@ -245,6 +246,15 @@ public struct TextureDesc
get;
set;
}
/// <summary>
/// Texture creation flags
/// </summary>
public TextureCreationFlags CreationFlags
{
get;
set;
}
}
/// <summary>
@@ -341,3 +351,10 @@ public enum MemoryType
Upload, // CPU-to-GPU memory
Readback // GPU-to-CPU memory
}
[Flags]
public enum TextureCreationFlags
{
None = 0,
Bindless = 1 << 0
}

View File

@@ -4,40 +4,26 @@ namespace Ghost.Graphics.RHI;
public interface IResourceAllocator
{
/// <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 IRenderTarget CreateRenderTarget(ref readonly RenderTargetDesc desc, bool tempResource = false);
/// <summary>
/// Creates a texture resource
/// </summary>
/// <param name="desc">Texture description</param>
/// <returns>A new texture handle point to the resource</returns>
public TextureHandle CreateTextureHandle(ref readonly TextureDesc desc, bool tempResource = false);
public TextureHandle CreateTexture(ref readonly TextureDesc desc, bool tempResource = false);
/// <summary>
/// Creates a texture resource
/// Creates a render target for off-screen rendering
/// </summary>
/// <param name="desc">Texture description</param>
/// <returns>A new texture instance</returns>
public ITexture CreateTexture(ref readonly TextureDesc desc, bool tempResource = false);
/// <param name="desc">Render target description</param>
/// <returns>A new render target instance</returns>
public TextureHandle 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 CreateBufferHandle(ref readonly BufferDesc desc, bool tempResource = false);
/// <summary>
/// Creates a buffer resource
/// </summary>
/// <param name="desc">Buffer description</param>
/// <returns>A new buffer instance</returns>
public IBuffer CreateBuffer(ref readonly BufferDesc desc, bool tempResource = false);
public BufferHandle CreateBuffer(ref readonly BufferDesc desc, bool tempResource = false);
/// <summary>
/// Release a resource given its handle

View File

@@ -1,4 +1,5 @@
using Ghost.Graphics.Data;
using Ghost.Core;
using Ghost.Graphics.Data;
namespace Ghost.Graphics.RHI;
@@ -32,4 +33,24 @@ public interface IResourceDatabase
/// </summary>
/// <param name="handle">The handle of the resource to be removed.</param>
public void RemoveResource(ResourceHandle handle);
public Identifier<Mesh> AddMesh(ref readonly Mesh mesh);
public bool HasMesh(Identifier<Mesh> id);
public Mesh GetMesh(Identifier<Mesh> id);
public ref Mesh GetMeshReference(Identifier<Mesh> id);
public void RemoveMesh(Identifier<Mesh> id);
public Identifier<Shader> AddShader(ref readonly Shader shader);
public bool HasShader(Identifier<Shader> id);
public Shader GetShader(Identifier<Shader> id);
public ref Shader GetShaderReference(Identifier<Shader> id);
public void RemoveShader(Identifier<Shader> id);
}

View File

@@ -100,9 +100,9 @@ float4 PSMain(PixelInput input) : SV_TARGET
";
// High-level bindless objects
private Mesh? _mesh;
private MeshClass? _mesh;
private Shader? _shader;
private Material? _material;
private MaterialClass? _material;
private Texture2D[]? _textures;
// Texture file paths for this demo
@@ -118,7 +118,7 @@ float4 PSMain(PixelInput input) : SV_TARGET
_mesh = MeshBuilder.CreateCube(0.75f);
_mesh.UploadMeshData();
_shader = new Shader(_HLSL_SOURCE);
_shader = new ShaderData(_HLSL_SOURCE);
_material = new Material(_shader);
_textures = new Texture2D[_textureFiles.Length];

View File

@@ -34,7 +34,7 @@ internal class RenderSystem
private const uint _FRAME_COUNT = 2;
private readonly IGraphicsEngine _engine = null!;
private readonly IGraphicsEngine _graphicsEngine = null!;
private readonly FrameResource[] _frameResources = null!;
private readonly Thread _renderThread = null!;
private readonly AutoResetEvent _shutdownEvent = null!;
@@ -47,14 +47,14 @@ internal class RenderSystem
private bool _isRunning;
private bool _disposed;
public IGraphicsEngine GraphicsEngine => _engine;
public IGraphicsEngine GraphicsEngine => _graphicsEngine;
public uint CPUFenceValue => _cpuFenceValue;
public uint GPUFenceValue => _gpuFenceValue;
public bool IsRunning => _isRunning;
public RenderSystem(GraphicsAPI api)
{
_engine = api switch
_graphicsEngine = api switch
{
GraphicsAPI.Direct3D12 => new D3D12.D3D12GraphicsEngine(this),
_ => throw new NotSupportedException($"Graphics API {api} is not supported.")
@@ -84,7 +84,7 @@ internal class RenderSystem
{
ObjectDisposedException.ThrowIf(_disposed, this);
var renderer = _engine.CreateRenderer();
var renderer = _graphicsEngine.CreateRenderer();
ImmutableInterlocked.Update(ref _renderers, renderers => renderers.Add(renderer));
return renderer;
}
@@ -166,12 +166,16 @@ internal class RenderSystem
// Only proceed if CPU ready event was signaled
if (waitResult == 0)
{
_graphicsEngine.BeginFrame();
foreach (var renderer in _renderers)
{
renderer.ExecutePendingResize();
renderer.Render();
}
_graphicsEngine.EndFrame();
_gpuFenceValue++;
frameResource.gpuReadyEvent.Set();
}

View File

@@ -8,10 +8,10 @@ public static class MeshBuilder
/// <summary>
/// Creates a unit cube centered at the origin with size 1.
/// </summary>
public static Mesh CreateCube(float size = 1.0f, Color16 color = default)
public static MeshClass CreateCube(float size = 1.0f, Color128 color = default)
{
var half = size * 0.5f;
var mesh = new Mesh(24, 36);
var mesh = new MeshClass(24, 36);
var corners = new Vector4[]
{
@@ -40,11 +40,11 @@ public static class MeshBuilder
{
var vertex = new Vertex
{
Position = corners[face[i]],
Normal = Vector4.Zero,
Tangent = Vector4.Zero,
Color = color,
UV = uvs[i].AsVector4()
position = corners[face[i]],
normal = Vector4.Zero,
tangent = Vector4.Zero,
color = color,
uv = uvs[i].AsVector4()
};
mesh.AddVertex(vertex);
}
@@ -61,11 +61,11 @@ public static class MeshBuilder
/// <summary>
/// Creates a plane on the XZ axis centered at the origin.
/// </summary>
public static Mesh CreatePlane(float width = 1.0f, float depth = 1.0f, Color16 color = default)
public static MeshClass CreatePlane(float width = 1.0f, float depth = 1.0f, Color128 color = default)
{
var hw = width * 0.5f;
var hd = depth * 0.5f;
var mesh = new Mesh(4, 6);
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)));
@@ -83,9 +83,9 @@ public static class MeshBuilder
/// <summary>
/// Creates a UV sphere centered at the origin.
/// </summary>
public static Mesh CreateSphere(int latitudeSegments = 16, int longitudeSegments = 24, float radius = 0.5f, Color16 color = default)
public static MeshClass CreateSphere(int latitudeSegments = 16, int longitudeSegments = 24, float radius = 0.5f, Color128 color = default)
{
var mesh = new Mesh((latitudeSegments + 1) * (longitudeSegments + 1), latitudeSegments * longitudeSegments * 6);
var mesh = new MeshClass((latitudeSegments + 1) * (longitudeSegments + 1), latitudeSegments * longitudeSegments * 6);
// Vertices
for (var lat = 0; lat <= latitudeSegments; lat++)
@@ -107,11 +107,11 @@ public static class MeshBuilder
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)
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)
});
}
}

View File

@@ -8,7 +8,7 @@
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
<!-- Other merged dictionaries here -->
<ResourceDictionary Source="ms-appx:///Microsoft.UI.Xaml/DensityStyles/Compact.xaml" />
</ResourceDictionary.MergedDictionaries>
<!-- Other app resources here -->
</ResourceDictionary>

View File

@@ -34,7 +34,7 @@ public sealed partial class GraphicsTestWindow : Window
_renderSystem = new (GraphicsAPI.Direct3D12);
_renderer = _renderSystem.CreateRenderer();
_swapChain = _renderSystem.GraphicsEngine.Device.CreateSwapChain(new SwapChainDesc((uint)AppWindow.Size.Width, (uint)AppWindow.Size.Height, SwapChainTarget.FromCompositionSurface(Panel)));
_swapChain = _renderSystem.GraphicsEngine.CreateSwapChain(new SwapChainDesc((uint)AppWindow.Size.Width, (uint)AppWindow.Size.Height, SwapChainTarget.FromCompositionSurface(Panel)));
_renderer.SetSwapChain(_swapChain);
CompositionTarget.Rendering += OnRendering;