perf(rendering): retain synchronous frame scratch
Reuse the PView frame input, publish mutation-invalidated landblock views, and avoid constructing optional shadow iterators while preserving title and lifecycle visibility facts.
This commit is contained in:
parent
b9cbf5e040
commit
b3427554c3
10 changed files with 397 additions and 129 deletions
|
|
@ -1321,41 +1321,100 @@ internal sealed class BuildingGroupScratch
|
|||
|
||||
public sealed class RetailPViewFrameInput
|
||||
{
|
||||
public required LoadedCell RootCell { get; init; }
|
||||
public LoadedCell RootCell { get; private set; } = null!;
|
||||
|
||||
/// <summary>R-A2: nearby building cells (BuildingId-tagged) flooded per-building when the root is the
|
||||
/// outdoor node. Null for interior roots. Grouped by BuildingId inside <see cref="DrawInside"/>.</summary>
|
||||
public IReadOnlyList<LoadedCell>? NearbyBuildingCells { get; init; }
|
||||
public IReadOnlyList<LoadedCell>? NearbyBuildingCells { get; private set; }
|
||||
|
||||
public required Vector3 ViewerEyePos { get; init; }
|
||||
public required Matrix4x4 ViewProjection { get; init; }
|
||||
public required IRetailPViewCellSource Cells { get; init; }
|
||||
public required ICamera Camera { get; init; }
|
||||
public required Vector3 CameraWorldPosition { get; init; }
|
||||
public required FrustumPlanes? Frustum { get; init; }
|
||||
public required uint? PlayerLandblockId { get; init; }
|
||||
public required HashSet<uint>? AnimatedEntityIds { get; init; }
|
||||
public required int RenderCenterLbX { get; init; }
|
||||
public required int RenderCenterLbY { get; init; }
|
||||
public required int RenderRadius { get; init; }
|
||||
public required IEnumerable<(uint LandblockId, Vector3 AabbMin, Vector3 AabbMax,
|
||||
public Vector3 ViewerEyePos { get; private set; }
|
||||
public Matrix4x4 ViewProjection { get; private set; }
|
||||
public IRetailPViewCellSource Cells { get; private set; } = null!;
|
||||
public ICamera Camera { get; private set; } = null!;
|
||||
public Vector3 CameraWorldPosition { get; private set; }
|
||||
public FrustumPlanes? Frustum { get; private set; }
|
||||
public uint? PlayerLandblockId { get; private set; }
|
||||
public HashSet<uint>? AnimatedEntityIds { get; private set; }
|
||||
public int RenderCenterLbX { get; private set; }
|
||||
public int RenderCenterLbY { get; private set; }
|
||||
public int RenderRadius { get; private set; }
|
||||
public IReadOnlyList<(uint LandblockId, Vector3 AabbMin, Vector3 AabbMax,
|
||||
IReadOnlyList<WorldEntity> Entities,
|
||||
IReadOnlyDictionary<uint, WorldEntity>? AnimatedById)> LandblockEntries
|
||||
{ get; init; }
|
||||
{ get; private set; } = Array.Empty<(uint, Vector3, Vector3,
|
||||
IReadOnlyList<WorldEntity>,
|
||||
IReadOnlyDictionary<uint, WorldEntity>?)>();
|
||||
|
||||
// Pass-presentation and diagnostic values consumed synchronously by the
|
||||
// typed executor. This input is data-only and is never retained.
|
||||
public required bool RenderSky { get; init; }
|
||||
public required bool RenderWeather { get; init; }
|
||||
public required float DayFraction { get; init; }
|
||||
public required DayGroupData? ActiveDayGroup { get; init; }
|
||||
public required SkyKeyframe SkyKeyframe { get; init; }
|
||||
public required bool EnvironOverrideActive { get; init; }
|
||||
public required uint ViewerCellId { get; init; }
|
||||
public required uint PlayerCellId { get; init; }
|
||||
public required Vector3 PlayerViewPosition { get; init; }
|
||||
public required Matrix4x4 CameraView { get; init; }
|
||||
public required CameraCellResolution CameraCellResolution { get; init; }
|
||||
public bool RenderSky { get; private set; }
|
||||
public bool RenderWeather { get; private set; }
|
||||
public float DayFraction { get; private set; }
|
||||
public DayGroupData? ActiveDayGroup { get; private set; }
|
||||
public SkyKeyframe SkyKeyframe { get; private set; }
|
||||
public bool EnvironOverrideActive { get; private set; }
|
||||
public uint ViewerCellId { get; private set; }
|
||||
public uint PlayerCellId { get; private set; }
|
||||
public Vector3 PlayerViewPosition { get; private set; }
|
||||
public Matrix4x4 CameraView { get; private set; }
|
||||
public CameraCellResolution CameraCellResolution { get; private set; }
|
||||
|
||||
internal RetailPViewFrameInput Reset(
|
||||
LoadedCell rootCell,
|
||||
IReadOnlyList<LoadedCell>? nearbyBuildingCells,
|
||||
Vector3 viewerEyePos,
|
||||
Matrix4x4 viewProjection,
|
||||
IRetailPViewCellSource cells,
|
||||
ICamera camera,
|
||||
Vector3 cameraWorldPosition,
|
||||
FrustumPlanes? frustum,
|
||||
uint? playerLandblockId,
|
||||
HashSet<uint>? animatedEntityIds,
|
||||
int renderCenterLbX,
|
||||
int renderCenterLbY,
|
||||
int renderRadius,
|
||||
IReadOnlyList<(uint LandblockId, Vector3 AabbMin, Vector3 AabbMax,
|
||||
IReadOnlyList<WorldEntity> Entities,
|
||||
IReadOnlyDictionary<uint, WorldEntity>? AnimatedById)> landblockEntries,
|
||||
bool renderSky,
|
||||
bool renderWeather,
|
||||
float dayFraction,
|
||||
DayGroupData? activeDayGroup,
|
||||
SkyKeyframe skyKeyframe,
|
||||
bool environOverrideActive,
|
||||
uint viewerCellId,
|
||||
uint playerCellId,
|
||||
Vector3 playerViewPosition,
|
||||
Matrix4x4 cameraView,
|
||||
CameraCellResolution cameraCellResolution)
|
||||
{
|
||||
RootCell = rootCell;
|
||||
NearbyBuildingCells = nearbyBuildingCells;
|
||||
ViewerEyePos = viewerEyePos;
|
||||
ViewProjection = viewProjection;
|
||||
Cells = cells;
|
||||
Camera = camera;
|
||||
CameraWorldPosition = cameraWorldPosition;
|
||||
Frustum = frustum;
|
||||
PlayerLandblockId = playerLandblockId;
|
||||
AnimatedEntityIds = animatedEntityIds;
|
||||
RenderCenterLbX = renderCenterLbX;
|
||||
RenderCenterLbY = renderCenterLbY;
|
||||
RenderRadius = renderRadius;
|
||||
LandblockEntries = landblockEntries;
|
||||
RenderSky = renderSky;
|
||||
RenderWeather = renderWeather;
|
||||
DayFraction = dayFraction;
|
||||
ActiveDayGroup = activeDayGroup;
|
||||
SkyKeyframe = skyKeyframe;
|
||||
EnvironOverrideActive = environOverrideActive;
|
||||
ViewerCellId = viewerCellId;
|
||||
PlayerCellId = playerCellId;
|
||||
PlayerViewPosition = playerViewPosition;
|
||||
CameraView = cameraView;
|
||||
CameraCellResolution = cameraCellResolution;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ internal interface IWorldSceneDiagnostics
|
|||
|
||||
WorldSceneDiagnosticOutcome DrawAndPublish(
|
||||
in WorldCameraFrame camera,
|
||||
IEnumerable<(uint LandblockId, Vector3 AabbMin, Vector3 AabbMax)> bounds);
|
||||
IReadOnlyList<(uint LandblockId, Vector3 AabbMin, Vector3 AabbMax)> bounds);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -168,16 +168,16 @@ internal sealed class WorldSceneDiagnosticsController : IWorldSceneDiagnostics
|
|||
|
||||
public WorldSceneDiagnosticOutcome DrawAndPublish(
|
||||
in WorldCameraFrame camera,
|
||||
IEnumerable<(uint LandblockId, Vector3 AabbMin, Vector3 AabbMax)> bounds)
|
||||
IReadOnlyList<(uint LandblockId, Vector3 AabbMin, Vector3 AabbMax)> bounds)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(bounds);
|
||||
DrawCollisionWireframes(in camera);
|
||||
|
||||
int visible = 0;
|
||||
int total = 0;
|
||||
foreach (var entry in bounds)
|
||||
int total = bounds.Count;
|
||||
for (int index = 0; index < bounds.Count; index++)
|
||||
{
|
||||
total++;
|
||||
var entry = bounds[index];
|
||||
if (FrustumCuller.IsAabbVisible(
|
||||
camera.Frustum,
|
||||
entry.AabbMin,
|
||||
|
|
@ -187,16 +187,21 @@ internal sealed class WorldSceneDiagnosticsController : IWorldSceneDiagnostics
|
|||
}
|
||||
}
|
||||
|
||||
Vector3 nearestOrigin =
|
||||
_mode.IsPlayerMode && _player.Controller is { } controller
|
||||
? controller.Position
|
||||
: camera.Position;
|
||||
_debugVm.PublishDebugVmFacts(
|
||||
_debugVmConsumerActive,
|
||||
visible,
|
||||
total,
|
||||
nearestOrigin,
|
||||
_physics.ShadowObjects.AllEntriesForDebug());
|
||||
// AllEntriesForDebug is a yield sequence. Do not even construct its
|
||||
// iterator unless the developer UI is mounted and consumes the facts.
|
||||
if (_debugVmConsumerActive)
|
||||
{
|
||||
Vector3 nearestOrigin =
|
||||
_mode.IsPlayerMode && _player.Controller is { } controller
|
||||
? controller.Position
|
||||
: camera.Position;
|
||||
_debugVm.PublishDebugVmFacts(
|
||||
consumerActive: true,
|
||||
visible,
|
||||
total,
|
||||
nearestOrigin,
|
||||
_physics.ShadowObjects.AllEntriesForDebug());
|
||||
}
|
||||
return new WorldSceneDiagnosticOutcome(visible, total);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ internal sealed class WorldSceneRenderer : IWorldSceneFramePhase
|
|||
private readonly IWorldRenderRangeSource _renderRange;
|
||||
private readonly IWorldSceneDiagnostics _diagnostics;
|
||||
private readonly IWorldGenerationAvailability _availability;
|
||||
private readonly RetailPViewFrameInput _pviewFrameInput = new();
|
||||
|
||||
public WorldSceneRenderer(
|
||||
IRenderFrameFoundationSource foundation,
|
||||
|
|
@ -174,34 +175,32 @@ internal sealed class WorldSceneRenderer : IWorldSceneFramePhase
|
|||
{
|
||||
pviewFrameStarted = true;
|
||||
pviewResult = _pview.DrawInside(
|
||||
new RetailPViewFrameInput
|
||||
{
|
||||
RootCell = clipRoot,
|
||||
NearbyBuildingCells = world.Buildings.NearbyBuildingCells,
|
||||
ViewerEyePos = roots.ViewerEyePosition,
|
||||
ViewProjection = camera.ViewProjection,
|
||||
Cells = _pviewCells,
|
||||
Camera = camera.Camera,
|
||||
CameraWorldPosition = camera.Position,
|
||||
Frustum = camera.Frustum,
|
||||
PlayerLandblockId = roots.PlayerLandblockId,
|
||||
AnimatedEntityIds = world.AnimatedEntityIds,
|
||||
RenderCenterLbX = roots.RenderCenterLandblockX,
|
||||
RenderCenterLbY = roots.RenderCenterLandblockY,
|
||||
RenderRadius = _renderRange.NearRadius,
|
||||
LandblockEntries = _entities.LandblockEntries,
|
||||
RenderSky = renderSky,
|
||||
RenderWeather = roots.PlayerSeenOutside,
|
||||
DayFraction = _sky.DayFraction,
|
||||
ActiveDayGroup = _sky.ActiveDayGroup,
|
||||
SkyKeyframe = foundation.Sky,
|
||||
EnvironOverrideActive = foundation.EnvironOverrideActive,
|
||||
ViewerCellId = roots.ViewerCellId,
|
||||
PlayerCellId = roots.PlayerCellId,
|
||||
PlayerViewPosition = roots.PlayerViewPosition,
|
||||
CameraView = camera.Camera.View,
|
||||
CameraCellResolution = _diagnostics.CameraCellResolution,
|
||||
});
|
||||
_pviewFrameInput.Reset(
|
||||
clipRoot,
|
||||
world.Buildings.NearbyBuildingCells,
|
||||
roots.ViewerEyePosition,
|
||||
camera.ViewProjection,
|
||||
_pviewCells,
|
||||
camera.Camera,
|
||||
camera.Position,
|
||||
camera.Frustum,
|
||||
roots.PlayerLandblockId,
|
||||
world.AnimatedEntityIds,
|
||||
roots.RenderCenterLandblockX,
|
||||
roots.RenderCenterLandblockY,
|
||||
_renderRange.NearRadius,
|
||||
_entities.LandblockEntries,
|
||||
renderSky,
|
||||
roots.PlayerSeenOutside,
|
||||
_sky.DayFraction,
|
||||
_sky.ActiveDayGroup,
|
||||
foundation.Sky,
|
||||
foundation.EnvironOverrideActive,
|
||||
roots.ViewerCellId,
|
||||
roots.PlayerCellId,
|
||||
roots.PlayerViewPosition,
|
||||
camera.Camera.View,
|
||||
_diagnostics.CameraCellResolution));
|
||||
|
||||
_particleVisibility.MarkVisibleCells(pviewResult.DrawableCells);
|
||||
_frames.ObserveDrawableCells(pviewResult.DrawableCells);
|
||||
|
|
|
|||
|
|
@ -15,12 +15,12 @@ internal interface IWorldSceneSkyStateSource
|
|||
|
||||
internal interface IWorldSceneEntitySource
|
||||
{
|
||||
IEnumerable<(uint LandblockId, Vector3 AabbMin, Vector3 AabbMax,
|
||||
IReadOnlyList<(uint LandblockId, Vector3 AabbMin, Vector3 AabbMax,
|
||||
IReadOnlyList<WorldEntity> Entities,
|
||||
IReadOnlyDictionary<uint, WorldEntity>? AnimatedById)> LandblockEntries
|
||||
{ get; }
|
||||
|
||||
IEnumerable<(uint LandblockId, Vector3 AabbMin, Vector3 AabbMax)> LandblockBounds { get; }
|
||||
IReadOnlyList<(uint LandblockId, Vector3 AabbMin, Vector3 AabbMax)> LandblockBounds { get; }
|
||||
}
|
||||
|
||||
internal sealed class RuntimeWorldSceneEntitySource : IWorldSceneEntitySource
|
||||
|
|
@ -32,12 +32,12 @@ internal sealed class RuntimeWorldSceneEntitySource : IWorldSceneEntitySource
|
|||
_world = world ?? throw new ArgumentNullException(nameof(world));
|
||||
}
|
||||
|
||||
public IEnumerable<(uint LandblockId, Vector3 AabbMin, Vector3 AabbMax,
|
||||
public IReadOnlyList<(uint LandblockId, Vector3 AabbMin, Vector3 AabbMax,
|
||||
IReadOnlyList<WorldEntity> Entities,
|
||||
IReadOnlyDictionary<uint, WorldEntity>? AnimatedById)> LandblockEntries =>
|
||||
_world.LandblockEntries;
|
||||
|
||||
public IEnumerable<(uint LandblockId, Vector3 AabbMin, Vector3 AabbMax)> LandblockBounds =>
|
||||
public IReadOnlyList<(uint LandblockId, Vector3 AabbMin, Vector3 AabbMax)> LandblockBounds =>
|
||||
_world.LandblockBounds;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -97,6 +97,21 @@ public sealed class GpuWorldState : ILiveEntitySpatialQuery
|
|||
private readonly Dictionary<uint, LandblockStreamTier> _tierByLandblock = new();
|
||||
private readonly Dictionary<uint, (Vector3 Min, Vector3 Max)> _aabbs = new();
|
||||
private readonly Dictionary<uint, AnimatedEntityIndex> _animatedIndexByLandblock = new();
|
||||
// H-a4: synchronous render borrows. The former yield properties created
|
||||
// fresh iterator state at every per-frame call site.
|
||||
private readonly List<(uint LandblockId, Vector3 AabbMin, Vector3 AabbMax,
|
||||
IReadOnlyList<WorldEntity> Entities,
|
||||
IReadOnlyDictionary<uint, WorldEntity>? AnimatedById)>
|
||||
_landblockEntriesView = [];
|
||||
private readonly List<(uint LandblockId, Vector3 AabbMin, Vector3 AabbMax,
|
||||
IReadOnlyList<WorldEntity> Entities,
|
||||
IReadOnlyDictionary<uint, WorldEntity>? AnimatedById)>
|
||||
_landblockEntriesWithoutAnimatedIndexView = [];
|
||||
private readonly List<(uint LandblockId, Vector3 AabbMin, Vector3 AabbMax)>
|
||||
_landblockBoundsView = [];
|
||||
private bool _landblockEntriesViewDirty = true;
|
||||
private bool _landblockEntriesWithoutAnimatedIndexViewDirty = true;
|
||||
private bool _landblockBoundsViewDirty = true;
|
||||
|
||||
private sealed record AnimatedEntityIndex(
|
||||
LoadedLandblock Source,
|
||||
|
|
@ -201,6 +216,7 @@ public sealed class GpuWorldState : ILiveEntitySpatialQuery
|
|||
public void SetLandblockAabb(uint landblockId, Vector3 min, Vector3 max)
|
||||
{
|
||||
_aabbs[landblockId] = (min, max);
|
||||
InvalidateLandblockRenderViews(entries: true, bounds: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -219,49 +235,77 @@ public sealed class GpuWorldState : ILiveEntitySpatialQuery
|
|||
/// explicitly invalidate exactly the affected index generation.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public IEnumerable<(uint LandblockId, Vector3 AabbMin, Vector3 AabbMax,
|
||||
IReadOnlyList<WorldEntity> Entities,
|
||||
IReadOnlyDictionary<uint, WorldEntity>? AnimatedById)> LandblockEntries
|
||||
=> EnumerateLandblockEntries(includeAnimatedIndex: true);
|
||||
public IReadOnlyList<(uint LandblockId, Vector3 AabbMin, Vector3 AabbMax,
|
||||
IReadOnlyList<WorldEntity> Entities,
|
||||
IReadOnlyDictionary<uint, WorldEntity>? AnimatedById)> LandblockEntries
|
||||
=> GetLandblockEntriesView(includeAnimatedIndex: true);
|
||||
|
||||
/// <summary>
|
||||
/// Per-landblock render entries without the animated lookup dictionary.
|
||||
/// Static render passes use this to avoid rebuilding an index they cannot
|
||||
/// consume.
|
||||
/// </summary>
|
||||
public IEnumerable<(uint LandblockId, Vector3 AabbMin, Vector3 AabbMax,
|
||||
IReadOnlyList<WorldEntity> Entities,
|
||||
IReadOnlyDictionary<uint, WorldEntity>? AnimatedById)> LandblockEntriesWithoutAnimatedIndex
|
||||
=> EnumerateLandblockEntries(includeAnimatedIndex: false);
|
||||
public IReadOnlyList<(uint LandblockId, Vector3 AabbMin, Vector3 AabbMax,
|
||||
IReadOnlyList<WorldEntity> Entities,
|
||||
IReadOnlyDictionary<uint, WorldEntity>? AnimatedById)> LandblockEntriesWithoutAnimatedIndex
|
||||
=> GetLandblockEntriesView(includeAnimatedIndex: false);
|
||||
|
||||
/// <summary>
|
||||
/// Lightweight bounds-only enumeration for overlays and diagnostics.
|
||||
/// Does not walk entity lists.
|
||||
/// </summary>
|
||||
public IEnumerable<(uint LandblockId, Vector3 AabbMin, Vector3 AabbMax)> LandblockBounds
|
||||
public IReadOnlyList<(uint LandblockId, Vector3 AabbMin, Vector3 AabbMax)> LandblockBounds
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!_availability.IsWorldAvailable)
|
||||
yield break;
|
||||
return Array.Empty<(uint, Vector3, Vector3)>();
|
||||
if (!_landblockBoundsViewDirty)
|
||||
return _landblockBoundsView;
|
||||
|
||||
foreach (var kvp in _loaded)
|
||||
_landblockBoundsView.Clear();
|
||||
for (int slot = 0; slot < _renderTraversalLandblockSlots.Count; slot++)
|
||||
{
|
||||
if (_aabbs.TryGetValue(kvp.Key, out var aabb))
|
||||
yield return (kvp.Key, aabb.Min, aabb.Max);
|
||||
uint landblockId = _renderTraversalLandblockSlots[slot];
|
||||
if (landblockId == 0)
|
||||
continue;
|
||||
if (_aabbs.TryGetValue(landblockId, out var aabb))
|
||||
_landblockBoundsView.Add((landblockId, aabb.Min, aabb.Max));
|
||||
else
|
||||
yield return (kvp.Key, Vector3.Zero, Vector3.Zero);
|
||||
_landblockBoundsView.Add(
|
||||
(landblockId, Vector3.Zero, Vector3.Zero));
|
||||
}
|
||||
|
||||
_landblockBoundsViewDirty = false;
|
||||
return _landblockBoundsView;
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<(uint LandblockId, Vector3 AabbMin, Vector3 AabbMax,
|
||||
IReadOnlyList<WorldEntity> Entities,
|
||||
IReadOnlyDictionary<uint, WorldEntity>? AnimatedById)> EnumerateLandblockEntries(
|
||||
private IReadOnlyList<(uint LandblockId, Vector3 AabbMin, Vector3 AabbMax,
|
||||
IReadOnlyList<WorldEntity> Entities,
|
||||
IReadOnlyDictionary<uint, WorldEntity>? AnimatedById)> GetLandblockEntriesView(
|
||||
bool includeAnimatedIndex)
|
||||
{
|
||||
if (!_availability.IsWorldAvailable)
|
||||
yield break;
|
||||
{
|
||||
return Array.Empty<(uint, Vector3, Vector3,
|
||||
IReadOnlyList<WorldEntity>,
|
||||
IReadOnlyDictionary<uint, WorldEntity>?)>();
|
||||
}
|
||||
|
||||
List<(uint LandblockId, Vector3 AabbMin, Vector3 AabbMax,
|
||||
IReadOnlyList<WorldEntity> Entities,
|
||||
IReadOnlyDictionary<uint, WorldEntity>? AnimatedById)> view =
|
||||
includeAnimatedIndex
|
||||
? _landblockEntriesView
|
||||
: _landblockEntriesWithoutAnimatedIndexView;
|
||||
bool dirty = includeAnimatedIndex
|
||||
? _landblockEntriesViewDirty
|
||||
: _landblockEntriesWithoutAnimatedIndexViewDirty;
|
||||
if (!dirty)
|
||||
return view;
|
||||
|
||||
view.Clear();
|
||||
|
||||
for (int slot = 0; slot < _renderTraversalLandblockSlots.Count; slot++)
|
||||
{
|
||||
|
|
@ -287,10 +331,29 @@ public sealed class GpuWorldState : ILiveEntitySpatialQuery
|
|||
}
|
||||
|
||||
if (_aabbs.TryGetValue(landblockId, out var aabb))
|
||||
yield return (landblockId, aabb.Min, aabb.Max, landblock.Entities, byId);
|
||||
view.Add(
|
||||
(landblockId, aabb.Min, aabb.Max, landblock.Entities, byId));
|
||||
else
|
||||
yield return (landblockId, Vector3.Zero, Vector3.Zero, landblock.Entities, byId);
|
||||
view.Add(
|
||||
(landblockId, Vector3.Zero, Vector3.Zero, landblock.Entities, byId));
|
||||
}
|
||||
|
||||
if (includeAnimatedIndex)
|
||||
_landblockEntriesViewDirty = false;
|
||||
else
|
||||
_landblockEntriesWithoutAnimatedIndexViewDirty = false;
|
||||
return view;
|
||||
}
|
||||
|
||||
private void InvalidateLandblockRenderViews(bool entries, bool bounds)
|
||||
{
|
||||
if (entries)
|
||||
{
|
||||
_landblockEntriesViewDirty = true;
|
||||
_landblockEntriesWithoutAnimatedIndexViewDirty = true;
|
||||
}
|
||||
if (bounds)
|
||||
_landblockBoundsViewDirty = true;
|
||||
}
|
||||
|
||||
internal bool TryGetRenderTraversalSortKey(
|
||||
|
|
@ -361,6 +424,7 @@ public sealed class GpuWorldState : ILiveEntitySpatialQuery
|
|||
throw new InvalidOperationException(
|
||||
$"Landblock 0x{landblockId:X8} already owns a render traversal slot.");
|
||||
}
|
||||
InvalidateLandblockRenderViews(entries: true, bounds: true);
|
||||
}
|
||||
|
||||
private bool RemoveLoadedLandblock(
|
||||
|
|
@ -381,6 +445,7 @@ public sealed class GpuWorldState : ILiveEntitySpatialQuery
|
|||
|
||||
_renderTraversalLandblockSlots[slot] = 0;
|
||||
_freeRenderTraversalLandblockSlots.Push(slot);
|
||||
InvalidateLandblockRenderViews(entries: true, bounds: true);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -726,6 +791,7 @@ public sealed class GpuWorldState : ILiveEntitySpatialQuery
|
|||
int bucketIndex = entities.Count;
|
||||
entities.Add(entity);
|
||||
_animatedIndexByLandblock.Remove(landblockId);
|
||||
InvalidateLandblockRenderViews(entries: true, bounds: false);
|
||||
AddFlatEntity(entity);
|
||||
SetProjectionLocation(entity, landblockId, isLoaded: true, bucketIndex);
|
||||
AdjustVisibleLiveProjection(entity, +1);
|
||||
|
|
@ -765,6 +831,7 @@ public sealed class GpuWorldState : ILiveEntitySpatialQuery
|
|||
entities.RemoveAt(lastIndex);
|
||||
|
||||
_animatedIndexByLandblock.Remove(location.LandblockId);
|
||||
InvalidateLandblockRenderViews(entries: true, bounds: false);
|
||||
RemoveFlatEntity(entity);
|
||||
RemoveProjectionLocation(entity);
|
||||
AdjustVisibleLiveProjection(entity, -1);
|
||||
|
|
@ -1463,6 +1530,7 @@ public sealed class GpuWorldState : ILiveEntitySpatialQuery
|
|||
}
|
||||
_loaded[canonical] = NormalizeLoadedLandblock(lb, retainedLive);
|
||||
_animatedIndexByLandblock.Remove(canonical);
|
||||
InvalidateLandblockRenderViews(entries: true, bounds: false);
|
||||
_tierByLandblock[canonical] = LandblockStreamTier.Far;
|
||||
}
|
||||
|
||||
|
|
@ -1595,6 +1663,7 @@ public sealed class GpuWorldState : ILiveEntitySpatialQuery
|
|||
AdjustVisibleLiveProjection(entity, +1);
|
||||
}
|
||||
_animatedIndexByLandblock.Remove(canonical);
|
||||
InvalidateLandblockRenderViews(entries: true, bounds: false);
|
||||
_tierByLandblock[canonical] = LandblockStreamTier.Near;
|
||||
ulong[] effectiveRenderIds = additionalRenderIds?.ToArray() ?? Array.Empty<ulong>();
|
||||
WorldEntity[] staticEntities = entities
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue