feat(render): support per-frame render payloads

Refactored the render pipeline system to introduce per-frame IRenderPayload management.
IRenderPipelineSettings now requires CreatePipeline and CreatePayload methods.
Updated RenderSystem and test pipeline to use the new payload model.
Removed legacy GhostRenderPipeline and test code.
Added RenderPipelineSystemAttribute for pipeline system registration.
Includes minor fixes such as version field type corrections and typo fixes.

BREAKING CHANGE: Render pipeline and payload creation APIs have changed; implementers must update to the new interface methods.
This commit is contained in:
2026-04-07 17:12:01 +09:00
parent 6c96d4cf50
commit a5c10cfe5a
16 changed files with 162 additions and 270 deletions

View File

@@ -1,112 +0,0 @@
#if flase
using Ghost.Core;
using Ghost.Graphics.Core;
using Ghost.Graphics.RenderGraphModule;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.Mathematics;
using System.Runtime.InteropServices;
namespace Ghost.Graphics.RenderPipeline;
public partial class GhostRenderPipeline
{
private class MeshRenderPassData
{
public RenderList renderList;
public Identifier<RGTexture> renderTarget;
}
private class BlitPassData
{
public Identifier<RGTexture> source;
public Identifier<RGTexture> destination;
public Handle<Material> blitMaterial;
public Identifier<Sampler> sampler;
}
[StructLayout(LayoutKind.Sequential)]
private struct ShaderProperties_MyShader_Standard
{
public float4 color;
public uint texture1;
public uint texture2;
public uint texture3;
public uint texture4;
public uint tex_sampler;
private readonly uint _padding1;
private readonly uint _padding2;
private readonly uint _padding3;
}
[StructLayout(LayoutKind.Sequential)]
private struct ShaderProperties_Hidden_Blit
{
public uint mainTex;
public uint sampler_mainTex;
private readonly uint _padding1;
private readonly uint _padding2;
}
private void RenderTest(RenderGraph graph, Identifier<RGTexture> backbuffer)
{
Identifier<RGTexture> renderTarget;
using (var builder = graph.AddRasterRenderPass<MeshRenderPassData>("Mesh Render Pass", out var passData))
{
passData.mesh = _mesh;
passData.material = _material;
passData.renderTarget = builder.CreateTexture(RGTextureDesc.Relative(1.0f, TextureFormat.R8G8B8A8_UNorm), "Render Target");
builder.SetColorAttachment(passData.renderTarget, 0);
renderTarget = passData.renderTarget;
builder.SetRenderFunc<MeshRenderPassData>(static (data, ctx) =>
{
ctx.SetActiveMaterial(data.material);
ctx.SetActiveMesh(data.mesh);
var threadGroupCountX = ((uint)ctx.ActiveMeshIndexCount + 2u) / 3u;
ctx.DispatchMesh(new uint3(threadGroupCountX, 1u, 1u));
});
}
using (var builder = graph.AddUnsafeRenderPass<BlitPassData>("Blit Pass", out var passData))
{
passData.source = renderTarget;
passData.destination = backbuffer;
passData.blitMaterial = _blitMaterial;
passData.sampler = _sampler;
builder.UseTexture(passData.source, AccessFlags.Read);
builder.UseTexture(passData.destination, AccessFlags.WriteAll);
builder.SetRenderFunc<BlitPassData>(static (data, ctx) =>
{
var r = ctx.ResourceManager.GetMaterialReference(data.blitMaterial);
if (r.IsFailure)
{
return;
}
ref var matRef = ref r.Value;
var blitProps = new ShaderProperties_Hidden_Blit
{
mainTex = ctx.ResourceDatabase.GetBindlessIndex(ctx.GetActualResource(data.source.AsResource())),
sampler_mainTex = (uint)data.sampler.Value,
};
matRef.SetPropertyCache(in blitProps).ThrowIfFailed();
matRef.UploadData(ctx.CommandBuffer, ctx.ResourceDatabase);
ctx.CommandBuffer.SetRenderTargets([ctx.GetActualTexture(data.destination)], Handle<Texture>.Invalid);
ctx.SetActiveMaterial(data.blitMaterial);
ctx.SetActiveMesh(Handle<Mesh>.Invalid); // Generate a full-screen triangle dynamically in mesh shader.
ctx.DispatchMesh(new uint3(1, 1, 1));
});
}
}
}
# endif

View File

@@ -1,71 +0,0 @@
using Ghost.Graphics.Core;
using Ghost.Graphics.RenderGraphModule;
using Ghost.Graphics.RHI;
using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace Ghost.Graphics.RenderPipeline;
public sealed class GhostRenderPipelineSettings : IRenderPipelineSettings
{
IRenderPipeline IRenderPipelineSettings.CreatePipeline(RenderSystem renderSystem)
{
return new GhostRenderPipeline(renderSystem);
}
}
internal unsafe partial class GhostRenderPipeline : IRenderPipeline
{
private readonly RenderGraph _renderGraph;
private bool _disposed;
~GhostRenderPipeline()
{
Dispose();
}
[Conditional("DEBUG")]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void ThrowIfDisposed()
{
ObjectDisposedException.ThrowIf(_disposed, this);
}
internal GhostRenderPipeline(RenderSystem renderSystem)
{
_renderGraph = new RenderGraph(renderSystem.ResourceManager,
renderSystem.GraphicsEngine.ResourceAllocator,
renderSystem.GraphicsEngine.ResourceDatabase,
renderSystem.GraphicsEngine.PipelineLibrary,
renderSystem.GraphicsEngine.ShaderCompiler);
}
public void Render(RenderContext ctx, ReadOnlySpan<RenderRequest> requests)
{
for (var i = 0; i < requests.Length; i++)
{
ref readonly var request = ref requests[i];
if (request.renderFunc != null)
{
request.renderFunc(in ctx, in request);
}
// TODO: Set up the rendering pipeline using render graph based on the request data
}
}
public void Dispose()
{
if (_disposed)
{
return;
}
_renderGraph.Dispose();
_disposed = true;
GC.SuppressFinalize(this);
}
}

View File

@@ -2,11 +2,15 @@ using Ghost.Graphics.Core;
namespace Ghost.Graphics.RenderPipeline;
public interface IRenderPayload : IDisposable;
public interface IRenderPayload : IDisposable
{
void Reset();
}
public interface IRenderPipelineSettings
{
void CreatePipeline(RenderSystem renderSystem, out IRenderPipeline renderPipeline, out IRenderPayload renderPayload);
IRenderPipeline CreatePipeline(RenderSystem renderSystem);
IRenderPayload CreatePayload(RenderSystem renderSystem);
}
public interface IRenderPipeline : IDisposable

View File

