From 86be98005c8f64ede8f178eb4f233ef582ef179c Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Tue, 22 Oct 2019 18:04:44 +0200 Subject: [PATCH] Some engine optimizations. --- .../Components/Renderable/SpriteComponent.cs | 200 ++++++++---------- .../GameObjects/EntitySystems/SpriteSystem.cs | 24 ++- .../Graphics/Clyde/Clyde.Rendering.cs | 108 ++++++---- Robust.Client/Graphics/Clyde/Clyde.cs | 32 +-- Robust.Client/Graphics/RSI/RSI.cs | 2 +- Robust.Client/UserInterface/Controls/Label.cs | 46 ++-- 6 files changed, 225 insertions(+), 187 deletions(-) diff --git a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs index 52b57ee6f5..14014a64cc 100644 --- a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs +++ b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs @@ -152,15 +152,6 @@ namespace Robust.Client.GameObjects [ViewVariables] private Dictionary LayerMap = new Dictionary(); [ViewVariables] private bool _layerMapShared; - - // To a future Clusterfack: - // REALLY BIG OPTIMIZATION POTENTIAL: - // Layer is god damn huge. Copying it is expensive. - // To be fair making it a class might be a good idea, making the following moot. - // List doesn't allow ref indexers because... reasons. Array does. - // It may be a good idea to re-implement this list to use Layer[], - // use ref locals EVERYWHERE, and handle the resizing ourselves. - // This may be worth the overhead of basically reimplementing List. [ViewVariables] private List Layers; #pragma warning disable 649 @@ -242,8 +233,7 @@ namespace Robust.Client.GameObjects public int AddBlankLayer(int? newIndex = null) { - var layer = Layer.New(); - layer.Visible = false; + var layer = new Layer {Visible = false}; return AddLayer(layer, newIndex); } @@ -269,15 +259,13 @@ namespace Robust.Client.GameObjects public int AddLayer(Texture texture, int? newIndex = null) { - var layer = Layer.New(); - layer.Texture = texture; + var layer = new Layer {Texture = texture}; return AddLayer(layer, newIndex); } public int AddLayer(RSI.StateId stateId, int? newIndex = null) { - var layer = Layer.New(); - layer.State = stateId; + var layer = new Layer {State = stateId}; if (BaseRSI.TryGetState(stateId, out var state)) { (layer.Texture, layer.AnimationTimeLeft) = state.GetFrame(CorrectLayerDir(ref layer, state), 0); @@ -323,9 +311,7 @@ namespace Robust.Client.GameObjects public int AddLayer(RSI.StateId stateId, RSI rsi, int? newIndex = null) { - var layer = Layer.New(); - layer.State = stateId; - layer.RSI = rsi; + var layer = new Layer {State = stateId, RSI = rsi}; if (rsi.TryGetState(stateId, out var state)) { (layer.Texture, layer.AnimationTimeLeft) = state.GetFrame(CorrectLayerDir(ref layer, state), 0); @@ -426,9 +412,8 @@ namespace Robust.Client.GameObjects return; } - var thelayer = Layers[layer]; - thelayer.Shader = shader; - Layers[layer] = thelayer; + var theLayer = Layers[layer]; + theLayer.Shader = shader; } public void LayerSetShader(object layerKey, ShaderInstance shader) @@ -510,10 +495,9 @@ namespace Robust.Client.GameObjects return; } - var thelayer = Layers[layer]; - thelayer.State = null; - thelayer.Texture = texture; - Layers[layer] = thelayer; + var theLayer = Layers[layer]; + theLayer.State = null; + theLayer.Texture = texture; } public void LayerSetTexture(object layerKey, Texture texture) @@ -574,37 +558,35 @@ namespace Robust.Client.GameObjects return; } - var thelayer = Layers[layer]; - if (thelayer.State == stateId) + var theLayer = Layers[layer]; + if (theLayer.State == stateId) { return; } - thelayer.State = stateId; - var rsi = thelayer.RSI ?? BaseRSI; + theLayer.State = stateId; + var rsi = theLayer.RSI ?? BaseRSI; if (rsi == null) { Logger.ErrorS(LogCategory, "No RSI to pull new state from! Trace:\n{0}", Environment.StackTrace); - thelayer.Texture = null; + theLayer.Texture = null; } else { if (rsi.TryGetState(stateId, out var state)) { - thelayer.AnimationFrame = 0; - thelayer.AnimationTime = 0; - (thelayer.Texture, thelayer.AnimationTimeLeft) = - state.GetFrame(CorrectLayerDir(ref thelayer, state), 0); + theLayer.AnimationFrame = 0; + theLayer.AnimationTime = 0; + (theLayer.Texture, theLayer.AnimationTimeLeft) = + state.GetFrame(CorrectLayerDir(ref theLayer, state), 0); } else { Logger.ErrorS(LogCategory, "State '{0}' does not exist in RSI. Trace:\n{1}", stateId, Environment.StackTrace); - thelayer.Texture = null; + theLayer.Texture = null; } } - - Layers[layer] = thelayer; } public void LayerSetState(object layerKey, RSI.StateId stateId) @@ -628,33 +610,31 @@ namespace Robust.Client.GameObjects return; } - var thelayer = Layers[layer]; - thelayer.State = stateId; - thelayer.RSI = rsi; - var actualrsi = thelayer.RSI ?? BaseRSI; - if (actualrsi == null) + var theLayer = Layers[layer]; + theLayer.State = stateId; + theLayer.RSI = rsi; + var actualRsi = theLayer.RSI ?? BaseRSI; + if (actualRsi == null) { Logger.ErrorS(LogCategory, "No RSI to pull new state from! Trace:\n{0}", Environment.StackTrace); - thelayer.Texture = null; + theLayer.Texture = null; } else { - if (actualrsi.TryGetState(stateId, out var state)) + if (actualRsi.TryGetState(stateId, out var state)) { - thelayer.AnimationFrame = 0; - thelayer.AnimationTime = 0; - (thelayer.Texture, thelayer.AnimationTimeLeft) = - state.GetFrame(CorrectLayerDir(ref thelayer, state), 0); + theLayer.AnimationFrame = 0; + theLayer.AnimationTime = 0; + (theLayer.Texture, theLayer.AnimationTimeLeft) = + state.GetFrame(CorrectLayerDir(ref theLayer, state), 0); } else { Logger.ErrorS(LogCategory, "State '{0}' does not exist in RSI. Trace:\n{1}", stateId, Environment.StackTrace); - thelayer.Texture = null; + theLayer.Texture = null; } } - - Layers[layer] = thelayer; } public void LayerSetState(object layerKey, RSI.StateId stateId, RSI rsi) @@ -710,37 +690,34 @@ namespace Robust.Client.GameObjects return; } - var thelayer = Layers[layer]; - thelayer.RSI = rsi; - if (!thelayer.State.IsValid) + var theLayer = Layers[layer]; + theLayer.RSI = rsi; + if (!theLayer.State.IsValid) { - Layers[layer] = thelayer; return; } // Gotta do this because somebody might use null as argument (totally valid). - var actualRsi = thelayer.RSI ?? BaseRSI; + var actualRsi = theLayer.RSI ?? BaseRSI; if (actualRsi == null) { Logger.ErrorS(LogCategory, "No RSI to pull new state from! Trace:\n{0}", Environment.StackTrace); - thelayer.Texture = null; + theLayer.Texture = null; } else { - if (rsi.TryGetState(thelayer.State, out var state)) + if (rsi.TryGetState(theLayer.State, out var state)) { - (thelayer.Texture, thelayer.AnimationTimeLeft) = - state.GetFrame(CorrectLayerDir(ref thelayer, state), 0); + (theLayer.Texture, theLayer.AnimationTimeLeft) = + state.GetFrame(CorrectLayerDir(ref theLayer, state), 0); } else { - Logger.ErrorS(LogCategory, "State '{0}' does not exist in set RSI. Trace:\n{1}", thelayer.State, + Logger.ErrorS(LogCategory, "State '{0}' does not exist in set RSI. Trace:\n{1}", theLayer.State, Environment.StackTrace); - thelayer.Texture = null; + theLayer.Texture = null; } } - - Layers[layer] = thelayer; } public void LayerSetRSI(object layerKey, RSI rsi) @@ -796,9 +773,8 @@ namespace Robust.Client.GameObjects return; } - var thelayer = Layers[layer]; - thelayer.Scale = scale; - Layers[layer] = thelayer; + var theLayer = Layers[layer]; + theLayer.Scale = scale; } public void LayerSetScale(object layerKey, Vector2 scale) @@ -823,9 +799,8 @@ namespace Robust.Client.GameObjects return; } - var thelayer = Layers[layer]; - thelayer.Rotation = rotation; - Layers[layer] = thelayer; + var theLayer = Layers[layer]; + theLayer.Rotation = rotation; } public void LayerSetRotation(object layerKey, Angle rotation) @@ -849,9 +824,8 @@ namespace Robust.Client.GameObjects return; } - var thelayer = Layers[layer]; - thelayer.Visible = visible; - Layers[layer] = thelayer; + var theLayer = Layers[layer]; + theLayer.Visible = visible; } public void LayerSetVisible(object layerKey, bool visible) @@ -875,9 +849,8 @@ namespace Robust.Client.GameObjects return; } - var thelayer = Layers[layer]; - thelayer.Color = color; - Layers[layer] = thelayer; + var theLayer = Layers[layer]; + theLayer.Color = color; } public void LayerSetColor(object layerKey, Color color) @@ -901,12 +874,9 @@ namespace Robust.Client.GameObjects return; } - var thelayer = Layers[layer]; - thelayer.DirOffset = offset; - Layers[layer] = thelayer; + var theLayer = Layers[layer]; + theLayer.DirOffset = offset; _recalcDirections = true; - // Do NOT queue redraw. - // FrameUpdate handles it. } public void LayerSetDirOffset(object layerKey, DirectionOffset offset) @@ -957,7 +927,6 @@ namespace Robust.Client.GameObjects _advanceFrameAnimation(ref theLayer, state, correctDir); // And set to said frame. theLayer.Texture = state.GetFrame(correctDir, theLayer.AnimationFrame).icon; - Layers[layer] = theLayer; } public void LayerSetAnimationTime(object layerKey, float animationTime) @@ -983,7 +952,6 @@ namespace Robust.Client.GameObjects var theLayer = Layers[layer]; theLayer.AutoAnimated = autoAnimated; - Layers[layer] = theLayer; } public void LayerSetAutoAnimated(object layerKey, bool autoAnimated) @@ -1120,7 +1088,11 @@ namespace Robust.Client.GameObjects { LayerMap = serializer.GetCacheData>(LayerMapSerializationCache); _layerMapShared = true; - Layers = layers.ShallowClone(); + Layers = new List(layers.Count); + foreach (var clone in layers) + { + Layers.Add(new Layer(clone)); + } // Do this because the directions in the cache may not be correct for us. _recalcDirections = true; return; @@ -1153,7 +1125,7 @@ namespace Robust.Client.GameObjects foreach (var layerDatum in layerData) { var anyTextureAttempted = false; - var layer = Layer.New(); + var layer = new Layer(); if (!string.IsNullOrWhiteSpace(layerDatum.RsiPath)) { var path = TextureRoot / layerDatum.RsiPath; @@ -1339,8 +1311,6 @@ namespace Robust.Client.GameObjects _advanceFrameAnimation(ref layer, state, layerSpecificDir); layer.Texture = state.GetFrame(layerSpecificDir, layer.AnimationFrame).icon; - - Layers[i] = layer; } } @@ -1391,18 +1361,18 @@ namespace Robust.Client.GameObjects } // Maybe optimize this to NOT full clear. - // At least we're not doing extra allocations, - // because the list doesn't reallocate. Layers.Clear(); for (var i = 0; i < thestate.Layers.Count; i++) { var netlayer = thestate.Layers[i]; - var layer = Layer.New(); - // These are easy so do them here. - layer.Scale = netlayer.Scale; - layer.Rotation = netlayer.Rotation; - layer.Visible = netlayer.Visible; - layer.Color = netlayer.Color; + var layer = new Layer + { + // These are easy so do them here. + Scale = netlayer.Scale, + Rotation = netlayer.Rotation, + Visible = netlayer.Visible, + Color = netlayer.Color + }; Layers.Add(layer); // Using the public API to handle errors. @@ -1556,7 +1526,7 @@ namespace Robust.Client.GameObjects Flip = 3, } - private struct Layer + private class Layer { public ShaderInstance Shader; public Texture Texture; @@ -1566,22 +1536,36 @@ namespace Robust.Client.GameObjects public float AnimationTimeLeft; public float AnimationTime; public int AnimationFrame; - public Vector2 Scale; + public Vector2 Scale = Vector2.One; public Angle Rotation; - public bool Visible; - public Color Color; + public bool Visible = true; + public Color Color = Color.White; public DirectionOffset DirOffset; - public bool AutoAnimated; + public bool AutoAnimated = true; - public static Layer New() + public Layer() { - return new Layer - { - Scale = Vector2.One, - Visible = true, - Color = Color.White, - AutoAnimated = true, - }; + Visible = true; + Scale = Vector2.One; + Color = Color.White; + AutoAnimated = true; + } + + public Layer(Layer toClone) + { + Shader = toClone.Shader; + Texture = toClone.Texture; + RSI = toClone.RSI; + State = toClone.State; + AnimationTimeLeft = toClone.AnimationTimeLeft; + AnimationTime = toClone.AnimationTime; + AnimationFrame = toClone.AnimationFrame; + Scale = toClone.Scale; + Rotation = toClone.Rotation; + Visible = toClone.Visible; + Color = toClone.Color; + DirOffset = toClone.DirOffset; + AutoAnimated = toClone.AutoAnimated; } } diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.cs index 8f2ef32e64..d77dbb6230 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.cs @@ -1,11 +1,19 @@ -using Robust.Client.Interfaces.GameObjects.Components; +using Robust.Client.Graphics.ClientEye; +using Robust.Client.Interfaces.GameObjects.Components; +using Robust.Client.Interfaces.Graphics; +using Robust.Client.Interfaces.Graphics.ClientEye; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Systems; +using Robust.Shared.IoC; +using Robust.Shared.Maths; namespace Robust.Client.GameObjects.EntitySystems { public class SpriteSystem : EntitySystem { + [Dependency] private readonly IClyde _clyde; + [Dependency] private readonly IEyeManager _eyeManager; + public SpriteSystem() { EntityQuery = new TypeEntityQuery(typeof(ISpriteComponent)); @@ -13,8 +21,22 @@ namespace Robust.Client.GameObjects.EntitySystems public override void FrameUpdate(float frameTime) { + var eye = _eyeManager.CurrentEye; + + // So we could calculate the correct size of the entities based on the contents of their sprite... + // Or we can just assume that no entity is larger than 10x10 and get a stupid easy check. + // TODO: Make this check more accurate. + var worldBounds = Box2.CenteredAround(eye.Position.Position, + _clyde.ScreenSize / EyeManager.PIXELSPERMETER * eye.Zoom).Enlarged(5); + foreach (var entity in EntityManager.GetEntities(EntityQuery)) { + var transform = entity.Transform; + if (!worldBounds.Contains(transform.WorldPosition)) + { + continue; + } + // TODO: Don't call this on components without RSIs loaded. // Serious performance benefit here. entity.GetComponent().FrameUpdate(frameTime); diff --git a/Robust.Client/Graphics/Clyde/Clyde.Rendering.cs b/Robust.Client/Graphics/Clyde/Clyde.Rendering.cs index 0e7b09d7e5..98bf2aa144 100644 --- a/Robust.Client/Graphics/Clyde/Clyde.Rendering.cs +++ b/Robust.Client/Graphics/Clyde/Clyde.Rendering.cs @@ -67,6 +67,8 @@ namespace Robust.Client.Graphics.Clyde private bool _isScissoring; + private readonly List _sortingSpritesList = new List(); + public void Render() { _debugStats.Reset(); @@ -113,7 +115,7 @@ namespace Robust.Client.Graphics.Clyde // Calculate world-space AABB for camera, to cull off-screen things. var eye = _eyeManager.CurrentEye; var worldBounds = Box2.CenteredAround(eye.Position.Position, - _windowSize / EyeManager.PIXELSPERMETER * eye.Zoom); + _screenSize / EyeManager.PIXELSPERMETER * eye.Zoom); using (DebugGroup("Lights")) { @@ -127,7 +129,7 @@ namespace Robust.Client.Graphics.Clyde using (DebugGroup("Entities")) { - var entityList = new List(100); + _sortingSpritesList.Clear(); var map = _eyeManager.CurrentMap; // So we could calculate the correct size of the entities based on the contents of their sprite... @@ -144,10 +146,10 @@ namespace Robust.Client.Graphics.Clyde continue; } - entityList.Add(sprite); + _sortingSpritesList.Add(sprite); } - entityList.Sort((a, b) => + _sortingSpritesList.Sort((a, b) => { var cmp = ((int) a.DrawDepth).CompareTo((int) b.DrawDepth); if (cmp != 0) @@ -165,7 +167,7 @@ namespace Robust.Client.Graphics.Clyde return a.Owner.Uid.CompareTo(b.Owner.Uid); }); - foreach (var sprite in entityList) + foreach (var sprite in _sortingSpritesList) { Vector2i roundedPos = default; if (sprite.PostShader != null) @@ -250,8 +252,8 @@ namespace Robust.Client.Graphics.Clyde // Screen projection matrix. var projMatrixScreen = Matrix3.Identity; - projMatrixScreen.R0C0 = 2f / _window.Width; - projMatrixScreen.R1C1 = -2f / _window.Height; + projMatrixScreen.R0C0 = 2f / ScreenSize.X; + projMatrixScreen.R1C1 = -2f / ScreenSize.Y; projMatrixScreen.R0C2 = -1; projMatrixScreen.R1C2 = 1; @@ -274,8 +276,8 @@ namespace Robust.Client.Graphics.Clyde viewMatrixWorld.R1C2 = -cameraWorldAdjusted.Y / eye.Zoom.Y; var projMatrixWorld = Matrix3.Identity; - projMatrixWorld.R0C0 = EyeManager.PIXELSPERMETER * 2f / _window.Width; - projMatrixWorld.R1C1 = EyeManager.PIXELSPERMETER * 2f / _window.Height; + projMatrixWorld.R0C0 = EyeManager.PIXELSPERMETER * 2f / ScreenSize.X; + projMatrixWorld.R1C1 = EyeManager.PIXELSPERMETER * 2f / ScreenSize.Y; return new ProjViewMatrices(projMatrixWorld, viewMatrixWorld); } @@ -289,36 +291,6 @@ namespace Robust.Client.Graphics.Clyde var map = _eyeManager.CurrentMap; - void DrawLight(Vector2 pos, float range, float power, Color color, Texture mask, Angle rotation) - { - var maskTexture = mask ?? Texture.White; - var maskHandle = _loadedTextures[((ClydeTexture) maskTexture).TextureId].OpenGLObject; - GL.ActiveTexture(TextureUnit.Texture0); - GL.BindTexture(TextureTarget.Texture2D, maskHandle.Handle); - _lightShader.SetUniform("lightCenter", pos); - _lightShader.SetUniform("lightRange", range); - _lightShader.SetUniform("lightPower", power); - _lightShader.SetUniform("lightColor", color); - _lightShader.SetUniformTexture("lightMask", TextureUnit.Texture0); - - var offset = new Vector2(range, range); - - Matrix3 matrix; - if (mask == null) - { - matrix = Matrix3.Identity; - } - else - { - // Only apply rotation if a mask is said, because else it doesn't matter. - matrix = Matrix3.CreateRotation(rotation); - } - - (matrix.R0C2, matrix.R1C2) = pos; - - _drawQuad(-offset, offset, ref matrix, _lightShader); - } - GL.BindFramebuffer(FramebufferTarget.Framebuffer, LightRenderTarget.ObjectHandle.Handle); var converted = Color.FromSrgb(new Color(0.1f, 0.1f, 0.1f)); GL.ClearColor(converted.R, converted.G, converted.B, 1); @@ -331,6 +303,11 @@ namespace Robust.Client.Graphics.Clyde GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.One); + var lastRange = float.NaN; + var lastPower = float.NaN; + var lastColor = new Color(float.NaN, float.NaN, float.NaN, float.NaN); + Texture lastMask = null; + foreach (var component in _componentManager.GetAllComponents()) { if (!component.Enabled || component.Owner.Transform.MapID != map) @@ -361,13 +338,58 @@ namespace Robust.Client.Graphics.Clyde } } - DrawLight(lightPos, component.Radius, component.Energy, component.Color, mask, rotation); + var maskTexture = mask ?? Texture.White; + if (lastMask != maskTexture) + { + var maskHandle = _loadedTextures[((ClydeTexture) maskTexture).TextureId].OpenGLObject; + GL.ActiveTexture(TextureUnit.Texture0); + GL.BindTexture(TextureTarget.Texture2D, maskHandle.Handle); + lastMask = maskTexture; + _lightShader.SetUniformTexture("lightMask", TextureUnit.Texture0); + } + + if (!FloatMath.CloseTo(lastRange, component.Radius)) + { + lastRange = component.Radius; + _lightShader.SetUniform("lightRange", lastRange); + } + + if (!FloatMath.CloseTo(lastPower, component.Energy)) + { + lastPower = component.Energy; + _lightShader.SetUniform("lightPower", lastPower); + } + + if (lastColor != component.Color) + { + lastColor = component.Color; + _lightShader.SetUniform("lightColor", lastColor); + } + + _lightShader.SetUniform("lightCenter", lightPos); + + var offset = new Vector2(component.Radius, component.Radius); + + Matrix3 matrix; + if (mask == null) + { + matrix = Matrix3.Identity; + } + else + { + // Only apply rotation if a mask is said, because else it doesn't matter. + matrix = Matrix3.CreateRotation(rotation); + } + + (matrix.R0C2, matrix.R1C2) = lightPos; + + _drawQuad(-offset, offset, ref matrix, _lightShader); } GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha); GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0); - GL.Viewport(0, 0, _window.Width, _window.Height); + GL.Viewport(0, 0, ScreenSize.X, ScreenSize.Y); _lightingReady = true; } @@ -880,8 +902,8 @@ namespace Robust.Client.Graphics.Clyde ref var commandViewMatrix = ref CommandList.RenderCommands.AllocAdd(); commandViewMatrix.Type = RenderCommandType.ViewMatrix; - var ofsX = position.X - _clyde._window.Width / 2f; - var ofsY = position.Y - _clyde._window.Height / 2f; + var ofsX = position.X - _clyde.ScreenSize.X / 2f; + var ofsY = position.Y - _clyde.ScreenSize.Y / 2f; ref var viewMatrix = ref commandViewMatrix.ViewMatrix.Matrix; viewMatrix = Matrix3.Identity; viewMatrix.R0C0 = scale.X; diff --git a/Robust.Client/Graphics/Clyde/Clyde.cs b/Robust.Client/Graphics/Clyde/Clyde.cs index 10cb501dc3..0c040e8cff 100644 --- a/Robust.Client/Graphics/Clyde/Clyde.cs +++ b/Robust.Client/Graphics/Clyde/Clyde.cs @@ -42,7 +42,6 @@ namespace Robust.Client.Graphics.Clyde [Shared.IoC.Dependency] private readonly IEyeManager _eyeManager; [Shared.IoC.Dependency] private readonly IMapManager _mapManager; [Shared.IoC.Dependency] private readonly IOverlayManager _overlayManager; - [Shared.IoC.Dependency] private readonly IEntityManager _entityManager; [Shared.IoC.Dependency] private readonly IComponentManager _componentManager; [Shared.IoC.Dependency] private readonly IUserInterfaceManagerInternal _userInterfaceManager; [Shared.IoC.Dependency] private readonly IClydeTileDefinitionManager _tileDefinitionManager; @@ -51,7 +50,7 @@ namespace Robust.Client.Graphics.Clyde private static readonly Version MinimumOpenGLVersion = new Version(3, 3); - private Vector2i _windowSize; + private Vector2i _screenSize; private GameWindow _window; private const int ProjViewBindingIndex = 0; @@ -100,8 +99,13 @@ namespace Robust.Client.Graphics.Clyde public override Vector2i ScreenSize { - get => new Vector2i(_window.Width, _window.Height); - set => _window.Size = new Size(value.X, value.Y); + get => _screenSize; + set + { + _window.Size = new Size(value.X, value.Y); + var s = _window.ClientSize; + _screenSize = new Vector2i(s.Width, s.Height); + } } private readonly HashSet OpenGLExtensions = new HashSet(); @@ -199,7 +203,8 @@ namespace Robust.Client.Graphics.Clyde VSyncChanged(); WindowModeChanged(); - _windowSize = new Vector2i(_window.Width, _window.Height); + var winSize = _window.ClientSize; + _screenSize = new Vector2i(winSize.Width, winSize.Height); _mainThread = Thread.CurrentThread; @@ -208,11 +213,12 @@ namespace Robust.Client.Graphics.Clyde _window.Closed += _onWindowClosed; _window.Resize += (sender, eventArgs) => { - var oldSize = _windowSize; - _windowSize = new Vector2i(_window.Width, _window.Height); - GL.Viewport(0, 0, _window.Width, _window.Height); + var oldSize = _screenSize; + var newWinSize = _window.ClientSize; + _screenSize = new Vector2i(newWinSize.Width, newWinSize.Height); + GL.Viewport(0, 0, newWinSize.Width, newWinSize.Height); _regenerateLightRenderTarget(); - OnWindowResized?.Invoke(new WindowResizedEventArgs(oldSize, _windowSize)); + OnWindowResized?.Invoke(new WindowResizedEventArgs(oldSize, _screenSize)); }; _window.MouseDown += (sender, eventArgs) => { _gameController.KeyDown((KeyEventArgs) eventArgs); }; _window.MouseUp += (sender, eventArgs) => { _gameController.KeyUp((KeyEventArgs) eventArgs); }; @@ -358,7 +364,7 @@ namespace Robust.Client.Graphics.Clyde _renderHandle = new RenderHandle(this); - GL.Viewport(0, 0, _window.Width, _window.Height); + GL.Viewport(0, 0, ScreenSize.X, ScreenSize.Y); // Quickly do a render with _drawingSplash = true so the screen isn't blank. if (!_lite) @@ -381,11 +387,11 @@ namespace Robust.Client.Graphics.Clyde { if (!_quartResLights) { - return (_window.Width, _window.Height); + return (ScreenSize.X, ScreenSize.Y); } - var w = (int) Math.Ceiling(_window.Width / 2f); - var h = (int) Math.Ceiling(_window.Height / 2f); + var w = (int) Math.Ceiling(ScreenSize.X / 2f); + var h = (int) Math.Ceiling(ScreenSize.Y / 2f); return (w, h); } diff --git a/Robust.Client/Graphics/RSI/RSI.cs b/Robust.Client/Graphics/RSI/RSI.cs index f7cbe46672..b787252b0b 100644 --- a/Robust.Client/Graphics/RSI/RSI.cs +++ b/Robust.Client/Graphics/RSI/RSI.cs @@ -108,7 +108,7 @@ namespace Robust.Client.Graphics public override int GetHashCode() { - return Name.GetHashCode() ^ Selectors.GetHashCode(); + return Name.GetHashCode() ^ (int)Selectors; } } } diff --git a/Robust.Client/UserInterface/Controls/Label.cs b/Robust.Client/UserInterface/Controls/Label.cs index e0807c8d6f..5aac1bb2cf 100644 --- a/Robust.Client/UserInterface/Controls/Label.cs +++ b/Robust.Client/UserInterface/Controls/Label.cs @@ -16,7 +16,9 @@ namespace Robust.Client.UserInterface.Controls public const string StylePropertyFontColor = "font-color"; public const string StylePropertyFont = "font"; - private (int Height, List Widths)? _textDimensionCache; + private int _cachedTextHeight; + private readonly List _cachedTextWidths = new List(); + private bool _textDimensionCacheValid; private string _text; private bool _clipText; @@ -36,7 +38,7 @@ namespace Robust.Client.UserInterface.Controls set { _text = value; - _textDimensionCache = null; + _textDimensionCacheValid = false; MinimumSizeChanged(); } } @@ -111,10 +113,10 @@ namespace Robust.Client.UserInterface.Controls return; } - if (!_textDimensionCache.HasValue) + if (!_textDimensionCacheValid) { _calculateTextDimension(); - DebugTools.Assert(_textDimensionCache.HasValue); + DebugTools.Assert(_textDimensionCacheValid); } int vOffset; @@ -125,10 +127,10 @@ namespace Robust.Client.UserInterface.Controls break; case VAlignMode.Fill: case VAlignMode.Center: - vOffset = (PixelSize.Y - _textDimensionCache.Value.Height) / 2; + vOffset = (PixelSize.Y - _cachedTextHeight) / 2; break; case VAlignMode.Bottom: - vOffset = PixelSize.Y - _textDimensionCache.Value.Height; + vOffset = PixelSize.Y - _cachedTextHeight; break; default: throw new ArgumentOutOfRangeException(); @@ -140,7 +142,7 @@ namespace Robust.Client.UserInterface.Controls Vector2 CalcBaseline() { - DebugTools.Assert(_textDimensionCache.HasValue); + DebugTools.Assert(_textDimensionCacheValid); int hOffset; switch (Align) @@ -150,10 +152,10 @@ namespace Robust.Client.UserInterface.Controls break; case AlignMode.Center: case AlignMode.Fill: - hOffset = (PixelSize.X - _textDimensionCache.Value.Widths[newlines]) / 2; + hOffset = (PixelSize.X - _cachedTextWidths[newlines]) / 2; break; case AlignMode.Right: - hOffset = PixelSize.X - _textDimensionCache.Value.Widths[newlines]; + hOffset = PixelSize.X - _cachedTextWidths[newlines]; break; default: throw new ArgumentOutOfRangeException(); @@ -195,49 +197,51 @@ namespace Robust.Client.UserInterface.Controls protected override Vector2 CalculateMinimumSize() { - if (!_textDimensionCache.HasValue) + if (!_textDimensionCacheValid) { _calculateTextDimension(); - DebugTools.Assert(_textDimensionCache.HasValue); + DebugTools.Assert(_textDimensionCacheValid); } if (ClipText) { - return (0, _textDimensionCache.Value.Height / UIScale); + return (0, _cachedTextHeight / UIScale); } var totalWidth = 0; - foreach (var width in _textDimensionCache.Value.Widths) + foreach (var width in _cachedTextWidths) { totalWidth = Math.Max(totalWidth, width); } - return (totalWidth / UIScale, _textDimensionCache.Value.Height / UIScale); + return (totalWidth / UIScale, _cachedTextHeight / UIScale); } protected internal override void UIScaleChanged() { - _textDimensionCache = null; + _textDimensionCacheValid = false; base.UIScaleChanged(); } private void _calculateTextDimension() { + _cachedTextWidths.Clear(); + _cachedTextWidths.Add(0); + if (_text == null) { - _textDimensionCache = (0, new List {0}); + _cachedTextHeight = 0; return; } var font = ActualFont; var height = font.GetHeight(UIScale); - var lineSizeList = new List { 0 }; foreach (var chr in _text) { if (chr == '\n') { - lineSizeList.Add(0); + _cachedTextWidths.Add(0); height += font.GetLineHeight(UIScale); } else @@ -248,16 +252,16 @@ namespace Robust.Client.UserInterface.Controls continue; } - lineSizeList[lineSizeList.Count-1] += metrics.Value.Advance; + _cachedTextWidths[_cachedTextWidths.Count-1] += metrics.Value.Advance; } } - _textDimensionCache = (height, lineSizeList); + _cachedTextHeight = height; } protected override void StylePropertiesChanged() { - _textDimensionCache = null; + _textDimensionCacheValid = false; base.StylePropertiesChanged(); }