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

@@ -26,6 +26,7 @@ public sealed partial class EngineCore : IDisposable
{ {
FrameBufferCount = 2, FrameBufferCount = 2,
GraphicsAPI = GraphicsAPI.Direct3D12, GraphicsAPI = GraphicsAPI.Direct3D12,
InitialRenderPipelineSettings = null! // TODO: We should allow user to specify the initial render pipeline settings.
}; };
_renderSystem = new RenderSystem(renderingConfig); _renderSystem = new RenderSystem(renderingConfig);

View File

@@ -0,0 +1,42 @@
using Ghost.Entities;
using Ghost.Graphics.RenderPipeline;
namespace Ghost.Engine.Systems;
public abstract class RenderPipelineSystemAttribute : Attribute
{
public abstract Type SettingsType { get; }
}
[AttributeUsage(AttributeTargets.Class)]
public class RenderPipelineSystemAttribute<T> : RenderPipelineSystemAttribute
where T : class, IRenderPipelineSettings
{
public override Type SettingsType => typeof(T);
}
public static class RenderPipelineSystemRegistry
{
private static readonly Dictionary<Type, List<Func<ISystem>>> s_renderPipelineSystems = new();
public static void RegisterRenderPipelineSystem(Type settingsType, Func<ISystem> systemFactory)
{
if (!s_renderPipelineSystems.TryGetValue(settingsType, out var systems))
{
systems = new List<Func<ISystem>>();
s_renderPipelineSystems[settingsType] = systems;
}
systems.Add(systemFactory);
}
internal static IEnumerable<Func<ISystem>> GetRenderPipelineSystems(Type settingsType)
{
if (s_renderPipelineSystems.TryGetValue(settingsType, out var systems))
{
return systems;
}
return Enumerable.Empty<Func<ISystem>>();
}
}

View File

@@ -0,0 +1,7 @@
using Ghost.Entities;
namespace Ghost.Engine.Systems;
internal class RenderSystemGroup : SystemGroup
{
}

View File

@@ -31,10 +31,10 @@ public abstract class SystemBase : ISystem
get; init; get; init;
} = null!; } = null!;
public int LastSystemVersion public uint LastSystemVersion
{ {
get; internal set; get; internal set;
} = -2; } = uint.MaxValue - 1;
private bool ShouldUpdate() private bool ShouldUpdate()
{ {

View File

@@ -28,13 +28,13 @@ internal unsafe struct JobEntityBatch<TJob, T0> : IJobParallelFor
public UnsafeList<int> bitsOffsets0; public UnsafeList<int> bitsOffsets0;
public UnsafeList<int> versionIndices0; public UnsafeList<int> versionIndices0;
public int version; public uint version;
public void Execute(int loopIndex, ref readonly JobExecutionContext ctx) public void Execute(int loopIndex, ref readonly JobExecutionContext ctx)
{ {
// 1. Get the specific pChunk for this thread // 1. Get the specific pChunk for this thread
var pChunk = (byte*)chunks[loopIndex]; var pChunk = (byte*)chunks[loopIndex];
var pVersions = (int*)chunkVersions[loopIndex]; var pVersions = (uint*)chunkVersions[loopIndex];
var count = chunkCount[loopIndex]; var count = chunkCount[loopIndex];
var off0 = offsets0[loopIndex]; var off0 = offsets0[loopIndex];
@@ -93,13 +93,13 @@ internal unsafe struct JobEntityBatch<TJob, T0, T1> : IJobParallelFor
public UnsafeList<int> bitsOffsets1; public UnsafeList<int> bitsOffsets1;
public UnsafeList<int> versionIndices1; public UnsafeList<int> versionIndices1;
public int version; public uint version;
public void Execute(int loopIndex, ref readonly JobExecutionContext ctx) public void Execute(int loopIndex, ref readonly JobExecutionContext ctx)
{ {
// 1. Get the specific pChunk for this thread // 1. Get the specific pChunk for this thread
var pChunk = (byte*)chunks[loopIndex]; var pChunk = (byte*)chunks[loopIndex];
var pVersions = (int*)chunkVersions[loopIndex]; var pVersions = (uint*)chunkVersions[loopIndex];
var count = chunkCount[loopIndex]; var count = chunkCount[loopIndex];
var off0 = offsets0[loopIndex]; var off0 = offsets0[loopIndex];
@@ -179,13 +179,13 @@ internal unsafe struct JobEntityBatch<TJob, T0, T1, T2> : IJobParallelFor
public UnsafeList<int> bitsOffsets2; public UnsafeList<int> bitsOffsets2;
public UnsafeList<int> versionIndices2; public UnsafeList<int> versionIndices2;
public int version; public uint version;
public void Execute(int loopIndex, ref readonly JobExecutionContext ctx) public void Execute(int loopIndex, ref readonly JobExecutionContext ctx)
{ {
// 1. Get the specific pChunk for this thread // 1. Get the specific pChunk for this thread
var pChunk = (byte*)chunks[loopIndex]; var pChunk = (byte*)chunks[loopIndex];
var pVersions = (int*)chunkVersions[loopIndex]; var pVersions = (uint*)chunkVersions[loopIndex];
var count = chunkCount[loopIndex]; var count = chunkCount[loopIndex];
var off0 = offsets0[loopIndex]; var off0 = offsets0[loopIndex];
@@ -286,13 +286,13 @@ internal unsafe struct JobEntityBatch<TJob, T0, T1, T2, T3> : IJobParallelFor
public UnsafeList<int> bitsOffsets3; public UnsafeList<int> bitsOffsets3;
public UnsafeList<int> versionIndices3; public UnsafeList<int> versionIndices3;
public int version; public uint version;
public void Execute(int loopIndex, ref readonly JobExecutionContext ctx) public void Execute(int loopIndex, ref readonly JobExecutionContext ctx)
{ {
// 1. Get the specific pChunk for this thread // 1. Get the specific pChunk for this thread
var pChunk = (byte*)chunks[loopIndex]; var pChunk = (byte*)chunks[loopIndex];
var pVersions = (int*)chunkVersions[loopIndex]; var pVersions = (uint*)chunkVersions[loopIndex];
var count = chunkCount[loopIndex]; var count = chunkCount[loopIndex];
var off0 = offsets0[loopIndex]; var off0 = offsets0[loopIndex];
@@ -414,13 +414,13 @@ internal unsafe struct JobEntityBatch<TJob, T0, T1, T2, T3, T4> : IJobParallelFo
public UnsafeList<int> bitsOffsets4; public UnsafeList<int> bitsOffsets4;
public UnsafeList<int> versionIndices4; public UnsafeList<int> versionIndices4;
public int version; public uint version;
public void Execute(int loopIndex, ref readonly JobExecutionContext ctx) public void Execute(int loopIndex, ref readonly JobExecutionContext ctx)
{ {
// 1. Get the specific pChunk for this thread // 1. Get the specific pChunk for this thread
var pChunk = (byte*)chunks[loopIndex]; var pChunk = (byte*)chunks[loopIndex];
var pVersions = (int*)chunkVersions[loopIndex]; var pVersions = (uint*)chunkVersions[loopIndex];
var count = chunkCount[loopIndex]; var count = chunkCount[loopIndex];
var off0 = offsets0[loopIndex]; var off0 = offsets0[loopIndex];
@@ -563,13 +563,13 @@ internal unsafe struct JobEntityBatch<TJob, T0, T1, T2, T3, T4, T5> : IJobParall
public UnsafeList<int> bitsOffsets5; public UnsafeList<int> bitsOffsets5;
public UnsafeList<int> versionIndices5; public UnsafeList<int> versionIndices5;
public int version; public uint version;
public void Execute(int loopIndex, ref readonly JobExecutionContext ctx) public void Execute(int loopIndex, ref readonly JobExecutionContext ctx)
{ {
// 1. Get the specific pChunk for this thread // 1. Get the specific pChunk for this thread
var pChunk = (byte*)chunks[loopIndex]; var pChunk = (byte*)chunks[loopIndex];
var pVersions = (int*)chunkVersions[loopIndex]; var pVersions = (uint*)chunkVersions[loopIndex];
var count = chunkCount[loopIndex]; var count = chunkCount[loopIndex];
var off0 = offsets0[loopIndex]; var off0 = offsets0[loopIndex];
@@ -733,13 +733,13 @@ internal unsafe struct JobEntityBatch<TJob, T0, T1, T2, T3, T4, T5, T6> : IJobPa
public UnsafeList<int> bitsOffsets6; public UnsafeList<int> bitsOffsets6;
public UnsafeList<int> versionIndices6; public UnsafeList<int> versionIndices6;
public int version; public uint version;
public void Execute(int loopIndex, ref readonly JobExecutionContext ctx) public void Execute(int loopIndex, ref readonly JobExecutionContext ctx)
{ {
// 1. Get the specific pChunk for this thread // 1. Get the specific pChunk for this thread
var pChunk = (byte*)chunks[loopIndex]; var pChunk = (byte*)chunks[loopIndex];
var pVersions = (int*)chunkVersions[loopIndex]; var pVersions = (uint*)chunkVersions[loopIndex];
var count = chunkCount[loopIndex]; var count = chunkCount[loopIndex];
var off0 = offsets0[loopIndex]; var off0 = offsets0[loopIndex];
@@ -924,13 +924,13 @@ internal unsafe struct JobEntityBatch<TJob, T0, T1, T2, T3, T4, T5, T6, T7> : IJ
public UnsafeList<int> bitsOffsets7; public UnsafeList<int> bitsOffsets7;
public UnsafeList<int> versionIndices7; public UnsafeList<int> versionIndices7;
public int version; public uint version;
public void Execute(int loopIndex, ref readonly JobExecutionContext ctx) public void Execute(int loopIndex, ref readonly JobExecutionContext ctx)
{ {
// 1. Get the specific pChunk for this thread // 1. Get the specific pChunk for this thread
var pChunk = (byte*)chunks[loopIndex]; var pChunk = (byte*)chunks[loopIndex];
var pVersions = (int*)chunkVersions[loopIndex]; var pVersions = (uint*)chunkVersions[loopIndex];
var count = chunkCount[loopIndex]; var count = chunkCount[loopIndex];
var off0 = offsets0[loopIndex]; var off0 = offsets0[loopIndex];
@@ -1167,7 +1167,7 @@ public unsafe partial struct EntityQuery
var it = _mask.writeAccess.GetIterator(); var it = _mask.writeAccess.GetIterator();
while (it.Next(out var id)) while (it.Next(out var id))
{ {
for (var i = 0; i < 1; i++) for (var i =0; i < 1; i++)
{ {
if (id == runner.componentIDs[i]) if (id == runner.componentIDs[i])
{ {
@@ -1321,7 +1321,7 @@ public unsafe partial struct EntityQuery
var it = _mask.writeAccess.GetIterator(); var it = _mask.writeAccess.GetIterator();
while (it.Next(out var id)) while (it.Next(out var id))
{ {
for (var i = 0; i < 1; i++) for (var i =0; i < 1; i++)
{ {
if (id == runner.componentIDs[i]) if (id == runner.componentIDs[i])
{ {
@@ -1501,7 +1501,7 @@ public unsafe partial struct EntityQuery
var it = _mask.writeAccess.GetIterator(); var it = _mask.writeAccess.GetIterator();
while (it.Next(out var id)) while (it.Next(out var id))
{ {
for (var i = 0; i < 1; i++) for (var i =0; i < 1; i++)
{ {
if (id == runner.componentIDs[i]) if (id == runner.componentIDs[i])
{ {
@@ -1707,7 +1707,7 @@ public unsafe partial struct EntityQuery
var it = _mask.writeAccess.GetIterator(); var it = _mask.writeAccess.GetIterator();
while (it.Next(out var id)) while (it.Next(out var id))
{ {
for (var i = 0; i < 1; i++) for (var i =0; i < 1; i++)
{ {
if (id == runner.componentIDs[i]) if (id == runner.componentIDs[i])
{ {
@@ -1939,7 +1939,7 @@ public unsafe partial struct EntityQuery
var it = _mask.writeAccess.GetIterator(); var it = _mask.writeAccess.GetIterator();
while (it.Next(out var id)) while (it.Next(out var id))
{ {
for (var i = 0; i < 1; i++) for (var i =0; i < 1; i++)
{ {
if (id == runner.componentIDs[i]) if (id == runner.componentIDs[i])
{ {
@@ -2197,7 +2197,7 @@ public unsafe partial struct EntityQuery
var it = _mask.writeAccess.GetIterator(); var it = _mask.writeAccess.GetIterator();
while (it.Next(out var id)) while (it.Next(out var id))
{ {
for (var i = 0; i < 1; i++) for (var i =0; i < 1; i++)
{ {
if (id == runner.componentIDs[i]) if (id == runner.componentIDs[i])
{ {
@@ -2481,7 +2481,7 @@ public unsafe partial struct EntityQuery
var it = _mask.writeAccess.GetIterator(); var it = _mask.writeAccess.GetIterator();
while (it.Next(out var id)) while (it.Next(out var id))
{ {
for (var i = 0; i < 1; i++) for (var i =0; i < 1; i++)
{ {
if (id == runner.componentIDs[i]) if (id == runner.componentIDs[i])
{ {
@@ -2791,7 +2791,7 @@ public unsafe partial struct EntityQuery
var it = _mask.writeAccess.GetIterator(); var it = _mask.writeAccess.GetIterator();
while (it.Next(out var id)) while (it.Next(out var id))
{ {
for (var i = 0; i < 1; i++) for (var i =0; i < 1; i++)
{ {
if (id == runner.componentIDs[i]) if (id == runner.componentIDs[i])
{ {

View File

@@ -41,13 +41,13 @@ internal unsafe struct JobEntityBatch<TJob, <#= generics #>> : IJobParallelFor
public UnsafeList<int> versionIndices<#= j #>; public UnsafeList<int> versionIndices<#= j #>;
<# } #> <# } #>
public int version; public uint version;
public void Execute(int loopIndex, ref readonly JobExecutionContext ctx) public void Execute(int loopIndex, ref readonly JobExecutionContext ctx)
{ {
// 1. Get the specific pChunk for this thread // 1. Get the specific pChunk for this thread
var pChunk = (byte*)chunks[loopIndex]; var pChunk = (byte*)chunks[loopIndex];
var pVersions = (int*)chunkVersions[loopIndex]; var pVersions = (uint*)chunkVersions[loopIndex];
var count = chunkCount[loopIndex]; var count = chunkCount[loopIndex];
<# for (var j = 0; j < i; j++){ #> <# for (var j = 0; j < i; j++){ #>

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; namespace Ghost.Graphics.RenderPipeline;
public interface IRenderPayload : IDisposable; public interface IRenderPayload : IDisposable
{
void Reset();
}
public interface IRenderPipelineSettings 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 public interface IRenderPipeline : IDisposable

View File

@@ -3,12 +3,9 @@ using Ghost.Graphics.Core;
using Ghost.Graphics.D3D12; using Ghost.Graphics.D3D12;
using Ghost.Graphics.RenderPipeline; using Ghost.Graphics.RenderPipeline;
using Ghost.Graphics.RHI; using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.Mathematics; using Misaki.HighPerformance.Mathematics;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
namespace Ghost.Graphics; namespace Ghost.Graphics;
@@ -17,21 +14,21 @@ internal enum GraphicsAPI
Direct3D12 Direct3D12
} }
internal struct RenderSystemDesc internal readonly struct RenderSystemDesc
{ {
public GraphicsAPI GraphicsAPI public GraphicsAPI GraphicsAPI
{ {
get; set; get; init;
} }
public uint FrameBufferCount 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; get; set;
} }
public IRenderPayload RenderPayload
{
get; set;
}
public readonly void Dispose() public readonly void Dispose()
{ {
CpuReadyEvent.Dispose(); CpuReadyEvent.Dispose();
GpuReadyEvent.Dispose(); GpuReadyEvent.Dispose();
CommandAllocator.Dispose(); CommandAllocator.Dispose();
RenderPayload.Dispose();
} }
} }
@@ -85,7 +88,6 @@ public class RenderSystem : IDisposable
private IRenderPipelineSettings _renderPipelineSettings; private IRenderPipelineSettings _renderPipelineSettings;
private IRenderPipeline _renderPipeline; private IRenderPipeline _renderPipeline;
private IRenderPayload _renderPayload;
private ulong _cpuFenceValue; private ulong _cpuFenceValue;
private ulong _submittedFenceValue; private ulong _submittedFenceValue;
@@ -104,7 +106,6 @@ public class RenderSystem : IDisposable
public uint MaxFrameLatency => _config.FrameBufferCount; public uint MaxFrameLatency => _config.FrameBufferCount;
public IRenderPayload RenderPayload => _renderPayload;
public IRenderPipelineSettings RenderPipelineSettings public IRenderPipelineSettings RenderPipelineSettings
{ {
get => _renderPipelineSettings; get => _renderPipelineSettings;
@@ -119,10 +120,18 @@ public class RenderSystem : IDisposable
} }
_renderPipeline?.Dispose(); _renderPipeline?.Dispose();
_renderPayload?.Dispose(); for (int i = 0; i < _frameResources.Length; i++)
{
_frameResources[i].RenderPayload?.Dispose();
}
_renderPipelineSettings = value; _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); _shutdownEvent = new AutoResetEvent(false);
_resizeRequest = new ConcurrentDictionary<ISwapChain, uint2>(); _resizeRequest = new ConcurrentDictionary<ISwapChain, uint2>();
_renderPipelineSettings = _config.InitialRenderPipelineSettings ?? new GhostRenderPipelineSettings(); _renderPipelineSettings = _config.InitialRenderPipelineSettings;
_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);
}
_isRunning = false; _isRunning = false;
_disposed = false; _disposed = false;
@@ -207,12 +221,13 @@ public class RenderSystem : IDisposable
var waitHandles = new WaitHandle[] { null!, _shutdownEvent }; var waitHandles = new WaitHandle[] { null!, _shutdownEvent };
while (_isRunning) while (_isRunning)
{
try
{ {
var frameIndex = (int)(_submittedFenceValue % _config.FrameBufferCount); var frameIndex = (int)(_submittedFenceValue % _config.FrameBufferCount);
ref var frameResource = ref _frameResources[frameIndex]; ref var frameResource = ref _frameResources[frameIndex];
try
{
// Wait for either CPU ready signal or shutdown signal // Wait for either CPU ready signal or shutdown signal
waitHandles[0] = frameResource.CpuReadyEvent; waitHandles[0] = frameResource.CpuReadyEvent;
var waitResult = WaitHandle.WaitAny(waitHandles); var waitResult = WaitHandle.WaitAny(waitHandles);
@@ -268,7 +283,7 @@ public class RenderSystem : IDisposable
var ctx = new RenderContext(_graphicsEngine, _resourceManager, cmd); var ctx = new RenderContext(_graphicsEngine, _resourceManager, cmd);
_renderPipeline.Render(ctx, frameIndex, _renderPayload); _renderPipeline.Render(ctx, frameIndex, frameResource.RenderPayload);
_swapChainManager.TransitionToPresent(cmd); _swapChainManager.TransitionToPresent(cmd);
// End recording commands and submit // 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. // End the frame and retire resources based on the freshest observed GPU progress.
_resourceManager.EndFrame(completedFrame); _resourceManager.EndFrame(completedFrame);
_graphicsEngine.EndFrame(completedFrame); _graphicsEngine.EndFrame(completedFrame);
frameResource.RenderPayload.Reset();
} }
catch (Exception ex) 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() public void Dispose()
{ {
if (_disposed) if (_disposed)

View File

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

View File

@@ -104,11 +104,10 @@ public unsafe partial class TestRenderPipeline : IRenderPipeline
{ {
var testPayload = (TestRenderPayload)payload; var testPayload = (TestRenderPayload)payload;
var renderSystem = testPayload.RenderSystem; var resourceManager = _renderSystem.ResourceManager;
var resourceManager = renderSystem.ResourceManager; var resourceDatabase = _renderSystem.GraphicsEngine.ResourceDatabase;
var resourceDatabase = renderSystem.GraphicsEngine.ResourceDatabase;
var requests = testPayload.FrameRequestData[frameIndex].renderRequests; var requests = testPayload.renderRequests;
for (var i = 0; i < requests.Count; i++) for (var i = 0; i < requests.Count; i++)
{ {
@@ -126,7 +125,7 @@ public unsafe partial class TestRenderPipeline : IRenderPipeline
{ {
rt = request.colorTarget; rt = request.colorTarget;
} }
else if (renderSystem.SwapChainManager.TryGetSwapChain(request.swapChainIndex, out var swapChain)) else if (_renderSystem.SwapChainManager.TryGetSwapChain(request.swapChainIndex, out var swapChain))
{ {
rt = swapChain.GetCurrentBackBuffer(); rt = swapChain.GetCurrentBackBuffer();
} }
@@ -137,7 +136,7 @@ public unsafe partial class TestRenderPipeline : IRenderPipeline
try try
{ {
var rtResult = renderSystem.GraphicsEngine.ResourceDatabase.GetResourceDescription(rt.AsResource()); var rtResult = _renderSystem.GraphicsEngine.ResourceDatabase.GetResourceDescription(rt.AsResource());
if (rtResult.IsFailure) if (rtResult.IsFailure)
{ {
continue; continue;
@@ -312,7 +311,7 @@ public unsafe partial class TestRenderPipeline : IRenderPipeline
{ {
if (request.swapChainIndex >= 0) if (request.swapChainIndex >= 0)
{ {
renderSystem.SwapChainManager.ReleaseSwapChain(request.swapChainIndex); _renderSystem.SwapChainManager.ReleaseSwapChain(request.swapChainIndex);
} }
} }
} }

View File

@@ -7,48 +7,38 @@ namespace Ghost.Graphics.Test.RenderPipeline;
internal sealed class TestRenderPayload : IRenderPayload internal sealed class TestRenderPayload : IRenderPayload
{ {
public class FrameData
{
public UnsafeList<RenderRequest> renderRequests; public UnsafeList<RenderRequest> renderRequests;
public TestRenderPayload()
{
renderRequests = new UnsafeList<RenderRequest>(2, Allocator.Persistent);
} }
private readonly RenderSystem _renderSystem; public void Reset()
private readonly FrameData[] _frameData;
public RenderSystem RenderSystem => _renderSystem;
public ReadOnlySpan<FrameData> FrameRequestData => _frameData;
public TestRenderPayload(RenderSystem renderSystem)
{ {
_renderSystem = renderSystem; for (int i = 0; i < renderRequests.Count; i++)
_frameData = new FrameData[renderSystem.MaxFrameLatency];
for (int i = 0; i < _frameData.Length; i++)
{ {
_frameData[i].renderRequests = new UnsafeList<RenderRequest>(2, Allocator.Persistent); renderRequests[i].Dispose();
}
} }
public void AddRenderRequest(RenderRequest request) renderRequests.Clear();
{
var index = (int)(_renderSystem.CPUFenceValue % (uint)_frameData.Length);
_frameData[index].renderRequests.Add(request);
} }
public void Dispose() public void Dispose()
{ {
for (int i = 0; i < _frameData.Length; i++) renderRequests.Dispose();
{
_frameData[i].renderRequests.Dispose();
}
} }
} }
internal sealed class TestRenderPipelineSettings : IRenderPipelineSettings internal sealed class TestRenderPipelineSettings : IRenderPipelineSettings
{ {
public void CreatePipeline(RenderSystem renderSystem, out IRenderPipeline renderPipeline, out IRenderPayload renderPayload) public IRenderPipeline CreatePipeline(RenderSystem renderSystem)
{ {
renderPipeline = new TestRenderPipeline(renderSystem); return new TestRenderPipeline(renderSystem);
renderPayload = new TestRenderPayload(renderSystem); }
public IRenderPayload CreatePayload(RenderSystem renderSystem)
{
return new TestRenderPayload();
} }
} }

View File

@@ -1,6 +1,7 @@
using Ghost.Core; using Ghost.Core;
using Ghost.Engine; using Ghost.Engine;
using Ghost.Engine.Components; using Ghost.Engine.Components;
using Ghost.Engine.Systems;
using Ghost.Entities; using Ghost.Entities;
using Ghost.Graphics.Core; using Ghost.Graphics.Core;
using Ghost.Graphics.Test.RenderPipeline; using Ghost.Graphics.Test.RenderPipeline;
@@ -9,6 +10,7 @@ using Misaki.HighPerformance.Mathematics;
namespace Ghost.Graphics.Test.Systems; namespace Ghost.Graphics.Test.Systems;
[RenderPipelineSystem<TestRenderPipelineSettings>]
public class RenderExtractionSystem : ISystem public class RenderExtractionSystem : ISystem
{ {
private RenderSystem _renderSystem = null!; private RenderSystem _renderSystem = null!;
@@ -135,7 +137,7 @@ public class RenderExtractionSystem : ISystem
}, },
}; };
((TestRenderPayload)_renderSystem.RenderPayload).AddRenderRequest(request); ((TestRenderPayload)_renderSystem.GetCurrentFramePayload()).renderRequests.Add(request);
} }
} }

View File

@@ -1,10 +1,10 @@
using Ghost.Core; using Ghost.Core;
using Ghost.Engine.Components; using Ghost.Engine.Components;
using Ghost.Engine.Systems;
using Ghost.Engine.Utilities; using Ghost.Engine.Utilities;
using Ghost.Entities; using Ghost.Entities;
using Ghost.Graphics.Core; using Ghost.Graphics.Core;
using Ghost.Graphics.RHI; using Ghost.Graphics.RHI;
using Ghost.Graphics.Test.Systems;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Media;
@@ -53,7 +53,7 @@ public sealed partial class GraphicsTestWindow : Window
{ {
FrameBufferCount = 2, FrameBufferCount = 2,
GraphicsAPI = GraphicsAPI.Direct3D12, GraphicsAPI = GraphicsAPI.Direct3D12,
InitialRenderPipelineSettings = new RenderPasses.TestRenderPipelineSettings() InitialRenderPipelineSettings = new RenderPipeline.TestRenderPipelineSettings()
}); });
_swapChain = _renderSystem.SwapChainManager.EnsureSwapChain(0, new SwapChainDesc _swapChain = _renderSystem.SwapChainManager.EnsureSwapChain(0, new SwapChainDesc
@@ -159,6 +159,9 @@ public sealed partial class GraphicsTestWindow : Window
} }
catch (Exception ex) catch (Exception ex)
{ {
#if DEBUG
System.Diagnostics.Debugger.Break();
#endif
Environment.FailFast("Failed to close the window properly.", ex); Environment.FailFast("Failed to close the window properly.", ex);
} }
finally finally

View File

@@ -52,7 +52,7 @@ internal class MeshoptBenchmark : ITest
}; };
var error = new ufbx_error(); var error = new ufbx_error();
using var pool = new MemoryPool<VirtualStack, VirtualStack.CreationOpts>(new VirtualStack.CreationOpts using var pool = new MemoryPool<VirtualStack, VirtualStack.CreationOptions>(new VirtualStack.CreationOptions
{ {
reserveCapacity = 256 * 1024 * 1024 // 256 MB should be enough for most models, adjust as needed. Note that this use virtual memory and does not actually consume physical memory until allocations are made. reserveCapacity = 256 * 1024 * 1024 // 256 MB should be enough for most models, adjust as needed. Note that this use virtual memory and does not actually consume physical memory until allocations are made.
}); });