refactor(render): extract frame resource preparation
Move ordered resource begin, clear/state setup, live upload/reveal, weather timing, and display pacing behind focused owners while preserving the accepted frame order and retryable shutdown ownership. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
733126a272
commit
bc6f09f987
8 changed files with 1068 additions and 205 deletions
373
src/AcDream.App/Rendering/RenderFrameResourceController.cs
Normal file
373
src/AcDream.App/Rendering/RenderFrameResourceController.cs
Normal file
|
|
@ -0,0 +1,373 @@
|
|||
using AcDream.App.Diagnostics;
|
||||
using AcDream.App.Input;
|
||||
using AcDream.App.Rendering.Vfx;
|
||||
using AcDream.App.Rendering.Wb;
|
||||
using AcDream.App.Streaming;
|
||||
using AcDream.App.World;
|
||||
using AcDream.Core.World;
|
||||
using Silk.NET.OpenGL;
|
||||
|
||||
namespace AcDream.App.Rendering;
|
||||
|
||||
internal readonly record struct RenderFrameFoundation(
|
||||
bool PortalViewportVisible,
|
||||
SkyKeyframe Sky,
|
||||
AtmosphereSnapshot Atmosphere)
|
||||
{
|
||||
public bool EnvironOverrideActive =>
|
||||
Atmosphere.Override != EnvironOverride.None;
|
||||
}
|
||||
|
||||
internal interface IRenderFrameBeginResources
|
||||
{
|
||||
void Begin(int gpuSlot);
|
||||
}
|
||||
|
||||
internal interface IRenderFrameSlotSource
|
||||
{
|
||||
int CurrentSlot { get; }
|
||||
}
|
||||
|
||||
internal interface IRenderFrameClearPhase
|
||||
{
|
||||
RenderFrameFoundation Clear();
|
||||
}
|
||||
|
||||
internal interface IRenderFrameLivePreparation
|
||||
{
|
||||
void Prepare(int gpuSlot);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Owns the pre-world resource transaction after GPU-flight begin. The three
|
||||
/// typed phases preserve resource begin, clear/state, then live publication
|
||||
/// order without exposing a renderer service bag.
|
||||
/// </summary>
|
||||
internal sealed class RenderFrameResourceController : IRenderFrameResourcePhase
|
||||
{
|
||||
private readonly IRenderFrameSlotSource _frameSlots;
|
||||
private readonly IRenderFrameBeginResources _resources;
|
||||
private readonly IRenderFrameClearPhase _clear;
|
||||
private readonly IRenderFrameLivePreparation _live;
|
||||
|
||||
public RenderFrameResourceController(
|
||||
IRenderFrameSlotSource frameSlots,
|
||||
IRenderFrameBeginResources resources,
|
||||
IRenderFrameClearPhase clear,
|
||||
IRenderFrameLivePreparation live)
|
||||
{
|
||||
_frameSlots = frameSlots
|
||||
?? throw new ArgumentNullException(nameof(frameSlots));
|
||||
_resources = resources ?? throw new ArgumentNullException(nameof(resources));
|
||||
_clear = clear ?? throw new ArgumentNullException(nameof(clear));
|
||||
_live = live ?? throw new ArgumentNullException(nameof(live));
|
||||
}
|
||||
|
||||
public RenderFrameFoundation Foundation { get; private set; }
|
||||
|
||||
public void Prepare(RenderFrameInput input)
|
||||
{
|
||||
int gpuSlot = _frameSlots.CurrentSlot;
|
||||
_resources.Begin(gpuSlot);
|
||||
Foundation = _clear.Clear();
|
||||
_live.Prepare(gpuSlot);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Exact ordered begin calls for resources shared by the frame.</summary>
|
||||
internal sealed class RuntimeRenderFrameBeginResources : IRenderFrameBeginResources
|
||||
{
|
||||
private readonly TextureCache? _textures;
|
||||
private readonly WbDrawDispatcher? _dispatcher;
|
||||
private readonly EnvCellRenderer? _environmentCells;
|
||||
private readonly PortalDepthMaskRenderer? _portalDepth;
|
||||
private readonly TextRenderer? _worldText;
|
||||
private readonly TextRenderer? _uiText;
|
||||
private readonly ClipFrame? _clip;
|
||||
private readonly TerrainModernRenderer? _terrain;
|
||||
private readonly SceneLightingUboBinding? _lighting;
|
||||
private readonly FrameProfiler _profiler;
|
||||
private readonly GL _gl;
|
||||
|
||||
public RuntimeRenderFrameBeginResources(
|
||||
TextureCache? textures,
|
||||
WbDrawDispatcher? dispatcher,
|
||||
EnvCellRenderer? environmentCells,
|
||||
PortalDepthMaskRenderer? portalDepth,
|
||||
TextRenderer? worldText,
|
||||
TextRenderer? uiText,
|
||||
ClipFrame? clip,
|
||||
TerrainModernRenderer? terrain,
|
||||
SceneLightingUboBinding? lighting,
|
||||
FrameProfiler profiler,
|
||||
GL gl)
|
||||
{
|
||||
_textures = textures;
|
||||
_dispatcher = dispatcher;
|
||||
_environmentCells = environmentCells;
|
||||
_portalDepth = portalDepth;
|
||||
_worldText = worldText;
|
||||
_uiText = uiText;
|
||||
_clip = clip;
|
||||
_terrain = terrain;
|
||||
_lighting = lighting;
|
||||
_profiler = profiler ?? throw new ArgumentNullException(nameof(profiler));
|
||||
_gl = gl ?? throw new ArgumentNullException(nameof(gl));
|
||||
}
|
||||
|
||||
public void Begin(int gpuSlot)
|
||||
{
|
||||
_textures?.BeginCompositeTextureFrame();
|
||||
_textures?.TickCompositeTextureCache();
|
||||
_dispatcher?.BeginFrame(gpuSlot);
|
||||
_environmentCells?.BeginFrame(gpuSlot);
|
||||
_portalDepth?.BeginFrame(gpuSlot);
|
||||
_worldText?.BeginFrame(gpuSlot);
|
||||
_uiText?.BeginFrame(gpuSlot);
|
||||
_clip?.BeginFrame(gpuSlot);
|
||||
_terrain?.BeginFrame(gpuSlot);
|
||||
_lighting?.BeginFrame(gpuSlot);
|
||||
_profiler.FrameBoundary(_gl);
|
||||
}
|
||||
}
|
||||
|
||||
internal interface IRenderFramePortalStateSource
|
||||
{
|
||||
bool IsPortalViewportVisible { get; }
|
||||
|
||||
uint ActiveDestinationCell { get; }
|
||||
}
|
||||
|
||||
internal sealed class LocalPlayerTeleportRenderStateSource
|
||||
: IRenderFramePortalStateSource
|
||||
{
|
||||
private readonly LocalPlayerTeleportController _teleport;
|
||||
|
||||
public LocalPlayerTeleportRenderStateSource(LocalPlayerTeleportController teleport)
|
||||
{
|
||||
_teleport = teleport ?? throw new ArgumentNullException(nameof(teleport));
|
||||
}
|
||||
|
||||
public bool IsPortalViewportVisible => _teleport.IsPortalViewportVisible;
|
||||
|
||||
public uint ActiveDestinationCell => _teleport.ActiveDestinationCell;
|
||||
}
|
||||
|
||||
/// <summary>Atmosphere clear and frame-global GL state establishment.</summary>
|
||||
internal sealed class RuntimeRenderFrameClearPhase : IRenderFrameClearPhase
|
||||
{
|
||||
private readonly GL _gl;
|
||||
private readonly WorldTimeService _worldTime;
|
||||
private readonly WeatherSystem _weather;
|
||||
private readonly IRenderFramePortalStateSource _portal;
|
||||
private readonly ParticleVisibilityController _particleVisibility;
|
||||
private readonly WorldRenderDiagnostics _diagnostics;
|
||||
|
||||
public RuntimeRenderFrameClearPhase(
|
||||
GL gl,
|
||||
WorldTimeService worldTime,
|
||||
WeatherSystem weather,
|
||||
IRenderFramePortalStateSource portal,
|
||||
ParticleVisibilityController particleVisibility,
|
||||
WorldRenderDiagnostics diagnostics)
|
||||
{
|
||||
_gl = gl ?? throw new ArgumentNullException(nameof(gl));
|
||||
_worldTime = worldTime ?? throw new ArgumentNullException(nameof(worldTime));
|
||||
_weather = weather ?? throw new ArgumentNullException(nameof(weather));
|
||||
_portal = portal ?? throw new ArgumentNullException(nameof(portal));
|
||||
_particleVisibility = particleVisibility
|
||||
?? throw new ArgumentNullException(nameof(particleVisibility));
|
||||
_diagnostics = diagnostics ?? throw new ArgumentNullException(nameof(diagnostics));
|
||||
}
|
||||
|
||||
public RenderFrameFoundation Clear()
|
||||
{
|
||||
bool portalViewportVisible = _portal.IsPortalViewportVisible;
|
||||
if (portalViewportVisible)
|
||||
_particleVisibility.Reset();
|
||||
|
||||
SkyKeyframe sky = _worldTime.CurrentSky;
|
||||
AtmosphereSnapshot atmosphere = _weather.Snapshot(in sky);
|
||||
if (portalViewportVisible)
|
||||
{
|
||||
// SceneTool::BeginScene @ 0x0043DAD0 starts the replacement
|
||||
// CreatureMode frame with an opaque black target.
|
||||
_gl.ClearColor(0f, 0f, 0f, 1f);
|
||||
}
|
||||
else
|
||||
{
|
||||
_gl.ClearColor(
|
||||
Math.Clamp(atmosphere.FogColor.X, 0f, 1f),
|
||||
Math.Clamp(atmosphere.FogColor.Y, 0f, 1f),
|
||||
Math.Clamp(atmosphere.FogColor.Z, 0f, 1f),
|
||||
1f);
|
||||
}
|
||||
|
||||
_gl.DepthMask(true);
|
||||
_gl.Clear(
|
||||
ClearBufferMask.ColorBufferBit
|
||||
| ClearBufferMask.DepthBufferBit
|
||||
| ClearBufferMask.StencilBufferBit);
|
||||
_gl.CullFace(TriangleFace.Back);
|
||||
_gl.FrontFace(FrontFaceDirection.CW);
|
||||
_diagnostics.EmitGlStateTripwireIfChanged(
|
||||
AcDream.Core.Rendering.RenderingDiagnostics.ProbeGlStateEnabled);
|
||||
|
||||
return new RenderFrameFoundation(
|
||||
portalViewportVisible,
|
||||
sky,
|
||||
atmosphere);
|
||||
}
|
||||
}
|
||||
|
||||
internal interface IRenderLoginStateSource
|
||||
{
|
||||
bool IsWaitingForLogin { get; }
|
||||
}
|
||||
|
||||
internal sealed class RenderLoginStateSource : IRenderLoginStateSource
|
||||
{
|
||||
private readonly bool _liveMode;
|
||||
private readonly ILocalPlayerModeSource _mode;
|
||||
|
||||
public RenderLoginStateSource(bool liveMode, ILocalPlayerModeSource mode)
|
||||
{
|
||||
_liveMode = liveMode;
|
||||
_mode = mode ?? throw new ArgumentNullException(nameof(mode));
|
||||
}
|
||||
|
||||
public bool IsWaitingForLogin => _liveMode && !_mode.ChaseModeEverEntered;
|
||||
}
|
||||
|
||||
internal interface ILoginRevealCellSource
|
||||
{
|
||||
bool TryGet(out uint cellId);
|
||||
}
|
||||
|
||||
internal sealed class LiveLoginRevealCellSource : ILoginRevealCellSource
|
||||
{
|
||||
private readonly LiveEntityRuntime _entities;
|
||||
private readonly ILocalPlayerIdentitySource _identity;
|
||||
|
||||
public LiveLoginRevealCellSource(
|
||||
LiveEntityRuntime entities,
|
||||
ILocalPlayerIdentitySource identity)
|
||||
{
|
||||
_entities = entities ?? throw new ArgumentNullException(nameof(entities));
|
||||
_identity = identity ?? throw new ArgumentNullException(nameof(identity));
|
||||
}
|
||||
|
||||
public bool TryGet(out uint cellId)
|
||||
{
|
||||
cellId = 0;
|
||||
if (!_entities.TryGetSnapshot(_identity.ServerGuid, out var playerSpawn)
|
||||
|| playerSpawn.Position is not { } position)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
cellId = position.LandblockId;
|
||||
return cellId != 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Render-thread mesh publication, reveal evaluation, and VFX begin.</summary>
|
||||
internal sealed class RuntimeRenderFrameLivePreparation
|
||||
: IRenderFrameLivePreparation
|
||||
{
|
||||
private readonly TextureCache? _textures;
|
||||
private readonly WbMeshAdapter? _meshes;
|
||||
private readonly WorldRevealCoordinator? _worldReveal;
|
||||
private readonly IRenderFramePortalStateSource _portal;
|
||||
private readonly IRenderLoginStateSource _login;
|
||||
private readonly ILoginRevealCellSource _loginCell;
|
||||
private readonly ParticleRenderer? _particles;
|
||||
private readonly FrameProfiler _profiler;
|
||||
private readonly bool _diagnosticsEnabled;
|
||||
private readonly RollingTimingSampleWindow _uploadTiming = new(256);
|
||||
|
||||
public RuntimeRenderFrameLivePreparation(
|
||||
TextureCache? textures,
|
||||
WbMeshAdapter? meshes,
|
||||
WorldRevealCoordinator? worldReveal,
|
||||
IRenderFramePortalStateSource portal,
|
||||
IRenderLoginStateSource login,
|
||||
ILoginRevealCellSource loginCell,
|
||||
ParticleRenderer? particles,
|
||||
FrameProfiler profiler,
|
||||
bool diagnosticsEnabled)
|
||||
{
|
||||
_textures = textures;
|
||||
_meshes = meshes;
|
||||
_worldReveal = worldReveal;
|
||||
_portal = portal ?? throw new ArgumentNullException(nameof(portal));
|
||||
_login = login ?? throw new ArgumentNullException(nameof(login));
|
||||
_loginCell = loginCell ?? throw new ArgumentNullException(nameof(loginCell));
|
||||
_particles = particles;
|
||||
_profiler = profiler ?? throw new ArgumentNullException(nameof(profiler));
|
||||
_diagnosticsEnabled = diagnosticsEnabled;
|
||||
}
|
||||
|
||||
public RollingTimingPercentiles UploadTiming => _uploadTiming.Snapshot();
|
||||
|
||||
public void Prepare(int gpuSlot)
|
||||
{
|
||||
_textures?.TickSurfaceHistogramDumpIfEnabled();
|
||||
long start = _diagnosticsEnabled
|
||||
? System.Diagnostics.Stopwatch.GetTimestamp()
|
||||
: 0L;
|
||||
using (var uploadStage = _profiler.BeginStage(FrameStage.Upload))
|
||||
{
|
||||
_meshes?.Tick();
|
||||
uint revealCell = _portal.ActiveDestinationCell;
|
||||
if (revealCell == 0
|
||||
&& _login.IsWaitingForLogin
|
||||
&& _loginCell.TryGet(out uint loginCell))
|
||||
{
|
||||
revealCell = loginCell;
|
||||
}
|
||||
|
||||
if (revealCell != 0)
|
||||
_worldReveal?.PrepareAndEvaluate(revealCell);
|
||||
_particles?.BeginFrame(gpuSlot);
|
||||
}
|
||||
|
||||
if (_diagnosticsEnabled)
|
||||
{
|
||||
_uploadTiming.PushStopwatchTicks(
|
||||
System.Diagnostics.Stopwatch.GetTimestamp() - start);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Owns the render-time weather clock and deterministic day index.</summary>
|
||||
internal sealed class RenderWeatherFrameController
|
||||
{
|
||||
private readonly WorldTimeService _worldTime;
|
||||
private readonly WeatherSystem _weather;
|
||||
private double _elapsedSeconds;
|
||||
|
||||
public RenderWeatherFrameController(
|
||||
WorldTimeService worldTime,
|
||||
WeatherSystem weather)
|
||||
{
|
||||
_worldTime = worldTime ?? throw new ArgumentNullException(nameof(worldTime));
|
||||
_weather = weather ?? throw new ArgumentNullException(nameof(weather));
|
||||
}
|
||||
|
||||
internal double ElapsedSeconds => _elapsedSeconds;
|
||||
|
||||
public void Tick(double deltaSeconds)
|
||||
{
|
||||
DerethDateTime.Calendar calendar = _worldTime.CurrentCalendar;
|
||||
int dayIndex = calendar.Year
|
||||
* (DerethDateTime.DaysInAMonth * DerethDateTime.MonthsInAYear)
|
||||
+ (int)calendar.Month * DerethDateTime.DaysInAMonth
|
||||
+ (calendar.Day - 1);
|
||||
_weather.Tick(
|
||||
nowSeconds: _elapsedSeconds,
|
||||
dayIndex,
|
||||
dtSeconds: (float)deltaSeconds);
|
||||
_elapsedSeconds += deltaSeconds;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue