Refactor graphics engine dependencies and structure

Removed references to `Misaki.HighPerformance.Unsafe` and replaced them with `Misaki.HighPerformance.LowLevel` in multiple files.
Removed calls to `AllocationManager.Initialize()` and `AllocationManager.Dispose()` in `EngineCore.cs`.
Added new methods to the `ICommandBuffer` interface for enhanced rendering capabilities.
Updated the `D3D12CommandBuffer` class to implement new graphics command handling methods.
Added the `Material` class to manage shader properties and caching for improved performance.
Encapsulated shader compilation and reflection processes within the `Shader` class for better organization.
Added the `CBufferCache` struct to optimize GPU resource management for constant buffer data.
Updated the `MeshRenderPass` class to utilize the new `Material` class for dynamic mesh rendering.
Updated various project files to reflect the restructuring of dependencies.
Modified XAML files and code-behind for improved readability and maintainability.
This commit is contained in:
2025-07-07 22:59:47 +09:00
parent 261afa4133
commit eed1b9d3d0
24 changed files with 630 additions and 344 deletions

View File

@@ -2,7 +2,6 @@
using Ghost.Engine.Services; using Ghost.Engine.Services;
using Ghost.Graphics; using Ghost.Graphics;
using Ghost.Graphics.Data; using Ghost.Graphics.Data;
using Misaki.HighPerformance.Unsafe.Buffer;
namespace Ghost.Engine; namespace Ghost.Engine;
@@ -12,8 +11,6 @@ internal class EngineCore
{ {
ActivationHandler.Handle(args); ActivationHandler.Handle(args);
AllocationManager.Initialize();
GraphicsPipeline.Initialize(GraphicsAPI.D3D12); GraphicsPipeline.Initialize(GraphicsAPI.D3D12);
GraphicsPipeline.Start(); GraphicsPipeline.Start();
@@ -29,6 +26,5 @@ internal class EngineCore
{ {
GraphicsPipeline.SignalCPUReady(); GraphicsPipeline.SignalCPUReady();
GraphicsPipeline.Shutdown(); GraphicsPipeline.Shutdown();
AllocationManager.Dispose();
} }
} }

View File

@@ -23,7 +23,7 @@
<ItemGroup> <ItemGroup>
<Reference Include="Misaki.HighPerformance.Unsafe"> <Reference Include="Misaki.HighPerformance.Unsafe">
<HintPath>..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance.Unsafe\bin\Release\net9.0\Misaki.HighPerformance.Unsafe.dll</HintPath> <HintPath>..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance.LowLevel\bin\Release\net9.0\Misaki.HighPerformance.LowLevel.dll</HintPath>
</Reference> </Reference>
</ItemGroup> </ItemGroup>

View File

@@ -1,5 +1,5 @@
using Ghost.Core; using Ghost.Core;
using Misaki.HighPerformance.Unsafe.Collections; using Misaki.HighPerformance.LowLevel.Collections;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;

View File

@@ -27,7 +27,7 @@
<ItemGroup> <ItemGroup>
<Reference Include="Misaki.HighPerformance.Unsafe"> <Reference Include="Misaki.HighPerformance.Unsafe">
<HintPath>..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance.Unsafe\bin\Release\net9.0\Misaki.HighPerformance.Unsafe.dll</HintPath> <HintPath>..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance.LowLevel\bin\Release\net9.0\Misaki.HighPerformance.LowLevel.dll</HintPath>
</Reference> </Reference>
</ItemGroup> </ItemGroup>

View File

@@ -1,5 +1,5 @@
using Ghost.Core; using Ghost.Core;
using Misaki.HighPerformance.Unsafe.Collections; using Misaki.HighPerformance.LowLevel.Collections;
namespace Ghost.Entities.Query; namespace Ghost.Entities.Query;

View File

@@ -3,7 +3,7 @@
using Ghost.Core; using Ghost.Core;
using Ghost.Entities.Components; using Ghost.Entities.Components;
using Ghost.Entities.Query; using Ghost.Entities.Query;
using Misaki.HighPerformance.Unsafe.Collections; using Misaki.HighPerformance.LowLevel.Collections;
namespace Ghost.Entities; namespace Ghost.Entities;

View File

@@ -8,7 +8,7 @@
using Ghost.Core; using Ghost.Core;
using Ghost.Entities.Components; using Ghost.Entities.Components;
using Ghost.Entities.Query; using Ghost.Entities.Query;
using Misaki.HighPerformance.Unsafe.Collections; using Misaki.HighPerformance.LowLevel.Collections;
namespace Ghost.Entities; namespace Ghost.Entities;

View File

@@ -5,7 +5,10 @@ namespace Ghost.Graphics.Contracts;
public interface ICommandBuffer public interface ICommandBuffer
{ {
public void DrawMesh(Mesh mesh); // TODO: They should be internal, maybe an interface ICommandBufferInternal?
public void CopyResource(IResource dstResource, uint dstOffset, IResource srcResource, uint srcOffset, uint size);
public void BarrierTransition(IResource resource, ResourceStates beforeState, ResourceStates afterState); public void BarrierTransition(IResource resource, ResourceStates beforeState, ResourceStates afterState);
public void SetGraphicsRootConstantBufferView(uint slot, ulong gpuAddress);
public void DrawMesh(Mesh mesh, Material material);
public void CopyResource(IResource dstResource, uint dstOffset, IResource srcResource, uint srcOffset, uint size);
} }

