forked from Misaki/GhostEngine
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:
@@ -1,3 +1,4 @@
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
using System.Drawing;
|
||||
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)
|
||||
{
|
||||
return r.Equals(other.r) && g.Equals(other.g) && b.Equals(other.b) && a.Equals(other.a);
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Core.Graphics;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.LowLevel;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ghost.Graphics.Core;
|
||||
|
||||
#if false
|
||||
public struct VariantMask
|
||||
{
|
||||
private ulong _mask;
|
||||
}
|
||||
#endif
|
||||
|
||||
internal struct CBufferCache : IResourceReleasable
|
||||
{
|
||||
private UnsafeArray<byte> _cpuData;
|
||||
@@ -49,8 +56,16 @@ internal struct CBufferCache : IResourceReleasable
|
||||
|
||||
public struct Material : IResourceReleasable, IHandleType
|
||||
{
|
||||
private struct PipelineOverride
|
||||
{
|
||||
public ShaderPassKey shaderPass;
|
||||
public PipelineState options;
|
||||
public MaterialPipelineKey pipelineKey;
|
||||
}
|
||||
|
||||
private Identifier<Shader> _shader;
|
||||
private CBufferCache _cBufferCache;
|
||||
private UnsafeArray<PipelineOverride> _passPipelineOverride;
|
||||
|
||||
internal readonly CBufferCache CBufferCache => _cBufferCache;
|
||||
|
||||
@@ -67,6 +82,30 @@ public struct Material : IResourceReleasable, IHandleType
|
||||
_shader = 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)
|
||||
{
|
||||
var desc = new BufferDesc
|
||||
@@ -100,7 +139,7 @@ public struct Material : IResourceReleasable, IHandleType
|
||||
{
|
||||
if (_cBufferCache.Size == 0)
|
||||
{
|
||||
return Span<byte>.Empty;
|
||||
return [];
|
||||
}
|
||||
|
||||
return _cBufferCache.CpuData.AsSpan(0, (int)_cBufferCache.Size);
|
||||
@@ -138,6 +177,26 @@ public struct Material : IResourceReleasable, IHandleType
|
||||
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)]
|
||||
void IResourceReleasable.ReleaseResource(IResourceDatabase database)
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@ using Ghost.Graphics.RHI;
|
||||
|
||||
namespace Ghost.Graphics.Core;
|
||||
|
||||
internal class SwapChainTargetStrategy : IRenderTargetStrategy
|
||||
internal class SwapChainRenderOutput : IRenderOutput
|
||||
{
|
||||
private readonly ISwapChain _swapChain;
|
||||
|
||||
@@ -18,7 +18,7 @@ internal class SwapChainTargetStrategy : IRenderTargetStrategy
|
||||
get; set;
|
||||
}
|
||||
|
||||
public SwapChainTargetStrategy(ISwapChain swapChain)
|
||||
public SwapChainRenderOutput(ISwapChain swapChain)
|
||||
{
|
||||
_swapChain = swapChain;
|
||||
|
||||
@@ -47,7 +47,7 @@ internal class SwapChainTargetStrategy : IRenderTargetStrategy
|
||||
}
|
||||
}
|
||||
|
||||
internal class TextureTargetStrategy : IRenderTargetStrategy
|
||||
internal class TextureRenderOutput : IRenderOutput
|
||||
{
|
||||
private readonly Handle<Texture> _texture;
|
||||
|
||||
@@ -61,7 +61,7 @@ internal class TextureTargetStrategy : IRenderTargetStrategy
|
||||
get; set;
|
||||
}
|
||||
|
||||
public TextureTargetStrategy(Handle<Texture> texture)
|
||||
public TextureRenderOutput(Handle<Texture> texture)
|
||||
{
|
||||
_texture = texture;
|
||||
}
|
||||
@@ -5,6 +5,7 @@ using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
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.
|
||||
// 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 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);
|
||||
if (keyResult.Error != ErrorStatus.None)
|
||||
var passIndex = shader.GetPassIndex(passID);
|
||||
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,
|
||||
RtvCount = 1,
|
||||
DsvFormat = TextureFormat.Unknown,
|
||||
};
|
||||
// TODO: Compile pso if not exist.
|
||||
// _engine.PipelineLibrary.CompilePSO(pipelineKey, ref shader, passIndex, materialRef.GetPassPipelineOverride());
|
||||
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.SetConstantBufferView(RootSignatureLayout.PER_OBJECT_BUFFER_SLOT, meshRef.ObjectDataBuffer);
|
||||
|
||||
// NOTE: We use fixed root signature layout for bindless rendering.
|
||||
|
||||
@@ -6,63 +6,61 @@ using Misaki.HighPerformance.LowLevel.Collections;
|
||||
|
||||
namespace Ghost.Graphics.Core;
|
||||
|
||||
public struct ShaderPass : IResourceReleasable
|
||||
public readonly struct ShaderPass : IResourceReleasable
|
||||
{
|
||||
public ShaderPassKey Identifier
|
||||
{
|
||||
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)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
/// A representation of a GPU shader, including all the passes it contains.
|
||||
/// </summary>
|
||||
public class Shader : IResourceReleasable, IIdentifierType
|
||||
public partial struct Shader : IResourceReleasable, IIdentifierType
|
||||
{
|
||||
private readonly uint _cbufferSize;
|
||||
private UnsafeArray<ShaderPass> _passes;
|
||||
// TODO: Optmize lookups with a better data structure if needed
|
||||
private readonly Dictionary<string, int> _passLookup; // pass name to index
|
||||
private UnsafeArray<ShaderPass> _shaderPasses;
|
||||
private UnsafeHashMap<int, int> _passLookup; // pass id to index
|
||||
|
||||
public int PassCount => _passes.Count;
|
||||
public uint CBufferSize => _cbufferSize;
|
||||
public readonly int PassCount => _shaderPasses.Count;
|
||||
public readonly uint CBufferSize => _cbufferSize;
|
||||
|
||||
internal Shader(ShaderDescriptor descriptor)
|
||||
{
|
||||
_cbufferSize = descriptor.cbufferSize;
|
||||
_passes = new UnsafeArray<ShaderPass>(descriptor.passes.Count, Allocator.Persistent);
|
||||
_passLookup = new Dictionary<string, int>(descriptor.passes.Count);
|
||||
_shaderPasses = new UnsafeArray<ShaderPass>(descriptor.passes.Count, Allocator.Persistent);
|
||||
_passLookup = new UnsafeHashMap<int, int>(descriptor.passes.Count, Allocator.Persistent);
|
||||
|
||||
for (var i = 0; i < descriptor.passes.Count; i++)
|
||||
{
|
||||
@@ -76,45 +74,56 @@ public class Shader : IResourceReleasable, IIdentifierType
|
||||
|
||||
var passKey = new ShaderPassKey(pass.Identifier);
|
||||
|
||||
_passes[i] = new ShaderPass
|
||||
_shaderPasses[i] = new ShaderPass
|
||||
{
|
||||
Identifier = passKey,
|
||||
ZTest = fullPass.localPipeline.zTest,
|
||||
ZWrite = fullPass.localPipeline.zWrite,
|
||||
Cull = fullPass.localPipeline.cull,
|
||||
Blend = fullPass.localPipeline.blend,
|
||||
ColorMask = fullPass.localPipeline.colorMask
|
||||
DeafaultState = fullPass.localPipeline
|
||||
};
|
||||
|
||||
_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);
|
||||
if (index == -1)
|
||||
return _shaderPasses[index];
|
||||
}
|
||||
|
||||
public readonly Result<ShaderPass, ErrorStatus> TryGetPass(Identifier<ShaderPass> passID, out int passIndex)
|
||||
{
|
||||
if (_passLookup.TryGetValue(passID.Value, out var index))
|
||||
{
|
||||
passIndex = -1;
|
||||
return ErrorStatus.NotFound;
|
||||
}
|
||||
|
||||
passIndex = index;
|
||||
return RefResult<ShaderPass, ErrorStatus>.Success(ref _passes[index]);
|
||||
return _shaderPasses[index];
|
||||
}
|
||||
|
||||
void IResourceReleasable.ReleaseResource(IResourceDatabase database)
|
||||
{
|
||||
_passes.Dispose();
|
||||
_shaderPasses.Dispose();
|
||||
_passLookup.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user