Refactor pipeline state and render output abstractions

- Replace old pipeline enums/structs with new strongly-typed PipelineState and enums (ZTest, ZWrite, Cull, Blend, ColorWriteMask)
- Redesign pipeline keying: introduce 128-bit GraphicsPipelineKey, MaterialPipelineKey, and PassPipelineKey for robust PSO caching
- Replace IRenderTargetStrategy with IRenderOutput; add SwapChainRenderOutput and TextureRenderOutput
- Update renderer and window code to use new render output abstraction and handle viewport/scissor updates
- Make ShaderPass a readonly struct and Shader a struct; use ID-based pass lookup for efficiency
- Materials now support per-pass pipeline overrides with new keying
- Add defensive checks in D3D12CommandBuffer; update D3D12PipelineLibrary for new keying/state
- Move test shader to test.gsdef and update for new pipeline state syntax
- Remove obsolete files/interfaces and perform general code cleanups
- Update all usages and parsing logic for new pipeline state system
This commit is contained in:
2025-12-24 19:06:34 +09:00
parent b8ce824292
commit a89719bfc9
25 changed files with 575 additions and 363 deletions

View File

@@ -0,0 +1,84 @@
namespace Ghost.Core.Graphics;
public enum ZTest
{
Disabled,
Less,
LessEqual,
Equal,
GreaterEqual,
Greater,
NotEqual,
Always
}
public enum ZWrite
{
Off,
On
}
public enum Cull
{
Off,
Front,
Back
}
public enum Blend
{
Opaque,
Alpha,
Additive,
Multiply,
PremultipliedAlpha
}
[Flags]
public enum ColorWriteMask
{
None = 0,
Red = 1 << 0,
Green = 1 << 1,
Blue = 1 << 2,
Alpha = 1 << 3,
All = Red | Green | Blue | Alpha
}
public struct PipelineState
{
public ZTest ZTest
{
get; set;
}
public ZWrite ZWrite
{
get; set;
}
public Cull Cull
{
get; set;
}
public Blend Blend
{
get; set;
}
public ColorWriteMask ColorMask
{
get; set;
}
public static PipelineState Default => new PipelineState
{
ZTest = ZTest.LessEqual,
ZWrite = ZWrite.On,
Cull = Cull.Back,
Blend = Blend.Opaque,
ColorMask = ColorWriteMask.All
};
}

View File

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

View File

@@ -33,24 +33,6 @@ public struct KeywordsGroup
public List<string>? keywords; public List<string>? keywords;
} }
public struct PipelineDescriptor
{
public ZTestOptions zTest;
public ZWriteOptions zWrite;
public CullOptions cull;
public BlendOptions blend;
public uint colorMask;
public static PipelineDescriptor Default = new PipelineDescriptor
{
zTest = ZTestOptions.LessEqual,
zWrite = ZWriteOptions.On,
cull = CullOptions.Back,
blend = BlendOptions.Opaque,
colorMask = 0
};
}
public interface IPassDescriptor public interface IPassDescriptor
{ {
public string Identifier public string Identifier
@@ -81,7 +63,7 @@ public class FullPassDescriptor : IPassDescriptor
public ShaderEntryPoint pixelShader; public ShaderEntryPoint pixelShader;
public List<string>? defines; public List<string>? defines;
public List<KeywordsGroup>? keywords; public List<KeywordsGroup>? keywords;
public PipelineDescriptor localPipeline; public PipelineState localPipeline;
public string Identifier => uniqueIdentifier; public string Identifier => uniqueIdentifier;
public string Name => name; public string Name => name;

View File

@@ -2,4 +2,5 @@ namespace Ghost.Editor.Core.SceneGraph;
public class SceneNode : SceneGraphNode public class SceneNode : SceneGraphNode
{ {
public override SceneGraphNodeType NodeType => SceneGraphNodeType.Scene;
} }

View File

@@ -53,7 +53,7 @@ public sealed partial class GraphicsTestWindow : Window
Target = SwapChainTarget.FromCompositionSurface(Panel) Target = SwapChainTarget.FromCompositionSurface(Panel)
}); });
_renderer.RenderTargetStrategy = new SwapChainTargetStrategy(_swapChain); _renderer.RenderOutput = new SwapChainRenderOutput(_swapChain);
_renderSystem.Start(); _renderSystem.Start();
CompositionTarget.Rendering += OnRendering; CompositionTarget.Rendering += OnRendering;
@@ -78,7 +78,7 @@ public sealed partial class GraphicsTestWindow : Window
private void SwapChainPanel_SizeChanged(object sender, SizeChangedEventArgs e) private void SwapChainPanel_SizeChanged(object sender, SizeChangedEventArgs e)
{ {
if (_renderSystem == null || _swapChain == null) if (_renderSystem == null || _swapChain == null || _renderer == null)
{ {
return; return;
} }
@@ -92,6 +92,8 @@ public sealed partial class GraphicsTestWindow : Window
} }
_renderSystem.RequestSwapChainResize(_swapChain, new uint2(newWidth, newHeight)); _renderSystem.RequestSwapChainResize(_swapChain, new uint2(newWidth, newHeight));
_renderer.RenderOutput!.Viewport = new ViewportDesc { Width = newWidth, Height = newHeight, MinDepth = 0.0f, MaxDepth = 1.0f };
_renderer.RenderOutput!.Scissor = new RectDesc { Right = newWidth, Bottom = newHeight };
} }
private void SwapChainPanel_CompositionScaleChanged(SwapChainPanel sender, object args) private void SwapChainPanel_CompositionScaleChanged(SwapChainPanel sender, object args)

View File

