Optimise DecalOverlay (#25266)
ChunkSize is still 32 so doesn't cut down on heaps of decals atm though we avoid passing many decals to drawing with the coordinates bounds check now.
This commit is contained in:
@@ -50,16 +50,8 @@ namespace Content.Client.Decals
|
|||||||
protected override void OnDecalRemoved(EntityUid gridId, uint decalId, DecalGridComponent component, Vector2i indices, DecalChunk chunk)
|
protected override void OnDecalRemoved(EntityUid gridId, uint decalId, DecalGridComponent component, Vector2i indices, DecalChunk chunk)
|
||||||
{
|
{
|
||||||
base.OnDecalRemoved(gridId, decalId, component, indices, chunk);
|
base.OnDecalRemoved(gridId, decalId, component, indices, chunk);
|
||||||
|
DebugTools.Assert(chunk.Decals.ContainsKey(decalId));
|
||||||
if (!component.DecalZIndexIndex.Remove(decalId, out var zIndex))
|
chunk.Decals.Remove(decalId);
|
||||||
return;
|
|
||||||
|
|
||||||
if (!component.DecalRenderIndex.TryGetValue(zIndex, out var renderIndex))
|
|
||||||
return;
|
|
||||||
|
|
||||||
renderIndex.Remove(decalId);
|
|
||||||
if (renderIndex.Count == 0)
|
|
||||||
component.DecalRenderIndex.Remove(zIndex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnHandleState(EntityUid gridUid, DecalGridComponent gridComp, ref ComponentHandleState args)
|
private void OnHandleState(EntityUid gridUid, DecalGridComponent gridComp, ref ComponentHandleState args)
|
||||||
@@ -133,8 +125,6 @@ namespace Content.Client.Decals
|
|||||||
private void UpdateChunks(EntityUid gridId, DecalGridComponent gridComp, Dictionary<Vector2i, DecalChunk> updatedGridChunks)
|
private void UpdateChunks(EntityUid gridId, DecalGridComponent gridComp, Dictionary<Vector2i, DecalChunk> updatedGridChunks)
|
||||||
{
|
{
|
||||||
var chunkCollection = gridComp.ChunkCollection.ChunkCollection;
|
var chunkCollection = gridComp.ChunkCollection.ChunkCollection;
|
||||||
var renderIndex = gridComp.DecalRenderIndex;
|
|
||||||
var zIndexIndex = gridComp.DecalZIndexIndex;
|
|
||||||
|
|
||||||
// Update any existing data / remove decals we didn't receive data for.
|
// Update any existing data / remove decals we didn't receive data for.
|
||||||
foreach (var (indices, newChunkData) in updatedGridChunks)
|
foreach (var (indices, newChunkData) in updatedGridChunks)
|
||||||
@@ -155,11 +145,6 @@ namespace Content.Client.Decals
|
|||||||
|
|
||||||
foreach (var (uid, decal) in newChunkData.Decals)
|
foreach (var (uid, decal) in newChunkData.Decals)
|
||||||
{
|
{
|
||||||
if (zIndexIndex.TryGetValue(uid, out var zIndex))
|
|
||||||
renderIndex[zIndex].Remove(uid);
|
|
||||||
|
|
||||||
renderIndex.GetOrNew(decal.ZIndex)[uid] = decal;
|
|
||||||
zIndexIndex[uid] = decal.ZIndex;
|
|
||||||
gridComp.DecalIndex[uid] = indices;
|
gridComp.DecalIndex[uid] = indices;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using Robust.Client.GameObjects;
|
|||||||
using Robust.Client.Graphics;
|
using Robust.Client.Graphics;
|
||||||
using Robust.Shared.Enums;
|
using Robust.Shared.Enums;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Map.Enumerators;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
namespace Content.Client.Decals.Overlays
|
namespace Content.Client.Decals.Overlays
|
||||||
@@ -15,6 +16,8 @@ namespace Content.Client.Decals.Overlays
|
|||||||
|
|
||||||
private readonly Dictionary<string, (Texture Texture, bool SnapCardinals)> _cachedTextures = new(64);
|
private readonly Dictionary<string, (Texture Texture, bool SnapCardinals)> _cachedTextures = new(64);
|
||||||
|
|
||||||
|
private readonly List<(uint Id, Decal Decal)> _decals = new();
|
||||||
|
|
||||||
public DecalOverlay(
|
public DecalOverlay(
|
||||||
SpriteSystem sprites,
|
SpriteSystem sprites,
|
||||||
IEntityManager entManager,
|
IEntityManager entManager,
|
||||||
@@ -30,10 +33,10 @@ namespace Content.Client.Decals.Overlays
|
|||||||
if (args.MapId == MapId.Nullspace)
|
if (args.MapId == MapId.Nullspace)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var grid = Grid;
|
var owner = Grid.Owner;
|
||||||
|
|
||||||
if (!_entManager.TryGetComponent(grid, out DecalGridComponent? decalGrid) ||
|
if (!_entManager.TryGetComponent(owner, out DecalGridComponent? decalGrid) ||
|
||||||
!_entManager.TryGetComponent(grid, out TransformComponent? xform))
|
!_entManager.TryGetComponent(owner, out TransformComponent? xform))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -46,46 +49,68 @@ namespace Content.Client.Decals.Overlays
|
|||||||
var xformSystem = _entManager.System<TransformSystem>();
|
var xformSystem = _entManager.System<TransformSystem>();
|
||||||
var eyeAngle = args.Viewport.Eye?.Rotation ?? Angle.Zero;
|
var eyeAngle = args.Viewport.Eye?.Rotation ?? Angle.Zero;
|
||||||
|
|
||||||
var zIndexDictionary = decalGrid.DecalRenderIndex;
|
var gridAABB = xformSystem.GetInvWorldMatrix(xform).TransformBox(args.WorldBounds.Enlarged(1f));
|
||||||
|
var chunkEnumerator = new ChunkIndicesEnumerator(gridAABB, SharedDecalSystem.ChunkSize);
|
||||||
|
_decals.Clear();
|
||||||
|
|
||||||
if (zIndexDictionary.Count == 0)
|
while (chunkEnumerator.MoveNext(out var index))
|
||||||
|
{
|
||||||
|
if (!decalGrid.ChunkCollection.ChunkCollection.TryGetValue(index.Value, out var chunk))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
foreach (var (id, decal) in chunk.Decals)
|
||||||
|
{
|
||||||
|
if (!gridAABB.Contains(decal.Coordinates))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
_decals.Add((id, decal));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_decals.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var (_, worldRot, worldMatrix) = xformSystem.GetWorldPositionRotationMatrix(xform);
|
_decals.Sort((x, y) =>
|
||||||
|
{
|
||||||
|
var zComp = x.Decal.ZIndex.CompareTo(y.Decal.ZIndex);
|
||||||
|
|
||||||
|
if (zComp != 0)
|
||||||
|
return zComp;
|
||||||
|
|
||||||
|
return x.Id.CompareTo(y.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
var (_, worldRot, worldMatrix) = xformSystem.GetWorldPositionRotationMatrix(xform);
|
||||||
handle.SetTransform(worldMatrix);
|
handle.SetTransform(worldMatrix);
|
||||||
|
|
||||||
foreach (var decals in zIndexDictionary.Values)
|
foreach (var (_, decal) in _decals)
|
||||||
{
|
{
|
||||||
foreach (var decal in decals.Values)
|
if (!_cachedTextures.TryGetValue(decal.Id, out var cache))
|
||||||
{
|
{
|
||||||
if (!_cachedTextures.TryGetValue(decal.Id, out var cache))
|
// Nothing to cache someone messed up
|
||||||
|
if (!_prototypeManager.TryIndex<DecalPrototype>(decal.Id, out var decalProto))
|
||||||
{
|
{
|
||||||
// Nothing to cache someone messed up
|
continue;
|
||||||
if (!_prototypeManager.TryIndex<DecalPrototype>(decal.Id, out var decalProto))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
cache = (_sprites.Frame0(decalProto.Sprite), decalProto.SnapCardinals);
|
|
||||||
_cachedTextures[decal.Id] = cache;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var cardinal = Angle.Zero;
|
cache = (_sprites.Frame0(decalProto.Sprite), decalProto.SnapCardinals);
|
||||||
|
_cachedTextures[decal.Id] = cache;
|
||||||
if (cache.SnapCardinals)
|
|
||||||
{
|
|
||||||
var worldAngle = eyeAngle + worldRot;
|
|
||||||
cardinal = worldAngle.GetCardinalDir().ToAngle();
|
|
||||||
}
|
|
||||||
|
|
||||||
var angle = decal.Angle - cardinal;
|
|
||||||
|
|
||||||
if (angle.Equals(Angle.Zero))
|
|
||||||
handle.DrawTexture(cache.Texture, decal.Coordinates, decal.Color);
|
|
||||||
else
|
|
||||||
handle.DrawTexture(cache.Texture, decal.Coordinates, angle, decal.Color);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var cardinal = Angle.Zero;
|
||||||
|
|
||||||
|
if (cache.SnapCardinals)
|
||||||
|
{
|
||||||
|
var worldAngle = eyeAngle + worldRot;
|
||||||
|
cardinal = worldAngle.GetCardinalDir().ToAngle();
|
||||||
|
}
|
||||||
|
|
||||||
|
var angle = decal.Angle - cardinal;
|
||||||
|
|
||||||
|
if (angle.Equals(Angle.Zero))
|
||||||
|
handle.DrawTexture(cache.Texture, decal.Coordinates, decal.Color);
|
||||||
|
else
|
||||||
|
handle.DrawTexture(cache.Texture, decal.Coordinates, angle, decal.Color);
|
||||||
}
|
}
|
||||||
|
|
||||||
handle.SetTransform(Matrix3.Identity);
|
handle.SetTransform(Matrix3.Identity);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ using Robust.Shared.Map;
|
|||||||
using Robust.Shared.Map.Components;
|
using Robust.Shared.Map.Components;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
|
using ChunkIndicesEnumerator = Robust.Shared.Map.Enumerators.ChunkIndicesEnumerator;
|
||||||
|
|
||||||
namespace Content.Shared.Chunking;
|
namespace Content.Shared.Chunking;
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ using Robust.Shared.Threading;
|
|||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
using static Content.Shared.Decals.DecalGridComponent;
|
using static Content.Shared.Decals.DecalGridComponent;
|
||||||
|
using ChunkIndicesEnumerator = Robust.Shared.Map.Enumerators.ChunkIndicesEnumerator;
|
||||||
|
|
||||||
namespace Content.Server.Decals
|
namespace Content.Server.Decals
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ using Robust.Shared.Prototypes;
|
|||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
using Robust.Shared.Threading;
|
using Robust.Shared.Threading;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
|
using ChunkIndicesEnumerator = Robust.Shared.Map.Enumerators.ChunkIndicesEnumerator;
|
||||||
|
|
||||||
namespace Content.Server.Parallax;
|
namespace Content.Server.Parallax;
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ namespace Content.Shared.Decals
|
|||||||
public sealed partial class Decal
|
public sealed partial class Decal
|
||||||
{
|
{
|
||||||
// if these are made not-readonly, then decal grid state handling needs to be updated to clone decals.
|
// if these are made not-readonly, then decal grid state handling needs to be updated to clone decals.
|
||||||
[DataField("coordinates")] public Vector2 Coordinates = Vector2.Zero;
|
[DataField("coordinates")] public Vector2 Coordinates = Vector2.Zero;
|
||||||
[DataField("id")] public string Id = string.Empty;
|
[DataField("id")] public string Id = string.Empty;
|
||||||
[DataField("color")] public Color? Color;
|
[DataField("color")] public Color? Color;
|
||||||
[DataField("angle")] public Angle Angle = Angle.Zero;
|
[DataField("angle")] public Angle Angle = Angle.Zero;
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ namespace Content.Shared.Decals
|
|||||||
[NetworkedComponent]
|
[NetworkedComponent]
|
||||||
public sealed partial class DecalGridComponent : Component
|
public sealed partial class DecalGridComponent : Component
|
||||||
{
|
{
|
||||||
[DataField("chunkCollection", serverOnly: true)]
|
[Access(Other = AccessPermissions.ReadExecute)]
|
||||||
|
[DataField(serverOnly: true)]
|
||||||
public DecalGridChunkCollection ChunkCollection = new(new ());
|
public DecalGridChunkCollection ChunkCollection = new(new ());
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -25,10 +26,6 @@ namespace Content.Shared.Decals
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public GameTick ForceTick { get; set; }
|
public GameTick ForceTick { get; set; }
|
||||||
|
|
||||||
// client-side data. I CBF creating a separate client-side comp for this. The server can survive with some empty dictionaries.
|
|
||||||
public readonly Dictionary<uint, int> DecalZIndexIndex = new();
|
|
||||||
public readonly SortedDictionary<int, SortedDictionary<uint, Decal>> DecalRenderIndex = new();
|
|
||||||
|
|
||||||
[DataDefinition]
|
[DataDefinition]
|
||||||
[Serializable, NetSerializable]
|
[Serializable, NetSerializable]
|
||||||
public sealed partial class DecalChunk
|
public sealed partial class DecalChunk
|
||||||
|
|||||||
@@ -122,39 +122,6 @@ namespace Content.Shared.Decals
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Pretty sure paul was moving this somewhere but just so people know
|
|
||||||
public struct ChunkIndicesEnumerator
|
|
||||||
{
|
|
||||||
private Vector2i _chunkLB;
|
|
||||||
private Vector2i _chunkRT;
|
|
||||||
|
|
||||||
private int _xIndex;
|
|
||||||
private int _yIndex;
|
|
||||||
|
|
||||||
public ChunkIndicesEnumerator(Box2 localAABB, int chunkSize)
|
|
||||||
{
|
|
||||||
_chunkLB = new Vector2i((int)Math.Floor(localAABB.Left / chunkSize), (int)Math.Floor(localAABB.Bottom / chunkSize));
|
|
||||||
_chunkRT = new Vector2i((int)Math.Floor(localAABB.Right / chunkSize), (int)Math.Floor(localAABB.Top / chunkSize));
|
|
||||||
|
|
||||||
_xIndex = _chunkLB.X;
|
|
||||||
_yIndex = _chunkLB.Y;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool MoveNext([NotNullWhen(true)] out Vector2i? indices)
|
|
||||||
{
|
|
||||||
if (_yIndex > _chunkRT.Y)
|
|
||||||
{
|
|
||||||
_yIndex = _chunkLB.Y;
|
|
||||||
_xIndex += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
indices = new Vector2i(_xIndex, _yIndex);
|
|
||||||
_yIndex += 1;
|
|
||||||
|
|
||||||
return _xIndex <= _chunkRT.X;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sent by clients to request that a decal is placed on the server.
|
/// Sent by clients to request that a decal is placed on the server.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
Reference in New Issue
Block a user