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;
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 IRenderFrameFoundationSource
{
RenderFrameFoundation Foundation { get; }
}
internal interface IRenderFrameLivePreparation
{
void Prepare(int gpuSlot);
}
///
/// 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.
///
internal sealed class RenderFrameResourceController :
IRenderFrameResourcePhase,
IRenderFrameFoundationSource
{
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);
}
}
/// Exact ordered begin calls for resources shared by the frame.
internal sealed class RuntimeRenderFrameBeginResources : IRenderFrameBeginResources
{
private readonly TextureCache? _textures;
private readonly WbDrawDispatcher? _dispatcher;
private readonly EnvCellRenderer? _environmentCells;
private readonly PortalDepthMaskRenderer? _portalDepth;
private readonly ClipFrame? _clip;
private readonly TerrainModernRenderer? _terrain;
private readonly SceneLightingUboBinding? _lighting;
public RuntimeRenderFrameBeginResources(
TextureCache? textures,
WbDrawDispatcher? dispatcher,
EnvCellRenderer? environmentCells,
PortalDepthMaskRenderer? portalDepth,
ClipFrame? clip,
TerrainModernRenderer? terrain,
SceneLightingUboBinding? lighting)
{
_textures = textures;
_dispatcher = dispatcher;
_environmentCells = environmentCells;
_portalDepth = portalDepth;
_clip = clip;
_terrain = terrain;
_lighting = lighting;
}
public void Begin(int gpuSlot)
{
_textures?.BeginCompositeTextureFrame();
_textures?.TickCompositeTextureCache();
_dispatcher?.BeginFrame(gpuSlot);
_environmentCells?.BeginFrame(gpuSlot);
_portalDepth?.BeginFrame(gpuSlot);
// The world-HUD and retained-UI TextRenderers no longer take a
// per-slot begin: Campaign V slice V4a ported them onto the shared
// IGpuFrame ring (see GpuDeviceFrameLifetime), which is reset once
// per frame by IGpuDevice.BeginFrame() rather than per renderer.
_clip?.BeginFrame(gpuSlot);
_terrain?.BeginFrame(gpuSlot);
_lighting?.BeginFrame(gpuSlot);
}
}
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;
}
// Campaign V slice V11 deleted RuntimeRenderFrameClearPhase, the raw-GL
// IRenderFrameClearPhase implementation (glClearColor/glClear +
// IRenderFrameGlState.RestoreFrameDefaults): it had zero remaining
// construction sites — VulkanRenderFrameClearPhase (VulkanCompositionFramePhases.cs)
// is the sole production implementer, expressing the same atmosphere-clear
// and portal-viewport-black logic as a Vulkan pass load op instead.
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;
}
}
/// Render-thread mesh publication, reveal evaluation, and VFX begin.
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);
}
}
}
/// Owns the render-time weather clock and deterministic day index.
internal sealed class RenderWeatherFrameController : IRenderWeatherFramePhase
{
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;
}
}