refactor(render): extract world scene frame owner

Move fallback and PView world drawing, shared alpha, particles, visibility, selection, and diagnostics behind focused frame owners. Make exceptional frames failure-atomic by restoring the shared GL baseline and preserving primary alpha failures through cleanup.

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-22 07:22:09 +02:00
parent 85239fb373
commit 28e1cf8029
25 changed files with 2679 additions and 844 deletions

View file

@ -0,0 +1,274 @@
using System.Numerics;
using AcDream.App.Input;
using AcDream.Core.Physics;
using AcDream.Core.Rendering;
namespace AcDream.App.Rendering;
internal readonly record struct WorldSceneDiagnosticOutcome(
int VisibleLandblocks,
int TotalLandblocks);
internal interface IWorldSceneDiagnostics
{
CameraCellResolution CameraCellResolution { get; }
void EmitPViewInput(
PortalVisibilityFrame portalFrame,
Matrix4x4 viewProjection,
LoadedCell clipRoot,
Vector3 cameraPosition,
Vector3 playerPosition);
void EmitRenderSignature(
string branch,
LoadedCell? clipRoot,
LoadedCell? viewerRoot,
LoadedCell? playerRoot,
uint viewerCellId,
uint playerCellId,
bool playerIndoorGate,
bool cameraInsideCell,
bool renderSky,
bool drawSkyThisFrame,
bool terrainDrawn,
TerrainClipMode terrainClipMode,
bool skyDrawn,
bool depthClear,
bool outdoorSceneryDrawn,
int liveDynamicDrawnCount,
string sceneParticles,
RetailPViewFrameResult? pviewResult,
Vector3 cameraPosition,
Vector3 playerPosition);
WorldSceneDiagnosticOutcome DrawAndPublish(
in WorldCameraFrame camera,
IEnumerable<(uint LandblockId, Vector3 AabbMin, Vector3 AabbMax)> bounds);
}
/// <summary>
/// Owns world-pass probes, collision wireframes, and the DebugVM facts derived
/// from one completed normal-world draw. None of these diagnostics influence
/// visibility or pass ordering.
/// </summary>
internal sealed class WorldSceneDiagnosticsController : IWorldSceneDiagnostics
{
private readonly WorldRenderDiagnostics _diagnostics;
private readonly IWorldScenePViewDiagnosticSource _pview;
private readonly IWorldSceneDebugStateSource _state;
private readonly DebugLineRenderer? _lines;
private readonly PhysicsEngine _physics;
private readonly ILocalPlayerModeSource _mode;
private readonly ILocalPlayerControllerSource _player;
private readonly DebugVmRenderFactsPublisher _debugVm;
private readonly bool _debugVmConsumerActive;
private int _debugDrawLogCount;
public WorldSceneDiagnosticsController(
WorldRenderDiagnostics diagnostics,
IWorldScenePViewDiagnosticSource pview,
IWorldSceneDebugStateSource state,
DebugLineRenderer? lines,
PhysicsEngine physics,
ILocalPlayerModeSource mode,
ILocalPlayerControllerSource player,
DebugVmRenderFactsPublisher debugVm,
bool debugVmConsumerActive)
{
_diagnostics = diagnostics ?? throw new ArgumentNullException(nameof(diagnostics));
_pview = pview ?? throw new ArgumentNullException(nameof(pview));
_state = state ?? throw new ArgumentNullException(nameof(state));
_lines = lines;
_physics = physics ?? throw new ArgumentNullException(nameof(physics));
_mode = mode ?? throw new ArgumentNullException(nameof(mode));
_player = player ?? throw new ArgumentNullException(nameof(player));
_debugVm = debugVm ?? throw new ArgumentNullException(nameof(debugVm));
_debugVmConsumerActive = debugVmConsumerActive;
}
public CameraCellResolution CameraCellResolution => _pview.CameraCellResolution;
public void EmitPViewInput(
PortalVisibilityFrame portalFrame,
Matrix4x4 viewProjection,
LoadedCell clipRoot,
Vector3 cameraPosition,
Vector3 playerPosition)
{
if (!RenderingDiagnostics.ProbePvInputEnabled)
return;
_diagnostics.EmitPViewInput(
enabled: true,
portalFrame,
viewProjection,
clipRoot.IsOutdoorNode,
cameraPosition,
playerPosition,
_pview.RawPlayerPositionOr(playerPosition),
_pview.PlayerYaw,
_pview.SampleTerrainZ(cameraPosition.X, cameraPosition.Y));
}
public void EmitRenderSignature(
string branch,
LoadedCell? clipRoot,
LoadedCell? viewerRoot,
LoadedCell? playerRoot,
uint viewerCellId,
uint playerCellId,
bool playerIndoorGate,
bool cameraInsideCell,
bool renderSky,
bool drawSkyThisFrame,
bool terrainDrawn,
TerrainClipMode terrainClipMode,
bool skyDrawn,
bool depthClear,
bool outdoorSceneryDrawn,
int liveDynamicDrawnCount,
string sceneParticles,
RetailPViewFrameResult? pviewResult,
Vector3 cameraPosition,
Vector3 playerPosition)
{
_diagnostics.EmitRenderSignatureIfChanged(
RenderingDiagnostics.ProbeFlapEnabled,
branch,
clipRoot,
viewerRoot,
playerRoot,
viewerCellId,
playerCellId,
playerIndoorGate,
cameraInsideCell,
renderSky,
drawSkyThisFrame,
terrainDrawn,
terrainClipMode,
skyDrawn,
depthClear,
outdoorSceneryDrawn,
outdoorPortalDrawn: false,
outdoorRootObjectCount: 0,
liveDynamicDrawnCount,
sceneParticles,
pviewResult?.PortalFrame,
pviewResult?.ClipAssembly,
pviewResult?.DrawableCells,
pviewResult?.Partition,
exteriorPortalFrame: null,
exteriorClipAssembly: null,
exteriorDrawableCells: null,
exteriorPartition: null,
cameraPosition,
playerPosition);
}
public WorldSceneDiagnosticOutcome DrawAndPublish(
in WorldCameraFrame camera,
IEnumerable<(uint LandblockId, Vector3 AabbMin, Vector3 AabbMax)> bounds)
{
ArgumentNullException.ThrowIfNull(bounds);
DrawCollisionWireframes(in camera);
int visible = 0;
int total = 0;
foreach (var entry in bounds)
{
total++;
if (FrustumCuller.IsAabbVisible(
camera.Frustum,
entry.AabbMin,
entry.AabbMax))
{
visible++;
}
}
Vector3 nearestOrigin =
_mode.IsPlayerMode && _player.Controller is { } controller
? controller.Position
: camera.Position;
_debugVm.PublishDebugVmFacts(
_debugVmConsumerActive,
visible,
total,
nearestOrigin,
_physics.ShadowObjects.AllEntriesForDebug());
return new WorldSceneDiagnosticOutcome(visible, total);
}
private void DrawCollisionWireframes(in WorldCameraFrame camera)
{
if (!_state.CollisionWireframesVisible || _lines is null)
return;
_lines.Begin();
int drawn = 0;
foreach (ShadowEntry shadow in _physics.ShadowObjects.AllEntriesForDebug())
{
if (shadow.CollisionType == ShadowCollisionType.Cylinder)
{
float height = shadow.CylHeight > 0f
? shadow.CylHeight
: shadow.Radius * 2f;
_lines.AddCylinder(
shadow.Position,
shadow.Radius,
height,
new Vector3(0f, 1f, 0f));
}
else
{
_lines.AddCylinder(
shadow.Position - new Vector3(0f, 0f, shadow.Radius),
shadow.Radius,
shadow.Radius * 2f,
new Vector3(1f, 0.5f, 0f));
}
drawn++;
}
if (_mode.IsPlayerMode && _player.Controller is { } localPlayer)
{
Vector3 position = localPlayer.Position;
_lines.AddCylinder(
new Vector3(position.X, position.Y, position.Z),
DebugVmRenderFactsPublisher.PlayerCollisionRadius,
1.8f,
new Vector3(1f, 0f, 0f));
LogNearbyCollisionObjects(position, drawn);
}
_lines.Flush(camera.Camera.View, camera.Projection);
}
private void LogNearbyCollisionObjects(Vector3 playerPosition, int drawn)
{
if (_debugDrawLogCount >= 5)
return;
Console.WriteLine(
$"debug frame {_debugDrawLogCount}: player=({playerPosition.X:F1},{playerPosition.Y:F1},{playerPosition.Z:F1}) drew={drawn} "
+ $"totalReg={_physics.ShadowObjects.TotalRegistered}");
int logged = 0;
foreach (ShadowEntry shadow in _physics.ShadowObjects.AllEntriesForDebug())
{
float dx = shadow.Position.X - playerPosition.X;
float dy = shadow.Position.Y - playerPosition.Y;
float horizontalDistance = MathF.Sqrt(dx * dx + dy * dy);
if (horizontalDistance >= 10f)
continue;
Console.WriteLine(
$" near id=0x{shadow.EntityId:X8} type={shadow.CollisionType} "
+ $"pos=({shadow.Position.X:F1},{shadow.Position.Y:F1},{shadow.Position.Z:F1}) "
+ $"r={shadow.Radius:F2} h={shadow.CylHeight:F2} dh={horizontalDistance:F2}");
if (++logged >= 5)
break;
}
_debugDrawLogCount++;
}
}