View File

@@ -1,6 +1,8 @@
namespace Ghost.Graphics.Contracts; using Misaki.HighPerformance.LowLevel.Collections;
public interface IResource : IDisposable namespace Ghost.Graphics.Contracts;
public unsafe interface IResource : IDisposable
{ {
public ulong GPUAddress public ulong GPUAddress
{ {
@@ -20,4 +22,17 @@ public interface IResource : IDisposable
public void SetData<T>(Span<T> data) public void SetData<T>(Span<T> data)
where T : unmanaged; where T : unmanaged;
public void SetData<T>(T* data, uint length)
where T : unmanaged;
public void SetData(void* data, uint size);
public UnsafeArray<T> ReadData<T>(Allocator allocator)
where T : unmanaged;
public void ReadData<T>(T* ppData, uint* size)
where T : unmanaged;
public void ReadData(void* ppData, uint* size);
} }

View File

@@ -17,8 +17,24 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
_commandList = commandList; _commandList = commandList;
} }
public void DrawMesh(Mesh mesh) public void BarrierTransition(IResource resource, ResourceStates beforeState, ResourceStates afterState)
{ {
var dxResource = (D3D12Resource)resource;
_commandList.Ptr->ResourceBarrierTransition(dxResource.NativeResource.Ptr, beforeState, afterState);
}
public void SetGraphicsRootConstantBufferView(uint slot, ulong gpuAddress)
{
_commandList.Ptr->SetGraphicsRootConstantBufferView(slot, gpuAddress);
}
public void DrawMesh(Mesh mesh, Material material)
{
_commandList.Ptr->SetGraphicsRootSignature(material.Shader.RootSignature);
_commandList.Ptr->SetPipelineState(material.Shader.PipelineState);
material.UploadAndBind(this);
_commandList.Ptr->IASetPrimitiveTopology(PrimitiveTopology.TriangleList); _commandList.Ptr->IASetPrimitiveTopology(PrimitiveTopology.TriangleList);
_commandList.Ptr->IASetVertexBuffers(0, 1, mesh.VertexBufferView); _commandList.Ptr->IASetVertexBuffers(0, 1, mesh.VertexBufferView);
_commandList.Ptr->IASetIndexBuffer(mesh.IndexBufferView); _commandList.Ptr->IASetIndexBuffer(mesh.IndexBufferView);
@@ -33,10 +49,4 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
_commandList.Ptr->CopyBufferRegion(dstDXResource.NativeResource, dstOffset, srcDXResource.NativeResource, srcOffset, size); _commandList.Ptr->CopyBufferRegion(dstDXResource.NativeResource, dstOffset, srcDXResource.NativeResource, srcOffset, size);
} }
public void BarrierTransition(IResource resource, ResourceStates beforeState, ResourceStates afterState)
{
var dxResource = (D3D12Resource)resource;
_commandList.Ptr->ResourceBarrierTransition(dxResource.NativeResource.Ptr, beforeState, afterState);
}
} }

View File

@@ -224,7 +224,7 @@ internal unsafe class D3D12Renderer : IRenderer
ref var frameResource = ref _frameResources[i]; ref var frameResource = ref _frameResources[i];
if (frameResource.backBuffer.Get() is not null) if (frameResource.backBuffer.Get() is not null)
{ {
frameResource.backBuffer.Dispose(); var c = frameResource.backBuffer.Reset();
_rtvHeap.ReleaseDescriptor(frameResource.backBufferDescriptorIndexes); _rtvHeap.ReleaseDescriptor(frameResource.backBufferDescriptorIndexes);
} }

View File

@@ -1,5 +1,6 @@
using Ghost.Core; using Ghost.Core;
using Ghost.Graphics.Contracts; using Ghost.Graphics.Contracts;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Win32; using Win32;
using Win32.Graphics.Direct3D12; using Win32.Graphics.Direct3D12;
@@ -8,13 +9,11 @@ namespace Ghost.Graphics.D3D12;
public unsafe class D3D12Resource : IResource public unsafe class D3D12Resource : IResource
{ {
private ComPtr<ID3D12Resource> _nativeResource private ComPtr<ID3D12Resource> _nativeResource;
{
get;
set;
}
private string _name = string.Empty; private string _name = string.Empty;
private bool _disposed;
internal ConstPtr<ID3D12Resource> NativeResource => new(_nativeResource.Get()); internal ConstPtr<ID3D12Resource> NativeResource => new(_nativeResource.Get());
public ulong GPUAddress => _nativeResource.Get()->GetGPUVirtualAddress(); public ulong GPUAddress => _nativeResource.Get()->GetGPUVirtualAddress();
@@ -40,15 +39,34 @@ public unsafe class D3D12Resource : IResource
TempResource = temp; TempResource = temp;
} }
~D3D12Resource()
{
DisposeInternal();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetData<T>(Span<T> data) public void SetData<T>(Span<T> data)
where T : unmanaged where T : unmanaged
{ {
var size = (uint)(data.Length * sizeof(T));
var range = new Win32.Graphics.Direct3D12.Range(0, size);
fixed (T* ptr = data) fixed (T* ptr = data)
{ {
SetData(ptr, (uint)data.Length);
}
}
public unsafe void SetData<T>(T* data, uint length)
where T : unmanaged
{
var size = (uint)(length * sizeof(T));
SetData((void*)data, size);
}
public unsafe void SetData(void* data, uint size)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var range = new Win32.Graphics.Direct3D12.Range(0, size);
void* mappedPtr; void* mappedPtr;
var hr = _nativeResource.Get()->Map(0, &range, &mappedPtr); var hr = _nativeResource.Get()->Map(0, &range, &mappedPtr);
if (hr.Failure) if (hr.Failure)
@@ -56,14 +74,66 @@ public unsafe class D3D12Resource : IResource
var message = hr.ToString(); var message = hr.ToString();
throw new InvalidOperationException($"Failed to map resource: {message}"); throw new InvalidOperationException($"Failed to map resource: {message}");
} }
Unsafe.CopyBlock(mappedPtr, ptr, size);
Unsafe.CopyBlock(mappedPtr, data, size);
_nativeResource.Get()->Unmap(0, &range); _nativeResource.Get()->Unmap(0, &range);
} }
public UnsafeArray<T> ReadData<T>(Allocator allocator)
where T : unmanaged
{
var size = (uint)_nativeResource.Get()->GetDesc().Width;
var data = new UnsafeArray<T>((int)(size / (uint)sizeof(T)), allocator);
try
{
ReadData(data.GetUnsafePtr(), &size);
return data;
}
catch (Exception)
{
data.Dispose();
throw;
}
}
public void ReadData<T>(T* pData, uint* size)
where T : unmanaged
{
ReadData(pData, size);
}
public void ReadData(void* pData, uint* size)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var range = new Win32.Graphics.Direct3D12.Range(0, (uint)_nativeResource.Get()->GetDesc().Width);
void* mappedPtr;
var hr = _nativeResource.Get()->Map(0, &range, &mappedPtr);
if (hr.Failure)
{
var message = hr.ToString();
throw new InvalidOperationException($"Failed to map resource: {message}");
}
Unsafe.CopyBlock(pData, mappedPtr, (uint)(range.End - range.Begin));
_nativeResource.Get()->Unmap(0, &range);
if (size != null)
{
*size = (uint)(range.End - range.Begin);
}
} }
internal void DisposeInternal() internal void DisposeInternal()
{ {
var c = _nativeResource.Reset(); if (_disposed)
{
return;
}
_nativeResource.Dispose();
_disposed = true;
} }
public void Dispose() public void Dispose()
@@ -71,6 +141,7 @@ public unsafe class D3D12Resource : IResource
if (!TempResource) if (!TempResource)
{ {
DisposeInternal(); DisposeInternal();
GC.SuppressFinalize(this);
} }
} }
} }

View File

@@ -1,21 +1,103 @@
using Win32; using Ghost.Graphics.Contracts;
using Win32.Graphics.Direct3D12; using Ghost.Graphics.Shading;
using System.Numerics;
using System.Runtime.CompilerServices;
namespace Ghost.Graphics.Data; namespace Ghost.Graphics.Data;
public class Material : IDisposable public class Material : IDisposable
{ {
// TODO: Pipeline state should be abstracted that can support multiple graphics APIs. private readonly Dictionary<string, CBufferCache> _cbufferCaches;
private ComPtr<ID3D12PipelineState> _pipelineState;
private bool _disposed;
public Shader Shader public Shader Shader
{ {
get; get;
set; set;
} = Shader.Empty; }
public Material(Shader shader)
{
Shader = shader;
_cbufferCaches = new();
foreach (var cbufferInfo in shader.ConstantBuffers.Values)
{
_cbufferCaches.Add(cbufferInfo.Name, new CBufferCache(cbufferInfo.Size));
}
}
~Material()
{
Dispose();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetFloat(string propertyName, in float value)
{
WriteToCache(propertyName, in value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetVector(string propertyName, in Vector4 value)
{
WriteToCache(propertyName, in value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetMatrix(string propertyName, in Matrix4x4 value)
{
WriteToCache(propertyName, in value);
}
private unsafe void WriteToCache<T>(string propertyName, in T value) where T : unmanaged
{
if (!Shader.Properties.TryGetValue(propertyName, out var propInfo))
{
throw new ArgumentException($"Property '{propertyName}' does not exist in the shader.", nameof(propertyName));
}
if (propInfo.Size != sizeof(T))
{
throw new ArgumentException($"Property '{propertyName}' has a size mismatch. Expected {sizeof(T)} bytes, but got {propInfo.Size} bytes.", nameof(propertyName));
}
var cache = _cbufferCaches[propInfo.CBufferName];
Unsafe.WriteUnaligned(ref cache.CpuData[(int)propInfo.ByteOffset], value);
}
public void UploadAndBind(ICommandBuffer cmb)
{
foreach (var cache in _cbufferCaches.Values)
{
cache.UploadToGpu();
}
foreach (var (name, cache) in _cbufferCaches)
{
var cbufferInfo = Shader.ConstantBuffers[name];
cmb.SetGraphicsRootConstantBufferView(cbufferInfo.RegisterSlot, cache.GpuResource.GPUAddress);
}
}
public void Dispose() public void Dispose()
{ {
_pipelineState.Dispose(); if (_disposed)
{
return;
}
foreach (var cache in _cbufferCaches.Values)
{
cache.Dispose();
}
_cbufferCaches.Clear();
GC.SuppressFinalize(this);
_disposed = true;
} }
} }

View File

@@ -1,7 +1,7 @@
using Ghost.Core; using Ghost.Core;
using Ghost.Graphics.Contracts; using Ghost.Graphics.Contracts;
using Misaki.HighPerformance.Unsafe.Collections; using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.Unsafe.Helpers; using Misaki.HighPerformance.LowLevel.Helpers;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Win32.Graphics.Direct3D12; using Win32.Graphics.Direct3D12;

View File

@@ -1,181 +0,0 @@
using Ghost.Core;
using Ghost.Graphics.D3D12;
using System.Runtime.InteropServices;
using System.Text;
using Win32;
using Win32.Graphics.Direct3D;
using Win32.Graphics.Direct3D.Fxc;
using Win32.Graphics.Direct3D12;
namespace Ghost.Graphics.Data;
public unsafe class Shader
{
private static readonly Shader s_empty = new("ErrorShader");
public static Shader Empty => s_empty;
private ComPtr<ID3D12RootSignature> _rootSignature;
public ConstPtr<ID3D12RootSignature> RootSignature => new(_rootSignature.Get());
public Shader(string shaderPath)
{
}
/// <summary>
/// Compiles HLSL source code from a string into shader bytecode.
/// </summary>
/// <param name="sourceCode">The string containing the HLSL code.</param>
/// <param name="entryPoint">The name of the shader entry point function (e.g., "VSMain").</param>
/// <param name="shaderProfile">The shader model to target (e.g., "vs_5_0", "ps_5_0").</param>
/// <returns>A byte array containing the compiled shader bytecode.</returns>
/// <exception cref="Exception">Thrown if shader compilation fails.</exception>
public static unsafe byte[] CompileShader(string sourceCode, string entryPoint, string shaderProfile)
{
ComPtr<ID3DBlob> bytecodeBlob = default;
ComPtr<ID3DBlob> errorBlob = default;
// Convert strings to null-terminated ASCII for the native function
var sourceCodeBytes = Encoding.UTF8.GetBytes(sourceCode);
var entryPointBytes = Encoding.UTF8.GetBytes(entryPoint);
var shaderProfileBytes = Encoding.UTF8.GetBytes(shaderProfile);
// Call the D3DCompile function
var hr = D3DCompile(
sourceCodeBytes.AsSpan(),
entryPointBytes.AsSpan(),
shaderProfileBytes.AsSpan(),
CompileFlags.EnableStrictness | CompileFlags.Debug,
bytecodeBlob.GetAddressOf(),
errorBlob.GetAddressOf()
);
if (hr.Failure)
{
// If compilation fails, get the error message from the error blob
var errorMessage = "Shader compilation failed.";
if (errorBlob.Get() is not null)
{
errorMessage += "\n" + Encoding.ASCII.GetString(
(byte*)errorBlob.Get()->GetBufferPointer(),
(int)errorBlob.Get()->GetBufferSize()
);
}
errorBlob.Dispose();
throw new Exception(errorMessage);
}
// Copy the compiled bytecode from the blob into a managed byte array
var bytecode = new byte[bytecodeBlob.Get()->GetBufferSize()];
Marshal.Copy((IntPtr)bytecodeBlob.Get()->GetBufferPointer(), bytecode, 0, bytecode.Length);
// Clean up the COM blobs
bytecodeBlob.Dispose();
errorBlob.Dispose();
return bytecode;
}
private void LoadShader(Span<byte> byteCode)
{
using ComPtr<ID3D12ShaderReflection> reflector = default;
fixed (void* codePtr = byteCode)
{
D3DReflect(codePtr, (nuint)byteCode.Length, __uuidof<ID3D12ShaderReflection>(), reflector.GetVoidAddressOf());
}
ShaderDescription shaderDesc;
reflector.Get()->GetDesc(&shaderDesc);
var rootParameters = new List<RootParameter>();
var staticSamplers = new List<StaticSamplerDescription>();
for (uint i = 0; i < shaderDesc.BoundResources; i++)
{
ShaderInputBindDescription bindDesc;
reflector.Get()->GetResourceBindingDesc(i, &bindDesc);
switch (bindDesc.Type)
{
case ShaderInputType.ConstantBuffer:
var cbufferParam = new RootParameter();
cbufferParam.ParameterType = RootParameterType.Cbv;
cbufferParam.ShaderVisibility = ShaderVisibility.All;
cbufferParam.Descriptor.RegisterSpace = bindDesc.Space;
cbufferParam.Descriptor.ShaderRegister = bindDesc.BindPoint;
rootParameters.Add(cbufferParam);
var cbuffer = reflector.Get()->GetConstantBufferByName(bindDesc.Name);
ShaderBufferDescription cbufferDesc;
cbuffer->GetDesc(&cbufferDesc);
for (var j = 0u; j < cbufferDesc.Variables; j++)
{
var variable = cbuffer->GetVariableByIndex(j);
ShaderVariableDescription varDesc;
variable->GetDesc(&varDesc);
}
break;
case ShaderInputType.TextureBuffer:
break;
case ShaderInputType.Texture:
break;
case ShaderInputType.Sampler:
var samplerDesc = new StaticSamplerDescription
{
Filter = Filter.MinMagMipLinear,
AddressU = TextureAddressMode.Wrap,
AddressV = TextureAddressMode.Wrap,
AddressW = TextureAddressMode.Wrap,
ShaderVisibility = ShaderVisibility.All,
ShaderRegister = bindDesc.BindPoint,
RegisterSpace = bindDesc.Space,
};
staticSamplers.Add(samplerDesc);
break;
case ShaderInputType.UavRwTyped:
break;
case ShaderInputType.Structured:
break;
case ShaderInputType.UavRwStructured:
break;
case ShaderInputType.ByteAddress:
break;
case ShaderInputType.UavRwByteAddress:
break;
case ShaderInputType.UavAppendStructured:
break;
case ShaderInputType.UavConsumeStructured:
break;
case ShaderInputType.UavRwStructuredWithCounter:
break;
case ShaderInputType.RtAccelerationStructure:
break;
case ShaderInputType.UavFeedbackTexture:
break;
default:
break;
}
}
}
private void CreateRootSignature()
{
var rootSignatureDesc = new RootSignatureDescription();
using ComPtr<ID3DBlob> signature = default;
using ComPtr<ID3DBlob> error = default;
var hr = D3D12SerializeRootSignature(&rootSignatureDesc, RootSignatureVersion.V1_2, signature.GetAddressOf(), error.GetAddressOf());
if (hr.Failure)
{
var errorMessage = System.Text.Encoding.ASCII.GetString((byte*)error.Get()->GetBufferPointer(), (int)error.Get()->GetBufferSize());
throw new Exception($"Failed to serialize root signature: {errorMessage}");
}
GraphicsPipeline.GetGraphicsDevice<D3D12GraphicsDevice>().NativeDevice.Ptr->CreateRootSignature(0, signature.Get()->GetBufferPointer(), signature.Get()->GetBufferSize(), __uuidof<ID3D12RootSignature>(), _rootSignature.GetVoidAddressOf());
}
}

View File

@@ -1,16 +0,0 @@
using Win32.Graphics.Direct3D;
namespace Ghost.Graphics.Data;
public class ShaderProperty
{
public string Name
{
get;
}
public ShaderInputType Type
{
get;
}
}

View File

@@ -27,8 +27,11 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Misaki.HighPerformance.Unsafe"> <Reference Include="Misaki.HighPerformance.Image">
<HintPath>..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance.Unsafe\bin\Release\net9.0\Misaki.HighPerformance.Unsafe.dll</HintPath> <HintPath>..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance.Image\bin\Release\net9.0\Misaki.HighPerformance.Image.dll</HintPath>
</Reference>
<Reference Include="Misaki.HighPerformance.LowLevel">
<HintPath>..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance.LowLevel\bin\Release\net9.0\Misaki.HighPerformance.LowLevel.dll</HintPath>
</Reference> </Reference>
</ItemGroup> </ItemGroup>

View File

@@ -1,24 +1,18 @@
using Ghost.Graphics.Contracts; using Ghost.Graphics.Contracts;
using Ghost.Graphics.D3D12;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.Data; using Ghost.Graphics.Data;
using Ghost.Graphics.Shading;
using Ghost.Graphics.Utilities; using Ghost.Graphics.Utilities;
using System.Drawing; using System.Drawing;
using System.Runtime.CompilerServices; using System.Numerics;
using Win32;
using Win32.Graphics.Direct3D;
using Win32.Graphics.Direct3D12;
using Win32.Graphics.Dxgi.Common;
namespace Ghost.Graphics.RenderPasses; namespace Ghost.Graphics.RenderPasses;
internal unsafe class MeshRenderPass : IRenderPass internal unsafe class MeshRenderPass : IRenderPass
{ {
private const string _HLSL_SOURCE = @" private const string _HLSL_SOURCE = @"
cbuffer ConstantBuffer : register(b0) cbuffer ConstantBuffer : register(b0)
{ {
float4x4 WVP_Matrix; float4 _Color;
}; };
struct VertexInput struct VertexInput
@@ -43,105 +37,35 @@ PixelInput VSMain(VertexInput input)
float4 PSMain(PixelInput input) : SV_TARGET float4 PSMain(PixelInput input) : SV_TARGET
{ {
return float4(1.0, 1.0, 1.0, 1.0); return float4(_Color.xyz, 1.0);
} }
"; ";
private Mesh? _mesh; private Mesh? _mesh;
private Shader? _shader;
private ComPtr<ID3D12RootSignature> _rootSignature; private Material? _material;
private ComPtr<ID3D12PipelineState> _pipelineState;
public void Initialize(ICommandBuffer cmb) public void Initialize(ICommandBuffer cmb)
{ {
_mesh = MeshBuilder.CreateCube(0.25f, new(Color.AliceBlue)); _mesh = MeshBuilder.CreateCube(0.25f);
_mesh.UploadMeshData(cmb); _mesh.UploadMeshData(cmb);
CreateRootSignature(); _shader = new(_HLSL_SOURCE);
CreatePipelineStateObject(); _material = new(_shader);
}
private void CreateRootSignature() var color = new Vector4(Color.Brown.R / 255f, Color.Brown.G / 255f, Color.Brown.B / 255f, 1.0f);
{ _material.SetVector("_Color", ref color);
var rootParameters = new RootParameter[]
{
new ()
{
ParameterType = RootParameterType.Cbv,
ShaderVisibility = ShaderVisibility.Vertex,
Descriptor = new RootDescriptor(0, 0)
}
};
var rootSignatureDesc = new RootSignatureDescription(0u, (RootParameter*)Unsafe.AsPointer(ref rootParameters[0]))
{
Flags = RootSignatureFlags.AllowInputAssemblerInputLayout
};
using ComPtr<ID3DBlob> signature = default;
using ComPtr<ID3DBlob> error = default;
var hr = D3D12SerializeRootSignature(&rootSignatureDesc, RootSignatureVersion.V1_0, signature.GetAddressOf(), error.GetAddressOf());
if (hr.Failure)
{
var errorMessage = System.Text.Encoding.ASCII.GetString((byte*)error.Get()->GetBufferPointer(), (int)error.Get()->GetBufferSize());
throw new InvalidOperationException($"Failed to serialize root signature: {errorMessage}");
}
GraphicsPipeline.GetGraphicsDevice<D3D12GraphicsDevice>().NativeDevice.Ptr->CreateRootSignature(0, signature.Get()->GetBufferPointer(), signature.Get()->GetBufferSize(), __uuidof<ID3D12RootSignature>(), _rootSignature.GetVoidAddressOf());
}
private void CreatePipelineStateObject()
{
try
{
var vertexShaderBytecode = Shader.CompileShader(_HLSL_SOURCE, "VSMain", "vs_5_0");
var pixelShaderBytecode = Shader.CompileShader(_HLSL_SOURCE, "PSMain", "ps_5_0");
fixed (byte* vsPtr = vertexShaderBytecode)
fixed (byte* psPtr = pixelShaderBytecode)
{
var psoDesc = new GraphicsPipelineStateDescription
{
pRootSignature = _rootSignature.Get(),
VS = new ShaderBytecode(vsPtr, (nuint)vertexShaderBytecode.Length),
PS = new ShaderBytecode(psPtr, (nuint)pixelShaderBytecode.Length),
InputLayout = D3D12PipelineResource.InputLayoutDescription,
RasterizerState = RasterizerDescription.CullNone,
BlendState = BlendDescription.Opaque,
DepthStencilState = DepthStencilDescription.Default,
SampleMask = uint.MaxValue,
PrimitiveTopologyType = PrimitiveTopologyType.Triangle,
NumRenderTargets = 1,
SampleDesc = new SampleDescription(1, 0),
DSVFormat = Format.Unknown,
};
psoDesc.RTVFormats[0] = D3D12PipelineResource.SWAP_CHAIN_BACK_BUFFER_FORMAT;
// Create the PSO
GraphicsPipeline.GetGraphicsDevice<D3D12GraphicsDevice>().NativeDevice.Ptr->CreateGraphicsPipelineState(&psoDesc, __uuidof<ID3D12PipelineState>(), _pipelineState.GetVoidAddressOf());
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
} }
public void Execute(ICommandBuffer cmb) public void Execute(ICommandBuffer cmb)
{ {
var dx12Cmb = (D3D12CommandBuffer)cmb; cmb.DrawMesh(_mesh!, _material!);
dx12Cmb.CommandList.Ptr->SetGraphicsRootSignature(_rootSignature.Get());
dx12Cmb.CommandList.Ptr->SetPipelineState(_pipelineState.Get());
cmb.DrawMesh(_mesh!);
} }
public void Dispose() public void Dispose()
{ {
_mesh?.Dispose(); _mesh?.Dispose();
_rootSignature.Dispose(); _shader?.Dispose();
_pipelineState.Dispose(); _material?.Dispose();
} }
} }

View File

@@ -0,0 +1,43 @@
using Ghost.Graphics.Contracts;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Helpers;
using System.Runtime.CompilerServices;
namespace Ghost.Graphics.Shading;
internal struct CBufferCache : IDisposable
{
public UnsafeArray<byte> CpuData
{
get;
}
public IResource GpuResource
{
get;
}
private readonly uint _alignedSize;
public unsafe CBufferCache(uint bufferSize)
{
CpuData = new((int)bufferSize, Allocator.Persistent);
_alignedSize = (bufferSize + 255u) & ~255u;
GpuResource = GraphicsPipeline.ResourceAllocator.CreateUploadBuffer(_alignedSize);
GpuResource.Name = "Material_CBufferCache";
UploadToGpu();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void UploadToGpu()
{
GpuResource.SetData(CpuData.AsSpan());
}
public readonly void Dispose()
{
GpuResource.Dispose();
}
}

View File

@@ -0,0 +1,291 @@
using Ghost.Core;
using Ghost.Graphics.D3D12;
using Ghost.Graphics.D3D12.Utilities;
using Misaki.HighPerformance.LowLevel.Helpers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using Win32;
using Win32.Graphics.Direct3D;
using Win32.Graphics.Direct3D.Fxc;
using Win32.Graphics.Direct3D12;
using Win32.Graphics.Dxgi.Common;
namespace Ghost.Graphics.Shading;
internal readonly struct PropertyInfo
{
public required string Name
{
get; init;
}
public required string CBufferName
{
get; init;
}
public uint ByteOffset
{
get; init;
}
public uint Size
{
get; init;
}
}
internal readonly struct CBufferInfo
{
public required string Name
{
get; init;
}
public uint Size
{
get; init;
}
public uint RegisterSlot
{
get; init;
}
}
public unsafe class Shader : IDisposable
{
private ComPtr<ID3D12PipelineState> _pipelineState;
private ComPtr<ID3D12RootSignature> _rootSignature;
private readonly byte[] _vertexShaderBytecode;
private readonly byte[] _pixelShaderBytecode;
private readonly Dictionary<string, CBufferInfo> _constantBuffers = new();
private readonly Dictionary<string, PropertyInfo> _properties = new();
private bool _disposed;
internal ConstPtr<ID3D12PipelineState> PipelineState => new(_pipelineState.Get());
internal ConstPtr<ID3D12RootSignature> RootSignature => new(_rootSignature.Get());
internal IReadOnlyDictionary<string, CBufferInfo> ConstantBuffers => _constantBuffers;
internal IReadOnlyDictionary<string, PropertyInfo> Properties => _properties;
//public Shader(string shaderPath)
//{
//}
public Shader(string shaderCode)
{
_vertexShaderBytecode = CompileShader(Encoding.UTF8.GetBytes(shaderCode), "VSMain", "vs_5_0");
_pixelShaderBytecode = CompileShader(Encoding.UTF8.GetBytes(shaderCode), "PSMain", "ps_5_0");
PerformReflection(_vertexShaderBytecode);
PerformReflection(_pixelShaderBytecode);
CreateRootSignature();
CreatePipelineStateObject();
}
~Shader()
{
Dispose();
}
/// <summary>
/// Compiles HLSL source code from a string into shader bytecode.
/// </summary>
/// <param name="sourceCode">The string containing the HLSL code.</param>
/// <param name="entryPoint">The name of the shader entry point function (e.g., "VSMain").</param>
/// <param name="shaderProfile">The shader model to target (e.g., "vs_5_0", "ps_5_0").</param>
/// <returns>A byte array containing the compiled shader bytecode.</returns>
/// <exception cref="Exception">Thrown if shader compilation fails.</exception>
public static unsafe byte[] CompileShader(byte[] sourceCodeBytes, string entryPoint, string shaderProfile)
{
using ComPtr<ID3DBlob> bytecodeBlob = default;
using ComPtr<ID3DBlob> errorBlob = default;
var entryPointBytes = Encoding.UTF8.GetBytes(entryPoint);
var shaderProfileBytes = Encoding.UTF8.GetBytes(shaderProfile);
// Call the D3DCompile function
var hr = D3DCompile(
sourceCodeBytes.AsSpan(),
entryPointBytes.AsSpan(),
shaderProfileBytes.AsSpan(),
CompileFlags.EnableStrictness | CompileFlags.Debug,
bytecodeBlob.GetAddressOf(),
errorBlob.GetAddressOf()
);
if (hr.Failure)
{
var errorMessage = "Shader compilation failed.";
if (errorBlob.Get() is not null)
{
errorMessage += "\n" + Encoding.ASCII.GetString(
(byte*)errorBlob.Get()->GetBufferPointer(),
(int)errorBlob.Get()->GetBufferSize()
);
}
throw new Exception(errorMessage);
}
var bytecode = new byte[bytecodeBlob.Get()->GetBufferSize()];
Unsafe.CopyBlock(bytecode.AsSpan().GetPointer(), bytecodeBlob.Get()->GetBufferPointer(), (uint)bytecode.Length);
return bytecode;
}
private void CreateRootSignature()
{
var rootParameters = new RootParameter1[_constantBuffers.Values.Count];
var i = 0;
foreach (var cbufferInfo in _constantBuffers.Values)
{
var rootParameter = new RootParameter1
{
ParameterType = RootParameterType.Cbv,
ShaderVisibility = ShaderVisibility.All,
Descriptor = new RootDescriptor1(cbufferInfo.RegisterSlot, 0),
};
rootParameters[i++] = rootParameter;
}
var rootSignatureDesc = new RootSignatureDescription((uint)rootParameters.Length, (RootParameter*)Unsafe.AsPointer(ref rootParameters[0]))
{
Flags = RootSignatureFlags.AllowInputAssemblerInputLayout
};
using ComPtr<ID3DBlob> signature = default;
using ComPtr<ID3DBlob> error = default;
var hr = D3D12SerializeRootSignature(&rootSignatureDesc, RootSignatureVersion.V1_0, signature.GetAddressOf(), error.GetAddressOf());
if (hr.Failure)
{
var errorMessage = System.Text.Encoding.ASCII.GetString((byte*)error.Get()->GetBufferPointer(), (int)error.Get()->GetBufferSize());
throw new InvalidOperationException($"Failed to serialize root signature: {errorMessage}");
}
GraphicsPipeline.GetGraphicsDevice<D3D12GraphicsDevice>().NativeDevice.Ptr->CreateRootSignature(0, signature.Get()->GetBufferPointer(), signature.Get()->GetBufferSize(), __uuidof<ID3D12RootSignature>(), _rootSignature.GetVoidAddressOf());
}
private void CreatePipelineStateObject()
{
try
{
fixed (byte* vsPtr = _vertexShaderBytecode)
fixed (byte* psPtr = _pixelShaderBytecode)
{
var psoDesc = new GraphicsPipelineStateDescription
{
pRootSignature = _rootSignature.Get(),
VS = new ShaderBytecode(vsPtr, (nuint)_vertexShaderBytecode.Length),
PS = new ShaderBytecode(psPtr, (nuint)_pixelShaderBytecode.Length),
InputLayout = D3D12PipelineResource.InputLayoutDescription,
RasterizerState = RasterizerDescription.CullNone,
BlendState = BlendDescription.Opaque,
DepthStencilState = DepthStencilDescription.Default,
SampleMask = uint.MaxValue,
PrimitiveTopologyType = PrimitiveTopologyType.Triangle,
NumRenderTargets = 1,
SampleDesc = new SampleDescription(1, 0),
DSVFormat = Format.Unknown,
};
psoDesc.RTVFormats[0] = D3D12PipelineResource.SWAP_CHAIN_BACK_BUFFER_FORMAT;
// Create the PSO
GraphicsPipeline.GetGraphicsDevice<D3D12GraphicsDevice>().NativeDevice.Ptr->CreateGraphicsPipelineState(&psoDesc, __uuidof<ID3D12PipelineState>(), _pipelineState.GetVoidAddressOf());
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
private unsafe void PerformReflection(Span<byte> byteCode)
{
using ComPtr<ID3D12ShaderReflection> reflection = default;
fixed (void* codePtr = byteCode)
{
D3DReflect(codePtr, (nuint)byteCode.Length, __uuidof<ID3D12ShaderReflection>(), reflection.GetVoidAddressOf());
}
ShaderDescription shaderDesc;
reflection.Get()->GetDesc(&shaderDesc);
for (uint i = 0; i < shaderDesc.BoundResources; i++)
{
ShaderInputBindDescription bindDesc;
reflection.Get()->GetResourceBindingDesc(i, &bindDesc);
if (bindDesc.Type == ShaderInputType.ConstantBuffer)
{
var cbufferName = Marshal.PtrToStringAnsi((IntPtr)bindDesc.Name);
if (cbufferName == null || _constantBuffers.ContainsKey(cbufferName))
{
continue;
}
var cbuffer = reflection.Get()->GetConstantBufferByName(bindDesc.Name);
ShaderBufferDescription cbufferDesc;
cbuffer->GetDesc(&cbufferDesc);
_constantBuffers.Add(cbufferName, new CBufferInfo
{
Name = cbufferName,
Size = cbufferDesc.Size,
RegisterSlot = bindDesc.BindPoint
});
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 || _properties.ContainsKey(variableName))
{
continue;
}
_properties.Add(variableName, new PropertyInfo
{
Name = variableName,
CBufferName = cbufferName,
ByteOffset = varDesc.StartOffset,
Size = varDesc.Size
});
}
}
}
reflection.Dispose();
}
public void Dispose()
{
if (_disposed)
{
return;
}
_pipelineState.Dispose();
_rootSignature.Dispose();
_constantBuffers.Clear();
_properties.Clear();
GC.SuppressFinalize(this);
_disposed = true;
}
}

View File

@@ -0,0 +1,48 @@
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Helpers;
namespace Ghost.Graphics.Shading;
public enum ShaderPropertyType
{
Float,
Float2,
Float3,
Float4,
Color,
Matrix,
Texture2D,
Texture3D
}
public struct ShaderProperty : IDisposable
{
private UnsafeArray<byte> _value;
private FixedString128 _name;
private readonly uint _valueOffset;
internal readonly uint Offset => _valueOffset;
public readonly string Name => _name.Value;
public readonly ReadOnlySpan<byte> Value => _value.AsSpan();
public ShaderPropertyType PropertyType
{
get;
}
public ShaderProperty(Span<byte> value, uint offset, string name, ShaderPropertyType type)
{
_value = new(value.Length, Allocator.Persistent);
_valueOffset = offset;
_name = new(name);
PropertyType = type;
}
public void Dispose()
{
_value.Dispose();
_name.Dispose();
}
}

View File

@@ -56,7 +56,7 @@
<ItemGroup> <ItemGroup>
<Reference Include="Misaki.HighPerformance.Unsafe"> <Reference Include="Misaki.HighPerformance.Unsafe">
<HintPath>..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance.Unsafe\bin\Release\net9.0\Misaki.HighPerformance.Unsafe.dll</HintPath> <HintPath>..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance.LowLevel\bin\Release\net9.0\Misaki.HighPerformance.LowLevel.dll</HintPath>
</Reference> </Reference>
</ItemGroup> </ItemGroup>

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<Application <Application
x:Class="Ghost.UnitTest.UnitTestApp" x:Class="Ghost.UnitTest.UnitTestApp"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

View File

@@ -2,7 +2,6 @@ using Ghost.Graphics;
using Ghost.Graphics.Contracts; using Ghost.Graphics.Contracts;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Media;
using Misaki.HighPerformance.Unsafe.Buffer;
using WinRT; using WinRT;
namespace Ghost.UnitTest; namespace Ghost.UnitTest;
@@ -32,7 +31,6 @@ public sealed partial class UnitTestAppWindow : Window
private void UnitTestAppWindow_Activated(object sender, WindowActivatedEventArgs args) private void UnitTestAppWindow_Activated(object sender, WindowActivatedEventArgs args)
{ {
AllocationManager.Initialize();
GraphicsPipeline.Initialize(Graphics.Data.GraphicsAPI.D3D12); GraphicsPipeline.Initialize(Graphics.Data.GraphicsAPI.D3D12);
GraphicsPipeline.Start(); GraphicsPipeline.Start();
@@ -49,7 +47,6 @@ public sealed partial class UnitTestAppWindow : Window
{ {
GraphicsPipeline.SignalCPUReady(); GraphicsPipeline.SignalCPUReady();
GraphicsPipeline.Shutdown(); GraphicsPipeline.Shutdown();
AllocationManager.Dispose();
CompositionTarget.Rendering -= OnRendering; CompositionTarget.Rendering -= OnRendering;
_swapChainPanelNative.Dispose(); _swapChainPanelNative.Dispose();
_renderView?.Dispose(); _renderView?.Dispose();