Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid recalculating tile visibility when nothing is changing #2191

Merged
merged 3 commits into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion OpenDreamClient/Rendering/DMISpriteSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,18 @@ public sealed class DMISpriteSystem : EntitySystem {

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 +54,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)
_mapOverlay.DirtyTileVisibility(); // A movable with opacity=TRUE 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, IEntityEventSubscriber {
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
Loading