refactor(render): compose render frame orchestrator

Move the accepted draw transaction and failure recovery behind typed frame-phase owners so GameWindow only supplies immutable frame input. Preserve retail draw order, make ImGui/bootstrap shutdown ownership explicit, and restore exact text-render GL state on failures.\n\nCo-authored-by: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-22 08:03:49 +02:00
parent 28e1cf8029
commit 9d7df1bfc5
25 changed files with 1675 additions and 279 deletions

View file

@ -0,0 +1,148 @@
using System.Numerics;
using AcDream.App.Diagnostics;
using AcDream.App.Streaming;
using AcDream.App.UI;
using Silk.NET.Input;
namespace AcDream.App.Rendering;
internal interface IPrivatePortalViewport
{
void Draw(int width, int height);
}
internal interface IPrivatePaperdollFrame
{
void Render();
}
internal interface IRetainedGameplayUiFrame
{
void Render(double deltaSeconds, int width, int height);
}
internal interface IPrivateFrameScreenshot
{
bool CapturePending(int width, int height);
}
/// <summary>
/// Draws the private and 2-D presentation layers after the world alpha scope
/// has closed: portal CreatureMode, paperdoll FBO, retained gameplay UI,
/// developer UI, then the complete-frame screenshot.
/// </summary>
internal sealed class PrivatePresentationRenderer : IPrivatePresentationFramePhase
{
private readonly IPrivatePortalViewport _portal;
private readonly IRenderFrameFoundationSource _foundation;
private readonly IPrivatePaperdollFrame? _paperdoll;
private readonly IRetainedGameplayUiFrame? _gameplayUi;
private readonly IDevToolsFrameLifecycle? _devTools;
private readonly IPrivateFrameScreenshot? _screenshots;
public PrivatePresentationRenderer(
IPrivatePortalViewport portal,
IRenderFrameFoundationSource foundation,
IPrivatePaperdollFrame? paperdoll,
IRetainedGameplayUiFrame? gameplayUi,
IDevToolsFrameLifecycle? devTools,
IPrivateFrameScreenshot? screenshots)
{
_portal = portal ?? throw new ArgumentNullException(nameof(portal));
_foundation = foundation
?? throw new ArgumentNullException(nameof(foundation));
_paperdoll = paperdoll;
_gameplayUi = gameplayUi;
_devTools = devTools;
_screenshots = screenshots;
}
public PrivatePresentationFrameOutcome Render(
RenderFrameInput input,
WorldRenderFrameOutcome world)
{
_ = world;
// Foundation is the immutable result of this frame's clear phase.
// Snapshot it before drawing so a portal transition raised by a
// private layer cannot relabel the viewport that was just presented.
bool portalViewportVisible =
_foundation.Foundation.PortalViewportVisible;
_portal.Draw(input.ViewportWidth, input.ViewportHeight);
_paperdoll?.Render();
_gameplayUi?.Render(
input.DeltaSeconds,
input.ViewportWidth,
input.ViewportHeight);
_devTools?.Render(
input.DeltaSeconds,
input.ViewportWidth,
input.ViewportHeight);
bool screenshotCaptured = _screenshots?.CapturePending(
input.ViewportWidth,
input.ViewportHeight) == true;
return new PrivatePresentationFrameOutcome(
portalViewportVisible,
screenshotCaptured);
}
}
/// <summary>Typed portal viewport adapter over the canonical teleport owner.</summary>
internal sealed class LocalPlayerPortalViewport : IPrivatePortalViewport
{
private readonly LocalPlayerTeleportController _teleport;
private readonly CameraController _camera;
public LocalPlayerPortalViewport(
LocalPlayerTeleportController teleport,
CameraController camera)
{
_teleport = teleport ?? throw new ArgumentNullException(nameof(teleport));
_camera = camera ?? throw new ArgumentNullException(nameof(camera));
}
public void Draw(int width, int height) =>
_teleport.DrawPortalViewport(width, height, _camera.Active.Projection);
}
/// <summary>
/// One retained-gameplay-UI tick/cursor/draw transaction. This is the explicit
/// boundary where the render graph reaches the retained UI composition owner;
/// <see cref="RetailUiRuntime"/> intentionally retains its documented state and
/// command bindings. The distinction between direct frame ownership and
/// recursive shared-owner reachability is recorded in
/// docs/architecture/code-structure.md.
/// </summary>
internal sealed class RetainedGameplayUiFrame : IRetainedGameplayUiFrame
{
private readonly RetailUiRuntime _runtime;
private readonly IInputContext? _input;
public RetainedGameplayUiFrame(
RetailUiRuntime runtime,
IInputContext? input)
{
_runtime = runtime ?? throw new ArgumentNullException(nameof(runtime));
_input = input;
}
public void Render(double deltaSeconds, int width, int height)
{
_runtime.Tick(deltaSeconds);
if (_input is not null)
_runtime.UpdateCursor(_input.Mice);
_runtime.Draw(new Vector2(width, height));
}
}
/// <summary>Optional complete-frame capture adapter.</summary>
internal sealed class PrivateFrameScreenshot : IPrivateFrameScreenshot
{
private readonly FrameScreenshotController _screenshots;
public PrivateFrameScreenshot(FrameScreenshotController screenshots) =>
_screenshots = screenshots
?? throw new ArgumentNullException(nameof(screenshots));
public bool CapturePending(int width, int height) =>
_screenshots.CapturePending(width, height);
}