feat(render): refactor pipeline & shader system for DX12 WG
Major refactor of render pipeline and shader system: - Replaced legacy shader properties with source generator and attribute-based HLSL struct generation. - Introduced ShaderPropertiesRegistry for runtime property layout/code registration. - Added modular IRenderPipeline, IRenderPipelineSettings, and IRenderPayload interfaces. - Implemented GhostRenderPipeline and ECS-driven GPUScene management. - Added experimental DirectX 12 Work Graph support. - Refactored shader compilation, variant hashing, and caching. - Updated APIs for consistency and improved codegen for registration. These changes modernize the rendering infrastructure for advanced features like work graphs and dynamic pipelines. BREAKING CHANGE: Shader DSL, pipeline, and property APIs have changed. Existing shaders and pipeline integrations must be updated.
This commit is contained in:
@@ -111,17 +111,17 @@ public struct Material : IResourceReleasable
|
||||
};
|
||||
}
|
||||
|
||||
if (shader.CBufferSize != 0)
|
||||
if (shader.PropertyBufferSize != 0)
|
||||
{
|
||||
var desc = new BufferDesc
|
||||
{
|
||||
Size = shader.CBufferSize,
|
||||
Size = shader.PropertyBufferSize,
|
||||
Usage = BufferUsage.Raw | BufferUsage.ShaderResource,
|
||||
HeapType = HeapType.Default,
|
||||
};
|
||||
|
||||
var buffer = resourceAllocator.CreateBuffer(ref desc, "MaterialCBuffer");
|
||||
_cBufferCache = new CBufferCache(buffer, shader.CBufferSize);
|
||||
_cBufferCache = new CBufferCache(buffer, shader.PropertyBufferSize);
|
||||
}
|
||||
|
||||
return Error.None;
|
||||
|
||||
@@ -14,18 +14,18 @@ namespace Ghost.Graphics.Core;
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Meshlet
|
||||
{
|
||||
public SphereBounds boundingSphere; // 16 bytes
|
||||
public SphereBounds parentBoundingSphere; // 16 bytes
|
||||
public AABB boundingBox; // 24 bytes
|
||||
public uint vertexOffset; // offset into meshlet vertex index array
|
||||
public uint triangleOffset; // offset into packed triangle array
|
||||
public uint groupIndex; // owning group
|
||||
public float clusterError; // geometric error of this meshlet/cluster
|
||||
public float parentError; // geometric refinement error carried into runtime LOD tests
|
||||
public byte vertexCount; // max 64
|
||||
public byte triangleCount; // max 124
|
||||
public byte localMaterialIndex; // mesh-local material slot
|
||||
public byte lodLevel; // this meshlet's LOD level
|
||||
public SphereBounds boundingSphere; // 16 bytes
|
||||
public SphereBounds parentBoundingSphere; // 16 bytes
|
||||
public AABB boundingBox; // 24 bytes
|
||||
public uint vertexOffset; // offset into meshlet vertex index array
|
||||
public uint triangleOffset; // offset into packed triangle array
|
||||
public uint groupIndex; // owning group
|
||||
public float clusterError; // geometric error of this meshlet/cluster
|
||||
public float parentError; // geometric refinement error carried into runtime LOD tests
|
||||
public byte vertexCount; // max 64
|
||||
public byte triangleCount; // max 124
|
||||
public byte localMaterialIndex; // mesh-local material slot
|
||||
public byte lodLevel; // this meshlet's LOD level
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Core.Graphics;
|
||||
using Ghost.Core.Utilities;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
@@ -83,19 +84,20 @@ public partial struct Shader : IResourceReleasable
|
||||
// We can use a int array since the number and index of tags are fixed at compile time.
|
||||
|
||||
public readonly int PassCount => _shaderPasses.Count;
|
||||
public readonly uint CBufferSize => _cbufferSize;
|
||||
public readonly uint PropertyBufferSize => _cbufferSize;
|
||||
|
||||
internal Shader(ShaderDescriptor descriptor)
|
||||
internal Shader(ShaderDescriptor descriptor, ref readonly GraphicsCompiledResult compiledResult)
|
||||
{
|
||||
_cbufferSize = (uint)descriptor.cbufferSize;
|
||||
_cbufferSize = descriptor.propertyBufferSize;
|
||||
_shaderPasses = new UnsafeArray<ShaderPass>(descriptor.passes.Length, Allocator.Persistent);
|
||||
_passIDToLocal = new UnsafeHashMap<int, int>(descriptor.passes.Length, Allocator.Persistent);
|
||||
_keywordIDToLocal = new UnsafeHashMap<int, int>(32, Allocator.Persistent);
|
||||
|
||||
for (var i = 0; i < descriptor.passes.Length; i++)
|
||||
{
|
||||
var pass = descriptor.passes[i];
|
||||
var passKey = RHIUtility.CreateShaderPassKey(pass.identifier);
|
||||
ref readonly var pass = ref descriptor.passes[i];
|
||||
|
||||
var passKey = RHIUtility.CreateShaderPassKey(pass.identifier, compiledResult.HashCode);
|
||||
var keywords = default(LocalKeywordSet);
|
||||
|
||||
if (pass.keywords.Length > 0)
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ghost.Graphics;
|
||||
|
||||
public unsafe class GPUScene : IDisposable
|
||||
{
|
||||
private readonly IResourceAllocator _resourceAllocator;
|
||||
private readonly IResourceDatabase _resourceDatabase;
|
||||
|
||||
private Handle<GPUBuffer> _sceneBuffer;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
internal GPUScene(IResourceAllocator resourceAllocator, IResourceDatabase resourceDatabase, ulong initialCount)
|
||||
{
|
||||
_resourceAllocator = resourceAllocator;
|
||||
_resourceDatabase = resourceDatabase;
|
||||
|
||||
var bufferDesc = new BufferDesc
|
||||
{
|
||||
Size = initialCount * (ulong)sizeof(InstanceData),
|
||||
Stride = (uint)sizeof(InstanceData),
|
||||
Usage = BufferUsage.Structured | BufferUsage.UnorderedAccess | BufferUsage.ShaderResource,
|
||||
HeapType = HeapType.Default,
|
||||
};
|
||||
|
||||
_sceneBuffer = _resourceAllocator.CreateBuffer(in bufferDesc, "SceneBuffer");
|
||||
|
||||
Debug.Assert(_sceneBuffer.IsValid, "Failed to create GPUScene buffer.");
|
||||
}
|
||||
|
||||
~GPUScene()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_resourceDatabase.ReleaseResource(_sceneBuffer.AsResource());
|
||||
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
131
src/Runtime/Ghost.Graphics/IRenderPipeline.cs
Normal file
131
src/Runtime/Ghost.Graphics/IRenderPipeline.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
|
||||
namespace Ghost.Graphics;
|
||||
|
||||
public interface IRenderPayload : IDisposable
|
||||
{
|
||||
ReadOnlySpan<RenderRequest> RenderRequests { get; }
|
||||
|
||||
void AddRenderRequest(ref readonly RenderRequest renderRequest);
|
||||
void Reset();
|
||||
}
|
||||
|
||||
public interface IRenderPipelineSettings
|
||||
{
|
||||
IRenderPipeline CreatePipeline(RenderSystem renderSystem);
|
||||
IRenderPayload CreatePayload(RenderSystem renderSystem, IRenderPipeline renderPipeline);
|
||||
}
|
||||
|
||||
public interface IRenderPipeline : IDisposable
|
||||
{
|
||||
void Render(RenderContext ctx, int frameIndex, IRenderPayload payload);
|
||||
}
|
||||
|
||||
|
||||
public static class RenderPipelineUtility
|
||||
{
|
||||
public static bool GetViewAndProjectionMatrices(RenderSystem renderSystem, ref readonly RenderRequest request, out float4x4 view, out float4x4 projection, out uint2 screenSize)
|
||||
{
|
||||
Handle<GPUTexture> rtHandle;
|
||||
if (request.swapChainIndex < 0)
|
||||
{
|
||||
rtHandle = request.colorTarget;
|
||||
}
|
||||
else if (renderSystem.SwapChainManager.TryGetSwapChain(request.swapChainIndex, out var swapChain))
|
||||
{
|
||||
rtHandle = swapChain.GetCurrentBackBuffer();
|
||||
}
|
||||
else
|
||||
{
|
||||
view = default;
|
||||
projection = default;
|
||||
screenSize = default;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var rtResult = renderSystem.GraphicsEngine.ResourceDatabase.GetResourceDescription(rtHandle.AsResource());
|
||||
if (rtResult.IsFailure)
|
||||
{
|
||||
view = default;
|
||||
projection = default;
|
||||
screenSize = default;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
screenSize = new uint2(rtResult.Value.TextureDescription.Width, rtResult.Value.TextureDescription.Height);
|
||||
var aspectScreen = (float)screenSize.x / screenSize.y;
|
||||
|
||||
view = math.inverse(request.view.localToWorld);
|
||||
|
||||
var vfov = 2.0f * math.atan(request.view.sensorSize.y / (2.0f * request.view.focalLength));
|
||||
var hfov = 2.0f * math.atan(request.view.sensorSize.x / (2.0f * request.view.focalLength));
|
||||
var aspectSensor = request.view.sensorSize.x / request.view.sensorSize.y;
|
||||
|
||||
float vfovF;
|
||||
switch (request.view.gateFit)
|
||||
{
|
||||
case GateFit.Vertical:
|
||||
vfovF = vfov;
|
||||
break;
|
||||
|
||||
case GateFit.Horizontal:
|
||||
// Adjust VFOV so that the sensor width fits the screen width
|
||||
var horizontalAspectBuffer = math.tan(hfov * 0.5f);
|
||||
vfovF = 2.0f * math.atan(horizontalAspectBuffer / aspectScreen);
|
||||
break;
|
||||
|
||||
case GateFit.Fill:
|
||||
if (aspectSensor > aspectScreen)
|
||||
{
|
||||
goto case GateFit.Vertical;
|
||||
}
|
||||
else
|
||||
{
|
||||
goto case GateFit.Horizontal;
|
||||
}
|
||||
|
||||
case GateFit.Overscan:
|
||||
if (aspectSensor > aspectScreen)
|
||||
{
|
||||
goto case GateFit.Horizontal;
|
||||
}
|
||||
else
|
||||
{
|
||||
goto case GateFit.Vertical;
|
||||
}
|
||||
default:
|
||||
vfovF = vfov;
|
||||
break;
|
||||
}
|
||||
|
||||
var m_11 = 1.0f / math.tan(vfovF * 0.5f);
|
||||
var m_00 = m_11 / aspectScreen;
|
||||
var m_22 = request.view.farClipPlane / (request.view.farClipPlane - request.view.nearClipPlane);
|
||||
var m_23 = -(request.view.farClipPlane * request.view.nearClipPlane) / (request.view.farClipPlane - request.view.nearClipPlane);
|
||||
|
||||
projection = new float4x4
|
||||
(
|
||||
m_00, 0, 0, 0,
|
||||
0, m_11, 0, 0,
|
||||
0, 0, m_22, m_23,
|
||||
0, 0, 1, 0
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (request.swapChainIndex >= 0)
|
||||
{
|
||||
renderSystem.SwapChainManager.ReleaseSwapChain(request.swapChainIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -189,7 +189,7 @@ internal sealed class RenderGraphContext : IUnsafeRenderContext
|
||||
};
|
||||
|
||||
var compiled = compiledCacheResult.Value;
|
||||
_pipelineLibrary.CompilePSO(in psoDes, in compiled).GetValueOrThrow();
|
||||
_pipelineLibrary.CreatePSO(in psoDes, in compiled).GetValueOrThrow();
|
||||
}
|
||||
|
||||
_activePerMaterialData = material._cBufferCache.GpuResource;
|
||||
|
||||
@@ -150,7 +150,7 @@ internal sealed class RenderGraphExecutor
|
||||
: AttachmentStoreOp.NoAccess,
|
||||
};
|
||||
|
||||
commandBuffer.BeginRenderPass(new Span<PassRenderTargetDesc>(pPassRTDescs, nativePass.colorAttachmentCount), depthDesc);
|
||||
commandBuffer.BeginRenderPass(new Span<PassRenderTargetDesc>(pPassRTDescs, nativePass.colorAttachmentCount), in depthDesc);
|
||||
|
||||
for (var i = 0; i < nativePass.colorAttachmentCount; i++)
|
||||
{
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
using Ghost.Graphics.Core;
|
||||
|
||||
namespace Ghost.Graphics.RenderPipeline;
|
||||
|
||||
public interface IRenderPayload : IDisposable
|
||||
{
|
||||
void Reset();
|
||||
}
|
||||
|
||||
public interface IRenderPipelineSettings
|
||||
{
|
||||
IRenderPipeline CreatePipeline(RenderSystem renderSystem);
|
||||
IRenderPayload CreatePayload(RenderSystem renderSystem);
|
||||
}
|
||||
|
||||
public interface IRenderPipeline : IDisposable
|
||||
{
|
||||
void Render(RenderContext ctx, int frameIndex, IRenderPayload payload);
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.Core;
|
||||
using Ghost.Graphics.D3D12;
|
||||
using Ghost.Graphics.RenderPipeline;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
using System.Collections.Concurrent;
|
||||
@@ -130,7 +129,7 @@ public class RenderSystem : IDisposable
|
||||
_renderPipeline = _renderPipelineSettings.CreatePipeline(this);
|
||||
for (var i = 0; i < _frameResources.Length; i++)
|
||||
{
|
||||
_frameResources[i].RenderPayload = _renderPipelineSettings.CreatePayload(this);
|
||||
_frameResources[i].RenderPayload = _renderPipelineSettings.CreatePayload(this, _renderPipeline);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -193,7 +192,7 @@ public class RenderSystem : IDisposable
|
||||
_renderPipeline = _renderPipelineSettings.CreatePipeline(this);
|
||||
for (var i = 0; i < _frameResources.Length; i++)
|
||||
{
|
||||
_frameResources[i].RenderPayload = _renderPipelineSettings.CreatePayload(this);
|
||||
_frameResources[i].RenderPayload = _renderPipelineSettings.CreatePayload(this, _renderPipeline);
|
||||
}
|
||||
|
||||
_isRunning = false;
|
||||
|
||||
@@ -5,6 +5,7 @@ using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Ghost.Graphics;
|
||||
|
||||
@@ -141,11 +142,11 @@ public sealed partial class ResourceManager : IDisposable
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="Identifier{Shader}"/> representing the newly created shader.</returns>
|
||||
/// <param name="descriptor">The viewGroup containing the shader's properties and passes.</param>
|
||||
public Identifier<Shader> CreateGraphicsShader(ShaderDescriptor descriptor)
|
||||
public Identifier<Shader> CreateGraphicsShader(ShaderDescriptor descriptor, ref readonly GraphicsCompiledResult compiledResult)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
|
||||
var shader = new Shader(descriptor);
|
||||
var shader = new Shader(descriptor, in compiledResult);
|
||||
|
||||
var id = _shaders.Count;
|
||||
_shaders.Add(shader);
|
||||
|
||||
@@ -1,15 +1,5 @@
|
||||
shader "MyShader/Standard"
|
||||
{
|
||||
properties
|
||||
{
|
||||
//float4 color = { 1, 1, 1, 1 };
|
||||
//tex2d texture1 = { black };
|
||||
//tex2d texture2 = { white };
|
||||
//tex2d texture3 = { grey };
|
||||
//tex2d texture4 = { normal };
|
||||
//sampler tex_sampler;
|
||||
}
|
||||
|
||||
pass "Forward"
|
||||
{
|
||||
pipeline
|
||||
|
||||
Reference in New Issue
Block a user