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>
328 lines
10 KiB
C#
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;
|
|
}
|