refactor(render): extract typed retail pview passes

This commit is contained in:
Erik 2026-07-22 06:40:09 +02:00
parent 6d6e5b5fa5
commit 85239fb373
13 changed files with 1888 additions and 841 deletions

View file

@ -2,6 +2,7 @@ using System.Numerics;
using System.Diagnostics;
using System.Text;
using AcDream.Core.Vfx;
using AcDream.Core.World;
using Silk.NET.OpenGL;
namespace AcDream.App.Rendering;
@ -123,6 +124,12 @@ internal sealed class WorldRenderDiagnostics
private string? _lastScissorSignature;
private long _scissorSequence;
private string? _lastOutStageSignature;
private string? _lastOutStageRoutingSignature;
private readonly Dictionary<uint, bool> _outStageOwnerVerdicts = [];
private readonly Dictionary<uint, int> _phantomObjectSignatures = [];
private string? _lastClipRouteSignature;
private long _clipRouteSequence;
private readonly List<uint> _clipRouteCellKeys = [];
public WorldRenderDiagnostics(
IRenderGlStateReader gl,
@ -192,6 +199,177 @@ internal sealed class WorldRenderDiagnostics
_log.WriteLine($"[clip-route-scis] n={_scissorSequence} {signature}");
}
public void EmitClipRouteProbe(
bool enabled,
ClipFrame clipFrame,
ClipFrameAssembly clipAssembly,
ClipViewSlice slice,
int sliceIndex)
{
if (!enabled)
return;
var text = new StringBuilder(256);
text.Append(FormattableString.Invariant(
$"slice={sliceIndex}/{clipAssembly.OutsideViewSlices.Length} slot={slice.Slot}"));
text.Append(FormattableString.Invariant(
$" ndc=({slice.NdcAabb.X:F3},{slice.NdcAabb.Y:F3},{slice.NdcAabb.Z:F3},{slice.NdcAabb.W:F3})"));
text.Append(FormattableString.Invariant($" planes={slice.Planes.Length}["));
for (int i = 0; i < slice.Planes.Length; i++)
{
Vector4 plane = slice.Planes[i];
if (i > 0)
text.Append(' ');
text.Append(FormattableString.Invariant(
$"({plane.X:F3},{plane.Y:F3},{plane.Z:F3},{plane.W:F3})"));
}
text.Append("] cells={");
_clipRouteCellKeys.Clear();
foreach (uint key in clipAssembly.CellIdToSlot.Keys)
_clipRouteCellKeys.Add(key);
_clipRouteCellKeys.Sort();
for (int i = 0; i < _clipRouteCellKeys.Count; i++)
{
if (i > 0)
text.Append(',');
text.Append(FormattableString.Invariant(
$"0x{_clipRouteCellKeys[i]:X8}:{clipAssembly.CellIdToSlot[_clipRouteCellKeys[i]]}"));
}
text.Append('}');
ReadOnlySpan<byte> regionBytes = clipFrame.RegionBytesForTest;
int offset = slice.Slot * ClipFrame.CellClipStrideBytes;
if (offset >= 0 && offset + ClipFrame.CellClipStrideBytes <= regionBytes.Length)
{
uint count = BitConverter.ToUInt32(regionBytes.Slice(offset, 4));
text.Append(FormattableString.Invariant($" ssbo[{slice.Slot}]: n={count}"));
int planeCount = (int)Math.Min(count, (uint)ClipFrame.MaxPlanes);
for (int i = 0; i < planeCount; i++)
{
int planeOffset = offset + ClipFrame.CellClipPlanesOffset + i * 16;
float x = BitConverter.ToSingle(regionBytes.Slice(planeOffset, 4));
float y = BitConverter.ToSingle(regionBytes.Slice(planeOffset + 4, 4));
float z = BitConverter.ToSingle(regionBytes.Slice(planeOffset + 8, 4));
float w = BitConverter.ToSingle(regionBytes.Slice(planeOffset + 12, 4));
text.Append(FormattableString.Invariant($" ({x:F3},{y:F3},{z:F3},{w:F3})"));
}
}
else
{
text.Append(FormattableString.Invariant(
$" ssbo[{slice.Slot}]: OUT-OF-RANGE len={regionBytes.Length}"));
}
ReadOnlySpan<byte> terrainBytes = clipFrame.TerrainBytesForTest;
int terrainCount = BitConverter.ToInt32(terrainBytes[..4]);
float p0 = BitConverter.ToSingle(terrainBytes.Slice(16, 4));
float p1 = BitConverter.ToSingle(terrainBytes.Slice(20, 4));
float p2 = BitConverter.ToSingle(terrainBytes.Slice(24, 4));
float p3 = BitConverter.ToSingle(terrainBytes.Slice(28, 4));
text.Append(FormattableString.Invariant(
$" ubo: n={terrainCount} p0=({p0:F3},{p1:F3},{p2:F3},{p3:F3})"));
string signature = text.ToString();
_clipRouteSequence++;
if (signature == _lastClipRouteSignature)
return;
_lastClipRouteSignature = signature;
_log.WriteLine($"[clip-route] n={_clipRouteSequence} {signature}");
}
public void EmitOutStageOwner(
bool enabled,
IReadOnlySet<uint> watchedEntityIds,
WorldEntity entity,
Vector3 sphereCenter,
float sphereRadius,
int sliceIndex,
bool passed)
{
if (!enabled
|| !watchedEntityIds.Contains(entity.Id)
|| (_outStageOwnerVerdicts.TryGetValue(entity.Id, out bool previous)
&& previous == passed))
{
return;
}
_outStageOwnerVerdicts[entity.Id] = passed;
_log.WriteLine(FormattableString.Invariant(
$"[outstage-own] id=0x{entity.Id:X8} src=0x{entity.SourceGfxObjOrSetupId:X8} pos=({entity.Position.X:F1},{entity.Position.Y:F1},{entity.Position.Z:F1}) c=({sphereCenter.X:F1},{sphereCenter.Y:F1},{sphereCenter.Z:F1}) r={sphereRadius:F1} slice={sliceIndex} {(passed ? "PASS" : "CULL")}"));
}
public void EmitOutStageRouting(
bool enabled,
int sliceIndex,
IReadOnlyList<WorldEntity> entities,
ViewconeCuller viewcone)
{
if (!enabled)
return;
var text = new StringBuilder(192);
text.Append("slice=").Append(sliceIndex)
.Append(" outStage=").Append(entities.Count).Append(" [");
for (int i = 0; i < entities.Count; i++)
{
WorldEntity entity = entities[i];
EntitySphere(entity, out Vector3 center, out float radius);
bool passed = viewcone.SphereVisibleInOutsideSlice(
sliceIndex,
center,
radius);
if (i > 0)
text.Append(' ');
text.Append(FormattableString.Invariant(
$"0x{(entity.ServerGuid != 0 ? entity.ServerGuid : entity.Id):X8}(s{entity.SourceGfxObjOrSetupId:X8}):{(passed ? "PASS" : "CULL")}:r={radius:F1}"));
}
text.Append(']');
string signature = text.ToString();
if (signature == _lastOutStageRoutingSignature)
return;
_lastOutStageRoutingSignature = signature;
_log.WriteLine("[outstage] " + signature);
}
public void EmitPhantomObjects(bool enabled, uint cellId, int survivorCount)
{
if (!enabled
|| (_phantomObjectSignatures.TryGetValue(cellId, out int previous)
&& previous == survivorCount))
{
return;
}
_phantomObjectSignatures[cellId] = survivorCount;
_log.WriteLine(
$"[phantom-objs] cell=0x{cellId:X8} entities={survivorCount} (drawn unclipped, no viewcone)");
}
public void EmitSeamMask(
bool enabled,
IReadOnlySet<uint> targetCells,
uint cellId,
int portalIndex,
bool forceFarZ,
ReadOnlySpan<Vector3> vertices)
{
if (!enabled || !targetCells.Contains(cellId))
return;
float minimumZ = float.MaxValue;
float maximumZ = float.MinValue;
foreach (Vector3 vertex in vertices)
{
minimumZ = Math.Min(minimumZ, vertex.Z);
maximumZ = Math.Max(maximumZ, vertex.Z);
}
_log.WriteLine(FormattableString.Invariant(
$"[seam-mask] t={Environment.TickCount64} cell=0x{cellId:X8} portal={portalIndex} far={forceFarZ} n={vertices.Length} z=[{minimumZ:F3},{maximumZ:F3}]"));
}
public void EmitPViewInput(
bool enabled,
PortalVisibilityFrame portalFrame,
@ -590,4 +768,15 @@ internal sealed class WorldRenderDiagnostics
.Append(" live=").Append(partition.Dynamics.Count)
.ToString();
}
private static void EntitySphere(
WorldEntity entity,
out Vector3 center,
out float radius)
{
if (entity.AabbDirty)
entity.RefreshAabb();
center = (entity.AabbMin + entity.AabbMax) * 0.5f;
radius = (entity.AabbMax - entity.AabbMin).Length() * 0.5f;
}
}