feat(overhaul): select pack shadows from retail visibility
Borrow the exact prior-completed landscape visibility transaction and S2 CELLARRAY owner for the opt-in IA-24 directional-shadow pack. Select terrain by authored 1..64 cells, ordinary casters by CELLARRAY, and buildings by outdoor EffectCellId with no fallback. Keep retained caster/material/terrain topology stable across visibility-only frames. Publish exact arbitrary active instance runs and bounded terrain commands through separate selection sequences; preserve transform-journal, fade/retry, deferral, shader/RHI, ordinary world, and pack-off behavior. Amend IA-24 and the S5 ledger. Pre-commit gates: Release solution build 0 warnings/0 errors; focused visibility/frame/caster/prepared/GPU/terrain/pack lane 137/137; warmed caster/prepared/terrain selectors 0 B; git diff --check clean. Official hermetic and InstalledDat evidence intentionally run post-commit from this exact clean tree. Mutation evidence (each restored exactly): (1) CELLARRAY->Parent first failed PriorLandscapeSelection expected [201,202,203,205], actual [204,205]. (2) all resident terrain first failed Assert.Single with 3 commands. (3) building EffectCell->anchor first failed expected trailing 205, actual 206. (4) admit missing membership first failed with extra 204. (5) completed->building scratch first failed completed-view Assert.True, expected true/actual false. (6) selection advanced BuildSequence first failed expected 1/actual 2. (7) alternating->prefix first failed active command count expected 3/actual 1. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
5c106bcdff
commit
a4de2efc4e
19 changed files with 1198 additions and 100 deletions
|
|
@ -5,6 +5,7 @@ using AcDream.App.Rendering.Gpu;
|
|||
using AcDream.App.Rendering.Packs;
|
||||
using AcDream.App.Rendering.Scene;
|
||||
using AcDream.App.Rendering.Wb;
|
||||
using AcDream.App.Rendering.Vfx;
|
||||
using DatReaderWriter.Enums;
|
||||
|
||||
namespace AcDream.App.Rendering;
|
||||
|
|
@ -39,6 +40,7 @@ internal readonly record struct DirectionalSunShadowRenderInput(
|
|||
bool MeasureGpuTimers = true,
|
||||
bool MeasureCpuStages = false,
|
||||
AtmosphericFrameBufferBinding AtmosphericFrame = default,
|
||||
RetailLandscapeVisibilityFrame PriorLandscapeVisibility = default,
|
||||
// #429 owner-approved pipelining: false keeps the retained caster/draw
|
||||
// topology this frame (transform refresh only) so the rebuild lands on a
|
||||
// quieter frame. The prepare seams below re-validate and rebuild anyway
|
||||
|
|
@ -73,7 +75,8 @@ internal readonly record struct DirectionalShadowTransformChurnDiagnostics(
|
|||
bool FlightFullDynamicFallback,
|
||||
bool DenseDirectUpload,
|
||||
bool DenseFlightReplay,
|
||||
DirectionalShadowCasterClassDiagnostics CasterClasses = default);
|
||||
DirectionalShadowCasterClassDiagnostics CasterClasses = default,
|
||||
int ActiveSelectedCasters = 0);
|
||||
|
||||
internal readonly record struct DirectionalSunShadowDiagnostics(
|
||||
DirectionalShadowGateReason GateReason,
|
||||
|
|
@ -96,7 +99,15 @@ internal readonly record struct DirectionalSunShadowDiagnostics(
|
|||
int SourceObjectIndex = -1,
|
||||
uint SourceGfxObjId = 0u,
|
||||
Vector3 SurfaceToLightDirection = default,
|
||||
float LightElevationSin = 0f);
|
||||
float LightElevationSin = 0f,
|
||||
int ResidentWorldCasters = 0,
|
||||
int ActiveWorldCasters = 0,
|
||||
int ResidentWorldInstances = 0,
|
||||
int ActiveWorldInstances = 0,
|
||||
int ResidentWorldCommands = 0,
|
||||
int ActiveWorldCommands = 0,
|
||||
int ResidentTerrainCommands = 0,
|
||||
int ActiveTerrainCommands = 0);
|
||||
|
||||
internal static class DirectionalShadowBatchFlags
|
||||
{
|
||||
|
|
@ -292,9 +303,9 @@ internal sealed class DirectionalSunShadowRenderer : IDirectionalShadowReceiverS
|
|||
internal DirectionalShadowFrameBinding CurrentFrameBinding => _currentFrameBinding;
|
||||
|
||||
/// <summary>
|
||||
/// Topology-only command metadata lives in pack-owned device-local buffers.
|
||||
/// It is rebuilt transactionally when the retained CPU product changes and
|
||||
/// is never copied through a per-frame ring on a stable scene.
|
||||
/// The bounded active indirect projection lives in pack-owned device-local
|
||||
/// buffers. Selection republishes these buffers without rebuilding the
|
||||
/// retained CPU topology and never copies them through a per-frame ring.
|
||||
/// </summary>
|
||||
internal long RetainedCommandBufferBytes => checked(
|
||||
(_worldBatchBuffer?.SizeBytes ?? 0L)
|
||||
|
|
@ -402,10 +413,15 @@ internal sealed class DirectionalSunShadowRenderer : IDirectionalShadowReceiverS
|
|||
world.PrepareDirectionalShadowDraws(
|
||||
input.Casters,
|
||||
input.AllowTopologyRebuild);
|
||||
RetailLandscapeVisibilityFrame priorLandscapeVisibility =
|
||||
input.PriorLandscapeVisibility;
|
||||
DirectionalShadowTerrainPreparedDraws terrainDraws =
|
||||
terrain.PrepareDirectionalShadowDraws();
|
||||
terrain.PrepareDirectionalShadowDraws(
|
||||
in priorLandscapeVisibility);
|
||||
DirectionalShadowMeshGeometry? worldGeometry =
|
||||
worldDraws.Commands.IsEmpty ? null : world.GetDirectionalShadowGeometry();
|
||||
worldDraws.ActiveCommands.IsEmpty
|
||||
? null
|
||||
: world.GetDirectionalShadowGeometry();
|
||||
DirectionalShadowTerrainGeometry? terrainGeometry =
|
||||
terrainDraws.Commands.IsEmpty ? null : terrain.GetDirectionalShadowGeometry();
|
||||
uint transformBindingSizeBytes =
|
||||
|
|
@ -433,7 +449,7 @@ internal sealed class DirectionalSunShadowRenderer : IDirectionalShadowReceiverS
|
|||
DirectionalShadowCasterClassDiagnostics casterClasses =
|
||||
CompleteCasterClassDiagnostics(
|
||||
in casterStats,
|
||||
terrainDraws.Commands.Length);
|
||||
terrainDraws.ResidentRanges.Length);
|
||||
DirectionalShadowTransformPublishStats publishStats =
|
||||
_transformBuffers.LastStats;
|
||||
var transformChurn = new DirectionalShadowTransformChurnDiagnostics(
|
||||
|
|
@ -457,7 +473,8 @@ internal sealed class DirectionalSunShadowRenderer : IDirectionalShadowReceiverS
|
|||
publishStats.UsedFullDynamicFallback,
|
||||
publishStats.DenseDirectUpload,
|
||||
publishStats.DenseFlightReplay,
|
||||
casterClasses);
|
||||
casterClasses,
|
||||
casterStats.ActiveSelected);
|
||||
long preparedDrawsAndTransformsTicks = input.MeasureCpuStages
|
||||
? Stopwatch.GetTimestamp() - cpuStageStarted
|
||||
: 0L;
|
||||
|
|
@ -661,7 +678,7 @@ internal sealed class DirectionalSunShadowRenderer : IDirectionalShadowReceiverS
|
|||
PublishDisabledReceiverBinding(frame, atmosphericFrame);
|
||||
return Disabled(in environment, cpuStages);
|
||||
}
|
||||
if (!worldDraws.Commands.IsEmpty && worldGeometry is null)
|
||||
if (!worldDraws.ActiveCommands.IsEmpty && worldGeometry is null)
|
||||
throw new ArgumentNullException(nameof(worldGeometry));
|
||||
if (!terrainDraws.Commands.IsEmpty && terrainGeometry is null)
|
||||
throw new ArgumentNullException(nameof(terrainGeometry));
|
||||
|
|
@ -812,9 +829,10 @@ internal sealed class DirectionalSunShadowRenderer : IDirectionalShadowReceiverS
|
|||
int drawsPerCascade = terrainDraws.Commands.IsEmpty ? 0 : 1;
|
||||
drawsPerCascade = checked(
|
||||
drawsPerCascade
|
||||
+ (worldDraws.Commands.IsEmpty
|
||||
+ (worldDraws.ActiveCommands.IsEmpty
|
||||
? 0
|
||||
: worldDraws.OpaqueRuns.Length + worldDraws.AlphaCutoutRuns.Length));
|
||||
: worldDraws.ActiveOpaqueRuns.Length
|
||||
+ worldDraws.ActiveAlphaCutoutRuns.Length));
|
||||
long finished = Stopwatch.GetTimestamp();
|
||||
cpuStages = cpuStages with
|
||||
{
|
||||
|
|
@ -833,8 +851,8 @@ internal sealed class DirectionalSunShadowRenderer : IDirectionalShadowReceiverS
|
|||
environment.Strength,
|
||||
cascadeCount,
|
||||
checked((MultiviewCascadesEnabled ? 1 : cascadeCount) * drawsPerCascade),
|
||||
worldDraws.OpaqueCommandCount,
|
||||
worldDraws.AlphaCutoutCommandCount,
|
||||
worldDraws.ActiveOpaqueCommandCount,
|
||||
worldDraws.ActiveAlphaCutoutCommandCount,
|
||||
terrainDraws.Commands.Length,
|
||||
worldDraws.BuildSequence,
|
||||
terrainDraws.BuildSequence,
|
||||
|
|
@ -848,7 +866,30 @@ internal sealed class DirectionalSunShadowRenderer : IDirectionalShadowReceiverS
|
|||
environment.SourceObjectIndex,
|
||||
environment.SourceGfxObjId,
|
||||
environment.SurfaceToLightDirection,
|
||||
environment.LightElevationSin);
|
||||
environment.LightElevationSin,
|
||||
ResidentWorldCasters: inputCasterClassesCount(transformChurn),
|
||||
ActiveWorldCasters: transformChurn.ActiveSelectedCasters,
|
||||
ResidentWorldInstances: worldDraws.Stats.PreparedInstances,
|
||||
ActiveWorldInstances: worldDraws.Stats.ActiveInstances,
|
||||
ResidentWorldCommands: worldDraws.Commands.Length,
|
||||
ActiveWorldCommands: worldDraws.ActiveCommands.Length,
|
||||
ResidentTerrainCommands: terrainDraws.ResidentRanges.Length,
|
||||
ActiveTerrainCommands: terrainDraws.Commands.Length);
|
||||
|
||||
static int inputCasterClassesCount(
|
||||
DirectionalShadowTransformChurnDiagnostics churn)
|
||||
{
|
||||
DirectionalShadowCasterClassDiagnostics classes = churn.CasterClasses;
|
||||
return checked(
|
||||
classes.OutdoorStatics
|
||||
+ classes.Buildings
|
||||
+ classes.AnimatedStatics
|
||||
+ classes.LocalPlayers
|
||||
+ classes.RemotePlayers
|
||||
+ classes.NonPlayerCreatures
|
||||
+ classes.OtherLiveDynamics
|
||||
+ classes.EquippedChildren);
|
||||
}
|
||||
}
|
||||
|
||||
private PreparedGpuUploads PrepareGpuData(
|
||||
|
|
@ -856,9 +897,9 @@ internal sealed class DirectionalSunShadowRenderer : IDirectionalShadowReceiverS
|
|||
DirectionalShadowPreparedDraws world,
|
||||
DirectionalShadowTerrainPreparedDraws terrain)
|
||||
{
|
||||
if (_worldGpuBuildSequence != world.BuildSequence)
|
||||
if (_worldGpuBuildSequence != world.ActiveSelectionSequence)
|
||||
RebuildWorldGpuData(world);
|
||||
if (_terrainGpuBuildSequence != terrain.BuildSequence)
|
||||
if (_terrainGpuBuildSequence != terrain.ActiveSelectionSequence)
|
||||
RebuildTerrainGpuData(terrain);
|
||||
|
||||
return new PreparedGpuUploads(
|
||||
|
|
@ -874,12 +915,12 @@ internal sealed class DirectionalSunShadowRenderer : IDirectionalShadowReceiverS
|
|||
IGpuBuffer? commands = null;
|
||||
try
|
||||
{
|
||||
if (!world.Commands.IsEmpty)
|
||||
if (!world.ActiveCommands.IsEmpty)
|
||||
{
|
||||
EnsureBatchCapacity(world.Batches.Length);
|
||||
for (int i = 0; i < world.Batches.Length; i++)
|
||||
EnsureBatchCapacity(world.ActiveBatches.Length);
|
||||
for (int i = 0; i < world.ActiveBatches.Length; i++)
|
||||
{
|
||||
DirectionalShadowPreparedBatch batch = world.Batches[i];
|
||||
DirectionalShadowPreparedBatch batch = world.ActiveBatches[i];
|
||||
_batchScratch[i] = new DirectionalShadowBatchGpuData(
|
||||
batch.TextureSlot.Index,
|
||||
0u,
|
||||
|
|
@ -892,15 +933,15 @@ internal sealed class DirectionalSunShadowRenderer : IDirectionalShadowReceiverS
|
|||
}
|
||||
|
||||
ReadOnlySpan<byte> batchBytes = MemoryMarshal.AsBytes(
|
||||
_batchScratch.AsSpan(0, world.Batches.Length));
|
||||
_batchScratch.AsSpan(0, world.ActiveBatches.Length));
|
||||
ReadOnlySpan<byte> commandBytes = MemoryMarshal.AsBytes(
|
||||
world.Commands);
|
||||
world.ActiveCommands);
|
||||
batches = CreateRetainedBuffer(
|
||||
$"directional-shadow-world-batches-{world.BuildSequence}",
|
||||
$"directional-shadow-world-batches-{world.ActiveSelectionSequence}",
|
||||
batchBytes,
|
||||
GpuBufferUsage.Storage);
|
||||
commands = CreateRetainedBuffer(
|
||||
$"directional-shadow-world-commands-{world.BuildSequence}",
|
||||
$"directional-shadow-world-commands-{world.ActiveSelectionSequence}",
|
||||
commandBytes,
|
||||
GpuBufferUsage.Indirect);
|
||||
}
|
||||
|
|
@ -916,7 +957,7 @@ internal sealed class DirectionalSunShadowRenderer : IDirectionalShadowReceiverS
|
|||
IGpuBuffer? previousCommands = _worldCommandBuffer;
|
||||
_worldBatchBuffer = batches;
|
||||
_worldCommandBuffer = commands;
|
||||
_worldGpuBuildSequence = world.BuildSequence;
|
||||
_worldGpuBuildSequence = world.ActiveSelectionSequence;
|
||||
previousCommands?.Dispose();
|
||||
previousBatches?.Dispose();
|
||||
}
|
||||
|
|
@ -927,14 +968,14 @@ internal sealed class DirectionalSunShadowRenderer : IDirectionalShadowReceiverS
|
|||
if (!terrain.Commands.IsEmpty)
|
||||
{
|
||||
commands = CreateRetainedBuffer(
|
||||
$"directional-shadow-terrain-commands-{terrain.BuildSequence}",
|
||||
$"directional-shadow-terrain-commands-{terrain.ActiveSelectionSequence}",
|
||||
MemoryMarshal.AsBytes(terrain.Commands),
|
||||
GpuBufferUsage.Indirect);
|
||||
}
|
||||
|
||||
IGpuBuffer? previous = _terrainCommandBuffer;
|
||||
_terrainCommandBuffer = commands;
|
||||
_terrainGpuBuildSequence = terrain.BuildSequence;
|
||||
_terrainGpuBuildSequence = terrain.ActiveSelectionSequence;
|
||||
previous?.Dispose();
|
||||
}
|
||||
|
||||
|
|
@ -997,7 +1038,7 @@ internal sealed class DirectionalSunShadowRenderer : IDirectionalShadowReceiverS
|
|||
IGpuPipeline? opaquePipeline = null,
|
||||
IGpuPipeline? cutoutPipeline = null)
|
||||
{
|
||||
if (draws.Commands.IsEmpty)
|
||||
if (draws.ActiveCommands.IsEmpty)
|
||||
return;
|
||||
DirectionalShadowMeshGeometry actual = geometry!.Value;
|
||||
encoder.BindStorageBuffer(
|
||||
|
|
@ -1013,14 +1054,14 @@ internal sealed class DirectionalSunShadowRenderer : IDirectionalShadowReceiverS
|
|||
DrawWorldRange(
|
||||
encoder,
|
||||
uploads.WorldCommands,
|
||||
draws.OpaqueRuns,
|
||||
draws.ActiveOpaqueRuns,
|
||||
cascadeIndex,
|
||||
opaquePipeline ?? _worldOpaquePipeline,
|
||||
actual);
|
||||
DrawWorldRange(
|
||||
encoder,
|
||||
uploads.WorldCommands,
|
||||
draws.AlphaCutoutRuns,
|
||||
draws.ActiveAlphaCutoutRuns,
|
||||
cascadeIndex,
|
||||
cutoutPipeline ?? _worldCutoutPipeline,
|
||||
actual);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using System.Numerics;
|
|||
using System.Runtime.InteropServices;
|
||||
using AcDream.App.Rendering.Gpu;
|
||||
using AcDream.App.Rendering.Scene;
|
||||
using AcDream.App.Rendering.Vfx;
|
||||
using AcDream.App.Rendering.Wb;
|
||||
using AcDream.Core.World;
|
||||
using AcDream.Plugin.Abstractions.Rendering;
|
||||
|
|
@ -421,6 +422,12 @@ internal sealed class AtmosphericPostProcessGraph :
|
|||
!shadowInputsChanged || _shadowRebuildDeferrals >= 2;
|
||||
ulong casterSequenceBefore = _shadowCasters.BuildSequence;
|
||||
_shadowCasters.Build(in scene, allowTopologyRebuild);
|
||||
RetailLandscapeVisibilityFrame priorLandscapeVisibility =
|
||||
world.PriorLandscapeVisibility;
|
||||
_shadowCasters.Select(
|
||||
in priorLandscapeVisibility,
|
||||
world.DirectionalShadowCellMembership
|
||||
?? EmptyDirectionalShadowCellMembership.Instance);
|
||||
if (_shadowCasters.BuildSequence != casterSequenceBefore)
|
||||
allowTopologyRebuild = true;
|
||||
_shadowRebuildDeferrals = allowTopologyRebuild
|
||||
|
|
@ -463,9 +470,10 @@ internal sealed class AtmosphericPostProcessGraph :
|
|||
frame.Serial),
|
||||
MeasureCpuStages: measureCpuStages,
|
||||
AtmosphericFrame: shadowAtmosphericFrame,
|
||||
PriorLandscapeVisibility: world.PriorLandscapeVisibility,
|
||||
AllowTopologyRebuild: allowTopologyRebuild);
|
||||
long environmentFinished = measureCpuStages ? Stopwatch.GetTimestamp() : 0L;
|
||||
_lastShadowCasterCount = _shadowCasters.Stats.Accepted;
|
||||
_lastShadowCasterCount = _shadowCasters.Stats.ActiveSelected;
|
||||
_lastShadowClassificationCalls = _shadowCasters.Stats.TopologyRebuilt ? 1 : 0;
|
||||
_lastShadowDiagnostics = _directionalShadows.Render(
|
||||
frame,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ using System.Numerics;
|
|||
using System.Runtime.InteropServices;
|
||||
using AcDream.App.Rendering.Gpu;
|
||||
using AcDream.App.Rendering.Scene;
|
||||
using AcDream.App.Rendering.Vfx;
|
||||
using AcDream.App.Rendering.Wb;
|
||||
using AcDream.Core.World;
|
||||
using AcDream.Plugin.Abstractions.Rendering;
|
||||
|
|
@ -201,6 +202,12 @@ internal class DeclaredFullscreenRenderPackGraph :
|
|||
?? throw new InvalidOperationException(
|
||||
$"Pack '{Descriptor.Id}' has no declared directional-shadow executor.");
|
||||
_shadowCasters.Build(in scene);
|
||||
RetailLandscapeVisibilityFrame priorLandscapeVisibility =
|
||||
world.PriorLandscapeVisibility;
|
||||
_shadowCasters.Select(
|
||||
in priorLandscapeVisibility,
|
||||
world.DirectionalShadowCellMembership
|
||||
?? EmptyDirectionalShadowCellMembership.Instance);
|
||||
AuthoredCelestialShadowSource source = world.CelestialShadowSource;
|
||||
float elevationStrength = RenderPackAtmospherePolicyEvaluation
|
||||
.DirectionalShadowFromSin(
|
||||
|
|
@ -225,8 +232,9 @@ internal class DeclaredFullscreenRenderPackGraph :
|
|||
world.Camera.Projection,
|
||||
_shadowCasters,
|
||||
ResidentMaximumReachMeters:
|
||||
world.ResidentStreamingWindow.MaximumReachMeters);
|
||||
_lastShadowCasterCount = _shadowCasters.Stats.Accepted;
|
||||
world.ResidentStreamingWindow.MaximumReachMeters,
|
||||
PriorLandscapeVisibility: world.PriorLandscapeVisibility);
|
||||
_lastShadowCasterCount = _shadowCasters.Stats.ActiveSelected;
|
||||
_lastShadowClassificationCalls = _shadowCasters.Stats.TopologyRebuilt ? 1 : 0;
|
||||
_lastShadowDiagnostics = renderer.Render(
|
||||
frame,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
using AcDream.App.Rendering.Vfx;
|
||||
|
||||
namespace AcDream.App.Rendering.Scene;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -80,7 +82,8 @@ internal readonly record struct DirectionalShadowCasterBuildStats(
|
|||
int LiveDynamicRootChanges = 0,
|
||||
int EquippedChildChanges = 0,
|
||||
bool DensityBulkRefresh = false,
|
||||
int BatchedProjectionCopyCalls = 0)
|
||||
int BatchedProjectionCopyCalls = 0,
|
||||
int ActiveSelected = 0)
|
||||
{
|
||||
public DirectionalShadowCasterClassDiagnostics CasterClasses { get; init; }
|
||||
}
|
||||
|
|
@ -96,6 +99,7 @@ internal sealed class DirectionalShadowCasterFrame
|
|||
private RenderProjectionRecord[] _outdoorStaticScratch = [];
|
||||
private RenderProjectionRecord[] _outdoorDynamicScratch = [];
|
||||
private DirectionalShadowCaster[] _casters = [];
|
||||
private bool[] _selectedCasters = [];
|
||||
private int[] _refreshCasterSlots = [];
|
||||
private DirectionalShadowChangedPose[] _changedCasterPoses = [];
|
||||
private bool[] _changedCasterFlags = [];
|
||||
|
|
@ -123,9 +127,16 @@ internal sealed class DirectionalShadowCasterFrame
|
|||
|
||||
public ulong BuildSequence { get; private set; }
|
||||
|
||||
/// <summary>Per-frame active-selection publication identity. It is
|
||||
/// intentionally independent from retained topology <see cref="BuildSequence"/>.</summary>
|
||||
public ulong SelectionSequence { get; private set; }
|
||||
|
||||
public ReadOnlySpan<DirectionalShadowCaster> Casters =>
|
||||
_casters.AsSpan(0, _casterCount);
|
||||
|
||||
internal ReadOnlySpan<bool> SelectedCasters =>
|
||||
_selectedCasters.AsSpan(0, _casterCount);
|
||||
|
||||
internal ReadOnlySpan<int> RefreshCasterSlots =>
|
||||
_refreshCasterSlots.AsSpan(0, _refreshCasterSlotCount);
|
||||
|
||||
|
|
@ -144,6 +155,7 @@ internal sealed class DirectionalShadowCasterFrame
|
|||
* System.Runtime.CompilerServices.Unsafe.SizeOf<RenderProjectionRecord>()
|
||||
+ (long)_casters.Length
|
||||
* System.Runtime.CompilerServices.Unsafe.SizeOf<DirectionalShadowCaster>()
|
||||
+ _selectedCasters.Length
|
||||
+ (long)_refreshCasterSlots.Length * sizeof(int)
|
||||
+ (long)_changedCasterPoses.Length
|
||||
* System.Runtime.CompilerServices.Unsafe.SizeOf<
|
||||
|
|
@ -233,6 +245,7 @@ internal sealed class DirectionalShadowCasterFrame
|
|||
RenderSceneIndex.OutdoorDynamic,
|
||||
_outdoorDynamicScratch.AsSpan(0, counts.OutdoorDynamic));
|
||||
EnsureCapacity(ref _casters, checked(staticCount + dynamicCount));
|
||||
EnsureCapacity(ref _selectedCasters, checked(staticCount + dynamicCount));
|
||||
_casterCount = 0;
|
||||
|
||||
int rejectedNotDrawable = 0;
|
||||
|
|
@ -394,6 +407,67 @@ internal sealed class DirectionalShadowCasterFrame
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Projects the prior-completed retail landscape product onto the retained
|
||||
/// caster topology. This performs no render-scene copy, classification,
|
||||
/// mesh lookup, or topology revision change.
|
||||
/// </summary>
|
||||
internal void Select(
|
||||
in RetailLandscapeVisibilityFrame visibility,
|
||||
IDirectionalShadowCellMembership membership)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(membership);
|
||||
IReadOnlySet<uint> visible = visibility.CellIds
|
||||
?? RetailLandscapeVisibilityFrame.None.CellIds;
|
||||
int selected = 0;
|
||||
for (int casterIndex = 0; casterIndex < _casterCount; casterIndex++)
|
||||
{
|
||||
ref readonly DirectionalShadowCaster caster =
|
||||
ref _casters[casterIndex];
|
||||
bool active = visibility.HasCompletedWorldView
|
||||
&& SelectsCaster(in caster, visible, membership);
|
||||
_selectedCasters[casterIndex] = active;
|
||||
if (active)
|
||||
selected++;
|
||||
}
|
||||
|
||||
SelectionSequence = checked(SelectionSequence + 1);
|
||||
Stats = Stats with { ActiveSelected = selected };
|
||||
}
|
||||
|
||||
private static bool SelectsCaster(
|
||||
in DirectionalShadowCaster caster,
|
||||
IReadOnlySet<uint> visible,
|
||||
IDirectionalShadowCellMembership membership)
|
||||
{
|
||||
RenderSourceMetadata source = caster.Projection.Source;
|
||||
if (caster.Kind is DirectionalShadowCasterKind.Building)
|
||||
return IsOutdoorLandCell(source.EffectCellId)
|
||||
&& visible.Contains(source.EffectCellId);
|
||||
|
||||
if (!membership.TryGetRetailCellArray(
|
||||
source.LocalEntityId,
|
||||
out IReadOnlyList<uint>? cells)
|
||||
|| cells.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int cellIndex = 0; cellIndex < cells.Count; cellIndex++)
|
||||
{
|
||||
uint cellId = cells[cellIndex];
|
||||
if (IsOutdoorLandCell(cellId) && visible.Contains(cellId))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsOutdoorLandCell(uint cellId)
|
||||
{
|
||||
uint low = cellId & 0xFFFFu;
|
||||
return low != 0u && low < 0x0100u;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pure pre-check for the deferral gate: would refreshing from the journal
|
||||
/// demand the dense by-id re-copy? The journal copy is a read; the state
|
||||
|
|
|
|||
|
|
@ -1,37 +1,49 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
using AcDream.App.Rendering.Wb;
|
||||
using AcDream.App.Rendering.Gpu;
|
||||
using AcDream.App.Rendering.Vfx;
|
||||
|
||||
namespace AcDream.App.Rendering;
|
||||
|
||||
internal readonly record struct DirectionalShadowTerrainRange(
|
||||
uint FirstIndex,
|
||||
int IndexCount);
|
||||
int IndexCount,
|
||||
uint LandblockId = 0u);
|
||||
|
||||
internal readonly record struct DirectionalShadowTerrainGeometry(
|
||||
IGpuBuffer VertexBuffer,
|
||||
IGpuBuffer IndexBuffer);
|
||||
|
||||
/// <summary>
|
||||
/// The complete resident terrain arena expressed once as indirect commands.
|
||||
/// It deliberately has no camera, PView, portal, or cascade input.
|
||||
/// The complete resident terrain arena expressed once as retained slot ranges,
|
||||
/// plus a separately sequenced active indirect projection.
|
||||
/// </summary>
|
||||
internal sealed class DirectionalShadowTerrainPreparedDraws
|
||||
{
|
||||
private DrawElementsIndirectCommand[] _commands = [];
|
||||
private int _count;
|
||||
private DirectionalShadowTerrainRange[] _ranges = [];
|
||||
private DrawElementsIndirectCommand[] _activeCommands = [];
|
||||
private int _rangeCount;
|
||||
private int _activeCount;
|
||||
private bool _building;
|
||||
|
||||
public long SourceFrameSequence { get; private set; }
|
||||
|
||||
public ulong BuildSequence { get; private set; }
|
||||
|
||||
public ulong ActiveSelectionSequence { get; private set; }
|
||||
|
||||
public ReadOnlySpan<DrawElementsIndirectCommand> Commands =>
|
||||
_commands.AsSpan(0, _count);
|
||||
_activeCommands.AsSpan(0, _activeCount);
|
||||
|
||||
public ReadOnlySpan<DirectionalShadowTerrainRange> ResidentRanges =>
|
||||
_ranges.AsSpan(0, _rangeCount);
|
||||
|
||||
public long RetainedScratchBytes =>
|
||||
checked((long)_commands.Length
|
||||
* Unsafe.SizeOf<DrawElementsIndirectCommand>());
|
||||
checked(
|
||||
(long)_ranges.Length
|
||||
* Unsafe.SizeOf<DirectionalShadowTerrainRange>()
|
||||
+ (long)_activeCommands.Length
|
||||
* Unsafe.SizeOf<DrawElementsIndirectCommand>());
|
||||
|
||||
public bool TryBegin(long frameSequence, int estimatedCommands)
|
||||
{
|
||||
|
|
@ -45,7 +57,7 @@ internal sealed class DirectionalShadowTerrainPreparedDraws
|
|||
return false;
|
||||
|
||||
EnsureCapacity(estimatedCommands);
|
||||
_count = 0;
|
||||
_rangeCount = 0;
|
||||
_building = true;
|
||||
return true;
|
||||
}
|
||||
|
|
@ -57,15 +69,8 @@ internal sealed class DirectionalShadowTerrainPreparedDraws
|
|||
"Begin a terrain shadow draw build before adding ranges.");
|
||||
if (range.IndexCount <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(range));
|
||||
EnsureCapacity(checked(_count + 1));
|
||||
_commands[_count++] = new DrawElementsIndirectCommand
|
||||
{
|
||||
Count = checked((uint)range.IndexCount),
|
||||
InstanceCount = 1,
|
||||
FirstIndex = range.FirstIndex,
|
||||
BaseVertex = 0,
|
||||
BaseInstance = 0,
|
||||
};
|
||||
EnsureCapacity(checked(_rangeCount + 1));
|
||||
_ranges[_rangeCount++] = range;
|
||||
}
|
||||
|
||||
public void Complete(long frameSequence)
|
||||
|
|
@ -78,22 +83,73 @@ internal sealed class DirectionalShadowTerrainPreparedDraws
|
|||
SourceFrameSequence = frameSequence;
|
||||
BuildSequence = checked(BuildSequence + 1);
|
||||
_building = false;
|
||||
RebuildActiveAll();
|
||||
}
|
||||
|
||||
internal void ApplySelection(in RetailLandscapeVisibilityFrame visibility)
|
||||
{
|
||||
IReadOnlySet<uint> visible = visibility.CellIds
|
||||
?? RetailLandscapeVisibilityFrame.None.CellIds;
|
||||
_activeCount = 0;
|
||||
if (visibility.HasCompletedWorldView)
|
||||
{
|
||||
for (int rangeIndex = 0; rangeIndex < _rangeCount; rangeIndex++)
|
||||
{
|
||||
DirectionalShadowTerrainRange range = _ranges[rangeIndex];
|
||||
uint prefix = range.LandblockId & 0xFFFF0000u;
|
||||
bool selected = false;
|
||||
for (uint low = 1u; low <= 64u; low++)
|
||||
{
|
||||
if (visible.Contains(prefix | low))
|
||||
{
|
||||
selected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (selected)
|
||||
Emit(in range);
|
||||
}
|
||||
}
|
||||
ActiveSelectionSequence = checked(ActiveSelectionSequence + 1);
|
||||
}
|
||||
|
||||
private void RebuildActiveAll()
|
||||
{
|
||||
EnsureCapacity(_rangeCount);
|
||||
_activeCount = 0;
|
||||
for (int rangeIndex = 0; rangeIndex < _rangeCount; rangeIndex++)
|
||||
Emit(in _ranges[rangeIndex]);
|
||||
ActiveSelectionSequence = checked(ActiveSelectionSequence + 1);
|
||||
}
|
||||
|
||||
private void Emit(in DirectionalShadowTerrainRange range)
|
||||
{
|
||||
_activeCommands[_activeCount++] = new DrawElementsIndirectCommand
|
||||
{
|
||||
Count = checked((uint)range.IndexCount),
|
||||
InstanceCount = 1,
|
||||
FirstIndex = range.FirstIndex,
|
||||
BaseVertex = 0,
|
||||
BaseInstance = 0,
|
||||
};
|
||||
}
|
||||
|
||||
public void Abort()
|
||||
{
|
||||
_count = 0;
|
||||
_rangeCount = 0;
|
||||
_activeCount = 0;
|
||||
_building = false;
|
||||
}
|
||||
|
||||
private void EnsureCapacity(int required)
|
||||
{
|
||||
if (_commands.Length >= required)
|
||||
if (_ranges.Length >= required)
|
||||
return;
|
||||
int capacity = _commands.Length == 0 ? 16 : _commands.Length;
|
||||
int capacity = _ranges.Length == 0 ? 16 : _ranges.Length;
|
||||
while (capacity < required)
|
||||
capacity = checked(capacity * 2);
|
||||
Array.Resize(ref _commands, capacity);
|
||||
Array.Resize(ref _ranges, capacity);
|
||||
Array.Resize(ref _activeCommands, capacity);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -101,46 +157,107 @@ public sealed partial class TerrainModernRenderer
|
|||
{
|
||||
private readonly DirectionalShadowTerrainPreparedDraws
|
||||
_directionalShadowTerrainDraws = new();
|
||||
// BeginFrame's established overflow guard/frame counter remains part of
|
||||
// the ordinary renderer lifecycle; shadow topology no longer keys on it.
|
||||
private long _directionalShadowFrameSequence;
|
||||
private uint[] _directionalShadowSlotLandblocks = [];
|
||||
private uint[] _directionalShadowSlotFirstIndices = [];
|
||||
private int[] _directionalShadowSlotIndexCounts = [];
|
||||
private bool[] _directionalShadowSlotPresent = [];
|
||||
private bool _directionalShadowTopologySnapshotValid;
|
||||
private long _directionalShadowTopologySequence;
|
||||
|
||||
internal DirectionalShadowTerrainGeometry GetDirectionalShadowGeometry() => new(
|
||||
_vertexStore ?? throw new InvalidOperationException("Terrain has no vertex store."),
|
||||
_indexStore ?? throw new InvalidOperationException("Terrain has no index store."));
|
||||
|
||||
/// <summary>
|
||||
/// Builds one all-resident indirect list for the current frame. Repeated
|
||||
/// calls by individual cascades return the same retained product.
|
||||
/// Retains exact loaded-slot topology and projects the borrowed prior-view
|
||||
/// selection into a bounded indirect list. Camera-only changes do not
|
||||
/// advance the topology build sequence or rebuild terrain geometry.
|
||||
/// </summary>
|
||||
internal DirectionalShadowTerrainPreparedDraws
|
||||
PrepareDirectionalShadowDraws()
|
||||
PrepareDirectionalShadowDraws(
|
||||
in RetailLandscapeVisibilityFrame visibility)
|
||||
{
|
||||
if (!_directionalShadowTerrainDraws.TryBegin(
|
||||
_directionalShadowFrameSequence,
|
||||
_alloc.LoadedCount))
|
||||
EnsureDirectionalShadowSlotCapacity(_slots.Length);
|
||||
bool topologyChanged = !_directionalShadowTopologySnapshotValid;
|
||||
for (int slot = 0; slot < _slots.Length; slot++)
|
||||
{
|
||||
return _directionalShadowTerrainDraws;
|
||||
SlotData? data = _slots[slot];
|
||||
bool present = data is not null;
|
||||
if (_directionalShadowSlotPresent[slot] != present
|
||||
|| present
|
||||
&& (_directionalShadowSlotLandblocks[slot] != data!.LandblockId
|
||||
|| _directionalShadowSlotFirstIndices[slot] != data.FirstIndex
|
||||
|| _directionalShadowSlotIndexCounts[slot] != data.IndexCount))
|
||||
{
|
||||
topologyChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
if (topologyChanged)
|
||||
{
|
||||
for (int slot = 0; slot < _slots.Length; slot++)
|
||||
_directionalShadowTopologySequence = checked(
|
||||
_directionalShadowTopologySequence + 1);
|
||||
_directionalShadowTerrainDraws.TryBegin(
|
||||
_directionalShadowTopologySequence,
|
||||
_alloc.LoadedCount);
|
||||
try
|
||||
{
|
||||
SlotData? data = _slots[slot];
|
||||
if (data is null)
|
||||
continue;
|
||||
var range = new DirectionalShadowTerrainRange(
|
||||
data.FirstIndex,
|
||||
data.IndexCount);
|
||||
_directionalShadowTerrainDraws.Add(in range);
|
||||
for (int slot = 0; slot < _slots.Length; slot++)
|
||||
{
|
||||
SlotData? data = _slots[slot];
|
||||
bool present = data is not null;
|
||||
_directionalShadowSlotPresent[slot] = present;
|
||||
if (!present)
|
||||
{
|
||||
_directionalShadowSlotLandblocks[slot] = 0u;
|
||||
_directionalShadowSlotFirstIndices[slot] = 0u;
|
||||
_directionalShadowSlotIndexCounts[slot] = 0;
|
||||
continue;
|
||||
}
|
||||
_directionalShadowSlotLandblocks[slot] = data!.LandblockId;
|
||||
_directionalShadowSlotFirstIndices[slot] = data.FirstIndex;
|
||||
_directionalShadowSlotIndexCounts[slot] = data.IndexCount;
|
||||
var range = new DirectionalShadowTerrainRange(
|
||||
data.FirstIndex,
|
||||
data.IndexCount,
|
||||
data.LandblockId);
|
||||
_directionalShadowTerrainDraws.Add(in range);
|
||||
}
|
||||
_directionalShadowTerrainDraws.Complete(
|
||||
_directionalShadowTopologySequence);
|
||||
_directionalShadowTopologySnapshotValid = true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// The snapshot fields are populated while the retained
|
||||
// product is built. If publication fails, force the next
|
||||
// frame to retry even when those fields already match the
|
||||
// live slots; an aborted build is never a valid topology.
|
||||
_directionalShadowTopologySnapshotValid = false;
|
||||
_directionalShadowTerrainDraws.Abort();
|
||||
throw;
|
||||
}
|
||||
_directionalShadowTerrainDraws.Complete(
|
||||
_directionalShadowFrameSequence);
|
||||
return _directionalShadowTerrainDraws;
|
||||
}
|
||||
catch
|
||||
{
|
||||
_directionalShadowTerrainDraws.Abort();
|
||||
throw;
|
||||
}
|
||||
|
||||
_directionalShadowTerrainDraws.ApplySelection(in visibility);
|
||||
return _directionalShadowTerrainDraws;
|
||||
}
|
||||
|
||||
private void EnsureDirectionalShadowSlotCapacity(int required)
|
||||
{
|
||||
if (_directionalShadowSlotPresent.Length >= required)
|
||||
return;
|
||||
int capacity = _directionalShadowSlotPresent.Length == 0
|
||||
? 16
|
||||
: _directionalShadowSlotPresent.Length;
|
||||
while (capacity < required)
|
||||
capacity = checked(capacity * 2);
|
||||
Array.Resize(ref _directionalShadowSlotLandblocks, capacity);
|
||||
Array.Resize(ref _directionalShadowSlotFirstIndices, capacity);
|
||||
Array.Resize(ref _directionalShadowSlotIndexCounts, capacity);
|
||||
Array.Resize(ref _directionalShadowSlotPresent, capacity);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,21 @@ internal interface IWorldSceneParticleVisibility
|
|||
void AbortFrame();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Borrowed prior-completed retail landscape visibility. <see cref="CellIds"/>
|
||||
/// is the controller's one retained completed set, not a reconstructed or
|
||||
/// current-camera answer. The borrow is valid through the enhanced-world
|
||||
/// prepass that captured it.
|
||||
/// </summary>
|
||||
internal readonly record struct RetailLandscapeVisibilityFrame(
|
||||
IReadOnlySet<uint> CellIds,
|
||||
bool HasCompletedWorldView)
|
||||
{
|
||||
internal static RetailLandscapeVisibilityFrame None { get; } = new(
|
||||
System.Collections.Frozen.FrozenSet<uint>.Empty,
|
||||
HasCompletedWorldView: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bridges the retained retail PView result into the next physics update's
|
||||
/// <c>CObjCell::IsInView</c> particle gate. The controller owns only immutable
|
||||
|
|
@ -115,6 +130,13 @@ public sealed class ParticleVisibilityController : IWorldSceneParticleVisibility
|
|||
rangeMultiplier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Borrows the exact prior-completed landscape product for the opt-in
|
||||
/// directional-shadow prepass. No set is copied or reconstructed.
|
||||
/// </summary>
|
||||
internal RetailLandscapeVisibilityFrame CaptureCompletedLandscapeVisibility() =>
|
||||
new(_completedCellIds, _hasCompletedWorldView);
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_buildingCellIds.Clear();
|
||||
|
|
|
|||
|
|
@ -42,7 +42,9 @@ internal readonly record struct DirectionalShadowPreparationStats(
|
|||
int RejectedTransparentBatches,
|
||||
int RejectedFadedParts,
|
||||
int MissingMeshes,
|
||||
int UnresolvedAlphaCutoutTextures);
|
||||
int UnresolvedAlphaCutoutTextures,
|
||||
int ActiveInstances = 0,
|
||||
int ActiveCommands = 0);
|
||||
|
||||
internal readonly record struct DirectionalShadowMeshGeometry(
|
||||
IGpuBuffer VertexBuffer,
|
||||
|
|
@ -55,6 +57,14 @@ internal readonly record struct DirectionalShadowTransformSource(
|
|||
bool IsSetupPart,
|
||||
Matrix4x4 SetupPartTransform)
|
||||
{
|
||||
public static DirectionalShadowTransformSource Static(int casterIndex) =>
|
||||
new(
|
||||
false,
|
||||
casterIndex,
|
||||
MeshIndex: 0,
|
||||
IsSetupPart: false,
|
||||
SetupPartTransform: default);
|
||||
|
||||
public static DirectionalShadowTransformSource Dynamic(
|
||||
int casterIndex,
|
||||
int meshIndex,
|
||||
|
|
@ -89,6 +99,9 @@ internal sealed class DirectionalShadowPreparedDraws
|
|||
private DrawElementsIndirectCommand[] _commands = [];
|
||||
private DirectionalShadowPreparedBatch[] _batches = [];
|
||||
private DirectionalShadowPreparedRun[] _runs = [];
|
||||
private DrawElementsIndirectCommand[] _activeCommands = [];
|
||||
private DirectionalShadowPreparedBatch[] _activeBatches = [];
|
||||
private DirectionalShadowPreparedRun[] _activeRuns = [];
|
||||
private int[] _drawNextInGroup = [];
|
||||
private int[] _groupHead = [];
|
||||
private int[] _groupTail = [];
|
||||
|
|
@ -101,6 +114,8 @@ internal sealed class DirectionalShadowPreparedDraws
|
|||
private int _sourceCount;
|
||||
private int _commandCount;
|
||||
private int _runCount;
|
||||
private int _activeCommandCount;
|
||||
private int _activeRunCount;
|
||||
private int _dynamicTransformSlotCount;
|
||||
private int _allDynamicTransformSlotCount;
|
||||
private int _mappedCasterCount;
|
||||
|
|
@ -117,6 +132,10 @@ internal sealed class DirectionalShadowPreparedDraws
|
|||
|
||||
public ulong BuildSequence { get; private set; }
|
||||
|
||||
public ulong SourceCasterSelectionSequence { get; private set; }
|
||||
|
||||
public ulong ActiveSelectionSequence { get; private set; }
|
||||
|
||||
public int LastDynamicTransformRefreshCount { get; private set; }
|
||||
|
||||
public bool LastDynamicTransformRefreshWasDense { get; private set; }
|
||||
|
|
@ -174,6 +193,27 @@ internal sealed class DirectionalShadowPreparedDraws
|
|||
public ReadOnlySpan<DirectionalShadowPreparedRun> AlphaCutoutRuns =>
|
||||
_runs.AsSpan(OpaqueRunCount, _runCount - OpaqueRunCount);
|
||||
|
||||
public int ActiveOpaqueCommandCount { get; private set; }
|
||||
|
||||
public int ActiveAlphaCutoutCommandCount =>
|
||||
_activeCommandCount - ActiveOpaqueCommandCount;
|
||||
|
||||
public int ActiveOpaqueRunCount { get; private set; }
|
||||
|
||||
public ReadOnlySpan<DrawElementsIndirectCommand> ActiveCommands =>
|
||||
_activeCommands.AsSpan(0, _activeCommandCount);
|
||||
|
||||
public ReadOnlySpan<DirectionalShadowPreparedBatch> ActiveBatches =>
|
||||
_activeBatches.AsSpan(0, _activeCommandCount);
|
||||
|
||||
public ReadOnlySpan<DirectionalShadowPreparedRun> ActiveOpaqueRuns =>
|
||||
_activeRuns.AsSpan(0, ActiveOpaqueRunCount);
|
||||
|
||||
public ReadOnlySpan<DirectionalShadowPreparedRun> ActiveAlphaCutoutRuns =>
|
||||
_activeRuns.AsSpan(
|
||||
ActiveOpaqueRunCount,
|
||||
_activeRunCount - ActiveOpaqueRunCount);
|
||||
|
||||
public DirectionalShadowPreparationStats Stats { get; private set; }
|
||||
|
||||
public long RetainedScratchBytes => checked(
|
||||
|
|
@ -193,6 +233,9 @@ internal sealed class DirectionalShadowPreparedDraws
|
|||
+ (long)_commands.Length * Unsafe.SizeOf<DrawElementsIndirectCommand>()
|
||||
+ (long)_batches.Length * Unsafe.SizeOf<DirectionalShadowPreparedBatch>()
|
||||
+ (long)_runs.Length * Unsafe.SizeOf<DirectionalShadowPreparedRun>()
|
||||
+ (long)_activeCommands.Length * Unsafe.SizeOf<DrawElementsIndirectCommand>()
|
||||
+ (long)_activeBatches.Length * Unsafe.SizeOf<DirectionalShadowPreparedBatch>()
|
||||
+ (long)_activeRuns.Length * Unsafe.SizeOf<DirectionalShadowPreparedRun>()
|
||||
+ (long)_drawNextInGroup.Length * sizeof(int)
|
||||
+ (long)_groupHead.Length * sizeof(int)
|
||||
+ (long)_groupTail.Length * sizeof(int)
|
||||
|
|
@ -248,6 +291,8 @@ internal sealed class DirectionalShadowPreparedDraws
|
|||
_sourceCount = 0;
|
||||
_commandCount = 0;
|
||||
_runCount = 0;
|
||||
_activeCommandCount = 0;
|
||||
_activeRunCount = 0;
|
||||
_dynamicTransformSlotCount = 0;
|
||||
_allDynamicTransformSlotCount = 0;
|
||||
if (_mappedCasterCount != 0)
|
||||
|
|
@ -260,6 +305,8 @@ internal sealed class DirectionalShadowPreparedDraws
|
|||
_mappedCasterCount = 0;
|
||||
OpaqueCommandCount = 0;
|
||||
OpaqueRunCount = 0;
|
||||
ActiveOpaqueCommandCount = 0;
|
||||
ActiveOpaqueRunCount = 0;
|
||||
Stats = default;
|
||||
LastDynamicTransformRefreshCount = 0;
|
||||
LastDynamicTransformRefreshWasDense = false;
|
||||
|
|
@ -443,6 +490,9 @@ internal sealed class DirectionalShadowPreparedDraws
|
|||
EnsureCapacity(ref _commands, _sourceCount);
|
||||
EnsureCapacity(ref _batches, _sourceCount);
|
||||
EnsureCapacity(ref _runs, _sourceCount);
|
||||
EnsureCapacity(ref _activeCommands, _sourceCount);
|
||||
EnsureCapacity(ref _activeBatches, _sourceCount);
|
||||
EnsureCapacity(ref _activeRuns, _sourceCount);
|
||||
|
||||
int maxCasterIndex = -1;
|
||||
for (int index = 0; index < _sourceCount; index++)
|
||||
|
|
@ -562,6 +612,152 @@ internal sealed class DirectionalShadowPreparedDraws
|
|||
PreparedAlphaCutoutCommands = commandIndex - opaqueCommands,
|
||||
};
|
||||
_building = false;
|
||||
RebuildActiveAll();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Projects arbitrary selected caster instances into contiguous transform
|
||||
/// runs without changing retained material/mesh topology or its transform
|
||||
/// address space.
|
||||
/// </summary>
|
||||
internal void ApplySelection(DirectionalShadowCasterFrame casters)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(casters);
|
||||
if (casters.SelectionSequence == 0)
|
||||
return;
|
||||
if (SourceGeneration != casters.Generation
|
||||
|| SourceCasterBuildSequence != casters.BuildSequence)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Directional-shadow selection does not match prepared topology.");
|
||||
}
|
||||
if (SourceCasterSelectionSequence == casters.SelectionSequence)
|
||||
return;
|
||||
|
||||
ApplySelection(casters.SelectedCasters, casters.SelectionSequence);
|
||||
}
|
||||
|
||||
internal void ApplySelection(
|
||||
ReadOnlySpan<bool> selected,
|
||||
ulong casterSelectionSequence)
|
||||
{
|
||||
if (casterSelectionSequence == 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(casterSelectionSequence));
|
||||
if (SourceCasterSelectionSequence == casterSelectionSequence)
|
||||
return;
|
||||
|
||||
_activeCommandCount = 0;
|
||||
int activeInstances = 0;
|
||||
for (int commandIndex = 0; commandIndex < _commandCount; commandIndex++)
|
||||
{
|
||||
DrawElementsIndirectCommand command = _commands[commandIndex];
|
||||
int first = checked((int)command.BaseInstance);
|
||||
int end = checked(first + (int)command.InstanceCount);
|
||||
int runStart = -1;
|
||||
for (int transformIndex = first; transformIndex < end; transformIndex++)
|
||||
{
|
||||
int casterIndex = _transformSources[transformIndex].CasterIndex;
|
||||
if ((uint)casterIndex >= (uint)selected.Length)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Prepared directional-shadow instance has a stale caster slot.");
|
||||
}
|
||||
bool active = selected[casterIndex];
|
||||
if (active && runStart < 0)
|
||||
runStart = transformIndex;
|
||||
if (!active && runStart >= 0)
|
||||
{
|
||||
EmitActive(commandIndex, in command, runStart, transformIndex - runStart);
|
||||
activeInstances += transformIndex - runStart;
|
||||
runStart = -1;
|
||||
}
|
||||
}
|
||||
if (runStart >= 0)
|
||||
{
|
||||
EmitActive(commandIndex, in command, runStart, end - runStart);
|
||||
activeInstances += end - runStart;
|
||||
}
|
||||
}
|
||||
|
||||
BuildActiveRuns();
|
||||
SourceCasterSelectionSequence = casterSelectionSequence;
|
||||
ActiveSelectionSequence = checked(ActiveSelectionSequence + 1);
|
||||
Stats = Stats with
|
||||
{
|
||||
ActiveInstances = activeInstances,
|
||||
ActiveCommands = _activeCommandCount,
|
||||
};
|
||||
}
|
||||
|
||||
private void RebuildActiveAll()
|
||||
{
|
||||
EnsureCapacity(ref _activeCommands, _commandCount);
|
||||
EnsureCapacity(ref _activeBatches, _commandCount);
|
||||
EnsureCapacity(ref _activeRuns, _runCount);
|
||||
_commands.AsSpan(0, _commandCount).CopyTo(_activeCommands);
|
||||
_batches.AsSpan(0, _commandCount).CopyTo(_activeBatches);
|
||||
_activeCommandCount = _commandCount;
|
||||
BuildActiveRuns();
|
||||
SourceCasterSelectionSequence = 0;
|
||||
ActiveSelectionSequence = checked(ActiveSelectionSequence + 1);
|
||||
Stats = Stats with
|
||||
{
|
||||
ActiveInstances = _sourceCount,
|
||||
ActiveCommands = _activeCommandCount,
|
||||
};
|
||||
}
|
||||
|
||||
private void EmitActive(
|
||||
int sourceCommandIndex,
|
||||
in DrawElementsIndirectCommand source,
|
||||
int baseInstance,
|
||||
int instanceCount)
|
||||
{
|
||||
int destination = _activeCommandCount++;
|
||||
_activeCommands[destination] = source with
|
||||
{
|
||||
BaseInstance = checked((uint)baseInstance),
|
||||
InstanceCount = checked((uint)instanceCount),
|
||||
};
|
||||
_activeBatches[destination] = _batches[sourceCommandIndex];
|
||||
}
|
||||
|
||||
private void BuildActiveRuns()
|
||||
{
|
||||
_activeRunCount = 0;
|
||||
ActiveOpaqueCommandCount = 0;
|
||||
while (ActiveOpaqueCommandCount < _activeCommandCount
|
||||
&& _activeBatches[ActiveOpaqueCommandCount].Material
|
||||
is DirectionalShadowCasterMaterial.Opaque)
|
||||
{
|
||||
ActiveOpaqueCommandCount++;
|
||||
}
|
||||
|
||||
int runStart = 0;
|
||||
while (runStart < _activeCommandCount)
|
||||
{
|
||||
DirectionalShadowPreparedBatch first = _activeBatches[runStart];
|
||||
int runEnd = runStart + 1;
|
||||
while (runEnd < _activeCommandCount
|
||||
&& _activeBatches[runEnd].CullMode == first.CullMode
|
||||
&& _activeBatches[runEnd].Material == first.Material)
|
||||
{
|
||||
runEnd++;
|
||||
}
|
||||
_activeRuns[_activeRunCount++] = new DirectionalShadowPreparedRun(
|
||||
runStart,
|
||||
runEnd - runStart,
|
||||
first.CullMode,
|
||||
first.Material);
|
||||
runStart = runEnd;
|
||||
}
|
||||
ActiveOpaqueRunCount = 0;
|
||||
while (ActiveOpaqueRunCount < _activeRunCount
|
||||
&& _activeRuns[ActiveOpaqueRunCount].Material
|
||||
is DirectionalShadowCasterMaterial.Opaque)
|
||||
{
|
||||
ActiveOpaqueRunCount++;
|
||||
}
|
||||
}
|
||||
|
||||
public void RefreshDynamicTransforms(
|
||||
|
|
@ -878,6 +1074,8 @@ internal sealed class DirectionalShadowPreparedDraws
|
|||
_sourceCount = 0;
|
||||
_commandCount = 0;
|
||||
_runCount = 0;
|
||||
_activeCommandCount = 0;
|
||||
_activeRunCount = 0;
|
||||
_dynamicTransformSlotCount = 0;
|
||||
_allDynamicTransformSlotCount = 0;
|
||||
if (_mappedCasterCount != 0)
|
||||
|
|
@ -890,11 +1088,14 @@ internal sealed class DirectionalShadowPreparedDraws
|
|||
_mappedCasterCount = 0;
|
||||
OpaqueCommandCount = 0;
|
||||
OpaqueRunCount = 0;
|
||||
ActiveOpaqueCommandCount = 0;
|
||||
ActiveOpaqueRunCount = 0;
|
||||
Stats = default;
|
||||
SourceGeneration = default;
|
||||
SourceCasterBuildSequence = 0;
|
||||
SourceRenderDataAvailabilityVersion = 0;
|
||||
SourceTranslucencyFadeRevision = 0;
|
||||
SourceCasterSelectionSequence = 0;
|
||||
LastDynamicTransformRefreshCount = 0;
|
||||
LastDynamicTransformRefreshWasDense = false;
|
||||
_retryClassificationNextFrame = false;
|
||||
|
|
@ -1034,6 +1235,7 @@ public sealed partial class WbDrawDispatcher
|
|||
translucencyFadeRevision))
|
||||
{
|
||||
_directionalShadowDraws.RefreshDynamicTransforms(casters);
|
||||
_directionalShadowDraws.ApplySelection(casters);
|
||||
return _directionalShadowDraws;
|
||||
}
|
||||
// #429 owner-approved pipelining: a deferred frame keeps the retained
|
||||
|
|
@ -1047,6 +1249,7 @@ public sealed partial class WbDrawDispatcher
|
|||
&& _directionalShadowDraws.SourceGeneration == casters.Generation)
|
||||
{
|
||||
_directionalShadowDraws.RefreshDynamicTransforms(casters);
|
||||
_directionalShadowDraws.ApplySelection(casters);
|
||||
return _directionalShadowDraws;
|
||||
}
|
||||
|
||||
|
|
@ -1064,6 +1267,7 @@ public sealed partial class WbDrawDispatcher
|
|||
renderDataAvailabilityVersion,
|
||||
translucencyFadeRevision))
|
||||
{
|
||||
_directionalShadowDraws.ApplySelection(casters);
|
||||
return _directionalShadowDraws;
|
||||
}
|
||||
|
||||
|
|
@ -1168,7 +1372,8 @@ public sealed partial class WbDrawDispatcher
|
|||
meshIndex,
|
||||
true,
|
||||
in partTransform)
|
||||
: default;
|
||||
: DirectionalShadowTransformSource.Static(
|
||||
casterIndex);
|
||||
AddDirectionalShadowBatches(
|
||||
partData,
|
||||
in candidate,
|
||||
|
|
@ -1203,7 +1408,8 @@ public sealed partial class WbDrawDispatcher
|
|||
meshIndex,
|
||||
false,
|
||||
in noSetupPart)
|
||||
: default;
|
||||
: DirectionalShadowTransformSource.Static(
|
||||
casterIndex);
|
||||
AddDirectionalShadowBatches(
|
||||
renderData,
|
||||
in candidate,
|
||||
|
|
@ -1236,6 +1442,7 @@ public sealed partial class WbDrawDispatcher
|
|||
in stats,
|
||||
renderDataAvailabilityVersion,
|
||||
translucencyFadeRevision);
|
||||
_directionalShadowDraws.ApplySelection(casters);
|
||||
return _directionalShadowDraws;
|
||||
}
|
||||
catch
|
||||
|
|
|
|||
|
|
@ -91,6 +91,50 @@ internal readonly record struct WorldRenderFrame(
|
|||
/// directional-shadow prepass.
|
||||
/// </summary>
|
||||
public AuthoredCelestialShadowSource CelestialShadowSource { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Pack-on-only borrow of the prior successfully completed retail
|
||||
/// landscape visibility transaction. It deliberately trails the camera
|
||||
/// resolved by this frame.
|
||||
/// </summary>
|
||||
public RetailLandscapeVisibilityFrame PriorLandscapeVisibility { get; init; }
|
||||
|
||||
/// <summary>Pack-on-only read seam over S2's exact retained CELLARRAY
|
||||
/// owner. The frame never copies or retains registry rows.</summary>
|
||||
public IDirectionalShadowCellMembership? DirectionalShadowCellMembership
|
||||
{ get; init; }
|
||||
}
|
||||
|
||||
internal interface IDirectionalShadowCellMembership
|
||||
{
|
||||
bool TryGetRetailCellArray(uint entityId, out IReadOnlyList<uint> cells);
|
||||
}
|
||||
|
||||
internal sealed class EmptyDirectionalShadowCellMembership
|
||||
: IDirectionalShadowCellMembership
|
||||
{
|
||||
internal static EmptyDirectionalShadowCellMembership Instance { get; } = new();
|
||||
|
||||
public bool TryGetRetailCellArray(
|
||||
uint entityId,
|
||||
out IReadOnlyList<uint> cells)
|
||||
{
|
||||
_ = entityId;
|
||||
cells = Array.Empty<uint>();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class RuntimeDirectionalShadowCellMembership(
|
||||
ShadowObjectRegistry source) : IDirectionalShadowCellMembership
|
||||
{
|
||||
private readonly ShadowObjectRegistry _source = source
|
||||
?? throw new ArgumentNullException(nameof(source));
|
||||
|
||||
public bool TryGetRetailCellArray(
|
||||
uint entityId,
|
||||
out IReadOnlyList<uint> cells) =>
|
||||
_source.TryGetRetailCellArray(entityId, out cells);
|
||||
}
|
||||
|
||||
internal interface IWorldRenderFrameBuilder
|
||||
|
|
@ -114,6 +158,8 @@ internal interface IWorldFrameRootSource
|
|||
|
||||
internal interface IWorldFrameVisibilityPreparation
|
||||
{
|
||||
RetailLandscapeVisibilityFrame CaptureCompletedLandscapeVisibility();
|
||||
|
||||
void Begin(in WorldCameraFrame camera, bool waitingForLogin);
|
||||
|
||||
void PublishViewProjection(in WorldCameraFrame camera);
|
||||
|
|
@ -160,6 +206,7 @@ internal sealed class WorldRenderFrameBuilder : IWorldRenderFrameBuilder
|
|||
private readonly IWorldFrameEnvironmentPreparation _environment;
|
||||
private readonly IWorldFrameAnimatedEntitySource _animated;
|
||||
private readonly IWorldFrameBuildingSource _buildings;
|
||||
private readonly IDirectionalShadowCellMembership _directionalShadowCells;
|
||||
|
||||
public WorldRenderFrameBuilder(
|
||||
IWorldFrameCameraSource camera,
|
||||
|
|
@ -168,7 +215,8 @@ internal sealed class WorldRenderFrameBuilder : IWorldRenderFrameBuilder
|
|||
IWorldFrameRootSource roots,
|
||||
IWorldFrameEnvironmentPreparation environment,
|
||||
IWorldFrameAnimatedEntitySource animated,
|
||||
IWorldFrameBuildingSource buildings)
|
||||
IWorldFrameBuildingSource buildings,
|
||||
IDirectionalShadowCellMembership directionalShadowCells)
|
||||
{
|
||||
_camera = camera ?? throw new ArgumentNullException(nameof(camera));
|
||||
_visibility = visibility ?? throw new ArgumentNullException(nameof(visibility));
|
||||
|
|
@ -177,6 +225,8 @@ internal sealed class WorldRenderFrameBuilder : IWorldRenderFrameBuilder
|
|||
_environment = environment ?? throw new ArgumentNullException(nameof(environment));
|
||||
_animated = animated ?? throw new ArgumentNullException(nameof(animated));
|
||||
_buildings = buildings ?? throw new ArgumentNullException(nameof(buildings));
|
||||
_directionalShadowCells = directionalShadowCells
|
||||
?? throw new ArgumentNullException(nameof(directionalShadowCells));
|
||||
}
|
||||
|
||||
public WorldRenderFrame Build(
|
||||
|
|
@ -184,6 +234,10 @@ internal sealed class WorldRenderFrameBuilder : IWorldRenderFrameBuilder
|
|||
bool waitingForLogin,
|
||||
DayGroupData? activeDayGroup)
|
||||
{
|
||||
RetailLandscapeVisibilityFrame priorLandscapeVisibility =
|
||||
_visibility.CaptureCompletedLandscapeVisibility();
|
||||
if (waitingForLogin)
|
||||
priorLandscapeVisibility = RetailLandscapeVisibilityFrame.None;
|
||||
WorldCameraFrame camera = _camera.Resolve();
|
||||
_visibility.Begin(in camera, waitingForLogin);
|
||||
_settings.Apply(in camera);
|
||||
|
|
@ -196,7 +250,11 @@ internal sealed class WorldRenderFrameBuilder : IWorldRenderFrameBuilder
|
|||
roots.ViewerRoot,
|
||||
roots.ViewerCellId,
|
||||
in frustum);
|
||||
return new WorldRenderFrame(camera, roots, buildings, animated);
|
||||
return new WorldRenderFrame(camera, roots, buildings, animated)
|
||||
{
|
||||
PriorLandscapeVisibility = priorLandscapeVisibility,
|
||||
DirectionalShadowCellMembership = _directionalShadowCells,
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -350,6 +408,9 @@ internal sealed class RuntimeWorldFrameVisibilityPreparation
|
|||
_environmentFrustum = environmentFrustum;
|
||||
}
|
||||
|
||||
public RetailLandscapeVisibilityFrame CaptureCompletedLandscapeVisibility() =>
|
||||
_particles.CaptureCompletedLandscapeVisibility();
|
||||
|
||||
public void Begin(in WorldCameraFrame camera, bool waitingForLogin)
|
||||
{
|
||||
_selection?.SetViewFrustum(camera.Frustum);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue