From 04b6d60d76a0031006b2f450fc9b06001b220885 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Tue, 12 Dec 2023 20:13:20 +1100 Subject: [PATCH] Fix TileEdgeOverlay drawing over grids (#4698) --- .../GameObjects/EntitySystems/MapSystem.cs | 40 ++--- .../Graphics/Clyde/Clyde.GridRendering.cs | 66 +++++-- Robust.Client/Graphics/Clyde/Clyde.HLR.cs | 2 +- Robust.Client/Graphics/Clyde/Clyde.cs | 2 + .../Graphics/Overlays/GridOverlay.cs | 14 ++ .../Graphics/Overlays/IGridOverlay.cs | 18 ++ Robust.Client/Map/TileEdgeOverlay.cs | 165 ++++++++---------- Robust.Shared/Enums/OverlaySpaces.cs | 17 +- 8 files changed, 195 insertions(+), 129 deletions(-) create mode 100644 Robust.Client/Graphics/Overlays/GridOverlay.cs create mode 100644 Robust.Client/Graphics/Overlays/IGridOverlay.cs diff --git a/Robust.Client/GameObjects/EntitySystems/MapSystem.cs b/Robust.Client/GameObjects/EntitySystems/MapSystem.cs index 7849f6eaf..44ef28275 100644 --- a/Robust.Client/GameObjects/EntitySystems/MapSystem.cs +++ b/Robust.Client/GameObjects/EntitySystems/MapSystem.cs @@ -8,30 +8,28 @@ using Robust.Shared.Map; using Robust.Shared.Map.Components; using Robust.Shared.Physics.Dynamics; -namespace Robust.Client.GameObjects +namespace Robust.Client.GameObjects; + +public sealed class MapSystem : SharedMapSystem { - public sealed class MapSystem : SharedMapSystem + [Dependency] private readonly IOverlayManager _overlayManager = default!; + [Dependency] private readonly IResourceCache _resource = default!; + [Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!; + + public override void Initialize() { - [Dependency] private readonly IMapManager _mapManager = default!; - [Dependency] private readonly IOverlayManager _overlayManager = default!; - [Dependency] private readonly IResourceCache _resource = default!; - [Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!; + base.Initialize(); + _overlayManager.AddOverlay(new TileEdgeOverlay(EntityManager, _resource, _tileDefinitionManager)); + } - public override void Initialize() - { - base.Initialize(); - _overlayManager.AddOverlay(new TileEdgeOverlay(EntityManager, _mapManager, _resource, _tileDefinitionManager)); - } + public override void Shutdown() + { + base.Shutdown(); + _overlayManager.RemoveOverlay(); + } - public override void Shutdown() - { - base.Shutdown(); - _overlayManager.RemoveOverlay(); - } - - protected override void OnMapAdd(EntityUid uid, MapComponent component, ComponentAdd args) - { - EnsureComp(uid); - } + protected override void OnMapAdd(EntityUid uid, MapComponent component, ComponentAdd args) + { + EnsureComp(uid); } } diff --git a/Robust.Client/Graphics/Clyde/Clyde.GridRendering.cs b/Robust.Client/Graphics/Clyde/Clyde.GridRendering.cs index 93c65cfb4..64e2e0c5e 100644 --- a/Robust.Client/Graphics/Clyde/Clyde.GridRendering.cs +++ b/Robust.Client/Graphics/Clyde/Clyde.GridRendering.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using System.Linq; using OpenToolkit.Graphics.OpenGL4; +using Robust.Shared.Enums; using Robust.Shared.GameObjects; using Robust.Shared.Graphics; using Robust.Shared.IoC; @@ -18,10 +20,15 @@ namespace Robust.Client.Graphics.Clyde private readonly Dictionary> _mapChunkData = new(); + /// + /// To avoid spamming errors we'll just log it once and move on. + /// + private HashSet _erroredGridOverlays = new(); + private int _verticesPerChunk(MapChunk chunk) => chunk.ChunkSize * chunk.ChunkSize * 4; private int _indicesPerChunk(MapChunk chunk) => chunk.ChunkSize * chunk.ChunkSize * GetQuadBatchIndexCount(); - private void _drawGrids(Viewport viewport, Box2Rotated worldBounds, IEye eye) + private void _drawGrids(Viewport viewport, Box2 worldAABB, Box2Rotated worldBounds, IEye eye) { var mapId = eye.Position.MapId; if (!_mapManager.MapExists(mapId)) @@ -30,27 +37,35 @@ namespace Robust.Client.Graphics.Clyde mapId = MapId.Nullspace; } - SetTexture(TextureUnit.Texture0, _tileDefinitionManager.TileTextureAtlas); - SetTexture(TextureUnit.Texture1, _lightingReady ? viewport.LightRenderTarget.Texture : _stockTextureWhite); - - var gridProgram = ActivateShaderInstance(_defaultShader.Handle).Item1; - SetupGlobalUniformsImmediate(gridProgram, (ClydeTexture) _tileDefinitionManager.TileTextureAtlas); - - gridProgram.SetUniformTextureMaybe(UniIMainTexture, TextureUnit.Texture0); - gridProgram.SetUniformTextureMaybe(UniILightTexture, TextureUnit.Texture1); - gridProgram.SetUniform(UniIModUV, new Vector4(0, 0, 1, 1)); - var grids = new List>(); _mapManager.FindGridsIntersecting(mapId, worldBounds, ref grids); + + var requiresFlush = true; + GLShaderProgram gridProgram = default!; + var gridOverlays = GetOverlaysForSpace(OverlaySpace.WorldSpaceGrids); + foreach (var mapGrid in grids) { - if (!_mapChunkData.ContainsKey(mapGrid)) + if (!_mapChunkData.TryGetValue(mapGrid, out var data)) + { continue; + } + + if (requiresFlush) + { + SetTexture(TextureUnit.Texture0, _tileDefinitionManager.TileTextureAtlas); + SetTexture(TextureUnit.Texture1, _lightingReady ? viewport.LightRenderTarget.Texture : _stockTextureWhite); + gridProgram = ActivateShaderInstance(_defaultShader.Handle).Item1; + SetupGlobalUniformsImmediate(gridProgram, (ClydeTexture) _tileDefinitionManager.TileTextureAtlas); + + gridProgram.SetUniformTextureMaybe(UniIMainTexture, TextureUnit.Texture0); + gridProgram.SetUniformTextureMaybe(UniILightTexture, TextureUnit.Texture1); + gridProgram.SetUniform(UniIModUV, new Vector4(0, 0, 1, 1)); + } var transform = _entityManager.GetComponent(mapGrid); gridProgram.SetUniform(UniIModelMatrix, transform.WorldMatrix); var enumerator = mapGrid.Comp.GetMapChunks(worldBounds); - var data = _mapChunkData[mapGrid]; while (enumerator.MoveNext(out var chunk)) { @@ -72,6 +87,31 @@ namespace Robust.Client.Graphics.Clyde GL.DrawElements(GetQuadGLPrimitiveType(), datum.TileCount * GetQuadBatchIndexCount(), DrawElementsType.UnsignedShort, 0); CheckGlError(); } + + requiresFlush = false; + + foreach (var overlay in gridOverlays) + { + if (overlay is not IGridOverlay iGrid) + { + if (!_erroredGridOverlays.Add(overlay.GetType())) + { + _clydeSawmill.Error($"Tried to render grid overlay {overlay.GetType()} that doesn't implement {nameof(IGridOverlay)}"); + } + + continue; + } + + iGrid.Grid = mapGrid; + iGrid.RequiresFlush = false; + RenderSingleWorldOverlay(overlay, viewport, OverlaySpace.WorldSpaceGrids, worldAABB, worldBounds); + requiresFlush |= iGrid.RequiresFlush; + } + + if (requiresFlush) + { + FlushRenderQueue(); + } } CullEmptyChunks(); diff --git a/Robust.Client/Graphics/Clyde/Clyde.HLR.cs b/Robust.Client/Graphics/Clyde/Clyde.HLR.cs index 1da177f6a..17e40bc9d 100644 --- a/Robust.Client/Graphics/Clyde/Clyde.HLR.cs +++ b/Robust.Client/Graphics/Clyde/Clyde.HLR.cs @@ -497,7 +497,7 @@ namespace Robust.Client.Graphics.Clyde using (DebugGroup("Grids")) using (_prof.Group("Grids")) { - _drawGrids(viewport, worldBounds, eye); + _drawGrids(viewport, worldAABB, worldBounds, eye); } // We will also render worldspace overlays here so we can do them under / above entities as necessary diff --git a/Robust.Client/Graphics/Clyde/Clyde.cs b/Robust.Client/Graphics/Clyde/Clyde.cs index 571d2f109..91f1febcb 100644 --- a/Robust.Client/Graphics/Clyde/Clyde.cs +++ b/Robust.Client/Graphics/Clyde/Clyde.cs @@ -76,6 +76,7 @@ namespace Robust.Client.Graphics.Clyde private Thread? _gameThread; + private ISawmill _clydeSawmill = default!; private ISawmill _sawmillOgl = default!; private IBindingsContext _glBindingsContext = default!; @@ -91,6 +92,7 @@ namespace Robust.Client.Graphics.Clyde public bool InitializePreWindowing() { + _clydeSawmill = _logManager.GetSawmill("clyde"); _sawmillOgl = _logManager.GetSawmill("clyde.ogl"); _cfg.OnValueChanged(CVars.DisplayOGLCheckErrors, b => _checkGLErrors = b, true); diff --git a/Robust.Client/Graphics/Overlays/GridOverlay.cs b/Robust.Client/Graphics/Overlays/GridOverlay.cs new file mode 100644 index 000000000..3fcdae8b7 --- /dev/null +++ b/Robust.Client/Graphics/Overlays/GridOverlay.cs @@ -0,0 +1,14 @@ +using Robust.Shared.Enums; +using Robust.Shared.GameObjects; +using Robust.Shared.Map.Components; + +namespace Robust.Client.Graphics; + +public abstract class GridOverlay : Overlay, IGridOverlay +{ + public override OverlaySpace Space => OverlaySpace.WorldSpaceGrids; + + public Entity Grid { get; set; } + + public bool RequiresFlush { get; set; } +} diff --git a/Robust.Client/Graphics/Overlays/IGridOverlay.cs b/Robust.Client/Graphics/Overlays/IGridOverlay.cs new file mode 100644 index 000000000..92a1889e4 --- /dev/null +++ b/Robust.Client/Graphics/Overlays/IGridOverlay.cs @@ -0,0 +1,18 @@ +using Robust.Shared.GameObjects; +using Robust.Shared.Map.Components; + +namespace Robust.Client.Graphics; + +/// +/// Marks this overlay as implementing per-grid rendering. +/// +public interface IGridOverlay +{ + Entity Grid { get; set; } + + /// + /// Should we flush the render or can we keep going. + /// + public bool RequiresFlush { get; set; } +} + diff --git a/Robust.Client/Map/TileEdgeOverlay.cs b/Robust.Client/Map/TileEdgeOverlay.cs index adc1d7be9..734b067f6 100644 --- a/Robust.Client/Map/TileEdgeOverlay.cs +++ b/Robust.Client/Map/TileEdgeOverlay.cs @@ -1,12 +1,9 @@ using System; -using System.Collections.Generic; using System.Numerics; using Robust.Client.Graphics; using Robust.Client.ResourceManagement; -using Robust.Shared.Enums; using Robust.Shared.GameObjects; using Robust.Shared.Map; -using Robust.Shared.Map.Components; using Robust.Shared.Maths; namespace Robust.Client.Map; @@ -14,21 +11,15 @@ namespace Robust.Client.Map; /// /// Draws border sprites for tiles that support them. /// -public sealed class TileEdgeOverlay : Overlay +public sealed class TileEdgeOverlay : GridOverlay { private readonly IEntityManager _entManager; - private readonly IMapManager _mapManager; private readonly IResourceCache _resource; private readonly ITileDefinitionManager _tileDefManager; - public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowEntities; - - private List> _grids = new(); - - public TileEdgeOverlay(IEntityManager entManager, IMapManager mapManager, IResourceCache resource, ITileDefinitionManager tileDefManager) + public TileEdgeOverlay(IEntityManager entManager, IResourceCache resource, ITileDefinitionManager tileDefManager) { _entManager = entManager; - _mapManager = mapManager; _resource = resource; _tileDefManager = tileDefManager; ZIndex = -1; @@ -39,94 +30,90 @@ public sealed class TileEdgeOverlay : Overlay if (args.MapId == MapId.Nullspace) return; - _grids.Clear(); - _mapManager.FindGridsIntersecting(args.MapId, args.WorldBounds, ref _grids); - var mapSystem = _entManager.System(); var xformSystem = _entManager.System(); - foreach (var grid in _grids) + var tileSize = Grid.Comp.TileSize; + var tileDimensions = new Vector2(tileSize, tileSize); + var (_, _, worldMatrix, invMatrix) = xformSystem.GetWorldPositionRotationMatrixWithInv(Grid.Owner); + args.WorldHandle.SetTransform(worldMatrix); + var localAABB = invMatrix.TransformBox(args.WorldBounds); + + var enumerator = mapSystem.GetLocalTilesEnumerator(Grid.Owner, Grid, localAABB, false); + + while (enumerator.MoveNext(out var tileRef)) { - var tileSize = grid.Comp.TileSize; - var tileDimensions = new Vector2(tileSize, tileSize); - var (_, _, worldMatrix, invMatrix) = xformSystem.GetWorldPositionRotationMatrixWithInv(grid.Owner); - args.WorldHandle.SetTransform(worldMatrix); - var localAABB = invMatrix.TransformBox(args.WorldBounds); + var tileDef = _tileDefManager[tileRef.Tile.TypeId]; - var enumerator = mapSystem.GetLocalTilesEnumerator(grid.Owner, grid.Comp, localAABB, false); + if (tileDef.EdgeSprites.Count == 0) + continue; - while (enumerator.MoveNext(out var tileRef)) + // Get what tiles border us to determine what sprites we need to draw. + for (var x = -1; x <= 1; x++) { - var tileDef = _tileDefManager[tileRef.Tile.TypeId]; - - if (tileDef.EdgeSprites.Count == 0) - continue; - - // Get what tiles border us to determine what sprites we need to draw. - for (var x = -1; x <= 1; x++) + for (var y = -1; y <= 1; y++) { - for (var y = -1; y <= 1; y++) + if (x == 0 && y == 0) + continue; + + var neighborIndices = new Vector2i(tileRef.GridIndices.X + x, tileRef.GridIndices.Y + y); + var neighborTile = mapSystem.GetTileRef(Grid.Owner, Grid, neighborIndices); + var neighborDef = _tileDefManager[neighborTile.Tile.TypeId]; + + // If it's the same tile then no edge to be drawn. + if (tileRef.Tile.TypeId == neighborTile.Tile.TypeId) + continue; + + // Don't draw if the the neighbor tile edges should draw over us (or if we have the same priority) + if (neighborDef.EdgeSprites.Count != 0 && neighborDef.EdgeSpritePriority >= tileDef.EdgeSpritePriority) + continue; + + var direction = new Vector2i(x, y).AsDirection(); + + // No edge tile + if (!tileDef.EdgeSprites.TryGetValue(direction, out var edgePath)) + continue; + + var texture = _resource.GetResource(edgePath); + var box = Box2.FromDimensions(neighborIndices, tileDimensions); + + var angle = Angle.Zero; + + // If we ever need one for both cardinals and corners then update this. + switch (direction) { - if (x == 0 && y == 0) - continue; - - var neighborIndices = new Vector2i(tileRef.GridIndices.X + x, tileRef.GridIndices.Y + y); - var neighborTile = mapSystem.GetTileRef(grid.Owner, grid.Comp, neighborIndices); - var neighborDef = _tileDefManager[neighborTile.Tile.TypeId]; - - // If it's the same tile then no edge to be drawn. - if (tileRef.Tile.TypeId == neighborTile.Tile.TypeId) - continue; - - // Don't draw if the the neighbor tile edges should draw over us (or if we have the same priority) - if (neighborDef.EdgeSprites.Count != 0 && neighborDef.EdgeSpritePriority >= tileDef.EdgeSpritePriority) - continue; - - var direction = new Vector2i(x, y).AsDirection(); - - // No edge tile - if (!tileDef.EdgeSprites.TryGetValue(direction, out var edgePath)) - continue; - - var texture = _resource.GetResource(edgePath); - var box = Box2.FromDimensions(neighborIndices, tileDimensions); - - var angle = Angle.Zero; - - // If we ever need one for both cardinals and corners then update this. - switch (direction) - { - // Corner sprites - case Direction.SouthEast: - break; - case Direction.NorthEast: - angle = new Angle(MathF.PI / 2f); - break; - case Direction.NorthWest: - angle = new Angle(MathF.PI); - break; - case Direction.SouthWest: - angle = new Angle(MathF.PI * 1.5f); - break; - // Edge sprites - case Direction.South: - break; - case Direction.East: - angle = new Angle(MathF.PI / 2f); - break; - case Direction.North: - angle = new Angle(MathF.PI); - break; - case Direction.West: - angle = new Angle(MathF.PI * 1.5f); - break; - } - - if (angle == Angle.Zero) - args.WorldHandle.DrawTextureRect(texture.Texture, box); - else - args.WorldHandle.DrawTextureRect(texture.Texture, new Box2Rotated(box, angle, box.Center)); + // Corner sprites + case Direction.SouthEast: + break; + case Direction.NorthEast: + angle = new Angle(MathF.PI / 2f); + break; + case Direction.NorthWest: + angle = new Angle(MathF.PI); + break; + case Direction.SouthWest: + angle = new Angle(MathF.PI * 1.5f); + break; + // Edge sprites + case Direction.South: + break; + case Direction.East: + angle = new Angle(MathF.PI / 2f); + break; + case Direction.North: + angle = new Angle(MathF.PI); + break; + case Direction.West: + angle = new Angle(MathF.PI * 1.5f); + break; } + + if (angle == Angle.Zero) + args.WorldHandle.DrawTextureRect(texture.Texture, box); + else + args.WorldHandle.DrawTextureRect(texture.Texture, new Box2Rotated(box, angle, box.Center)); + + RequiresFlush = true; } } } diff --git a/Robust.Shared/Enums/OverlaySpaces.cs b/Robust.Shared/Enums/OverlaySpaces.cs index 80849ce03..deefdc64c 100644 --- a/Robust.Shared/Enums/OverlaySpaces.cs +++ b/Robust.Shared/Enums/OverlaySpaces.cs @@ -2,12 +2,14 @@ using System; using System.Collections.Generic; using System.Text; -namespace Robust.Shared.Enums { +namespace Robust.Shared.Enums +{ /// /// Determines in which canvas layers an overlay gets drawn. /// [Flags] - public enum OverlaySpace : byte { + public enum OverlaySpace : ushort + { /// /// Used for matching bit flags. /// @@ -34,19 +36,24 @@ namespace Robust.Shared.Enums { /// WorldSpaceEntities = 1 << 4, + /// + /// Drawn after every grid. + /// + WorldSpaceGrids = 1 << 5, + /// /// This overlay will be drawn beneath entities, lighting, and FOV; above grids. /// - WorldSpaceBelowEntities = 1 << 5, + WorldSpaceBelowEntities = 1 << 6, /// /// This overlay will be drawn in screen coordinates behind the world. /// - ScreenSpaceBelowWorld = 1 << 6, + ScreenSpaceBelowWorld = 1 << 7, /// /// Overlay will be rendered below grids, entities, and everything else. In world space. /// - WorldSpaceBelowWorld = 1 << 7 + WorldSpaceBelowWorld = 1 << 8 } }