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,
GraphicsAPI = GraphicsAPI.Direct3D12,
InitialRenderPipelineSettings = null! // TODO: We should allow user to specify the initial render pipeline settings.
};
_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;
} = null!;
public int LastSystemVersion
public uint LastSystemVersion
{
get; internal set;
} = -2;
} = uint.MaxValue - 1;
private bool ShouldUpdate()
{

View File

@@ -28,13 +28,13 @@ internal unsafe struct JobEntityBatch<TJob, T0> : IJobParallelFor
public UnsafeList<int> bitsOffsets0;
public UnsafeList<int> versionIndices0;
public int version;
public uint version;
public void Execute(int loopIndex, ref readonly JobExecutionContext ctx)
{
// 1. Get the specific pChunk for this thread
var pChunk = (byte*)chunks[loopIndex];
var pVersions = (int*)chunkVersions[loopIndex];
var pVersions = (uint*)chunkVersions[loopIndex];
var count = chunkCount[loopIndex];
var off0 = offsets0[loopIndex];
@@ -93,13 +93,13 @@ internal unsafe struct JobEntityBatch<TJob, T0, T1> : IJobParallelFor
public UnsafeList<int> bitsOffsets1;
public UnsafeList<int> versionIndices1;
public int version;
public uint version;
public void Execute(int loopIndex, ref readonly JobExecutionContext ctx)
{
// 1. Get the specific pChunk for this thread
var pChunk = (byte*)chunks[loopIndex];
var pVersions = (int*)chunkVersions[loopIndex];
var pVersions = (uint*)chunkVersions[loopIndex];
var count = chunkCount[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> versionIndices2;
public int version;
public uint version;
public void Execute(int loopIndex, ref readonly JobExecutionContext ctx)
{
// 1. Get the specific pChunk for this thread
var pChunk = (byte*)chunks[loopIndex];
var pVersions = (int*)chunkVersions[loopIndex];
var pVersions = (uint*)chunkVersions[loopIndex];
var count = chunkCount[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> versionIndices3;
public int version;
public uint version;
public void Execute(int loopIndex, ref readonly JobExecutionContext ctx)
{
// 1. Get the specific pChunk for this thread
var pChunk = (byte*)chunks[loopIndex];
var pVersions = (int*)chunkVersions[loopIndex];
var pVersions = (uint*)chunkVersions[loopIndex];
var count = chunkCount[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> versionIndices4;
public int version;
public uint version;
public void Execute(int loopIndex, ref readonly JobExecutionContext ctx)
{
// 1. Get the specific pChunk for this thread
var pChunk = (byte*)chunks[loopIndex];
var pVersions = (int*)chunkVersions[loopIndex];
var pVersions = (uint*)chunkVersions[loopIndex];
var count = chunkCount[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> versionIndices5;
public int version;
public uint version;
public void Execute(int loopIndex, ref readonly JobExecutionContext ctx)
{
// 1. Get the specific pChunk for this thread
var pChunk = (byte*)chunks[loopIndex];
var pVersions = (int*)chunkVersions[loopIndex];
var pVersions = (uint*)chunkVersions[loopIndex];
var count = chunkCount[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> versionIndices6;
public int version;
public uint version;
public void Execute(int loopIndex, ref readonly JobExecutionContext ctx)
{
// 1. Get the specific pChunk for this thread
var pChunk = (byte*)chunks[loopIndex];
var pVersions = (int*)chunkVersions[loopIndex];
var pVersions = (uint*)chunkVersions[loopIndex];
var count = chunkCount[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> versionIndices7;
public int version;
public uint version;
public void Execute(int loopIndex, ref readonly JobExecutionContext ctx)
{
// 1. Get the specific pChunk for this thread
var pChunk = (byte*)chunks[loopIndex];
var pVersions = (int*)chunkVersions[loopIndex];
var pVersions = (uint*)chunkVersions[loopIndex];
var count = chunkCount[loopIndex];
var off0 = offsets0[loopIndex];
@@ -1167,7 +1167,7 @@ public unsafe partial struct EntityQuery
var it = _mask.writeAccess.GetIterator();
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])
{
@@ -1321,7 +1321,7 @@ public unsafe partial struct EntityQuery
var it = _mask.writeAccess.GetIterator();
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])
{
@@ -1501,7 +1501,7 @@ public unsafe partial struct EntityQuery
var it = _mask.writeAccess.GetIterator();
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])
{
@@ -1707,7 +1707,7 @@ public unsafe partial struct EntityQuery
var it = _mask.writeAccess.GetIterator();
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])
{
@@ -1939,7 +1939,7 @@ public unsafe partial struct EntityQuery
var it = _mask.writeAccess.GetIterator();
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])
{
@@ -2197,7 +2197,7 @@ public unsafe partial struct EntityQuery
var it = _mask.writeAccess.GetIterator();
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])
{
@@ -2481,7 +2481,7 @@ public unsafe partial struct EntityQuery
var it = _mask.writeAccess.GetIterator();
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])
{
@@ -2791,7 +2791,7 @@ public unsafe partial struct EntityQuery
var it = _mask.writeAccess.GetIterator();
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])
{

View File

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

View File

@@ -104,11 +104,10 @@ public unsafe partial class TestRenderPipeline : IRenderPipeline
{
var testPayload = (TestRenderPayload)payload;
var renderSystem = testPayload.RenderSystem;
var resourceManager = renderSystem.ResourceManager;
var resourceDatabase = renderSystem.GraphicsEngine.ResourceDatabase;
var resourceManager = _renderSystem.ResourceManager;
var resourceDatabase = _renderSystem.GraphicsEngine.ResourceDatabase;
var requests = testPayload.FrameRequestData[frameIndex].renderRequests;
var requests = testPayload.renderRequests;
for (var i = 0; i < requests.Count; i++)
{
@@ -126,7 +125,7 @@ public unsafe partial class TestRenderPipeline : IRenderPipeline
{
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();
}
@@ -137,7 +136,7 @@ public unsafe partial class TestRenderPipeline : IRenderPipeline
try
{
var rtResult = renderSystem.GraphicsEngine.ResourceDatabase.GetResourceDescription(rt.AsResource());
var rtResult = _renderSystem.GraphicsEngine.ResourceDatabase.GetResourceDescription(rt.AsResource());
if (rtResult.IsFailure)
{
continue;
@@ -312,7 +311,7 @@ public unsafe partial class TestRenderPipeline : IRenderPipeline
{
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
{
public class FrameData
public UnsafeList<RenderRequest> renderRequests;
public TestRenderPayload()
{
public UnsafeList<RenderRequest> renderRequests;
renderRequests = new UnsafeList<RenderRequest>(2, Allocator.Persistent);
}
private readonly RenderSystem _renderSystem;
private readonly FrameData[] _frameData;
public RenderSystem RenderSystem => _renderSystem;
public ReadOnlySpan<FrameData> FrameRequestData => _frameData;
public TestRenderPayload(RenderSystem renderSystem)
public void Reset()
{
_renderSystem = renderSystem;
_frameData = new FrameData[renderSystem.MaxFrameLatency];
for (int i = 0; i < _frameData.Length; i++)
for (int i = 0; i < renderRequests.Count; i++)
{
_frameData[i].renderRequests = new UnsafeList<RenderRequest>(2, Allocator.Persistent);
renderRequests[i].Dispose();
}
}
public void AddRenderRequest(RenderRequest request)
{
var index = (int)(_renderSystem.CPUFenceValue % (uint)_frameData.Length);
_frameData[index].renderRequests.Add(request);
renderRequests.Clear();
}
public void Dispose()
{
for (int i = 0; i < _frameData.Length; i++)
{
_frameData[i].renderRequests.Dispose();
}
renderRequests.Dispose();
}
}
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);
renderPayload = new TestRenderPayload(renderSystem);
return new TestRenderPipeline(renderSystem);
}
public IRenderPayload CreatePayload(RenderSystem renderSystem)
{
return new TestRenderPayload();
}
}

View File

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

View File

@@ -52,7 +52,7 @@ internal class MeshoptBenchmark : ITest
};
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.
});