feat(render): implement Campaign AR and terrain fidelity

This commit is contained in:
Erik 2026-08-22 13:13:29 +02:00
parent 99cf26e00c
commit 7a5f96ede5
368 changed files with 50611 additions and 950 deletions

View file

@ -1,5 +1,6 @@
using AcDream.App.Rendering.Scene;
using AcDream.App.Rendering.Selection;
using AcDream.App.Rendering.Packs;
using AcDream.App.Rendering.Vfx;
using AcDream.App.Streaming;
using AcDream.Core.Rendering;
@ -15,6 +16,28 @@ internal interface IWorldScenePViewRenderer
void AbortFrame();
}
internal readonly record struct PreparedWorldSceneFrame(
bool ShouldRender,
RenderFrameFoundation Foundation,
WorldRenderFrame World,
int ActiveDayGroup);
/// <summary>
/// Pack-on-only two-stage seam. Preparation resolves the canonical camera and
/// borrowed world roots once; the shadow prepass may consume those exact facts
/// before the normal world pass executes them without a second PView/build.
/// </summary>
internal interface IPreparedWorldSceneFramePhase : IWorldSceneFramePhase
{
PreparedWorldSceneFrame PrepareEnhanced(RenderFrameInput input);
WorldRenderFrameOutcome RenderPreparedEnhanced(
RenderFrameInput input,
in PreparedWorldSceneFrame prepared);
void CancelPreparedEnhanced(in PreparedWorldSceneFrame prepared);
}
internal sealed class WorldScenePViewRenderer : IWorldScenePViewRenderer
{
private readonly RetailPViewRenderer _renderer;
@ -46,7 +69,7 @@ internal sealed class WorldScenePViewRenderer : IWorldScenePViewRenderer
/// decision: a resolved viewer root uses DrawInside; only an unresolved root
/// uses the flat safety path.
/// </summary>
internal sealed class WorldSceneRenderer : IWorldSceneFramePhase
internal sealed class WorldSceneRenderer : IPreparedWorldSceneFramePhase
{
private readonly IRenderFrameFoundationSource _foundation;
private readonly IRenderLoginStateSource _login;
@ -62,7 +85,10 @@ internal sealed class WorldSceneRenderer : IWorldSceneFramePhase
private readonly IWorldRenderRangeSource _renderRange;
private readonly IWorldSceneDiagnostics _diagnostics;
private readonly IWorldGenerationAvailability _availability;
private readonly IAtmosphericWorldFrameSink? _atmosphere;
private readonly RetailPViewFrameInput _pviewFrameInput = new();
private WorldRenderFrame _preparedEnhancedWorld;
private bool _hasPreparedEnhancedWorld;
public WorldSceneRenderer(
IRenderFrameFoundationSource foundation,
@ -78,7 +104,8 @@ internal sealed class WorldSceneRenderer : IWorldSceneFramePhase
IWorldScenePassExecutor passes,
IWorldRenderRangeSource renderRange,
IWorldSceneDiagnostics diagnostics,
IWorldGenerationAvailability? availability = null)
IWorldGenerationAvailability? availability = null,
IAtmosphericWorldFrameSink? atmosphere = null)
{
_foundation = foundation ?? throw new ArgumentNullException(nameof(foundation));
_login = login ?? throw new ArgumentNullException(nameof(login));
@ -95,6 +122,7 @@ internal sealed class WorldSceneRenderer : IWorldSceneFramePhase
_renderRange = renderRange ?? throw new ArgumentNullException(nameof(renderRange));
_diagnostics = diagnostics ?? throw new ArgumentNullException(nameof(diagnostics));
_availability = availability ?? AlwaysAvailableWorldGeneration.Instance;
_atmosphere = atmosphere;
}
public WorldRenderFrameOutcome Render(RenderFrameInput input)
@ -105,7 +133,15 @@ internal sealed class WorldSceneRenderer : IWorldSceneFramePhase
bool pviewFrameStarted = false;
try
{
_selection?.BeginFrame();
// Enhanced rendering builds the canonical camera before this world
// transaction so the shadow prepass can consume it. Carry that
// exact frustum across BeginFrame: clearing it here would make every
// subsequently drawn part fail RetailSelectionScene's visibility
// gate, leaving radar selection alive but disabling world picking.
FrustumPlanes? preparedSelectionFrustum = _hasPreparedEnhancedWorld
? _preparedEnhancedWorld.Camera.Frustum
: null;
_selection?.BeginFrame(preparedSelectionFrustum);
if (!_availability.IsWorldAvailable)
{
_selection?.CompleteFrame();
@ -125,10 +161,23 @@ internal sealed class WorldSceneRenderer : IWorldSceneFramePhase
// the same abort path instead of making every later frame fail.
worldFrameStarted = true;
_alpha.BeginFrame();
WorldRenderFrame world = _frames.Build(
WorldRenderFrame world;
if (_hasPreparedEnhancedWorld)
{
world = _preparedEnhancedWorld;
_hasPreparedEnhancedWorld = false;
}
else
{
world = _frames.Build(
in foundation,
_login.IsWaitingForLogin,
_sky.ActiveDayGroup);
}
_atmosphere?.Publish(
in foundation,
_login.IsWaitingForLogin,
_sky.ActiveDayGroup);
in world,
_sky.ActiveDayGroupIndex);
_passes.BeginFrame();
WorldCameraFrame camera = world.Camera;
@ -311,6 +360,78 @@ internal sealed class WorldSceneRenderer : IWorldSceneFramePhase
}
}
public PreparedWorldSceneFrame PrepareEnhanced(RenderFrameInput input)
{
_ = input;
if (_hasPreparedEnhancedWorld)
{
throw new InvalidOperationException(
"The prior enhanced world preparation was not consumed.");
}
RenderFrameFoundation foundation = _foundation.Foundation;
if (!_availability.IsWorldAvailable || foundation.PortalViewportVisible)
return new PreparedWorldSceneFrame(false, foundation, default, -1);
WorldRenderFrame world = _frames.Build(
in foundation,
_login.IsWaitingForLogin,
_sky.ActiveDayGroup);
WorldRootFrame roots = world.Roots;
world = world with
{
ResidentStreamingWindow =
_entities.CaptureResidentStreamingWindow(
roots.RenderCenterLandblockX,
roots.RenderCenterLandblockY),
CelestialShadowSource =
AuthoredCelestialShadowSourceResolver.Resolve(
_sky.ActiveDayGroup,
_sky.DayFraction,
foundation.Sky),
};
_preparedEnhancedWorld = world;
_hasPreparedEnhancedWorld = true;
return new PreparedWorldSceneFrame(
true,
foundation,
world,
_sky.ActiveDayGroupIndex);
}
public WorldRenderFrameOutcome RenderPreparedEnhanced(
RenderFrameInput input,
in PreparedWorldSceneFrame prepared)
{
if (prepared.ShouldRender != _hasPreparedEnhancedWorld)
{
throw new InvalidOperationException(
"The prepared enhanced-world token does not match the pending frame.");
}
try
{
return Render(input);
}
finally
{
// A gate may change between prepare and execute. Never let that
// exceptional edge leak borrowed builder scratch into another frame.
_hasPreparedEnhancedWorld = false;
_preparedEnhancedWorld = default;
}
}
public void CancelPreparedEnhanced(in PreparedWorldSceneFrame prepared)
{
if (!prepared.ShouldRender)
return;
if (!_hasPreparedEnhancedWorld)
return;
_hasPreparedEnhancedWorld = false;
_preparedEnhancedWorld = default;
}
private void CompleteWorldFrame()
{
_alpha.EndFrame();