refactor(render): establish frame orchestration contract
This commit is contained in:
parent
f316bbd817
commit
7e4cfb37c3
4 changed files with 489 additions and 5 deletions
|
|
@ -214,7 +214,10 @@ internal sealed class ImmediateGpuResourceRetirementQueue : IGpuResourceRetireme
|
|||
}
|
||||
}
|
||||
|
||||
internal sealed class GpuFrameFlightController : IGpuResourceRetirementQueue, IDisposable
|
||||
internal sealed class GpuFrameFlightController :
|
||||
IGpuResourceRetirementQueue,
|
||||
IRenderFrameLifetime,
|
||||
IDisposable
|
||||
{
|
||||
internal const int DefaultMaximumFramesInFlight = 3;
|
||||
private const ulong WaitSliceNanoseconds = 1_000_000;
|
||||
|
|
|
|||
131
src/AcDream.App/Rendering/RenderFrameOrchestrator.cs
Normal file
131
src/AcDream.App/Rendering/RenderFrameOrchestrator.cs
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
namespace AcDream.App.Rendering;
|
||||
|
||||
/// <summary>
|
||||
/// Host-owned values for one render callback. Render phases borrow this value
|
||||
/// for the duration of <see cref="RenderFrameOrchestrator.Render"/> only.
|
||||
/// </summary>
|
||||
internal readonly record struct RenderFrameInput(
|
||||
double DeltaSeconds,
|
||||
int ViewportWidth,
|
||||
int ViewportHeight);
|
||||
|
||||
/// <summary>
|
||||
/// World facts published after the normal-world phase has either drawn or
|
||||
/// intentionally skipped the viewport.
|
||||
/// </summary>
|
||||
internal readonly record struct WorldRenderFrameOutcome(
|
||||
int VisibleLandblocks,
|
||||
int TotalLandblocks,
|
||||
bool NormalWorldDrawn);
|
||||
|
||||
/// <summary>
|
||||
/// Private-presentation facts published after portal, paperdoll, retained UI,
|
||||
/// developer UI, and screenshot work has completed.
|
||||
/// </summary>
|
||||
internal readonly record struct PrivatePresentationFrameOutcome(
|
||||
bool PortalViewportDrawn,
|
||||
bool ScreenshotCaptured);
|
||||
|
||||
/// <summary>The immutable result observed by post-screenshot diagnostics.</summary>
|
||||
internal readonly record struct RenderFrameOutcome(
|
||||
WorldRenderFrameOutcome World,
|
||||
PrivatePresentationFrameOutcome Presentation);
|
||||
|
||||
internal interface IRenderFrameLifetime
|
||||
{
|
||||
void BeginFrame();
|
||||
|
||||
void EndFrame();
|
||||
}
|
||||
|
||||
internal interface IRenderFrameResourcePhase
|
||||
{
|
||||
void Prepare(RenderFrameInput input);
|
||||
}
|
||||
|
||||
internal interface IWorldSceneFramePhase
|
||||
{
|
||||
WorldRenderFrameOutcome Render(RenderFrameInput input);
|
||||
}
|
||||
|
||||
internal interface IPrivatePresentationFramePhase
|
||||
{
|
||||
PrivatePresentationFrameOutcome Render(
|
||||
RenderFrameInput input,
|
||||
WorldRenderFrameOutcome world);
|
||||
}
|
||||
|
||||
internal interface IRenderFrameDiagnosticsPhase
|
||||
{
|
||||
void Publish(RenderFrameInput input, RenderFrameOutcome outcome);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Owns the accepted outer render transaction. Focused phase owners retain the
|
||||
/// detailed PView, private-viewport, UI, and diagnostic order inside their
|
||||
/// typed boundaries.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="IRenderFrameLifetime.BeginFrame"/> intentionally runs outside
|
||||
/// the protected render body. Once begin succeeds, close is attempted exactly
|
||||
/// once. This preserves the production GPU-flight contract: a render failure
|
||||
/// and close failure are reported together, while either failure alone
|
||||
/// propagates directly.
|
||||
/// </remarks>
|
||||
internal sealed class RenderFrameOrchestrator
|
||||
{
|
||||
private readonly IRenderFrameLifetime _lifetime;
|
||||
private readonly IRenderFrameResourcePhase _resources;
|
||||
private readonly IWorldSceneFramePhase _world;
|
||||
private readonly IPrivatePresentationFramePhase _presentation;
|
||||
private readonly IRenderFrameDiagnosticsPhase _diagnostics;
|
||||
|
||||
public RenderFrameOrchestrator(
|
||||
IRenderFrameLifetime lifetime,
|
||||
IRenderFrameResourcePhase resources,
|
||||
IWorldSceneFramePhase world,
|
||||
IPrivatePresentationFramePhase presentation,
|
||||
IRenderFrameDiagnosticsPhase diagnostics)
|
||||
{
|
||||
_lifetime = lifetime ?? throw new ArgumentNullException(nameof(lifetime));
|
||||
_resources = resources ?? throw new ArgumentNullException(nameof(resources));
|
||||
_world = world ?? throw new ArgumentNullException(nameof(world));
|
||||
_presentation = presentation ?? throw new ArgumentNullException(nameof(presentation));
|
||||
_diagnostics = diagnostics ?? throw new ArgumentNullException(nameof(diagnostics));
|
||||
}
|
||||
|
||||
public RenderFrameOutcome Render(RenderFrameInput input)
|
||||
{
|
||||
_lifetime.BeginFrame();
|
||||
Exception? renderFailure = null;
|
||||
try
|
||||
{
|
||||
_resources.Prepare(input);
|
||||
WorldRenderFrameOutcome world = _world.Render(input);
|
||||
PrivatePresentationFrameOutcome presentation =
|
||||
_presentation.Render(input, world);
|
||||
var outcome = new RenderFrameOutcome(world, presentation);
|
||||
_diagnostics.Publish(input, outcome);
|
||||
return outcome;
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
renderFailure = error;
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
_lifetime.EndFrame();
|
||||
}
|
||||
catch (Exception closeFailure) when (renderFailure is not null)
|
||||
{
|
||||
throw new AggregateException(
|
||||
"Rendering failed and the in-flight GPU frame could not be closed.",
|
||||
renderFailure,
|
||||
closeFailure);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue