acdream/src/AcDream.App/Rendering/WorldScenePassExecutor.cs
Erik 28e1cf8029 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>
2026-07-22 07:22:09 +02:00

328 lines
10 KiB
C#

using AcDream.App.Rendering.Sky;
using AcDream.App.Rendering.Wb;
using AcDream.Core.Rendering;
using AcDream.Core.Vfx;
using AcDream.Core.World;
using Silk.NET.OpenGL;
namespace AcDream.App.Rendering;
internal interface IWorldScenePassExecutor
{
HashSet<uint>? TerrainVisibleCellIds { get; }
void BeginFrame();
void PrepareFlatWorldClip();
void DrawFlatSky(
in WorldCameraFrame camera,
in RenderFrameFoundation foundation,
DayGroupData? activeDayGroup,
float dayFraction);
void DrawFlatTerrain(in WorldCameraFrame camera, uint? playerLandblockId);
void DrawFlatEntities(
in WorldCameraFrame camera,
IEnumerable<(uint LandblockId, System.Numerics.Vector3 AabbMin,
System.Numerics.Vector3 AabbMax,
IReadOnlyList<WorldEntity> Entities,
IReadOnlyDictionary<uint, WorldEntity>? AnimatedById)> entries,
uint? playerLandblockId,
HashSet<uint> animatedEntityIds);
string DrawPostWorldParticles(
LoadedCell? clipRoot,
ClipFrameAssembly? clipAssembly,
in WorldCameraFrame camera,
IReadOnlySet<uint> outdoorOwnerIds,
string currentSignature);
void DrawFlatWeather(
in WorldCameraFrame camera,
in RenderFrameFoundation foundation,
DayGroupData? activeDayGroup,
float dayFraction);
void DisableClipDistances();
void AbortFrame();
}
/// <summary>
/// Concrete GL leaf for the flat-world safety path and the post-world particle
/// and weather passes. Retail PView frames remain owned by
/// <see cref="RetailPViewRenderer"/> and <see cref="RetailPViewPassExecutor"/>.
/// </summary>
internal sealed class WorldScenePassExecutor : IWorldScenePassExecutor
{
private readonly GL _gl;
private readonly IRenderFrameGlState _frameGlState;
private readonly ClipFrame _clipFrame;
private readonly WbDrawDispatcher _entities;
private readonly EnvCellRenderer _environmentCells;
private readonly TerrainModernRenderer? _terrain;
private readonly TerrainDrawDiagnosticsController _terrainDiagnostics;
private readonly SkyRenderer? _sky;
private readonly ParticleSystem? _particles;
private readonly ParticleRenderer? _particleRenderer;
private readonly HashSet<uint> _visibleParticleOwners = [];
private readonly HashSet<uint> _noExcludedParticleOwners = [];
public WorldScenePassExecutor(
GL gl,
IRenderFrameGlState frameGlState,
ClipFrame clipFrame,
WbDrawDispatcher entities,
EnvCellRenderer environmentCells,
TerrainModernRenderer? terrain,
TerrainDrawDiagnosticsController terrainDiagnostics,
SkyRenderer? sky,
ParticleSystem? particles,
ParticleRenderer? particleRenderer)
{
_gl = gl ?? throw new ArgumentNullException(nameof(gl));
_frameGlState = frameGlState
?? throw new ArgumentNullException(nameof(frameGlState));
_clipFrame = clipFrame ?? throw new ArgumentNullException(nameof(clipFrame));
_entities = entities ?? throw new ArgumentNullException(nameof(entities));
_environmentCells = environmentCells
?? throw new ArgumentNullException(nameof(environmentCells));
_terrain = terrain;
_terrainDiagnostics = terrainDiagnostics
?? throw new ArgumentNullException(nameof(terrainDiagnostics));
_sky = sky;
_particles = particles;
_particleRenderer = particleRenderer;
}
public HashSet<uint>? TerrainVisibleCellIds => _terrain?.VisibleCellIds;
public void BeginFrame()
{
_visibleParticleOwners.Clear();
_clipFrame.Reset();
_entities.ClearClipRouting();
_environmentCells.SetClipRouting(null);
}
public void PrepareFlatWorldClip()
{
_clipFrame.ReserveTerrainUploads(_gl, 1);
_clipFrame.UploadRegions(_gl);
TerrainClipBufferBinding terrainBinding = _clipFrame.UploadTerrainClip(_gl);
_entities.SetClipRegionSsbo(_clipFrame.RegionSsbo);
_environmentCells.SetClipRegionSsbo(_clipFrame.RegionSsbo);
_terrain?.SetClipUbo(terrainBinding);
}
public void DrawFlatSky(
in WorldCameraFrame camera,
in RenderFrameFoundation foundation,
DayGroupData? activeDayGroup,
float dayFraction)
{
_clipFrame.BindTerrainClip(_gl);
EnableClipDistances();
Exception? drawFailure = null;
try
{
_sky?.RenderSky(
camera.Camera,
camera.Position,
dayFraction,
activeDayGroup,
foundation.Sky,
foundation.EnvironOverrideActive);
}
catch (Exception error)
{
drawFailure = error;
throw;
}
finally
{
try
{
DisableClipDistances();
}
catch (Exception closeFailure) when (drawFailure is not null)
{
throw new AggregateException(
"Sky drawing failed and its clip-distance bracket could not be closed.",
drawFailure,
closeFailure);
}
}
if (_particles is not null && _particleRenderer is not null)
{
_particleRenderer.Draw(
camera.Camera,
camera.Position,
ParticleRenderPass.SkyPreScene);
}
}
public void DrawFlatTerrain(
in WorldCameraFrame camera,
uint? playerLandblockId)
{
EnableClipDistances();
_terrainDiagnostics.Begin();
_terrain?.Draw(
camera.Camera,
camera.Frustum,
neverCullLandblockId: playerLandblockId);
_terrainDiagnostics.Complete();
}
public void DrawFlatEntities(
in WorldCameraFrame camera,
IEnumerable<(uint LandblockId, System.Numerics.Vector3 AabbMin,
System.Numerics.Vector3 AabbMax,
IReadOnlyList<WorldEntity> Entities,
IReadOnlyDictionary<uint, WorldEntity>? AnimatedById)> entries,
uint? playerLandblockId,
HashSet<uint> animatedEntityIds) =>
_entities.Draw(
camera.Camera,
entries,
camera.Frustum,
neverCullLandblockId: playerLandblockId,
visibleCellIds: null,
animatedEntityIds: animatedEntityIds);
public string DrawPostWorldParticles(
LoadedCell? clipRoot,
ClipFrameAssembly? clipAssembly,
in WorldCameraFrame camera,
IReadOnlySet<uint> outdoorOwnerIds,
string currentSignature)
{
if (_particles is null || _particleRenderer is null)
return currentSignature;
if (clipRoot is null)
{
if (clipAssembly is not null)
{
_particleRenderer.DrawForOwners(
camera.Camera,
camera.Position,
ParticleRenderPass.Scene,
_visibleParticleOwners,
includeUnattached: true,
excludedAttachedOwnerIds: _noExcludedParticleOwners);
return AppendSignature(currentSignature, "filtered");
}
_particleRenderer.Draw(
camera.Camera,
camera.Position,
ParticleRenderPass.Scene);
return AppendSignature(currentSignature, "global");
}
if (clipRoot.IsOutdoorNode)
{
_particleRenderer.DrawForOwners(
camera.Camera,
camera.Position,
ParticleRenderPass.Scene,
outdoorOwnerIds,
includeUnattached: true);
return AppendSignature(currentSignature, "unattached");
}
return currentSignature;
}
public void DrawFlatWeather(
in WorldCameraFrame camera,
in RenderFrameFoundation foundation,
DayGroupData? activeDayGroup,
float dayFraction)
{
_clipFrame.BindTerrainClip(_gl);
EnableClipDistances();
Exception? drawFailure = null;
try
{
_sky?.RenderWeather(
camera.Camera,
camera.Position,
dayFraction,
activeDayGroup,
foundation.Sky,
foundation.EnvironOverrideActive);
}
catch (Exception error)
{
drawFailure = error;
throw;
}
finally
{
try
{
DisableClipDistances();
}
catch (Exception closeFailure) when (drawFailure is not null)
{
throw new AggregateException(
"Weather drawing failed and its clip-distance bracket could not be closed.",
drawFailure,
closeFailure);
}
}
if (_particles is not null && _particleRenderer is not null)
{
_particleRenderer.Draw(
camera.Camera,
camera.Position,
ParticleRenderPass.SkyPostScene);
}
}
public void DisableClipDistances()
{
for (int index = 0; index < ClipFrame.MaxPlanes; index++)
_gl.Disable(EnableCap.ClipDistance0 + index);
}
public void AbortFrame()
{
List<Exception>? failures = null;
TryAbort(_frameGlState.RestoreFrameDefaults);
TryAbort(_clipFrame.Reset);
TryAbort(_entities.ClearClipRouting);
TryAbort(() => _environmentCells.SetClipRouting(null));
_visibleParticleOwners.Clear();
if (failures is { Count: > 0 })
throw new AggregateException("World scene pass abort failed.", failures);
void TryAbort(Action operation)
{
try
{
operation();
}
catch (Exception error)
{
(failures ??= []).Add(error);
}
}
}
private void EnableClipDistances()
{
for (int index = 0; index < ClipFrame.MaxPlanes; index++)
_gl.Enable(EnableCap.ClipDistance0 + index);
}
private static string AppendSignature(string current, string value) =>
current == "none" ? value : current + "+" + value;
}