using AcDream.App.Rendering.Scene; using AcDream.App.Rendering.Selection; using AcDream.App.Rendering.Vfx; using AcDream.App.Streaming; using AcDream.Core.Rendering; namespace AcDream.App.Rendering; internal interface IWorldScenePViewRenderer { IReadOnlySet OutdoorSceneParticleEntityIds { get; } RetailPViewFrameResult DrawInside(RetailPViewFrameInput input); void AbortFrame(); } internal sealed class WorldScenePViewRenderer : IWorldScenePViewRenderer { private readonly RetailPViewRenderer _renderer; private readonly IRetailPViewPassExecutor _passes; private readonly IOutdoorSceneParticleOwnerSource _particles; public WorldScenePViewRenderer( RetailPViewRenderer renderer, IRetailPViewPassExecutor passes, IOutdoorSceneParticleOwnerSource particles) { _renderer = renderer ?? throw new ArgumentNullException(nameof(renderer)); _passes = passes ?? throw new ArgumentNullException(nameof(passes)); _particles = particles ?? throw new ArgumentNullException(nameof(particles)); } public IReadOnlySet OutdoorSceneParticleEntityIds => _particles.OutdoorSceneParticleEntityIds; public RetailPViewFrameResult DrawInside(RetailPViewFrameInput input) => _renderer.DrawInside(input, _passes); public void AbortFrame() => _passes.AbortFrame(); } /// /// Owns the normal-world render transaction between shared frame-resource /// preparation and private presentation. It preserves retail's one PView /// decision: a resolved viewer root uses DrawInside; only an unresolved root /// uses the flat safety path. /// internal sealed class WorldSceneRenderer : IWorldSceneFramePhase { private readonly IRenderFrameFoundationSource _foundation; private readonly IRenderLoginStateSource _login; private readonly IWorldSceneSkyStateSource _sky; private readonly IWorldRenderFrameBuilder _frames; private readonly IWorldSceneEntitySource _entities; private readonly IWorldSceneSelectionFrame? _selection; private readonly IWorldSceneAlphaFrame _alpha; private readonly IWorldSceneParticleVisibility _particleVisibility; private readonly IWorldScenePViewRenderer _pview; private readonly IRetailPViewCellSource _pviewCells; private readonly IWorldScenePassExecutor _passes; private readonly IWorldRenderRangeSource _renderRange; private readonly IWorldSceneDiagnostics _diagnostics; private readonly IWorldGenerationAvailability _availability; private readonly RetailPViewFrameInput _pviewFrameInput = new(); public WorldSceneRenderer( IRenderFrameFoundationSource foundation, IRenderLoginStateSource login, IWorldSceneSkyStateSource sky, IWorldRenderFrameBuilder frames, IWorldSceneEntitySource entities, IWorldSceneSelectionFrame? selection, IWorldSceneAlphaFrame alpha, IWorldSceneParticleVisibility particleVisibility, IWorldScenePViewRenderer pview, IRetailPViewCellSource pviewCells, IWorldScenePassExecutor passes, IWorldRenderRangeSource renderRange, IWorldSceneDiagnostics diagnostics, IWorldGenerationAvailability? availability = null) { _foundation = foundation ?? throw new ArgumentNullException(nameof(foundation)); _login = login ?? throw new ArgumentNullException(nameof(login)); _sky = sky ?? throw new ArgumentNullException(nameof(sky)); _frames = frames ?? throw new ArgumentNullException(nameof(frames)); _entities = entities ?? throw new ArgumentNullException(nameof(entities)); _selection = selection; _alpha = alpha ?? throw new ArgumentNullException(nameof(alpha)); _particleVisibility = particleVisibility ?? throw new ArgumentNullException(nameof(particleVisibility)); _pview = pview ?? throw new ArgumentNullException(nameof(pview)); _pviewCells = pviewCells ?? throw new ArgumentNullException(nameof(pviewCells)); _passes = passes ?? throw new ArgumentNullException(nameof(passes)); _renderRange = renderRange ?? throw new ArgumentNullException(nameof(renderRange)); _diagnostics = diagnostics ?? throw new ArgumentNullException(nameof(diagnostics)); _availability = availability ?? AlwaysAvailableWorldGeneration.Instance; } public WorldRenderFrameOutcome Render(RenderFrameInput input) { _ = input; bool selectionFrameStarted = _selection is not null; bool worldFrameStarted = false; bool pviewFrameStarted = false; try { _selection?.BeginFrame(); if (!_availability.IsWorldAvailable) { _selection?.CompleteFrame(); selectionFrameStarted = false; return default; } RenderFrameFoundation foundation = _foundation.Foundation; if (foundation.PortalViewportVisible) { _selection?.CompleteFrame(); selectionFrameStarted = false; return default; } // Set before BeginFrame so an already-poisoned queue is recovered by // the same abort path instead of making every later frame fail. worldFrameStarted = true; _alpha.BeginFrame(); WorldRenderFrame world = _frames.Build( in foundation, _login.IsWaitingForLogin, _sky.ActiveDayGroup); _passes.BeginFrame(); WorldCameraFrame camera = world.Camera; WorldRootFrame roots = world.Roots; LoadedCell? clipRoot = world.ClipRoot; bool renderSky = roots.RenderSky; bool drawSkyThisFrame = false; bool terrainDrawn = false; bool skyDrawn = false; bool depthClear = false; bool outdoorSceneryDrawn = false; int liveDynamicDrawnCount = 0; string sceneParticles = "none"; TerrainClipMode terrainClipMode = TerrainClipMode.Planes; RetailPViewFrameResult? pviewResult = null; if (clipRoot is null) { _passes.PrepareFlatWorldClip(); drawSkyThisFrame = renderSky; skyDrawn = drawSkyThisFrame; if (drawSkyThisFrame) { _passes.DrawFlatSky( in camera, in foundation, _sky.ActiveDayGroup, _sky.DayFraction); } // The former sky-only "waiting for login" skip lived here. // It is unreachable now: LocalPlayerTeleportRenderStateSource // folds IsWaitingForLogin into PortalViewportVisible (retail's // pre-player gameplay screen draws NO world — black, not sky), // so the portal-visible return above already covers every // waiting frame. One gate computes the frame's visibility; // this phase only enforces it. _passes.DrawFlatTerrain(in camera, roots.PlayerLandblockId); terrainDrawn = true; } else { terrainClipMode = TerrainClipMode.Skip; } if (clipRoot is not null) { pviewFrameStarted = true; pviewResult = _pview.DrawInside( _pviewFrameInput.Reset( clipRoot, world.Buildings.NearbyBuildingCells, roots.ViewerEyePosition, camera.ViewProjection, _pviewCells, camera.Camera, camera.Position, camera.Frustum, roots.PlayerLandblockId, world.AnimatedEntityIds, roots.RenderCenterLandblockX, roots.RenderCenterLandblockY, _renderRange.NearRadius, _entities.LandblockEntries, renderSky, roots.PlayerSeenOutside, _sky.DayFraction, _sky.ActiveDayGroup, foundation.Sky, foundation.EnvironOverrideActive, roots.ViewerCellId, roots.PlayerCellId, roots.PlayerViewPosition, camera.Camera.View, _diagnostics.CameraCellResolution)); _particleVisibility.MarkVisibleCells(pviewResult.DrawableCells); _frames.ObserveDrawableCells(pviewResult.DrawableCells); _diagnostics.EmitPViewInput( pviewResult.PortalFrame, camera.ViewProjection, clipRoot, camera.Position, roots.PlayerViewPosition); bool hasOutsideSlice = pviewResult.ClipAssembly.OutsideViewSlices.Length > 0; terrainDrawn = hasOutsideSlice; skyDrawn = renderSky && hasOutsideSlice; // This mirrors the established diagnostic meaning, including // outdoor roots whose PView executor does not issue an interior // depth clear. depthClear = hasOutsideSlice; RenderProjectionCounts sourceCounts = pviewResult.SourceCounts; if (sourceCounts.IndoorCellStatic > 0 || hasOutsideSlice) sceneParticles = "pviewScoped"; outdoorSceneryDrawn = sourceCounts.OutdoorStatic > 0 && hasOutsideSlice; liveDynamicDrawnCount = sourceCounts.LiveDynamicRoot; } else { _passes.DrawFlatEntities( in camera, _entities.LandblockEntries, roots.PlayerLandblockId, world.AnimatedEntityIds); _frames.ClearDrawableCells(); } _passes.DisableClipDistances(); sceneParticles = _passes.DrawPostWorldParticles( clipRoot, pviewResult?.ClipAssembly, in camera, _pview.OutdoorSceneParticleEntityIds, sceneParticles); if (clipRoot is null && drawSkyThisFrame) { skyDrawn = true; _passes.DrawFlatWeather( in camera, in foundation, _sky.ActiveDayGroup, _sky.DayFraction); } _diagnostics.EmitRenderSignature( clipRoot is null ? "OutdoorRoot" : "RetailPViewInside", clipRoot, roots.ViewerRoot, roots.PlayerRoot, roots.ViewerCellId, roots.PlayerCellId, roots.PlayerIndoorGate, roots.CameraInsideCell, renderSky, drawSkyThisFrame, terrainDrawn, terrainClipMode, skyDrawn, depthClear, outdoorSceneryDrawn, liveDynamicDrawnCount, sceneParticles, pviewResult, camera.Position, roots.PlayerViewPosition); WorldSceneDiagnosticOutcome diagnostic = _diagnostics.DrawAndPublish( in camera, _entities.LandblockBounds); CompleteWorldFrame(); worldFrameStarted = false; pviewFrameStarted = false; _selection?.CompleteFrame(); selectionFrameStarted = false; return new WorldRenderFrameOutcome( diagnostic.VisibleLandblocks, diagnostic.TotalLandblocks, NormalWorldDrawn: true); } catch (Exception renderFailure) { List? abortFailures = AbortFrame( worldFrameStarted, pviewFrameStarted, selectionFrameStarted); if (abortFailures is { Count: > 0 }) { abortFailures.Insert(0, renderFailure); throw new AggregateException( "World rendering failed and the incomplete frame could not be fully aborted.", abortFailures); } throw; } } private void CompleteWorldFrame() { _alpha.EndFrame(); if (_passes.TerrainVisibleCellIds is { } visibleTerrainCells) _particleVisibility.MarkVisibleCells(visibleTerrainCells); _particleVisibility.CompleteFrame(); } private List? AbortFrame( bool worldFrameStarted, bool pviewFrameStarted, bool selectionFrameStarted) { List? failures = null; if (worldFrameStarted) { if (pviewFrameStarted) TryAbort(_pview.AbortFrame); TryAbort(_passes.AbortFrame); TryAbort(_particleVisibility.AbortFrame); TryAbort(_alpha.AbortFrame); } if (selectionFrameStarted && _selection is not null) TryAbort(_selection.AbortFrame); return failures; void TryAbort(Action abort) { try { abort(); } catch (Exception error) { (failures ??= []).Add(error); } } } }