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;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool CpuWriteOpen
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
[UnscopedRef]
|
[UnscopedRef]
|
||||||
public ref UnsafeList<RenderRequest> RenderRequests => ref _renderRequests;
|
public ref UnsafeList<RenderRequest> RenderRequests => ref _renderRequests;
|
||||||
|
|
||||||
@@ -262,11 +267,13 @@ public class RenderSystem : IDisposable
|
|||||||
_submittedFenceValue = _completedFenceValue;
|
_submittedFenceValue = _completedFenceValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var nextFenceValue = _submittedFenceValue + 1;
|
||||||
|
|
||||||
// Begin rendering for this frame
|
// Begin rendering for this frame
|
||||||
frameResource.CommandAllocator.Reset();
|
frameResource.CommandAllocator.Reset();
|
||||||
|
|
||||||
_resourceManager.BeginFrame(_submittedFenceValue + 1);
|
_resourceManager.BeginFrame(nextFenceValue);
|
||||||
var r = _graphicsEngine.BeginFrame(_submittedFenceValue + 1);
|
var r = _graphicsEngine.BeginFrame(nextFenceValue);
|
||||||
|
|
||||||
if (r.IsFailure)
|
if (r.IsFailure)
|
||||||
{
|
{
|
||||||
@@ -275,7 +282,7 @@ public class RenderSystem : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Start recording commands
|
// Start recording commands
|
||||||
|
Debug.WriteLine($"GPU: Frame started.");
|
||||||
// TODO: How can we support async compute and async copy?
|
// TODO: How can we support async compute and async copy?
|
||||||
var cmd = _graphicsEngine.GetPooledCommandBuffer(CommandBufferType.Graphics);
|
var cmd = _graphicsEngine.GetPooledCommandBuffer(CommandBufferType.Graphics);
|
||||||
ref var renderRequests = ref frameResource.RenderRequests;
|
ref var renderRequests = ref frameResource.RenderRequests;
|
||||||
@@ -286,7 +293,6 @@ public class RenderSystem : IDisposable
|
|||||||
|
|
||||||
var renderCtx = new RenderContext(_graphicsEngine, _resourceManager, cmd);
|
var renderCtx = new RenderContext(_graphicsEngine, _resourceManager, cmd);
|
||||||
|
|
||||||
//Debug.WriteLine($"GPU: Frame started.");
|
|
||||||
_renderPipeline.Render(renderCtx, renderRequests.AsSpan());
|
_renderPipeline.Render(renderCtx, renderRequests.AsSpan());
|
||||||
_swapChainManager.TransitionToPresent(cmd);
|
_swapChainManager.TransitionToPresent(cmd);
|
||||||
|
|
||||||
@@ -313,7 +319,13 @@ public class RenderSystem : IDisposable
|
|||||||
renderRequests.Clear();
|
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);
|
_resourceManager.EndFrame(_completedFenceValue);
|
||||||
r = _graphicsEngine.EndFrame(_completedFenceValue);
|
r = _graphicsEngine.EndFrame(_completedFenceValue);
|
||||||
|
|
||||||
@@ -323,9 +335,6 @@ public class RenderSystem : IDisposable
|
|||||||
break;
|
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.");
|
Debug.Assert(!_disposed, "Cannot signal CPU ready on a disposed RenderSystem.");
|
||||||
|
|
||||||
var eventIndex = (int)(_cpuFenceValue % _config.FrameBufferCount);
|
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++;
|
_cpuFenceValue++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -381,7 +395,12 @@ public class RenderSystem : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
var eventIndex = (int)(_cpuFenceValue % _config.FrameBufferCount);
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -391,14 +410,23 @@ public class RenderSystem : IDisposable
|
|||||||
Debug.Assert(!_disposed, "Cannot add render request to a disposed RenderSystem.");
|
Debug.Assert(!_disposed, "Cannot add render request to a disposed RenderSystem.");
|
||||||
|
|
||||||
var frameIndex = (int)(_cpuFenceValue % _config.FrameBufferCount);
|
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)
|
public bool WaitForGPUReady(int timeOut = -1)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed, "Cannot wait for GPU ready on a disposed RenderSystem.");
|
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);
|
return _frameResources[eventIndex].GpuReadyEvent.WaitOne(timeOut);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -259,7 +259,7 @@ public partial class ResourceManager
|
|||||||
_retiringPages.Enqueue(new RetiringPage
|
_retiringPages.Enqueue(new RetiringPage
|
||||||
{
|
{
|
||||||
page = page,
|
page = page,
|
||||||
retireFrame = _cpuFrame
|
retireFrame = _submittedFrame
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ public sealed partial class ResourceManager : IDisposable
|
|||||||
|
|
||||||
private readonly MaterialPaletteStore _materialPalettes;
|
private readonly MaterialPaletteStore _materialPalettes;
|
||||||
|
|
||||||
private ulong _cpuFrame;
|
private ulong _submittedFrame;
|
||||||
|
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
@@ -59,13 +59,16 @@ public sealed partial class ResourceManager : IDisposable
|
|||||||
internal void BeginFrame(ulong cpuFrame)
|
internal void BeginFrame(ulong cpuFrame)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Debug.Assert(!_disposed);
|
||||||
_cpuFrame = cpuFrame;
|
_submittedFrame = cpuFrame;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void EndFrame(ulong gpuFrame)
|
internal void EndFrame(ulong completedFrame)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Debug.Assert(!_disposed);
|
||||||
EndFramePool(gpuFrame);
|
|
||||||
|
//_submittedFrame = submittedFrame;
|
||||||
|
|
||||||
|
EndFramePool(completedFrame);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -125,6 +125,9 @@ public sealed partial class GraphicsTestWindow : Window
|
|||||||
ctx.UpdateObjectData(_meshHandle);
|
ctx.UpdateObjectData(_meshHandle);
|
||||||
|
|
||||||
directCmd.End().ThrowIfFailed();
|
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.Submit(directCmd);
|
||||||
_renderSystem.GraphicsEngine.Device.GraphicsQueue.WaitIdle();
|
_renderSystem.GraphicsEngine.Device.GraphicsQueue.WaitIdle();
|
||||||
|
|
||||||
@@ -205,7 +208,7 @@ public sealed partial class GraphicsTestWindow : Window
|
|||||||
|
|
||||||
if (_renderSystem.TryAcquireCPUFrame())
|
if (_renderSystem.TryAcquireCPUFrame())
|
||||||
{
|
{
|
||||||
//Debug.WriteLine($"CPU: Frame started.");
|
Debug.WriteLine($"CPU: Frame started.");
|
||||||
_world.SystemManager.UpdateAll(default);
|
_world.SystemManager.UpdateAll(default);
|
||||||
_renderSystem.SignalCPUReady();
|
_renderSystem.SignalCPUReady();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user