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 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 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; } /// Atmosphere clear and frame-global GL state establishment. 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; private readonly IRenderFrameGlState _frameGlState; public RuntimeRenderFrameClearPhase( GL gl, WorldTimeService worldTime, WeatherSystem weather, IRenderFramePortalStateSource portal, ParticleVisibilityController particleVisibility, WorldRenderDiagnostics diagnostics, IRenderFrameGlState frameGlState) { _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)); _frameGlState = frameGlState ?? throw new ArgumentNullException(nameof(frameGlState)); } 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); } _frameGlState.RestoreFrameDefaults(); _gl.Clear( ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit); _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; } } /// 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 { 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; } }