@@ -3,12 +3,9 @@ using Ghost.Graphics.Core;
using Ghost.Graphics.D3D12;
using Ghost.Graphics.RenderPipeline;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.Mathematics;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
namespace Ghost.Graphics;
@@ -17,21 +14,21 @@ internal enum GraphicsAPI
Direct3D12
}
internal struct RenderSystemDesc
internal readonly struct RenderSystemDesc
{
public GraphicsAPI GraphicsAPI
{
get; set;
get; init;
}
public uint FrameBufferCount
{
get; set;
get; init;
}
public IRenderPipelineSettings? InitialRenderPipelineSettings
public required IRenderPipelineSettings InitialRenderPipelineSettings
{
get; set;
get; init;
}
}
@@ -63,11 +60,17 @@ public class RenderSystem : IDisposable
get; set;
}
public IRenderPayload RenderPayload
{
get; set;
}
public readonly void Dispose()
{
CpuReadyEvent.Dispose();
GpuReadyEvent.Dispose();
CommandAllocator.Dispose();
RenderPayload.Dispose();
}
}
@@ -85,7 +88,6 @@ public class RenderSystem : IDisposable
private IRenderPipelineSettings _renderPipelineSettings;
private IRenderPipeline _renderPipeline;
private IRenderPayload _renderPayload;
private ulong _cpuFenceValue;
private ulong _submittedFenceValue;
@@ -104,7 +106,6 @@ public class RenderSystem : IDisposable
public uint MaxFrameLatency => _config.FrameBufferCount;
public IRenderPayload RenderPayload => _renderPayload;
public IRenderPipelineSettings RenderPipelineSettings
{
get => _renderPipelineSettings;
@@ -119,10 +120,18 @@ public class RenderSystem : IDisposable
}
_renderPipeline?.Dispose();
_renderPayload?.Dispose();
for (int i = 0; i < _frameResources.Length; i++)
{
_frameResources[i].RenderPayload?.Dispose();
}
_renderPipelineSettings = value;
_renderPipelineSettings.CreatePipeline(this, out _renderPipeline, out _renderPayload);
_renderPipeline = _renderPipelineSettings.CreatePipeline(this);
for (var i = 0; i < _frameResources.Length; i++)
{
_frameResources[i].RenderPayload = _renderPipelineSettings.CreatePayload(this);
}
}
}
@@ -179,8 +188,13 @@ public class RenderSystem : IDisposable
_shutdownEvent = new AutoResetEvent(false);
_resizeRequest = new ConcurrentDictionary<ISwapChain, uint2>();
_renderPipelineSettings = _config.InitialRenderPipelineSettings ?? new GhostRenderPipelineSettings();
_renderPipelineSettings.CreatePipeline(this, out _renderPipeline, out _renderPayload);
_renderPipelineSettings = _config.InitialRenderPipelineSettings;
_renderPipeline = _renderPipelineSettings.CreatePipeline(this);
for (var i = 0; i < _frameResources.Length; i++)
{
_frameResources[i].RenderPayload = _renderPipelineSettings.CreatePayload(this);
}
_isRunning = false;
_disposed = false;
@@ -208,10 +222,11 @@ public class RenderSystem : IDisposable
while (_isRunning)
{
var frameIndex = (int)(_submittedFenceValue % _config.FrameBufferCount);
ref var frameResource = ref _frameResources[frameIndex];
try
{
var frameIndex = (int)(_submittedFenceValue % _config.FrameBufferCount);
ref var frameResource = ref _frameResources[frameIndex];
// Wait for either CPU ready signal or shutdown signal
waitHandles[0] = frameResource.CpuReadyEvent;
@@ -268,7 +283,7 @@ public class RenderSystem : IDisposable
var ctx = new RenderContext(_graphicsEngine, _resourceManager, cmd);
_renderPipeline.Render(ctx, frameIndex, _renderPayload);
_renderPipeline.Render(ctx, frameIndex, frameResource.RenderPayload);
_swapChainManager.TransitionToPresent(cmd);
// End recording commands and submit
@@ -296,6 +311,8 @@ public class RenderSystem : IDisposable
// End the frame and retire resources based on the freshest observed GPU progress.
_resourceManager.EndFrame(completedFrame);
_graphicsEngine.EndFrame(completedFrame);
frameResource.RenderPayload.Reset();
}
catch (Exception ex)
{
@@ -391,6 +408,16 @@ public class RenderSystem : IDisposable
}
}
public IRenderPayload GetCurrentFramePayload()
{
Debug.Assert(!_disposed, "Cannot get current frame payload from a disposed RenderSystem.");
var eventIndex = (int)(_cpuFenceValue % _config.FrameBufferCount);
ref var frameResource = ref _frameResources[eventIndex];
return frameResource.RenderPayload;
}
public void Dispose()
{
if (_disposed)
@@ -410,7 +437,7 @@ public class RenderSystem : IDisposable
_resourceManager.Dispose();
_swapChainManager.Dispose();
_graphicsEngine.Dispose();
_shutdownEvent.Dispose();

View File

@@ -37,11 +37,11 @@ public class ResourceUploadBatch
public void WaitIdle()
{
_device.GraphicsQueue.WaitIdle();
_device.CopyQueue.WaitIdle();
}
public Task WaitAsync()
{
return _device.GraphicsQueue.WaitAsync();
return _device.CopyQueue.WaitAsync();
}
}