@@ -4,16 +4,16 @@ using Ghost.Graphics.RHI;
namespace Ghost.Graphics.Contracts; namespace Ghost.Graphics.Contracts;
public interface IRenderTargetStrategy public interface IRenderOutput
{ {
ViewportDesc Viewport ViewportDesc Viewport
{ {
get; get; set;
} }
RectDesc Scissor RectDesc Scissor
{ {
get; get; set;
} }
/// <summary> /// <summary>
@@ -21,16 +21,19 @@ public interface IRenderTargetStrategy
/// </summary> /// </summary>
/// <returns>A handle to the texture that is currently set as the render target.</returns> /// <returns>A handle to the texture that is currently set as the render target.</returns>
Handle<Texture> GetRenderTarget(); Handle<Texture> GetRenderTarget();
/// <summary> /// <summary>
/// Begins a rendering operation using the specified command buffer. Typically this will include resource barriers, /// Begins a rendering operation using the specified command buffer. Typically this will include resource barriers,
/// </summary> /// </summary>
/// <param name="cmd">The command buffer that records rendering commands.</param> /// <param name="cmd">The command buffer that records rendering commands.</param>
///
void BeginRender(ICommandBuffer cmd); void BeginRender(ICommandBuffer cmd);
/// <summary> /// <summary>
/// Finalizes the rendering process using the specified command buffer. /// Finalizes the rendering process using the specified command buffer.
/// </summary> /// </summary>
/// <param name="cmd">The command buffer that contains the rendering commands to be finalized.</param> /// <param name="cmd">The command buffer that contains the rendering commands to be finalized.</param>
void EndRender(ICommandBuffer cmd); void EndRender(ICommandBuffer cmd);
/// <summary> /// <summary>
/// Displays the current frame to the output device or screen. /// Displays the current frame to the output device or screen.
/// </summary> /// </summary>

View File

@@ -1,3 +1,4 @@
using Misaki.HighPerformance.Mathematics;
using System.Drawing; using System.Drawing;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@@ -87,6 +88,11 @@ public struct Color128 : IEquatable<Color128>
{ {
} }
public Color128(float4 v)
: this(v.x, v.y, v.z, v.w)
{
}
public readonly bool Equals(Color128 other) public readonly bool Equals(Color128 other)
{ {
return r.Equals(other.r) && g.Equals(other.g) && b.Equals(other.b) && a.Equals(other.a); return r.Equals(other.r) && g.Equals(other.g) && b.Equals(other.b) && a.Equals(other.a);

View File

@@ -1,12 +1,19 @@
using Ghost.Core; using Ghost.Core;
using Ghost.Core.Graphics;
using Ghost.Graphics.RHI; using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel;
using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Collections;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace Ghost.Graphics.Core; namespace Ghost.Graphics.Core;
#if false
public struct VariantMask
{
private ulong _mask;
}
#endif
internal struct CBufferCache : IResourceReleasable internal struct CBufferCache : IResourceReleasable
{ {
private UnsafeArray<byte> _cpuData; private UnsafeArray<byte> _cpuData;
@@ -49,8 +56,16 @@ internal struct CBufferCache : IResourceReleasable
public struct Material : IResourceReleasable, IHandleType public struct Material : IResourceReleasable, IHandleType
{ {
private struct PipelineOverride
{
public ShaderPassKey shaderPass;
public PipelineState options;
public MaterialPipelineKey pipelineKey;
}
private Identifier<Shader> _shader; private Identifier<Shader> _shader;
private CBufferCache _cBufferCache; private CBufferCache _cBufferCache;
private UnsafeArray<PipelineOverride> _passPipelineOverride;
internal readonly CBufferCache CBufferCache => _cBufferCache; internal readonly CBufferCache CBufferCache => _cBufferCache;
@@ -67,6 +82,30 @@ public struct Material : IResourceReleasable, IHandleType
_shader = shaderId; _shader = shaderId;
var shader = database.GetShaderReference(shaderId); var shader = database.GetShaderReference(shaderId);
if (_passPipelineOverride.Count < shader.PassCount)
{
if (!_passPipelineOverride.IsCreated)
{
_passPipelineOverride = new UnsafeArray<PipelineOverride>(shader.PassCount, Allocator.Persistent);
}
else
{
_passPipelineOverride.Resize(shader.PassCount);
}
}
for (var i = 0; i < shader.PassCount; i++)
{
var pass = shader.GetPass(i);
_passPipelineOverride[i] = new PipelineOverride
{
shaderPass = pass.Identifier,
options = pass.DeafaultState,
pipelineKey = new MaterialPipelineKey(pass.Identifier, pass.DeafaultState),
};
}
if (shader.CBufferSize != 0) if (shader.CBufferSize != 0)
{ {
var desc = new BufferDesc var desc = new BufferDesc
@@ -100,7 +139,7 @@ public struct Material : IResourceReleasable, IHandleType
{ {
if (_cBufferCache.Size == 0) if (_cBufferCache.Size == 0)
{ {
return Span<byte>.Empty; return [];
} }
return _cBufferCache.CpuData.AsSpan(0, (int)_cBufferCache.Size); return _cBufferCache.CpuData.AsSpan(0, (int)_cBufferCache.Size);
@@ -138,6 +177,26 @@ public struct Material : IResourceReleasable, IHandleType
cmb.ResourceBarrier(_cBufferCache.GpuResource.AsResource(), ResourceState.VertexAndConstantBuffer); cmb.ResourceBarrier(_cBufferCache.GpuResource.AsResource(), ResourceState.VertexAndConstantBuffer);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly PipelineState GetPassPipelineOverride(int passIndex)
{
return _passPipelineOverride[passIndex].options;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void SetPassPipelineOverride(int passIndex, in PipelineState options)
{
ref var pipelineOverride = ref _passPipelineOverride[passIndex];
pipelineOverride.options = options;
pipelineOverride.pipelineKey = new MaterialPipelineKey(pipelineOverride.shaderPass, options);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal readonly MaterialPipelineKey GetPassPipelineKey(int passIndex)
{
return _passPipelineOverride[passIndex].pipelineKey;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
void IResourceReleasable.ReleaseResource(IResourceDatabase database) void IResourceReleasable.ReleaseResource(IResourceDatabase database)
{ {

View File

@@ -4,7 +4,7 @@ using Ghost.Graphics.RHI;
namespace Ghost.Graphics.Core; namespace Ghost.Graphics.Core;
internal class SwapChainTargetStrategy : IRenderTargetStrategy internal class SwapChainRenderOutput : IRenderOutput
{ {
private readonly ISwapChain _swapChain; private readonly ISwapChain _swapChain;
@@ -18,7 +18,7 @@ internal class SwapChainTargetStrategy : IRenderTargetStrategy
get; set; get; set;
} }
public SwapChainTargetStrategy(ISwapChain swapChain) public SwapChainRenderOutput(ISwapChain swapChain)
{ {
_swapChain = swapChain; _swapChain = swapChain;
@@ -47,7 +47,7 @@ internal class SwapChainTargetStrategy : IRenderTargetStrategy
} }
} }
internal class TextureTargetStrategy : IRenderTargetStrategy internal class TextureRenderOutput : IRenderOutput
{ {
private readonly Handle<Texture> _texture; private readonly Handle<Texture> _texture;
@@ -61,7 +61,7 @@ internal class TextureTargetStrategy : IRenderTargetStrategy
get; set; get; set;
} }
public TextureTargetStrategy(Handle<Texture> texture) public TextureRenderOutput(Handle<Texture> texture)
{ {
_texture = texture; _texture = texture;
} }

View File

@@ -5,6 +5,7 @@ using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Utilities; using Misaki.HighPerformance.LowLevel.Utilities;
using Misaki.HighPerformance.Mathematics; using Misaki.HighPerformance.Mathematics;
using System.Runtime.CompilerServices;
namespace Ghost.Graphics.Core; namespace Ghost.Graphics.Core;
@@ -163,29 +164,30 @@ public readonly unsafe ref struct RenderingContext
// TODO: Ideally we should queue the draw call to our rendering system, and render it in a full rendering pipeline. // TODO: Ideally we should queue the draw call to our rendering system, and render it in a full rendering pipeline.
// This is just a place holder for now for testing purpose. // This is just a place holder for now for testing purpose.
public void DispatchMesh(Handle<Mesh> mesh, Handle<Material> material, string passName, uint numThreadsX) public void DispatchMesh(Handle<Mesh> mesh, Handle<Material> material, Identifier<ShaderPass> passID, uint numThreadsX)
{ {
ref var meshRef = ref ResourceDatabase.GetMeshReference(mesh); ref var meshRef = ref ResourceDatabase.GetMeshReference(mesh);
ref var materialRef = ref ResourceDatabase.GetMaterialReference(material); ref var materialRef = ref ResourceDatabase.GetMaterialReference(material);
var shader = ResourceDatabase.GetShaderReference(materialRef.Shader); ref var shader = ref ResourceDatabase.GetShaderReference(materialRef.Shader);
var keyResult = shader.TryGetPassKey(passName, out var passIndex); var passIndex = shader.GetPassIndex(passID);
if (keyResult.Error != ErrorStatus.None) if (passIndex == -1)
{ {
throw new Exception(keyResult.ToString()); throw new InvalidOperationException("Shader pass not found in the material's shader.");
} }
var hash = new GraphicsPipelineHash var passPipelineKey = new PassPipelineKey([TextureFormat.B8G8R8A8_UNorm], TextureFormat.Unknown);
var materialPipelineKey = materialRef.GetPassPipelineKey(passIndex);
var pipelineKey = GraphicsPipelineKey.Combine(materialPipelineKey, passPipelineKey);
if (!_engine.PipelineLibrary.HasPipeline(pipelineKey))
{ {
Id = keyResult.Value.Identifier, // TODO: Compile pso if not exist.
RtvCount = 1, // _engine.PipelineLibrary.CompilePSO(pipelineKey, ref shader, passIndex, materialRef.GetPassPipelineOverride());
DsvFormat = TextureFormat.Unknown, throw new InvalidOperationException("Pipeline state object not found in the pipeline library.");
}; }
hash.RtvFormats[0] = TextureFormat.B8G8R8A8_UNorm;
var pipelineKey = hash.GetKey();
_directCmd.SetPipelineState(pipelineKey); _directCmd.SetPipelineState(pipelineKey);
_directCmd.SetConstantBufferView(RootSignatureLayout.PER_OBJECT_BUFFER_SLOT, meshRef.ObjectDataBuffer); _directCmd.SetConstantBufferView(RootSignatureLayout.PER_OBJECT_BUFFER_SLOT, meshRef.ObjectDataBuffer);
// NOTE: We use fixed root signature layout for bindless rendering. // NOTE: We use fixed root signature layout for bindless rendering.

View File

@@ -6,63 +6,61 @@ using Misaki.HighPerformance.LowLevel.Collections;
namespace Ghost.Graphics.Core; namespace Ghost.Graphics.Core;
public struct ShaderPass : IResourceReleasable public readonly struct ShaderPass : IResourceReleasable
{ {
public ShaderPassKey Identifier public ShaderPassKey Identifier
{ {
get; init; get; init;
} }
public ZTestOptions ZTest public PipelineState DeafaultState
{ {
get; set; get; init;
} }
public ZWriteOptions ZWrite
{
get; set;
}
public CullOptions Cull
{
get; set;
}
public BlendOptions Blend
{
get; set;
}
public uint ColorMask
{
get; set;
}
// TODO: Shader variant.
readonly void IResourceReleasable.ReleaseResource(IResourceDatabase database) readonly void IResourceReleasable.ReleaseResource(IResourceDatabase database)
{ {
} }
} }
public struct ShaderProperty;
public partial struct Shader
{
private static readonly Dictionary<string, int> s_passNameToID = new Dictionary<string, int>();
private static int s_nextPassID = 0;
private static readonly Dictionary<string, int> s_propertyNameToID = new Dictionary<string, int>();
private static int s_nextPropertyID = 0;
public static Identifier<ShaderPass> GetPassID(string passName)
{
return new Identifier<ShaderPass>(s_passNameToID.GetValueOrDefault(passName, s_nextPassID++));
}
public static Identifier<ShaderProperty> GetPropertyID(string propertyName)
{
return new Identifier<ShaderProperty>(s_propertyNameToID.GetValueOrDefault(propertyName, s_nextPropertyID++));
}
}
/// <summary> /// <summary>
/// A representation of a GPU shader, including all the passes it contains. /// A representation of a GPU shader, including all the passes it contains.
/// </summary> /// </summary>
public class Shader : IResourceReleasable, IIdentifierType public partial struct Shader : IResourceReleasable, IIdentifierType
{ {
private readonly uint _cbufferSize; private readonly uint _cbufferSize;
private UnsafeArray<ShaderPass> _passes; private UnsafeArray<ShaderPass> _shaderPasses;
// TODO: Optmize lookups with a better data structure if needed private UnsafeHashMap<int, int> _passLookup; // pass id to index
private readonly Dictionary<string, int> _passLookup; // pass name to index
public int PassCount => _passes.Count; public readonly int PassCount => _shaderPasses.Count;
public uint CBufferSize => _cbufferSize; public readonly uint CBufferSize => _cbufferSize;
internal Shader(ShaderDescriptor descriptor) internal Shader(ShaderDescriptor descriptor)
{ {
_cbufferSize = descriptor.cbufferSize; _cbufferSize = descriptor.cbufferSize;
_passes = new UnsafeArray<ShaderPass>(descriptor.passes.Count, Allocator.Persistent); _shaderPasses = new UnsafeArray<ShaderPass>(descriptor.passes.Count, Allocator.Persistent);
_passLookup = new Dictionary<string, int>(descriptor.passes.Count); _passLookup = new UnsafeHashMap<int, int>(descriptor.passes.Count, Allocator.Persistent);
for (var i = 0; i < descriptor.passes.Count; i++) for (var i = 0; i < descriptor.passes.Count; i++)
{ {
@@ -76,45 +74,56 @@ public class Shader : IResourceReleasable, IIdentifierType
var passKey = new ShaderPassKey(pass.Identifier); var passKey = new ShaderPassKey(pass.Identifier);
_passes[i] = new ShaderPass _shaderPasses[i] = new ShaderPass
{ {
Identifier = passKey, Identifier = passKey,
ZTest = fullPass.localPipeline.zTest, DeafaultState = fullPass.localPipeline
ZWrite = fullPass.localPipeline.zWrite,
Cull = fullPass.localPipeline.cull,
Blend = fullPass.localPipeline.blend,
ColorMask = fullPass.localPipeline.colorMask
}; };
_passLookup[pass.Name] = i; _passLookup[GetPassID(pass.Name)] = i;
} }
} }
public int GetPassIndex(string passName) public readonly int GetPassIndex(Identifier<ShaderPass> passID)
{ {
return _passLookup.GetValueOrDefault(passName, -1); if (_passLookup.TryGetValue(passID.Value, out var index))
{
return index;
}
return -1;
} }
public ref ShaderPass GetPassReference(int index) public readonly int GetPassIndex(string passName)
{ {
return ref _passes[index]; if (_passLookup.TryGetValue(GetPassID(passName), out var index))
{
return index;
}
return -1;
} }
public RefResult<ShaderPass, ErrorStatus> TryGetPassKey(string passName, out int passIndex) public readonly ShaderPass GetPass(int index)
{ {
var index = _passLookup.GetValueOrDefault(passName, -1); return _shaderPasses[index];
if (index == -1) }
public readonly Result<ShaderPass, ErrorStatus> TryGetPass(Identifier<ShaderPass> passID, out int passIndex)
{
if (_passLookup.TryGetValue(passID.Value, out var index))
{ {
passIndex = -1; passIndex = -1;
return ErrorStatus.NotFound; return ErrorStatus.NotFound;
} }
passIndex = index; passIndex = index;
return RefResult<ShaderPass, ErrorStatus>.Success(ref _passes[index]); return _shaderPasses[index];
} }
void IResourceReleasable.ReleaseResource(IResourceDatabase database) void IResourceReleasable.ReleaseResource(IResourceDatabase database)
{ {
_passes.Dispose(); _shaderPasses.Dispose();
_passLookup.Dispose();
} }
} }

View File

@@ -26,7 +26,9 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
private readonly D3D12DescriptorAllocator _descriptorAllocator; private readonly D3D12DescriptorAllocator _descriptorAllocator;
private readonly CommandBufferType _type; private readonly CommandBufferType _type;
#if !DEBUG
private CommandError _lastError; private CommandError _lastError;
#endif
private ushort _commandCount; private ushort _commandCount;
private bool _isRecording; private bool _isRecording;
private bool _disposed; private bool _disposed;
@@ -166,10 +168,12 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
_commandList.Get()->Close(); _commandList.Get()->Close();
_isRecording = false; _isRecording = false;
#if !DEBUG
if (_lastError.Status != ErrorStatus.None) if (_lastError.Status != ErrorStatus.None)
{ {
return Result.Failure($"Command buffer ended with errors at {_lastError.CommandIndex}, command '{_lastError.CommandName}': {_lastError.Status}"); return Result.Failure($"Command buffer ended with errors at {_lastError.CommandIndex}, command '{_lastError.CommandName}': {_lastError.Status}");
} }
#endif
return Result.Success(); return Result.Success();
} }
@@ -178,6 +182,12 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
{ {
ThrowIfDisposed(); ThrowIfDisposed();
ThrowIfNotRecording(); ThrowIfNotRecording();
#if !DEBUG
if (_lastError.Status != ErrorStatus.None)
{
return;
}
#endif
IncrementCommandCount(); IncrementCommandCount();
var d3d12Rect = new RECT((int)rect.Left, (int)rect.Top, (int)rect.Right, (int)rect.Bottom); var d3d12Rect = new RECT((int)rect.Left, (int)rect.Top, (int)rect.Right, (int)rect.Bottom);
@@ -188,6 +198,12 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
{ {
ThrowIfDisposed(); ThrowIfDisposed();
ThrowIfNotRecording(); ThrowIfNotRecording();
#if !DEBUG
if (_lastError.Status != ErrorStatus.None)
{
return;
}
#endif
IncrementCommandCount(); IncrementCommandCount();
var count = 0u; var count = 0u;
@@ -238,6 +254,12 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
{ {
ThrowIfDisposed(); ThrowIfDisposed();
ThrowIfNotRecording(); ThrowIfNotRecording();
#if !DEBUG
if (_lastError.Status != ErrorStatus.None)
{
return;
}
#endif
IncrementCommandCount(); IncrementCommandCount();
if (stateBefore == stateAfter) if (stateBefore == stateAfter)
@@ -264,6 +286,12 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
{ {
ThrowIfDisposed(); ThrowIfDisposed();
ThrowIfNotRecording(); ThrowIfNotRecording();
#if !DEBUG
if (_lastError.Status != ErrorStatus.None)
{
return;
}
#endif
IncrementCommandCount(); IncrementCommandCount();
var recordResult = _resourceDatabase.GetResourceRecord(resource); var recordResult = _resourceDatabase.GetResourceRecord(resource);
@@ -290,6 +318,12 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
{ {
ThrowIfDisposed(); ThrowIfDisposed();
ThrowIfNotRecording(); ThrowIfNotRecording();
#if !DEBUG
if (_lastError.Status != ErrorStatus.None)
{
return;
}
#endif
IncrementCommandCount(); IncrementCommandCount();
var pRtvHandles = stackalloc D3D12_CPU_DESCRIPTOR_HANDLE[renderTargets.Length]; var pRtvHandles = stackalloc D3D12_CPU_DESCRIPTOR_HANDLE[renderTargets.Length];
@@ -337,6 +371,12 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
{ {
ThrowIfDisposed(); ThrowIfDisposed();
ThrowIfNotRecording(); ThrowIfNotRecording();
#if !DEBUG
if (_lastError.Status != ErrorStatus.None)
{
return;
}
#endif
IncrementCommandCount(); IncrementCommandCount();
var pRtvDescs = stackalloc D3D12_RENDER_PASS_RENDER_TARGET_DESC[rtDescs.Length]; var pRtvDescs = stackalloc D3D12_RENDER_PASS_RENDER_TARGET_DESC[rtDescs.Length];
@@ -419,6 +459,12 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
{ {
ThrowIfDisposed(); ThrowIfDisposed();
ThrowIfNotRecording(); ThrowIfNotRecording();
#if !DEBUG
if (_lastError.Status != ErrorStatus.None)
{
return;
}
#endif
IncrementCommandCount(); IncrementCommandCount();
_commandList.Get()->EndRenderPass(); _commandList.Get()->EndRenderPass();
@@ -428,6 +474,12 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
{ {
ThrowIfDisposed(); ThrowIfDisposed();
ThrowIfNotRecording(); ThrowIfNotRecording();
#if !DEBUG
if (_lastError.Status != ErrorStatus.None)
{
return;
}
#endif
IncrementCommandCount(); IncrementCommandCount();
var d3d12Viewport = new D3D12_VIEWPORT(viewport.X, viewport.Y, viewport.Width, viewport.Height, viewport.MinDepth, viewport.MaxDepth); var d3d12Viewport = new D3D12_VIEWPORT(viewport.X, viewport.Y, viewport.Width, viewport.Height, viewport.MinDepth, viewport.MaxDepth);
@@ -438,14 +490,18 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
{ {
ThrowIfDisposed(); ThrowIfDisposed();
ThrowIfNotRecording(); ThrowIfNotRecording();
#if !DEBUG
if (_lastError.Status != ErrorStatus.None)
{
return;
}
#endif
IncrementCommandCount(); IncrementCommandCount();
var psor = _pipelineLibrary.GetGraphicsPSO(pipelineKey); var psor = _pipelineLibrary.GetGraphicsPSO(pipelineKey);
if (psor.Error != ErrorStatus.None) if (psor.Error != ErrorStatus.None)
{ {
#if DEBUG || GHOST_EDITOR RecordError(nameof(SetPipelineState), psor.Error);
Logger.LogError($"Failed to get graphics pipeline state object for key {pipelineKey}: {psor.Error}");
#endif
return; return;
} }
@@ -457,6 +513,12 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
{ {
ThrowIfDisposed(); ThrowIfDisposed();
ThrowIfNotRecording(); ThrowIfNotRecording();
#if !DEBUG
if (_lastError.Status != ErrorStatus.None)
{
return;
}
#endif
IncrementCommandCount(); IncrementCommandCount();
var resource = _resourceDatabase.GetResource(buffer.AsResource()); var resource = _resourceDatabase.GetResource(buffer.AsResource());
@@ -467,6 +529,12 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
{ {
ThrowIfDisposed(); ThrowIfDisposed();
ThrowIfNotRecording(); ThrowIfNotRecording();
#if !DEBUG
if (_lastError.Status != ErrorStatus.None)
{
return;
}
#endif
IncrementCommandCount(); IncrementCommandCount();
var recordResult = _resourceDatabase.GetResourceRecord(buffer.AsResource()); var recordResult = _resourceDatabase.GetResourceRecord(buffer.AsResource());
@@ -491,6 +559,12 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
{ {
ThrowIfDisposed(); ThrowIfDisposed();
ThrowIfNotRecording(); ThrowIfNotRecording();
#if !DEBUG
if (_lastError.Status != ErrorStatus.None)
{
return;
}
#endif
IncrementCommandCount(); IncrementCommandCount();
var resource = _resourceDatabase.GetResource(buffer.AsResource()); var resource = _resourceDatabase.GetResource(buffer.AsResource());
@@ -508,6 +582,12 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
{ {
ThrowIfDisposed(); ThrowIfDisposed();
ThrowIfNotRecording(); ThrowIfNotRecording();
#if !DEBUG
if (_lastError.Status != ErrorStatus.None)
{
return;
}
#endif
IncrementCommandCount(); IncrementCommandCount();
var d3d12Topology = topology switch var d3d12Topology = topology switch
@@ -525,6 +605,12 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
{ {
ThrowIfDisposed(); ThrowIfDisposed();
ThrowIfNotRecording(); ThrowIfNotRecording();
#if !DEBUG
if (_lastError.Status != ErrorStatus.None)
{
return;
}
#endif
IncrementCommandCount(); IncrementCommandCount();
_commandList.Get()->DrawInstanced(vertexCount, instanceCount, startVertex, startInstance); _commandList.Get()->DrawInstanced(vertexCount, instanceCount, startVertex, startInstance);
@@ -534,6 +620,12 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
{ {
ThrowIfDisposed(); ThrowIfDisposed();
ThrowIfNotRecording(); ThrowIfNotRecording();
#if !DEBUG
if (_lastError.Status != ErrorStatus.None)
{
return;
}
#endif
IncrementCommandCount(); IncrementCommandCount();
_commandList.Get()->DrawIndexedInstanced(indexCount, instanceCount, startIndex, baseVertex, startInstance); _commandList.Get()->DrawIndexedInstanced(indexCount, instanceCount, startIndex, baseVertex, startInstance);
@@ -543,6 +635,12 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
{ {
ThrowIfDisposed(); ThrowIfDisposed();
ThrowIfNotRecording(); ThrowIfNotRecording();
#if !DEBUG
if (_lastError.Status != ErrorStatus.None)
{
return;
}
#endif
IncrementCommandCount(); IncrementCommandCount();
_commandList.Get()->Dispatch(threadGroupCountX, threadGroupCountY, threadGroupCountZ); _commandList.Get()->Dispatch(threadGroupCountX, threadGroupCountY, threadGroupCountZ);
@@ -552,6 +650,12 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
{ {
ThrowIfDisposed(); ThrowIfDisposed();
ThrowIfNotRecording(); ThrowIfNotRecording();
#if !DEBUG
if (_lastError.Status != ErrorStatus.None)
{
return;
}
#endif
IncrementCommandCount(); IncrementCommandCount();
_commandList.Get()->DispatchMesh(threadGroupCountX, threadGroupCountY, threadGroupCountZ); _commandList.Get()->DispatchMesh(threadGroupCountX, threadGroupCountY, threadGroupCountZ);
@@ -573,6 +677,12 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
{ {
ThrowIfDisposed(); ThrowIfDisposed();
ThrowIfNotRecording(); ThrowIfNotRecording();
#if !DEBUG
if (_lastError.Status != ErrorStatus.None)
{
return;
}
#endif
IncrementCommandCount(); IncrementCommandCount();
var sizeInBytes = (uint)(data.Length * sizeof(T)); var sizeInBytes = (uint)(data.Length * sizeof(T));
@@ -599,6 +709,12 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
{ {
ThrowIfDisposed(); ThrowIfDisposed();
ThrowIfNotRecording(); ThrowIfNotRecording();
#if !DEBUG
if (_lastError.Status != ErrorStatus.None)
{
return;
}
#endif
IncrementCommandCount(); IncrementCommandCount();
var resource = _resourceDatabase.GetResource(texture.AsResource()); var resource = _resourceDatabase.GetResource(texture.AsResource());
@@ -634,6 +750,12 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
{ {
ThrowIfDisposed(); ThrowIfDisposed();
ThrowIfNotRecording(); ThrowIfNotRecording();
#if !DEBUG
if (_lastError.Status != ErrorStatus.None)
{
return;
}
#endif
IncrementCommandCount(); IncrementCommandCount();
var pDestResource = _resourceDatabase.GetResource(dest.AsResource()); var pDestResource = _resourceDatabase.GetResource(dest.AsResource());

View File

@@ -233,23 +233,23 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary
return Result.Success(cbufferInfo); return Result.Success(cbufferInfo);
} }
private static D3D12_COMPARISON_FUNC ToD3DCompare(ZTestOptions z) => z switch private static D3D12_COMPARISON_FUNC ToD3DCompare(ZTest z) => z switch
{ {
ZTestOptions.Disabled => D3D12_COMPARISON_FUNC_NEVER, ZTest.Disabled => D3D12_COMPARISON_FUNC_NEVER,
ZTestOptions.Less => D3D12_COMPARISON_FUNC_LESS, ZTest.Less => D3D12_COMPARISON_FUNC_LESS,
ZTestOptions.LessEqual => D3D12_COMPARISON_FUNC_LESS_EQUAL, ZTest.LessEqual => D3D12_COMPARISON_FUNC_LESS_EQUAL,
ZTestOptions.Equal => D3D12_COMPARISON_FUNC_EQUAL, ZTest.Equal => D3D12_COMPARISON_FUNC_EQUAL,
ZTestOptions.GreaterEqual => D3D12_COMPARISON_FUNC_GREATER_EQUAL, ZTest.GreaterEqual => D3D12_COMPARISON_FUNC_GREATER_EQUAL,
ZTestOptions.Greater => D3D12_COMPARISON_FUNC_GREATER, ZTest.Greater => D3D12_COMPARISON_FUNC_GREATER,
ZTestOptions.NotEqual => D3D12_COMPARISON_FUNC_NOT_EQUAL, ZTest.NotEqual => D3D12_COMPARISON_FUNC_NOT_EQUAL,
ZTestOptions.Always => D3D12_COMPARISON_FUNC_ALWAYS, ZTest.Always => D3D12_COMPARISON_FUNC_ALWAYS,
_ => D3D12_COMPARISON_FUNC_LESS_EQUAL _ => D3D12_COMPARISON_FUNC_LESS_EQUAL
}; };
private static D3D12_DEPTH_STENCIL_DESC BuildDepthStencil(ZTestOptions ztest, ZWriteOptions zwrite) private static D3D12_DEPTH_STENCIL_DESC BuildDepthStencil(ZTest ztest, ZWrite zwrite)
{ {
var depthEnabled = ztest != ZTestOptions.Disabled; var depthEnabled = ztest != ZTest.Disabled;
var writeEnabled = zwrite == ZWriteOptions.On; var writeEnabled = zwrite == ZWrite.On;
var cmp = ToD3DCompare(ztest); var cmp = ToD3DCompare(ztest);
return D3D12Utility.D3D12_DEPTH_STENCIL_DESC_CREATE(depthEnabled, writeEnabled, cmp); return D3D12Utility.D3D12_DEPTH_STENCIL_DESC_CREATE(depthEnabled, writeEnabled, cmp);
} }
@@ -295,23 +295,16 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary
return psr.Value; return psr.Value;
} }
var hash = new GraphicsPipelineHash if (descriptor.RtvFormats.Length > D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT)
{ {
Id = descriptor.PassId, return Result.Failure($"RTV format count exceeds the maximum supported render target count of {D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT}.");
RtvCount = (uint)descriptor.RtvFormats.Length,
DsvFormat = descriptor.DsvFormat,
};
var rtvCount = (uint)Math.Min(descriptor.RtvFormats.Length, D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT);
for (var i = 0; i < rtvCount && i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; i++)
{
hash.RtvFormats[i] = descriptor.RtvFormats[i];
} }
var key = hash.GetKey(); var passPipelineKey = new PassPipelineKey(descriptor.RtvFormats, descriptor.DsvFormat);
var materialPipelineKey = new MaterialPipelineKey(descriptor.PassId, descriptor.PipelineOption);
var pipelineKey = GraphicsPipelineKey.Combine(materialPipelineKey, passPipelineKey);
if (!_pipelineCache.ContainsKey(key)) if (!_pipelineCache.ContainsKey(pipelineKey))
{ {
var result = ValidatePassReflectionData(in compiled); var result = ValidatePassReflectionData(in compiled);
if (result.IsFailure) if (result.IsFailure)
@@ -327,26 +320,26 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary
PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE, PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE,
SampleMask = UINT32_MAX, SampleMask = UINT32_MAX,
SampleDesc = new DXGI_SAMPLE_DESC(1, 0), SampleDesc = new DXGI_SAMPLE_DESC(1, 0),
NumRenderTargets = rtvCount, NumRenderTargets = (uint)descriptor.RtvFormats.Length,
DSVFormat = descriptor.DsvFormat.ToDXGIFormat(), DSVFormat = descriptor.DsvFormat.ToDXGIFormat(),
DepthStencilState = BuildDepthStencil(descriptor.ZTest, descriptor.ZWrite), DepthStencilState = BuildDepthStencil(descriptor.PipelineOption.ZTest, descriptor.PipelineOption.ZWrite),
NodeMask = 0, NodeMask = 0,
Flags = D3D12_PIPELINE_STATE_FLAG_NONE, Flags = D3D12_PIPELINE_STATE_FLAG_NONE,
BlendState = descriptor.Blend switch BlendState = descriptor.PipelineOption.Blend switch
{ {
BlendOptions.Opaque => D3D12Utility.D3D12_BLEND_DESC_OPAQUE, Blend.Opaque => D3D12Utility.D3D12_BLEND_DESC_OPAQUE,
BlendOptions.Alpha => D3D12Utility.D3D12_BLEND_DESC_ALPHA_BLEND, Blend.Alpha => D3D12Utility.D3D12_BLEND_DESC_ALPHA_BLEND,
BlendOptions.Additive => D3D12Utility.D3D12_BLEND_DESC_ADDITIVE, Blend.Additive => D3D12Utility.D3D12_BLEND_DESC_ADDITIVE,
BlendOptions.Multiply => D3D12Utility.D3D12_BLEND_DESC_MULTIPLY, Blend.Multiply => D3D12Utility.D3D12_BLEND_DESC_MULTIPLY,
BlendOptions.PremultipliedAlpha => D3D12Utility.D3D12_BLEND_DESC_PREMULTIPLIED, Blend.PremultipliedAlpha => D3D12Utility.D3D12_BLEND_DESC_PREMULTIPLIED,
_ => D3D12Utility.D3D12_BLEND_DESC_OPAQUE _ => D3D12Utility.D3D12_BLEND_DESC_OPAQUE
}, },
RasterizerState = descriptor.Cull switch RasterizerState = descriptor.PipelineOption.Cull switch
{ {
CullOptions.Off => D3D12Utility.D3D12_RASTERIZER_DESC_CULL_NONE, Cull.Off => D3D12Utility.D3D12_RASTERIZER_DESC_CULL_NONE,
CullOptions.Front => D3D12Utility.D3D12_RASTERIZER_DESC_CULL_CLOCKWISE, Cull.Front => D3D12Utility.D3D12_RASTERIZER_DESC_CULL_CLOCKWISE,
CullOptions.Back => D3D12Utility.D3D12_RASTERIZER_DESC_CULL_COUNTER_CLOCKWISE, Cull.Back => D3D12Utility.D3D12_RASTERIZER_DESC_CULL_COUNTER_CLOCKWISE,
_ => D3D12Utility.D3D12_RASTERIZER_DESC_CULL_NONE _ => D3D12Utility.D3D12_RASTERIZER_DESC_CULL_NONE
}, },
}; };
@@ -356,10 +349,10 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary
desc.AS = new D3D12_SHADER_BYTECODE(compiled.tsResult.bytecode.GetUnsafePtr(), (nuint)compiled.tsResult.bytecode.Count); desc.AS = new D3D12_SHADER_BYTECODE(compiled.tsResult.bytecode.GetUnsafePtr(), (nuint)compiled.tsResult.bytecode.Count);
} }
for (var i = 0; i < rtvCount && i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; i++) for (var i = 0; i < descriptor.RtvFormats.Length; i++)
{ {
desc.RTVFormats[i] = descriptor.RtvFormats[i].ToDXGIFormat(); desc.RTVFormats[i] = descriptor.RtvFormats[i].ToDXGIFormat();
desc.BlendState.RenderTarget[i].RenderTargetWriteMask = (byte)(descriptor.ColorMask & 0x0F); desc.BlendState.RenderTarget[i].RenderTargetWriteMask = (byte)((int)descriptor.PipelineOption.ColorMask & 0x0F);
} }
var meshStream = new CD3DX12_PIPELINE_MESH_STATE_STREAM(in desc); var meshStream = new CD3DX12_PIPELINE_MESH_STATE_STREAM(in desc);
@@ -373,7 +366,7 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary
var pKeyStr = stackalloc char[GraphicsPipelineKey.KEY_STRING_LENGTH]; var pKeyStr = stackalloc char[GraphicsPipelineKey.KEY_STRING_LENGTH];
var keySpan = new Span<char>(pKeyStr, GraphicsPipelineKey.KEY_STRING_LENGTH); var keySpan = new Span<char>(pKeyStr, GraphicsPipelineKey.KEY_STRING_LENGTH);
var kr = key.GetString(keySpan); var kr = pipelineKey.GetString(keySpan);
if (kr.IsFailure) if (kr.IsFailure)
{ {
return kr; return kr;
@@ -396,10 +389,15 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary
pso.psoDesc = desc; pso.psoDesc = desc;
pso.pso.Attach(pPipelineState); pso.pso.Attach(pPipelineState);
_pipelineCache[key] = pso; _pipelineCache[pipelineKey] = pso;
} }
return key; return pipelineKey;
}
public bool HasPipeline(GraphicsPipelineKey key)
{
return _pipelineCache.ContainsKey(key);
} }
public Result<SharedPtr<ID3D12PipelineState>, ErrorStatus> GetGraphicsPSO(GraphicsPipelineKey key) public Result<SharedPtr<ID3D12PipelineState>, ErrorStatus> GetGraphicsPSO(GraphicsPipelineKey key)

View File

@@ -22,7 +22,7 @@ internal class D3D12Renderer : IRenderer
// NOTE: Testing only. // NOTE: Testing only.
private readonly MeshRenderPass _pass; private readonly MeshRenderPass _pass;
public IRenderTargetStrategy? RenderTargetStrategy public IRenderOutput? RenderOutput
{ {
get; set; get; set;
} }
@@ -47,31 +47,31 @@ internal class D3D12Renderer : IRenderer
public Result Render(ICommandAllocator commandAllocator) public Result Render(ICommandAllocator commandAllocator)
{ {
if (RenderTargetStrategy is null) if (RenderOutput is null)
{ {
return Result.Failure("Render target strategy is not set."); return Result.Failure("Render target strategy is not set.");
} }
var target = RenderTargetStrategy.GetRenderTarget(); var target = RenderOutput.GetRenderTarget();
if (target.IsInvalid) if (target.IsInvalid)
{ {
return Result.Failure("Render target is invalid."); return Result.Failure("Render target is invalid.");
} }
_commandBuffer.Begin(commandAllocator); _commandBuffer.Begin(commandAllocator);
RenderTargetStrategy.BeginRender(_commandBuffer); RenderOutput.BeginRender(_commandBuffer);
// NOTE: Temperary solution: render directly to the swap chain back buffer if available. // NOTE: Temperary solution: render directly to the swap chain back buffer if available.
// HACK: This is hard coded for testing purposes only. // HACK: This is hard coded for testing purposes only.
var error = RenderScene(target, RenderTargetStrategy.Viewport, RenderTargetStrategy.Scissor); var error = RenderScene(target, RenderOutput.Viewport, RenderOutput.Scissor);
if (error != ErrorStatus.None) if (error != ErrorStatus.None)
{ {
_commandBuffer.End(); _commandBuffer.End();
return Result.Failure(error); return Result.Failure(error);
} }
RenderTargetStrategy.EndRender(_commandBuffer); RenderOutput.EndRender(_commandBuffer);
var r = _commandBuffer.End(); var r = _commandBuffer.End();
if (r.IsFailure) if (r.IsFailure)
{ {
@@ -79,7 +79,7 @@ internal class D3D12Renderer : IRenderer
} }
_graphicsEngine.Device.GraphicsQueue.Submit(_commandBuffer); _graphicsEngine.Device.GraphicsQueue.Submit(_commandBuffer);
RenderTargetStrategy.Present(); RenderOutput.Present();
return Result.Success(); return Result.Success();
} }

View File

@@ -100,8 +100,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase
private UnsafeHashMap<SamplerDesc, Identifier<Sampler>> _samplers; private UnsafeHashMap<SamplerDesc, Identifier<Sampler>> _samplers;
private UnsafeSlotMap<Mesh> _meshes; private UnsafeSlotMap<Mesh> _meshes;
private UnsafeSlotMap<Material> _materials; private UnsafeSlotMap<Material> _materials;
private readonly DynamicArray<Shader?> _shaders; // NOTE: We use a simple list since shader is not frequently added/removed. This can save 4 bytes for each ecs component. private readonly DynamicArray<Shader> _shaders; // TODO: Use SlotMap?
// private UnsafeHashMap<ShaderPassKey, ShaderPass> _shaderPasses; // NOTE: The reason we use Dictionary here is that ShaderPassKey is a presistence identifier across multiple application sessions.
private bool _disposed; private bool _disposed;
@@ -116,7 +115,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase
_samplers = new UnsafeHashMap<SamplerDesc, Identifier<Sampler>>(32, Allocator.Persistent); _samplers = new UnsafeHashMap<SamplerDesc, Identifier<Sampler>>(32, Allocator.Persistent);
_meshes = new UnsafeSlotMap<Mesh>(64, Allocator.Persistent, AllocationOption.Clear); _meshes = new UnsafeSlotMap<Mesh>(64, Allocator.Persistent, AllocationOption.Clear);
_materials = new UnsafeSlotMap<Material>(16, Allocator.Persistent, AllocationOption.Clear); _materials = new UnsafeSlotMap<Material>(16, Allocator.Persistent, AllocationOption.Clear);
_shaders = new DynamicArray<Shader?>(16); _shaders = new DynamicArray<Shader>(16);
// _shaderPasses = new UnsafeHashMap<ShaderPassKey, ShaderPass>(32, Allocator.Persistent); // _shaderPasses = new UnsafeHashMap<ShaderPassKey, ShaderPass>(32, Allocator.Persistent);
} }
@@ -410,10 +409,10 @@ internal class D3D12ResourceDatabase : IResourceDatabase
public bool HasShader(Identifier<Shader> id) public bool HasShader(Identifier<Shader> id)
{ {
ObjectDisposedException.ThrowIf(_disposed, this); ObjectDisposedException.ThrowIf(_disposed, this);
return id.Value >= 0 && id.Value < _shaders.Count && _shaders[id.Value] != null; return id.Value >= 0 && id.Value < _shaders.Count;
} }
public Shader GetShaderReference(Identifier<Shader> id) public ref Shader GetShaderReference(Identifier<Shader> id)
{ {
ObjectDisposedException.ThrowIf(_disposed, this); ObjectDisposedException.ThrowIf(_disposed, this);
@@ -422,8 +421,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase
throw new ArgumentOutOfRangeException(nameof(id), $"Shader id {id} is invalid."); throw new ArgumentOutOfRangeException(nameof(id), $"Shader id {id} is invalid.");
} }
var shader = _shaders[id.Value]!; return ref _shaders[id.Value];
return shader;
} }
public void ReleaseShader(Identifier<Shader> id) public void ReleaseShader(Identifier<Shader> id)
@@ -470,11 +468,6 @@ internal class D3D12ResourceDatabase : IResourceDatabase
for (var i = 0; i < _shaders.Count; i++) for (var i = 0; i < _shaders.Count; i++)
{ {
ref var shader = ref _shaders[i]; ref var shader = ref _shaders[i];
if (shader == null)
{
continue;
}
ReleaseResource(ref shader); ReleaseResource(ref shader);
} }

View File

@@ -55,17 +55,47 @@ public readonly struct ShaderPassKey : IEquatable<ShaderPassKey>
} }
} }
public readonly struct GraphicsPipelineKey public readonly struct GraphicsPipelineKey
{ {
public const int KEY_STRING_LENGTH = 17; // 16 chars + null terminator public const int KEY_STRING_LENGTH = 17; // 16 chars + null terminator
public readonly ulong value; public readonly UInt128 value;
public GraphicsPipelineKey(ulong value) public GraphicsPipelineKey(UInt128 value)
{ {
this.value = value; this.value = value;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static GraphicsPipelineKey Combine(MaterialPipelineKey materialKey, PassPipelineKey passKey)
{
// Order-sensitive 128-bit mix. Cheap and stable, avoids span hashing.
static ulong Mix64(ulong x)
{
x ^= x >> 30;
x *= 0xBF58476D1CE4E5B9ul;
x ^= x >> 27;
x *= 0x94D049BB133111EBul;
x ^= x >> 31;
return x;
}
unsafe static ulong GetLow(UInt128 value) => ((ulong*)&value)[0];
unsafe static ulong GetHigh(UInt128 value) => ((ulong*)&value)[1];
var mLo = GetLow(materialKey.value);
var mHi = GetHigh(materialKey.value);
var pLo = GetLow(passKey.value);
var pHi = GetHigh(passKey.value);
// Distinct constants + cross-feeding to reduce structural collisions.
var lo = Mix64(mLo ^ (pLo + 0x9E3779B97F4A7C15ul) ^ (mHi * 0xD6E8FEB86659FD93ul));
var hi = Mix64(mHi ^ (pHi + 0xC2B2AE3D27D4EB4Ful) ^ (pLo * 0x165667B19E3779F9ul));
return new GraphicsPipelineKey(new UInt128(lo, hi));
}
public Result GetString(Span<char> destination) public Result GetString(Span<char> destination)
{ {
if (!value.TryFormat(destination, out _, "X16")) if (!value.TryFormat(destination, out _, "X16"))
@@ -88,49 +118,70 @@ public readonly struct GraphicsPipelineKey
} }
} }
internal struct GraphicsPipelineHash public readonly struct MaterialPipelineKey : IEquatable<MaterialPipelineKey>
{ {
[InlineArray(D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT)] public readonly UInt128 value;
public struct rtv_array
{
public TextureFormat rtvFormats;
}
public ShaderPassKey Id
{
get; set;
}
public rtv_array RtvFormats;
public uint RtvCount
{
get; set;
}
public TextureFormat DsvFormat
{
get; set;
}
// Do we need to store blend state?
// TODO: Variants // TODO: Variants
public readonly GraphicsPipelineKey GetKey() public MaterialPipelineKey(ShaderPassKey passKey, PipelineState psoOptions)
{ {
Span<ulong> data = stackalloc ulong[3 + D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT]; // 32-bit packed key for states controlled by material / overrides.
data[0] = Id.value; // layout:
data[1] = RtvCount; // 0..3 Blend (4 bits)
data[2] = (ulong)DsvFormat; // 4..6 Cull (3 bits)
// 7..10 DeafaultState (4 bits)
// 11 ZWrite (1 bit)
// 12..15 ColorMask (4 bits)
for (var i = 0; i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; i++) var key = 0u;
key |= ((uint)psoOptions.Blend & 0xFu) << 0;
key |= ((uint)psoOptions.Cull & 0x7u) << 4;
key |= ((uint)psoOptions.ZTest & 0xFu) << 7;
key |= ((uint)psoOptions.ZWrite & 0x1u) << 11;
key |= ((uint)psoOptions.ColorMask & 0xFu) << 12;
value = new UInt128(passKey.value, key);
}
public bool Equals(MaterialPipelineKey other) => value == other.value;
public override bool Equals(object? obj) => obj is MaterialPipelineKey other && Equals(other);
public override int GetHashCode() => value.GetHashCode();
public static bool operator ==(MaterialPipelineKey left, MaterialPipelineKey right) => left.Equals(right);
public static bool operator !=(MaterialPipelineKey left, MaterialPipelineKey right) => !(left == right);
}
public readonly struct PassPipelineKey : IEquatable<PassPipelineKey>
{
public readonly UInt128 value;
public PassPipelineKey(ReadOnlySpan<TextureFormat> rtvFormats, TextureFormat dsvFormat)
{
if (rtvFormats.Length > 8)
{ {
data[3 + i] = (ulong)RtvFormats[i]; throw new ArgumentException($"RTV formats length exceeds maximum supported count of {8}.");
} }
var bytes = MemoryMarshal.AsBytes(data); // layout:
return new GraphicsPipelineKey(XxHash3.HashToUInt64(bytes)); // 0..64 8 RTV formats (8 bits each)
// 64..72 DSV format (8 bits)
var rtvPart = 0UL;
for (var i = 0; i < rtvFormats.Length; i++)
{
rtvPart |= ((ulong)(byte)rtvFormats[i]) << (i * 8);
}
value = new UInt128(rtvPart, (ulong)dsvFormat);
} }
public bool Equals(PassPipelineKey other) => value == other.value;
public override bool Equals(object? obj) => obj is PassPipelineKey other && Equals(other);
public override int GetHashCode() => value.GetHashCode();
public static bool operator ==(PassPipelineKey left, PassPipelineKey right) => left.Equals(right);
public static bool operator !=(PassPipelineKey left, PassPipelineKey right) => !(left == right);
} }
public ref struct GraphicsPSODescriptor public ref struct GraphicsPSODescriptor
@@ -140,27 +191,7 @@ public ref struct GraphicsPSODescriptor
get; set; get; set;
} }
public ZTestOptions ZTest public PipelineState PipelineOption
{
get; set;
}
public ZWriteOptions ZWrite
{
get; set;
}
public CullOptions Cull
{
get; set;
}
public BlendOptions Blend
{
get; set;
}
public uint ColorMask
{ {
get; set; get; set;
} }

View File

@@ -22,5 +22,6 @@ public interface IPipelineLibrary : IDisposable
/// <param name="filePath">File path. If null, load default library.</param> /// <param name="filePath">File path. If null, load default library.</param>
void InitializeLibrary(string? filePath); void InitializeLibrary(string? filePath);
void SaveLibraryToDisk(string filePath); void SaveLibraryToDisk(string filePath);
bool HasPipeline(GraphicsPipelineKey key);
Result<GraphicsPipelineKey> CompilePSO(ref readonly GraphicsPSODescriptor descriptor, ref readonly GraphicsCompiledResult compiled); Result<GraphicsPipelineKey> CompilePSO(ref readonly GraphicsPSODescriptor descriptor, ref readonly GraphicsCompiledResult compiled);
} }

View File

@@ -8,7 +8,7 @@ namespace Ghost.Graphics.RHI;
/// </summary> /// </summary>
public interface IRenderer : IDisposable public interface IRenderer : IDisposable
{ {
IRenderTargetStrategy? RenderTargetStrategy IRenderOutput? RenderOutput
{ {
get; set; get; set;
} }

View File

@@ -168,7 +168,7 @@ public interface IResourceDatabase : IDisposable
/// </summary> /// </summary>
/// <param name="id">The identifier of the shader to retrieve. Must refer to a valid shader.</param> /// <param name="id">The identifier of the shader to retrieve. Must refer to a valid shader.</param>
/// <returns>A reference to the shader corresponding to the specified identifier.</returns> /// <returns>A reference to the shader corresponding to the specified identifier.</returns>
Shader GetShaderReference(Identifier<Shader> id); ref Shader GetShaderReference(Identifier<Shader> id);
/// <summary> /// <summary>
/// Releases the shader associated with the specified identifier, freeing any resources allocated to it. /// Releases the shader associated with the specified identifier, freeing any resources allocated to it.

View File

@@ -39,6 +39,8 @@ internal class MeshRenderPass : IRenderPass
private GraphicsCompiledResult[]? _compileResults; private GraphicsCompiledResult[]? _compileResults;
private Identifier<ShaderPass> _forwardPassID;
// Texture file paths for this demo // Texture file paths for this demo
private readonly string[] _textureFiles = [ private readonly string[] _textureFiles = [
"C:/Users/Misaki/Downloads/Im/Icon.png", "C:/Users/Misaki/Downloads/Im/Icon.png",
@@ -64,11 +66,7 @@ internal class MeshRenderPass : IRenderPass
var psoDes = new GraphicsPSODescriptor var psoDes = new GraphicsPSODescriptor
{ {
PassId = new ShaderPassKey(fullPass.Identifier), PassId = new ShaderPassKey(fullPass.Identifier),
ZTest = fullPass.localPipeline.zTest, PipelineOption = fullPass.localPipeline,
ZWrite = fullPass.localPipeline.zWrite,
Cull = fullPass.localPipeline.cull,
Blend = fullPass.localPipeline.blend,
ColorMask = fullPass.localPipeline.colorMask,
RtvFormats = [TextureFormat.B8G8R8A8_UNorm], RtvFormats = [TextureFormat.B8G8R8A8_UNorm],
DsvFormat = TextureFormat.Unknown, DsvFormat = TextureFormat.Unknown,
@@ -103,7 +101,7 @@ internal class MeshRenderPass : IRenderPass
Usage = TextureUsage.ShaderResource, Usage = TextureUsage.ShaderResource,
}; };
_textures[i] = ctx.CreateTexture(ref desc, imageData.AsSpan()); _textures[i] = ctx.CreateTexture(in desc, imageData.AsSpan());
} }
var samplerDesc = new SamplerDesc var samplerDesc = new SamplerDesc
@@ -130,11 +128,13 @@ internal class MeshRenderPass : IRenderPass
Debug.Assert(matRef.SetPropertyCache(in matProps) == ErrorStatus.None); Debug.Assert(matRef.SetPropertyCache(in matProps) == ErrorStatus.None);
matRef.UploadData(ctx.DirectCommandBuffer); matRef.UploadData(ctx.DirectCommandBuffer);
_forwardPassID = Shader.GetPassID("Forward");
} }
public void Execute(ref readonly RenderingContext ctx) public void Execute(ref readonly RenderingContext ctx)
{ {
ctx.DispatchMesh(_mesh, _material, "Forward", 3); ctx.DispatchMesh(_mesh, _material, _forwardPassID, 3);
} }
public void Cleanup(IResourceDatabase resourceDatabase) public void Cleanup(IResourceDatabase resourceDatabase)

View File

@@ -18,7 +18,7 @@ shader "MyShader/Standard"
zwrite = off; zwrite = off;
cull = off; cull = off;
blend = opaque; blend = opaque;
color_mask = 15; color_mask = all;
} }
ms("F:/csharp/GhostEngine/Ghost.Graphics/RenderPasses/ShaderCode.hlsl", "MSMain"); ms("F:/csharp/GhostEngine/Ghost.Graphics/RenderPasses/ShaderCode.hlsl", "MSMain");

View File

@@ -5,7 +5,7 @@ using System.Numerics;
//ShaderStructGenerator.GenerateHLSL([typeof(TestStruct), typeof(TestEnum), typeof(TestEnumFlags)], PackingRules.Exact, "C:/Users/Misaki/Downloads/Archive/Test.cs.hlsl"); //ShaderStructGenerator.GenerateHLSL([typeof(TestStruct), typeof(TestEnum), typeof(TestEnumFlags)], PackingRules.Exact, "C:/Users/Misaki/Downloads/Archive/Test.cs.hlsl");
//return; //return;
#if false
var source = File.ReadAllText("F:/csharp/GhostEngine/Ghost.Graphics/test.gshader"); var source = File.ReadAllText("F:/csharp/GhostEngine/Ghost.Graphics/test.gshader");
var lexer = new Lexer(source); var lexer = new Lexer(source);
@@ -33,7 +33,7 @@ var descriptor = SDLCompiler.ResolveShader(model);
SDLCompiler.GenerateShaderCode(descriptor, "C:/Users/Misaki/Downloads/Archive"); SDLCompiler.GenerateShaderCode(descriptor, "C:/Users/Misaki/Downloads/Archive");
Console.WriteLine("Shader compiled successfully:"); Console.WriteLine("Shader compiled successfully:");
#endif
public struct TestStruct public struct TestStruct
{ {

View File

@@ -53,117 +53,71 @@ internal class PipelineBlock : IBlockParser<PipelineSyntax, PipelineSemantic>
switch (valueDecl.name.lexeme) switch (valueDecl.name.lexeme)
{ {
case TokenLexicon.KnownPipelineProperties.ZTEST: case TokenLexicon.KnownPipelineProperties.ZTEST:
switch (valueDecl.value.lexeme) if (Enum.TryParse<ZTest>(valueDecl.value.lexeme, true, out var zTest))
{ {
case "disable": semantic.zTest = zTest;
semantic.zTest = ZTestOptions.Disabled; }
break; else
case "less": {
semantic.zTest = ZTestOptions.Less; errors.Add(new SDLError
break; {
case "less_equal": message = $"Invalid ZTest option: {valueDecl.value.lexeme}",
semantic.zTest = ZTestOptions.LessEqual; line = valueDecl.value.line,
break; column = valueDecl.value.column
case "equal": });
semantic.zTest = ZTestOptions.Equal;
break;
case "greater_equal":
semantic.zTest = ZTestOptions.GreaterEqual;
break;
case "greater":
semantic.zTest = ZTestOptions.Greater;
break;
case "not_equal":
semantic.zTest = ZTestOptions.NotEqual;
break;
case "always":
semantic.zTest = ZTestOptions.Always;
break;
default:
errors.Add(new SDLError
{
message = $"Invalid ZTest option: {valueDecl.value.lexeme}",
line = valueDecl.value.line,
column = valueDecl.value.column
});
break;
} }
break; break;
case TokenLexicon.KnownPipelineProperties.ZWRITE: case TokenLexicon.KnownPipelineProperties.ZWRITE:
switch (valueDecl.value.lexeme) if (Enum.TryParse<ZWrite>(valueDecl.value.lexeme, true, out var zWrite))
{ {
case "on": semantic.zWrite = zWrite;
semantic.zWrite = ZWriteOptions.On; }
break; else
case "off": {
semantic.zWrite = ZWriteOptions.Off; errors.Add(new SDLError
break; {
default: message = $"Invalid ZWrite option: {valueDecl.value.lexeme}",
errors.Add(new SDLError line = valueDecl.value.line,
{ column = valueDecl.value.column
message = $"Invalid ZWrite option: {valueDecl.value.lexeme}", });
line = valueDecl.value.line,
column = valueDecl.value.column
});
break;
} }
break; break;
case TokenLexicon.KnownPipelineProperties.CULL: case TokenLexicon.KnownPipelineProperties.CULL:
switch (valueDecl.value.lexeme) if (Enum.TryParse<Cull>(valueDecl.value.lexeme, true, out var cull))
{ {
case "off": semantic.cull = cull;
semantic.cull = CullOptions.Off; }
break; else
case "front": {
semantic.cull = CullOptions.Front; errors.Add(new SDLError
break; {
case "back": message = $"Invalid Cull option: {valueDecl.value.lexeme}",
semantic.cull = CullOptions.Back; line = valueDecl.value.line,
break; column = valueDecl.value.column
default: });
errors.Add(new SDLError
{
message = $"Invalid Cull option: {valueDecl.value.lexeme}",
line = valueDecl.value.line,
column = valueDecl.value.column
});
break;
} }
break; break;
case TokenLexicon.KnownPipelineProperties.BLEND: case TokenLexicon.KnownPipelineProperties.BLEND:
switch (valueDecl.value.lexeme) if (Enum.TryParse<Blend>(valueDecl.value.lexeme, true, out var blend))
{ {
case "opaque": semantic.blend = blend;
semantic.blend = BlendOptions.Opaque; }
break; else
case "alpha": {
semantic.blend = BlendOptions.Alpha; errors.Add(new SDLError
break; {
case "additive": message = $"Invalid Blend option: {valueDecl.value.lexeme}",
semantic.blend = BlendOptions.Additive; line = valueDecl.value.line,
break; column = valueDecl.value.column
case "multiply": });
semantic.blend = BlendOptions.Multiply;
break;
case "premultiplied":
semantic.blend = BlendOptions.PremultipliedAlpha;
break;
default:
errors.Add(new SDLError
{
message = $"Invalid Blend option: {valueDecl.value.lexeme}",
line = valueDecl.value.line,
column = valueDecl.value.column
});
break;
} }
break; break;
case TokenLexicon.KnownPipelineProperties.COLORMASK: case TokenLexicon.KnownPipelineProperties.COLORMASK:
if (uint.TryParse(valueDecl.value.lexeme, out var colorMask)) if (Enum.TryParse<ColorWriteMask>(valueDecl.value.lexeme, true, out var colorMask))
{ {
semantic.colorMask = colorMask; semantic.colorMask = colorMask;
} }

View File

@@ -128,20 +128,20 @@ internal static class SDLCompiler
return $"{shader.name}_{pass.name}"; return $"{shader.name}_{pass.name}";
} }
private static PipelineDescriptor MeragePipeline(PipelineSemantic? semantic, PipelineDescriptor parent) private static PipelineState MeragePipeline(PipelineSemantic? semantic, PipelineState parent)
{ {
if (semantic == null) if (semantic == null)
{ {
return parent; return parent;
} }
return new PipelineDescriptor return new PipelineState
{ {
zTest = semantic.zTest ?? parent.zTest, ZTest = semantic.zTest ?? parent.ZTest,
zWrite = semantic.zWrite ?? parent.zWrite, ZWrite = semantic.zWrite ?? parent.ZWrite,
cull = semantic.cull ?? parent.cull, Cull = semantic.cull ?? parent.Cull,
blend = semantic.blend ?? parent.blend, Blend = semantic.blend ?? parent.Blend,
colorMask = semantic.colorMask ?? parent.colorMask ColorMask = semantic.colorMask ?? parent.ColorMask
}; };
} }
@@ -208,7 +208,7 @@ internal static class SDLCompiler
{ {
foreach (var pass in semantics.passes) foreach (var pass in semantics.passes)
{ {
var localPipeline = MeragePipeline(pass.localPipeline, PipelineDescriptor.Default); var localPipeline = MeragePipeline(pass.localPipeline, PipelineState.Default);
var fullPass = new FullPassDescriptor var fullPass = new FullPassDescriptor
{ {
uniqueIdentifier = GetPassUniqueId(semantics, pass), uniqueIdentifier = GetPassUniqueId(semantics, pass),

View File

@@ -18,11 +18,11 @@ internal class PropertySemantic
internal class PipelineSemantic internal class PipelineSemantic
{ {
public ZTestOptions? zTest; public ZTest? zTest;
public ZWriteOptions? zWrite; public ZWrite? zWrite;
public CullOptions? cull; public Cull? cull;
public BlendOptions? blend; public Blend? blend;
public uint? colorMask; public ColorWriteMask? colorMask;
} }
internal class PassSemantic internal class PassSemantic