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

@@ -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();
}
}