test(overhaul): integrate reviewed consumed-light checkpoint observation

This commit is contained in:
Erik 2026-09-05 13:56:49 +02:00
parent ad866b7f65
commit 59b0b6e72f
9 changed files with 703 additions and 1 deletions

View file

@ -596,6 +596,8 @@ internal sealed class FrameRootCompositionPhase
foundation.MeshAdapter,
foundation.TextureCache,
live.DrawDispatcher,
live.EnvCellRenderer,
d.Lighting,
d.FrameProfiler,
content.Dats,
foundation.Residency,

View file

@ -2,6 +2,7 @@ using System.Text.Json;
using AcDream.App.Rendering;
using AcDream.App.Rendering.Residency;
using AcDream.App.Rendering.Scene;
using AcDream.App.Rendering.Wb;
using AcDream.App.Streaming;
using AcDream.App.UI.Testing;
using AcDream.Core.Physics;
@ -96,7 +97,8 @@ internal sealed record WorldLifecycleResourceSnapshot(
ResidencySnapshot Residency,
double Fps,
double FrameMilliseconds,
string? LastFrameProfile);
string? LastFrameProfile,
EnvCellLightingConsumptionSnapshot? EnvCellLighting = null);
internal sealed record WorldLifecycleCheckpoint(
int Sequence,

View file

@ -7,6 +7,7 @@ using AcDream.App.Streaming;
using AcDream.App.World;
using AcDream.Content;
using AcDream.Core.Physics;
using AcDream.Core.Lighting;
using AcDream.Core.Vfx;
using DatReaderWriter.Lib.IO;
@ -39,6 +40,8 @@ internal sealed class WorldLifecycleResourceSnapshotSource
private readonly WbMeshAdapter? _meshes;
private readonly TextureCache _textures;
private readonly WbDrawDispatcher? _dispatcher;
private readonly EnvCellRenderer? _envCellRenderer;
private readonly LightManager _lighting;
private readonly FrameProfiler _frameProfiler;
private readonly IDatReaderWriter _dats;
private readonly ResidencyManager _residency;
@ -62,6 +65,8 @@ internal sealed class WorldLifecycleResourceSnapshotSource
WbMeshAdapter? meshes,
TextureCache textures,
WbDrawDispatcher? dispatcher,
EnvCellRenderer? envCellRenderer,
LightManager lighting,
FrameProfiler frameProfiler,
IDatReaderWriter dats,
ResidencyManager residency,
@ -88,6 +93,8 @@ internal sealed class WorldLifecycleResourceSnapshotSource
_meshes = meshes;
_textures = textures ?? throw new ArgumentNullException(nameof(textures));
_dispatcher = dispatcher;
_envCellRenderer = envCellRenderer;
_lighting = lighting ?? throw new ArgumentNullException(nameof(lighting));
_frameProfiler = frameProfiler
?? throw new ArgumentNullException(nameof(frameProfiler));
_dats = dats ?? throw new ArgumentNullException(nameof(dats));
@ -156,6 +163,10 @@ internal sealed class WorldLifecycleResourceSnapshotSource
ParticleOwners: _particleSink.TrackedOwnerCount,
EffectOwners: _effects.ReadyOwnerCount,
LightOwners: _lights.TrackedOwnerCount,
EnvCellLighting: CaptureEnvCellLighting(
outcome,
_envCellRenderer,
_lighting),
ScriptOwners: _scripts.ActiveOwnerCount,
ActiveScripts: _scripts.ActiveScriptCount,
MeshRenderData: mesh.RenderData,
@ -203,4 +214,17 @@ internal sealed class WorldLifecycleResourceSnapshotSource
FrameMilliseconds: render.FrameMilliseconds,
LastFrameProfile: _frameProfiler.LastReport);
}
internal static EnvCellLightingConsumptionSnapshot? CaptureEnvCellLighting(
RenderFrameOutcome outcome,
EnvCellRenderer? renderer,
LightManager lighting)
{
ArgumentNullException.ThrowIfNull(lighting);
return renderer?.CaptureLightingConsumption(
lighting,
currentWorldFrame:
!outcome.SkippedZeroArea
&& outcome.World.NormalWorldDrawn);
}
}

View file

@ -0,0 +1,176 @@
using AcDream.Core.Lighting;
namespace AcDream.App.Rendering.Wb;
internal sealed record EnvCellLightingVectorBits(
uint X,
uint Y,
uint Z);
internal sealed record EnvCellLightingAmbientSnapshot(
EnvCellLightingVectorBits AmbientColor,
EnvCellLightingVectorBits SunColor,
EnvCellLightingVectorBits SunDirection);
internal sealed record EnvCellLightingSourceSnapshot(
int Index,
uint OwnerId,
uint CellId,
int Kind,
bool IsDynamic,
bool IsLit,
bool TracksOwnerPose,
EnvCellLightingVectorBits Position,
EnvCellLightingVectorBits Forward,
EnvCellLightingVectorBits Color,
uint Intensity,
uint Range,
uint ConeAngle,
uint DistSq,
uint[] LocalPose);
internal sealed record EnvCellLightingCellSetSnapshot(
uint CellId,
int[] Indices);
/// <summary>
/// Checkpoint-only copy of the point-light inputs the EnvCell RHI submission
/// already consumed. PackedGlobalLightBits is the authoritative binding-4
/// payload; Sources records its identity/provenance and CellSets records the
/// exact cached eight-index values copied to binding 5.
/// </summary>
internal sealed record EnvCellLightingConsumptionSnapshot(
bool HasCurrentEnvCellInput,
int FrameGeneration,
int FrameSlot,
int RegisteredLightCount,
int LogicalGlobalLightCount,
int UploadedGlobalLightCount,
uint[] PackedGlobalLightBits,
EnvCellLightingSourceSnapshot[] Sources,
EnvCellLightingCellSetSnapshot[] CellSets,
EnvCellLightingAmbientSnapshot Ambient);
public sealed partial class EnvCellRenderer
{
/// <summary>
/// Copies the retained inputs from this frame's completed EnvCell submission
/// for an explicitly requested lifecycle checkpoint. This method never
/// rebuilds a light snapshot or invokes either cell selector.
/// </summary>
internal EnvCellLightingConsumptionSnapshot CaptureLightingConsumption(
LightManager lighting,
bool currentWorldFrame)
{
ArgumentNullException.ThrowIfNull(lighting);
EnvCellLightingAmbientSnapshot ambient = CaptureAmbient(
lighting.CurrentAmbient);
if (!currentWorldFrame)
return EmptyLightingConsumption(lighting.RegisteredCount, ambient);
List<KeyValuePair<uint, CachedCellLightSet>> currentCells = [];
foreach (KeyValuePair<uint, CachedCellLightSet> entry in _cellLightSetCache)
{
if (entry.Value.FrameGeneration == _lightFrameGeneration)
currentCells.Add(entry);
}
currentCells.Sort(static (left, right) => left.Key.CompareTo(right.Key));
if (currentCells.Count == 0)
return EmptyLightingConsumption(lighting.RegisteredCount, ambient);
IReadOnlyList<LightSource>? pointSnapshot = _pointSnapshot;
int logicalCount = pointSnapshot?.Count ?? 0;
int uploadedCount = Math.Max(logicalCount, 1);
int packedFloatCount = checked(
uploadedCount * GlobalLightPacker.FloatsPerLight);
var packedBits = new uint[packedFloatCount];
for (int index = 0; index < packedFloatCount; index++)
packedBits[index] = Bits(_globalLightData[index]);
var sources = new EnvCellLightingSourceSnapshot[logicalCount];
for (int index = 0; index < logicalCount; index++)
sources[index] = CaptureSource(index, pointSnapshot![index]);
var cellSets = new EnvCellLightingCellSetSnapshot[currentCells.Count];
for (int index = 0; index < currentCells.Count; index++)
{
KeyValuePair<uint, CachedCellLightSet> entry = currentCells[index];
cellSets[index] = new EnvCellLightingCellSetSnapshot(
entry.Key,
(int[])entry.Value.Indices.Clone());
}
return new EnvCellLightingConsumptionSnapshot(
HasCurrentEnvCellInput: true,
FrameGeneration: _lightFrameGeneration,
FrameSlot: _dynamicFrameSlot,
RegisteredLightCount: lighting.RegisteredCount,
LogicalGlobalLightCount: logicalCount,
UploadedGlobalLightCount: uploadedCount,
PackedGlobalLightBits: packedBits,
Sources: sources,
CellSets: cellSets,
Ambient: ambient);
}
private EnvCellLightingConsumptionSnapshot EmptyLightingConsumption(
int registeredLightCount,
EnvCellLightingAmbientSnapshot ambient) =>
new(
HasCurrentEnvCellInput: false,
FrameGeneration: _lightFrameGeneration,
FrameSlot: _dynamicFrameSlot,
RegisteredLightCount: registeredLightCount,
LogicalGlobalLightCount: 0,
UploadedGlobalLightCount: 0,
PackedGlobalLightBits: [],
Sources: [],
CellSets: [],
Ambient: ambient);
private static EnvCellLightingSourceSnapshot CaptureSource(
int index,
LightSource source) =>
new(
Index: index,
OwnerId: source.OwnerId,
CellId: source.CellId,
Kind: (int)source.Kind,
IsDynamic: source.IsDynamic,
IsLit: source.IsLit,
TracksOwnerPose: source.TracksOwnerPose,
Position: CaptureVector(source.WorldPosition),
Forward: CaptureVector(source.WorldForward),
Color: CaptureVector(source.ColorLinear),
Intensity: Bits(source.Intensity),
Range: Bits(source.Range),
ConeAngle: Bits(source.ConeAngle),
DistSq: Bits(source.DistSq),
LocalPose:
[
Bits(source.LocalPose.M11), Bits(source.LocalPose.M12),
Bits(source.LocalPose.M13), Bits(source.LocalPose.M14),
Bits(source.LocalPose.M21), Bits(source.LocalPose.M22),
Bits(source.LocalPose.M23), Bits(source.LocalPose.M24),
Bits(source.LocalPose.M31), Bits(source.LocalPose.M32),
Bits(source.LocalPose.M33), Bits(source.LocalPose.M34),
Bits(source.LocalPose.M41), Bits(source.LocalPose.M42),
Bits(source.LocalPose.M43), Bits(source.LocalPose.M44),
]);
private static EnvCellLightingAmbientSnapshot CaptureAmbient(
CellAmbientState ambient) =>
new(
CaptureVector(ambient.AmbientColor),
CaptureVector(ambient.SunColor),
CaptureVector(ambient.SunDirection));
private static EnvCellLightingVectorBits CaptureVector(
System.Numerics.Vector3 value) =>
new(Bits(value.X), Bits(value.Y), Bits(value.Z));
private static uint Bits(float value) =>
BitConverter.SingleToUInt32Bits(value);
}