Skip to content

Commit

Permalink
Avoid recalculating tile visibility when nothing is changing (#2191)
Browse files Browse the repository at this point in the history
  • Loading branch information
wixoaGit authored Jan 28, 2025
1 parent fdc07d9 commit 02ee3e1
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 68 deletions.
23 changes: 21 additions & 2 deletions OpenDreamClient/Rendering/DMISpriteSystem.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using OpenDreamShared.Rendering;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.GameStates;
using Robust.Shared.Timing;

Expand All @@ -9,25 +10,30 @@ namespace OpenDreamClient.Rendering;
public sealed class DMISpriteSystem : EntitySystem {
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly EntityLookupSystem _lookupSystem = default!;
[Dependency] private readonly ClientAppearanceSystem _appearanceSystem = default!;
[Dependency] private readonly IOverlayManager _overlayManager = default!;
[Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly EntityLookupSystem _lookupSystem = default!;
[Dependency] private readonly TransformSystem _transformSystem = default!;
[Dependency] private readonly MapSystem _mapSystem = default!;
[Dependency] private readonly ClientScreenOverlaySystem _screenOverlaySystem = default!;
[Dependency] private readonly ClientImagesSystem _clientImagesSystem = default!;

public RenderTargetPool RenderTargetPool = default!;

private EntityQuery<DMISpriteComponent> _spriteQuery;
private DreamViewOverlay _mapOverlay = default!;

public override void Initialize() {
SubscribeLocalEvent<DMISpriteComponent, ComponentAdd>(HandleComponentAdd);
SubscribeLocalEvent<DMISpriteComponent, ComponentHandleState>(HandleComponentState);
SubscribeLocalEvent<DMISpriteComponent, ComponentRemove>(HandleComponentRemove);
SubscribeLocalEvent<TransformComponent, MoveEvent>(HandleTransformMove);
SubscribeLocalEvent<TileChangedEvent>(HandleTileChanged);

RenderTargetPool = new(_clyde);
_spriteQuery = _entityManager.GetEntityQuery<DMISpriteComponent>();
_mapOverlay = new DreamViewOverlay(RenderTargetPool, _transformSystem, _mapSystem, _lookupSystem, _appearanceSystem, _screenOverlaySystem, _clientImagesSystem);
_overlayManager.AddOverlay(_mapOverlay);
}
Expand All @@ -50,15 +56,28 @@ private void HandleComponentAdd(EntityUid uid, DMISpriteComponent component, ref
component.Icon.SizeChanged += () => OnIconSizeChanged(uid);
}

private static void HandleComponentState(EntityUid uid, DMISpriteComponent component, ref ComponentHandleState args) {
private void HandleComponentState(EntityUid uid, DMISpriteComponent component, ref ComponentHandleState args) {
SharedDMISpriteComponent.DMISpriteComponentState? state = (SharedDMISpriteComponent.DMISpriteComponentState?)args.Current;
if (state == null)
return;

_mapOverlay.DirtyTileVisibility(); // Our icon's opacity may have changed
component.ScreenLocation = state.ScreenLocation;
component.Icon.SetAppearance(state.AppearanceId);
}

private void HandleTransformMove(EntityUid uid, TransformComponent component, ref MoveEvent args) {
if (!_spriteQuery.TryGetComponent(uid, out var sprite))
return;

if (sprite.Icon.Appearance?.Opacity is true || uid == _playerManager.LocalSession?.AttachedEntity)
_mapOverlay.DirtyTileVisibility(); // A movable with opacity=TRUE, or our eye, has moved
}

private void HandleTileChanged(ref TileChangedEvent ev) {
_mapOverlay.DirtyTileVisibility();
}

private static void HandleComponentRemove(EntityUid uid, DMISpriteComponent component, ref ComponentRemove args) {
component.Icon.Dispose();
}
Expand Down
83 changes: 83 additions & 0 deletions OpenDreamClient/Rendering/DreamViewOverlay.TileVisibility.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using OpenDreamShared.Dream;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;

namespace OpenDreamClient.Rendering;

internal partial class DreamViewOverlay {
private ViewAlgorithm.Tile?[,]? _tileInfo;
private bool _tileInfoDirty;

public void DirtyTileVisibility() {
_tileInfoDirty = true;
}

private ViewAlgorithm.Tile?[,] CalculateTileVisibility(EntityUid gridUid, MapGridComponent grid, TileRef eyeTile, int seeVis) {
using var _ = _prof.Group("visible turfs");

var viewRange = _interfaceManager.View;
if (_tileInfo == null || _tileInfo.GetLength(0) != viewRange.Width + 2 ||
_tileInfo.GetLength(1) != viewRange.Height + 2) {
// _tileInfo hasn't been created yet or view range has changed, so create a new array.
// Leave a 1 tile buffer on each side
_tileInfo = new ViewAlgorithm.Tile[viewRange.Width + 2, viewRange.Height + 2];
_tileInfoDirty = true;
}

if (!_tileInfoDirty)
return _tileInfo;

var eyeWorldPos = _mapSystem.GridTileToWorld(gridUid, grid, eyeTile.GridIndices);
var tileRefs = _mapSystem.GetTilesEnumerator(gridUid, grid,
Box2.CenteredAround(eyeWorldPos.Position, new Vector2(_tileInfo.GetLength(0), _tileInfo.GetLength(1))));

// Gather up all the data the view algorithm needs
while (tileRefs.MoveNext(out var tileRef)) {
var delta = tileRef.GridIndices - eyeTile.GridIndices;
var appearance = _appearanceSystem.GetTurfIcon((uint)tileRef.Tile.TypeId).Appearance;
if (appearance == null)
continue;

int xIndex = delta.X + viewRange.CenterX;
int yIndex = delta.Y + viewRange.CenterY;
if (xIndex < 0 || yIndex < 0 || xIndex >= _tileInfo.GetLength(0) || yIndex >= _tileInfo.GetLength(1))
continue;

var tile = new ViewAlgorithm.Tile {
Opaque = appearance.Opacity,
Luminosity = 0,
DeltaX = delta.X,
DeltaY = delta.Y
};

_tileInfo[xIndex, yIndex] = tile;
}

// Apply entities' opacity
foreach (EntityUid entity in EntitiesInView) {
// TODO use a sprite tree.
if (!_spriteQuery.TryGetComponent(entity, out var sprite))
continue;

var transform = _xformQuery.GetComponent(entity);
if (!sprite.IsVisible(transform, seeVis))
continue;
if (sprite.Icon.Appearance == null) //appearance hasn't loaded yet
continue;

var worldPos = _transformSystem.GetWorldPosition(transform);
var tilePos = _mapSystem.WorldToTile(gridUid, grid, worldPos) - eyeTile.GridIndices + viewRange.Center;
if (tilePos.X < 0 || tilePos.Y < 0 || tilePos.X >= _tileInfo.GetLength(0) ||
tilePos.Y >= _tileInfo.GetLength(1))
continue;

var tile = _tileInfo[tilePos.X, tilePos.Y];
if (tile != null)
tile.Opaque |= sprite.Icon.Appearance.Opacity;
}

ViewAlgorithm.CalculateVisibility(_tileInfo);
_tileInfoDirty = false;
return _tileInfo;
}
}
67 changes: 1 addition & 66 deletions OpenDreamClient/Rendering/DreamViewOverlay.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ namespace OpenDreamClient.Rendering;
/// <summary>
/// Overlay for rendering world atoms
/// </summary>
internal sealed class DreamViewOverlay : Overlay {
internal sealed partial class DreamViewOverlay : Overlay {
public static ShaderInstance ColorInstance = default!;

public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowWorld;
Expand Down Expand Up @@ -76,9 +76,6 @@ internal sealed class DreamViewOverlay : Overlay {
M22 = -1
};

// Defined here so it isn't recreated every frame
private ViewAlgorithm.Tile?[,]? _tileInfo;

public DreamViewOverlay(RenderTargetPool renderTargetPool, TransformSystem transformSystem, MapSystem mapSystem, EntityLookupSystem lookupSystem,
ClientAppearanceSystem appearanceSystem, ClientScreenOverlaySystem screenOverlaySystem, ClientImagesSystem clientImagesSystem) {
IoCManager.InjectDependencies(this);
Expand Down Expand Up @@ -599,68 +596,6 @@ private void DrawPlanes(DrawingHandleWorld handle, Box2 worldAABB) {
}
}

private ViewAlgorithm.Tile?[,] CalculateTileVisibility(EntityUid gridUid, MapGridComponent grid, TileRef eyeTile, int seeVis) {
using var _ = _prof.Group("visible turfs");

var viewRange = _interfaceManager.View;
if (_tileInfo == null || _tileInfo.GetLength(0) != viewRange.Width + 2 || _tileInfo.GetLength(1) != viewRange.Height + 2) {
// _tileInfo hasn't been created yet or view range has changed, so create a new array.
// Leave a 1 tile buffer on each side
_tileInfo = new ViewAlgorithm.Tile[viewRange.Width + 2, viewRange.Height + 2];
}

var eyeWorldPos = _mapSystem.GridTileToWorld(gridUid, grid, eyeTile.GridIndices);
var tileRefs = _mapSystem.GetTilesEnumerator(gridUid, grid,
Box2.CenteredAround(eyeWorldPos.Position, new Vector2(_tileInfo.GetLength(0), _tileInfo.GetLength(1))));

// Gather up all the data the view algorithm needs
while (tileRefs.MoveNext(out var tileRef)) {
var delta = tileRef.GridIndices - eyeTile.GridIndices;
var appearance = _appearanceSystem.GetTurfIcon((uint)tileRef.Tile.TypeId).Appearance;
if (appearance == null)
continue;

int xIndex = delta.X + viewRange.CenterX;
int yIndex = delta.Y + viewRange.CenterY;
if (xIndex < 0 || yIndex < 0 || xIndex >= _tileInfo.GetLength(0) || yIndex >= _tileInfo.GetLength(1))
continue;

var tile = new ViewAlgorithm.Tile {
Opaque = appearance.Opacity,
Luminosity = 0,
DeltaX = delta.X,
DeltaY = delta.Y
};

_tileInfo[xIndex, yIndex] = tile;
}

// Apply entities' opacity
foreach (EntityUid entity in EntitiesInView) {
// TODO use a sprite tree.
if (!_spriteQuery.TryGetComponent(entity, out var sprite))
continue;

var transform = _xformQuery.GetComponent(entity);
if (!sprite.IsVisible(transform, seeVis))
continue;
if (sprite.Icon.Appearance == null) //appearance hasn't loaded yet
continue;

var worldPos = _transformSystem.GetWorldPosition(transform);
var tilePos = _mapSystem.WorldToTile(gridUid, grid, worldPos) - eyeTile.GridIndices + viewRange.Center;
if (tilePos.X < 0 || tilePos.Y < 0 || tilePos.X >= _tileInfo.GetLength(0) || tilePos.Y >= _tileInfo.GetLength(1))
continue;

var tile = _tileInfo[tilePos.X, tilePos.Y];
if (tile != null)
tile.Opaque |= sprite.Icon.Appearance.Opacity;
}

ViewAlgorithm.CalculateVisibility(_tileInfo);
return _tileInfo;
}

private void CollectVisibleSprites(ViewAlgorithm.Tile?[,] tiles, EntityUid gridUid, MapGridComponent grid, TileRef eyeTile, sbyte seeVis, SightFlags sight, Box2 worldAABB) {
_spriteContainer.Clear();

Expand Down

0 comments on commit 02ee3e1

Please sign in to comment.