feat(render): improve frame sync and CPU write tracking
Refactored frame submission and synchronization logic for more accurate GPU/CPU coordination. Introduced CpuWriteOpen property to enforce correct CPU frame access patterns. Updated ResourceManager to track _submittedFrame and improved event waiting logic. Added debug assertions and enhanced logging for frame start events. Documented known command submission issue in GraphicsTestWindow.
This commit is contained in:
@@ -65,6 +65,11 @@ public class RenderSystem : IDisposable
|
||||
get; set;
|
||||
}
|
||||
|
||||
public bool CpuWriteOpen
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[UnscopedRef]
|
||||
public ref UnsafeList<RenderRequest> RenderRequests => ref _renderRequests;
|
||||
|
||||
@@ -262,11 +267,13 @@ public class RenderSystem : IDisposable
|
||||
_submittedFenceValue = _completedFenceValue;
|
||||
}
|
||||
|
||||
var nextFenceValue = _submittedFenceValue + 1;
|
||||
|
||||
// Begin rendering for this frame
|
||||
frameResource.CommandAllocator.Reset();
|
||||
|
||||
_resourceManager.BeginFrame(_submittedFenceValue + 1);
|
||||
var r = _graphicsEngine.BeginFrame(_submittedFenceValue + 1);
|
||||
_resourceManager.BeginFrame(nextFenceValue);
|
||||
var r = _graphicsEngine.BeginFrame(nextFenceValue);
|
||||
|
||||
if (r.IsFailure)
|
||||
{
|
||||
@@ -275,7 +282,7 @@ public class RenderSystem : IDisposable
|
||||
}
|
||||
|
||||
// Start recording commands
|
||||
|
||||
Debug.WriteLine($"GPU: Frame started.");
|
||||
// TODO: How can we support async compute and async copy?
|
||||
var cmd = _graphicsEngine.GetPooledCommandBuffer(CommandBufferType.Graphics);
|
||||
ref var renderRequests = ref frameResource.RenderRequests;
|
||||
@@ -286,7 +293,6 @@ public class RenderSystem : IDisposable
|
||||
|
||||
var renderCtx = new RenderContext(_graphicsEngine, _resourceManager, cmd);
|
||||
|
||||
//Debug.WriteLine($"GPU: Frame started.");
|
||||
_renderPipeline.Render(renderCtx, renderRequests.AsSpan());
|
||||
_swapChainManager.TransitionToPresent(cmd);
|
||||
|
||||
@@ -313,7 +319,13 @@ public class RenderSystem : IDisposable
|
||||
renderRequests.Clear();
|
||||
}
|
||||
|
||||
// End the frame and retire resources based on actual GPU progress.
|
||||
_submittedFenceValue = nextFenceValue;
|
||||
frameResource.FenceValue = _graphicsEngine.Device.GraphicsQueue.Signal(nextFenceValue);
|
||||
frameResource.GpuReadyEvent.Set();
|
||||
|
||||
_completedFenceValue = _graphicsEngine.Device.GraphicsQueue.GetCompletedValue();
|
||||
|
||||
// End the frame and retire resources based on the freshest observed GPU progress.
|
||||
_resourceManager.EndFrame(_completedFenceValue);
|
||||
r = _graphicsEngine.EndFrame(_completedFenceValue);
|
||||
|
||||
@@ -323,9 +335,6 @@ public class RenderSystem : IDisposable
|
||||
break;
|
||||
}
|
||||
|
||||
_submittedFenceValue++;
|
||||
frameResource.GpuReadyEvent.Set();
|
||||
frameResource.FenceValue = _graphicsEngine.Device.GraphicsQueue.Signal(_submittedFenceValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -361,7 +370,12 @@ public class RenderSystem : IDisposable
|
||||
Debug.Assert(!_disposed, "Cannot signal CPU ready on a disposed RenderSystem.");
|
||||
|
||||
var eventIndex = (int)(_cpuFenceValue % _config.FrameBufferCount);
|
||||
_frameResources[eventIndex].CpuReadyEvent.Set();
|
||||
ref var frameResource = ref _frameResources[eventIndex];
|
||||
|
||||
Debug.Assert(frameResource.CpuWriteOpen, "SignalCPUReady called without a matching successful TryAcquireCPUFrame.");
|
||||
frameResource.CpuWriteOpen = false;
|
||||
|
||||
frameResource.CpuReadyEvent.Set();
|
||||
_cpuFenceValue++;
|
||||
}
|
||||
|
||||
@@ -381,7 +395,12 @@ public class RenderSystem : IDisposable
|
||||
}
|
||||
|
||||
var eventIndex = (int)(_cpuFenceValue % _config.FrameBufferCount);
|
||||
_frameResources[eventIndex].RenderRequests.Clear();
|
||||
ref var frameResource = ref _frameResources[eventIndex];
|
||||
|
||||
Debug.Assert(!frameResource.CpuWriteOpen, "TryAcquireCPUFrame called while the previous CPU frame is still open. Call SignalCPUReady first.");
|
||||
|
||||
frameResource.CpuWriteOpen = true;
|
||||
frameResource.RenderRequests.Clear();
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -391,14 +410,23 @@ public class RenderSystem : IDisposable
|
||||
Debug.Assert(!_disposed, "Cannot add render request to a disposed RenderSystem.");
|
||||
|
||||
var frameIndex = (int)(_cpuFenceValue % _config.FrameBufferCount);
|
||||
_frameResources[frameIndex].RenderRequests.Add(request);
|
||||
ref var frameResource = ref _frameResources[frameIndex];
|
||||
|
||||
Debug.Assert(frameResource.CpuWriteOpen, "AddRenderRequest requires a successful TryAcquireCPUFrame and must happen before SignalCPUReady.");
|
||||
frameResource.RenderRequests.Add(request);
|
||||
}
|
||||
|
||||
public bool WaitForGPUReady(int timeOut = -1)
|
||||
{
|
||||
Debug.Assert(!_disposed, "Cannot wait for GPU ready on a disposed RenderSystem.");
|
||||
|
||||
var eventIndex = (int)(_cpuFenceValue % _config.FrameBufferCount);
|
||||
var submittedFenceValue = Volatile.Read(ref _submittedFenceValue);
|
||||
if (submittedFenceValue == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var eventIndex = (int)((submittedFenceValue - 1) % _config.FrameBufferCount);
|
||||
return _frameResources[eventIndex].GpuReadyEvent.WaitOne(timeOut);
|
||||
}
|
||||
|
||||
|
||||
@@ -259,7 +259,7 @@ public partial class ResourceManager
|
||||
_retiringPages.Enqueue(new RetiringPage
|
||||
{
|
||||
page = page,
|
||||
retireFrame = _cpuFrame
|
||||
retireFrame = _submittedFrame
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ public sealed partial class ResourceManager : IDisposable
|
||||
|
||||
private readonly MaterialPaletteStore _materialPalettes;
|
||||
|
||||
private ulong _cpuFrame;
|
||||
private ulong _submittedFrame;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
@@ -59,13 +59,16 @@ public sealed partial class ResourceManager : IDisposable
|
||||
internal void BeginFrame(ulong cpuFrame)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
_cpuFrame = cpuFrame;
|
||||
_submittedFrame = cpuFrame;
|
||||
}
|
||||
|
||||
internal void EndFrame(ulong gpuFrame)
|
||||
internal void EndFrame(ulong completedFrame)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
EndFramePool(gpuFrame);
|
||||
|
||||
//_submittedFrame = submittedFrame;
|
||||
|
||||
EndFramePool(completedFrame);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -125,6 +125,9 @@ public sealed partial class GraphicsTestWindow : Window
|
||||
ctx.UpdateObjectData(_meshHandle);
|
||||
|
||||
directCmd.End().ThrowIfFailed();
|
||||
|
||||
// FIX: This will bump the complete value of the queue and cause the render thread render the first frame twice, which is not expected. We should have a better way to handle this.
|
||||
// Maybe a async upload support in the future?
|
||||
_renderSystem.GraphicsEngine.Device.GraphicsQueue.Submit(directCmd);
|
||||
_renderSystem.GraphicsEngine.Device.GraphicsQueue.WaitIdle();
|
||||
|
||||
@@ -205,7 +208,7 @@ public sealed partial class GraphicsTestWindow : Window
|
||||
|
||||
if (_renderSystem.TryAcquireCPUFrame())
|
||||
{
|
||||
//Debug.WriteLine($"CPU: Frame started.");
|
||||
Debug.WriteLine($"CPU: Frame started.");
|
||||
_world.SystemManager.UpdateAll(default);
|
||||
_renderSystem.SignalCPUReady();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user