From 8ba3f1a825d023138b0bd624ad897dbe556bbc41 Mon Sep 17 00:00:00 2001 From: Ethan Moffat Date: Thu, 30 May 2024 13:06:06 -0700 Subject: [PATCH] Reduce allocations/generated garbage in renderers - CharacterRenderer: don't reallocate RT color data array unless recreating the target. - MapRenderer: use for loops instead of foreach loops to reduce garbage generated from enumerators. - MainCharacterEntityRenderer: direct comparison instead of call to params method with only one parameter. --- .../Rendering/Character/CharacterRenderer.cs | 38 ++++++++++++------- EndlessClient/Rendering/Map/MapRenderer.cs | 7 +++- .../MainCharacterEntityRenderer.cs | 2 +- 3 files changed, 30 insertions(+), 17 deletions(-) diff --git a/EndlessClient/Rendering/Character/CharacterRenderer.cs b/EndlessClient/Rendering/Character/CharacterRenderer.cs index 97580d078..55c8c96b4 100644 --- a/EndlessClient/Rendering/Character/CharacterRenderer.cs +++ b/EndlessClient/Rendering/Character/CharacterRenderer.cs @@ -124,15 +124,14 @@ public CharacterRenderer(Game game, _chatBubble = new Lazy(() => _chatBubbleFactory.CreateChatBubble(this)); - _clientWindowSizeRepository.GameWindowSizeChanged += RecreateRenderTarget; + _clientWindowSizeRepository.GameWindowSizeChanged += RecreateRenderTargetEvent; } #region Game Component public override void Initialize() { - lock(_rt_locker_) - _charRenderTarget = _renderTargetFactory.CreateRenderTarget(); + RecreateRenderTarget(); _sb = new SpriteBatch(Game.GraphicsDevice); @@ -437,16 +436,20 @@ private void ClipHair() _hatMetadataProvider.GetValueOrDefault(Character.RenderProperties.HatGraphic).ClipMode != HatMaskType.Standard) return; - // oof. I really need to learn how to use shaders or stencil buffer. - // https://gamedev.stackexchange.com/questions/38118/best-way-to-mask-2d-sprites-in-xna/38150#38150 - _rtColorData = new Color[_charRenderTarget.Width * _charRenderTarget.Height]; - _charRenderTarget.GetData(_rtColorData); - for (int i = 0; i < _rtColorData.Length; i++) + lock (_rt_locker_) { - if (_rtColorData[i] == Color.Black) - _rtColorData[i].A = 0; + // oof. I really need to learn how to use shaders or stencil buffer. + // https://gamedev.stackexchange.com/questions/38118/best-way-to-mask-2d-sprites-in-xna/38150#38150 + + // note: this operation causes a high number of GC events as the character's frame changes (walking/attacking) + _charRenderTarget.GetData(_rtColorData); + for (int i = 0; i < _rtColorData.Length; i++) + { + if (_rtColorData[i] == Color.Black) + _rtColorData[i].A = 0; + } + _charRenderTarget.SetData(_rtColorData); } - _charRenderTarget.SetData(_rtColorData); } #endregion @@ -507,13 +510,20 @@ public void ShowChatBubble(string message, bool isGroupChat) _chatBubble.Value.SetMessage(message, isGroupChat); } - private void RecreateRenderTarget(object sender, EventArgs e) + private void RecreateRenderTarget() { lock (_rt_locker_) { - _charRenderTarget.Dispose(); + _charRenderTarget?.Dispose(); _charRenderTarget = _renderTargetFactory.CreateRenderTarget(); + + _rtColorData = new Color[_charRenderTarget.Width * _charRenderTarget.Height]; } + } + + private void RecreateRenderTargetEvent(object sender, EventArgs e) + { + RecreateRenderTarget(); if (_character == _characterProvider.MainCharacter) SetToCenterScreenPosition(); @@ -534,7 +544,7 @@ protected override void Dispose(bool disposing) _sb?.Dispose(); - _clientWindowSizeRepository.GameWindowSizeChanged -= RecreateRenderTarget; + _clientWindowSizeRepository.GameWindowSizeChanged -= RecreateRenderTargetEvent; lock(_rt_locker_) _charRenderTarget?.Dispose(); diff --git a/EndlessClient/Rendering/Map/MapRenderer.cs b/EndlessClient/Rendering/Map/MapRenderer.cs index 0b3f2eab2..4ce993b61 100644 --- a/EndlessClient/Rendering/Map/MapRenderer.cs +++ b/EndlessClient/Rendering/Map/MapRenderer.cs @@ -373,8 +373,10 @@ void RenderGridSpace(int row, int col) { var alpha = GetAlphaForCoordinates(col, row, immutableCharacter); - foreach (var renderer in _mapEntityRendererProvider.MapEntityRenderers) + for (int i = 0; i < _mapEntityRendererProvider.MapEntityRenderers.Count; i++) { + var renderer = _mapEntityRendererProvider.MapEntityRenderers[i]; + if (!renderer.CanRender(row, col)) continue; @@ -432,8 +434,9 @@ private void DrawBaseLayers(SpriteBatch spriteBatch) { var alpha = GetAlphaForCoordinates(col, row, _characterProvider.MainCharacter); - foreach (var renderer in _mapEntityRendererProvider.BaseRenderers) + for (int i = 0; i < _mapEntityRendererProvider.BaseRenderers.Count; i++) { + var renderer = _mapEntityRendererProvider.BaseRenderers[i]; if (renderer.CanRender(row, col)) renderer.RenderElementAt(spriteBatch, row, col, alpha, new Vector2(offset, 0)); } diff --git a/EndlessClient/Rendering/MapEntityRenderers/MainCharacterEntityRenderer.cs b/EndlessClient/Rendering/MapEntityRenderers/MainCharacterEntityRenderer.cs index b5a9814cd..f88df7d2a 100644 --- a/EndlessClient/Rendering/MapEntityRenderers/MainCharacterEntityRenderer.cs +++ b/EndlessClient/Rendering/MapEntityRenderers/MainCharacterEntityRenderer.cs @@ -31,7 +31,7 @@ public MainCharacterEntityRenderer(ICharacterProvider characterProvider, protected override bool ElementExistsAt(int row, int col) { var rp = _characterProvider.MainCharacter.RenderProperties; - if (!rp.IsActing(CharacterActionState.Walking)) + if (rp.CurrentAction != CharacterActionState.Walking) { return row == rp.MapY && col == rp.MapX; }