diff --git a/Content.Client/Graphics/OverlayResourceCache.cs b/Content.Client/Graphics/OverlayResourceCache.cs new file mode 100644 index 0000000000..ef7ebfd2b7 --- /dev/null +++ b/Content.Client/Graphics/OverlayResourceCache.cs @@ -0,0 +1,90 @@ +using Robust.Client.Graphics; + +namespace Content.Client.Graphics; + +/// +/// A cache for s to store per-viewport render resources, such as render targets. +/// +/// The type of data stored in the cache. +public sealed class OverlayResourceCache : IDisposable where T : class, IDisposable +{ + private readonly Dictionary _cache = new(); + + /// + /// Get the data for a specific viewport, creating a new entry if necessary. + /// + /// + /// The cached data may be cleared at any time if gets invoked. + /// + /// The viewport for which to retrieve cached data. + /// A delegate used to create the cached data, if necessary. + public T GetForViewport(IClydeViewport viewport, Func factory) + { + return GetForViewport(viewport, out _, factory); + } + + /// + /// Get the data for a specific viewport, creating a new entry if necessary. + /// + /// + /// The cached data may be cleared at any time if gets invoked. + /// + /// The viewport for which to retrieve cached data. + /// True if the data was pulled from cache, false if it was created anew. + /// A delegate used to create the cached data, if necessary. + public T GetForViewport(IClydeViewport viewport, out bool wasCached, Func factory) + { + if (_cache.TryGetValue(viewport.Id, out var entry)) + { + wasCached = true; + return entry.Data; + } + + wasCached = false; + + entry = new CacheEntry + { + Data = factory(viewport), + Viewport = new WeakReference(viewport), + }; + _cache.Add(viewport.Id, entry); + + viewport.ClearCachedResources += ViewportOnClearCachedResources; + + return entry.Data; + } + + private void ViewportOnClearCachedResources(ClearCachedViewportResourcesEvent ev) + { + if (!_cache.Remove(ev.ViewportId, out var entry)) + { + // I think this could theoretically happen if you manually dispose the cache *after* a leaked viewport got + // GC'd, but before its ClearCachedResources got invoked. + return; + } + + entry.Data.Dispose(); + + if (ev.Viewport != null) + ev.Viewport.ClearCachedResources -= ViewportOnClearCachedResources; + } + + public void Dispose() + { + foreach (var entry in _cache) + { + if (entry.Value.Viewport.TryGetTarget(out var viewport)) + viewport.ClearCachedResources -= ViewportOnClearCachedResources; + + entry.Value.Data.Dispose(); + } + + _cache.Clear(); + } + + private struct CacheEntry + { + public T Data; + public WeakReference Viewport; + } +} diff --git a/Content.Client/Light/AfterLightTargetOverlay.cs b/Content.Client/Light/AfterLightTargetOverlay.cs index 7856fd4ded..8f19ce922d 100644 --- a/Content.Client/Light/AfterLightTargetOverlay.cs +++ b/Content.Client/Light/AfterLightTargetOverlay.cs @@ -30,6 +30,7 @@ public sealed class AfterLightTargetOverlay : Overlay return; var lightOverlay = _overlay.GetOverlay(); + var lightRes = lightOverlay.GetCachedForViewport(args.Viewport); var bounds = args.WorldBounds; // at 1-1 render scale it's mostly fine but at 4x4 it's way too fkn big @@ -38,7 +39,7 @@ public sealed class AfterLightTargetOverlay : Overlay var localMatrix = viewport.LightRenderTarget.GetWorldToLocalMatrix(viewport.Eye, newScale); - var diff = (lightOverlay.EnlargedLightTarget.Size - viewport.LightRenderTarget.Size); + var diff = (lightRes.EnlargedLightTarget.Size - viewport.LightRenderTarget.Size); var halfDiff = diff / 2; // Pixels -> Metres -> Half distance. @@ -53,7 +54,7 @@ public sealed class AfterLightTargetOverlay : Overlay viewport.LightRenderTarget.Size.Y + halfDiff.Y); worldHandle.SetTransform(localMatrix); - worldHandle.DrawTextureRectRegion(lightOverlay.EnlargedLightTarget.Texture, bounds, subRegion: subRegion); + worldHandle.DrawTextureRectRegion(lightRes.EnlargedLightTarget.Texture, bounds, subRegion: subRegion); }, Color.Transparent); } } diff --git a/Content.Client/Light/AmbientOcclusionOverlay.cs b/Content.Client/Light/AmbientOcclusionOverlay.cs index 4caf654494..aa8c3b52a1 100644 --- a/Content.Client/Light/AmbientOcclusionOverlay.cs +++ b/Content.Client/Light/AmbientOcclusionOverlay.cs @@ -1,4 +1,5 @@ using System.Numerics; +using Content.Client.Graphics; using Content.Shared.CCVar; using Content.Shared.Maps; using Robust.Client.Graphics; @@ -27,11 +28,7 @@ public sealed class AmbientOcclusionOverlay : Overlay public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowEntities; - private IRenderTexture? _aoTarget; - private IRenderTexture? _aoBlurBuffer; - - // Couldn't figure out a way to avoid this so if you can then please do. - private IRenderTexture? _aoStencilTarget; + private readonly OverlayResourceCache _resources = new (); public AmbientOcclusionOverlay() { @@ -69,30 +66,32 @@ public sealed class AmbientOcclusionOverlay : Overlay var turfSystem = _entManager.System(); var invMatrix = args.Viewport.GetWorldToLocalMatrix(); - if (_aoTarget?.Texture.Size != target.Size) + var res = _resources.GetForViewport(args.Viewport, static _ => new CachedResources()); + + if (res.AOTarget?.Texture.Size != target.Size) { - _aoTarget?.Dispose(); - _aoTarget = _clyde.CreateRenderTarget(target.Size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "ambient-occlusion-target"); + res.AOTarget?.Dispose(); + res.AOTarget = _clyde.CreateRenderTarget(target.Size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "ambient-occlusion-target"); } - if (_aoBlurBuffer?.Texture.Size != target.Size) + if (res.AOBlurBuffer?.Texture.Size != target.Size) { - _aoBlurBuffer?.Dispose(); - _aoBlurBuffer = _clyde.CreateRenderTarget(target.Size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "ambient-occlusion-blur-target"); + res.AOBlurBuffer?.Dispose(); + res.AOBlurBuffer = _clyde.CreateRenderTarget(target.Size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "ambient-occlusion-blur-target"); } - if (_aoStencilTarget?.Texture.Size != target.Size) + if (res.AOStencilTarget?.Texture.Size != target.Size) { - _aoStencilTarget?.Dispose(); - _aoStencilTarget = _clyde.CreateRenderTarget(target.Size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "ambient-occlusion-stencil-target"); + res.AOStencilTarget?.Dispose(); + res.AOStencilTarget = _clyde.CreateRenderTarget(target.Size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "ambient-occlusion-stencil-target"); } // Draw the texture data to the texture. - args.WorldHandle.RenderInRenderTarget(_aoTarget, + args.WorldHandle.RenderInRenderTarget(res.AOTarget, () => { worldHandle.UseShader(_proto.Index(UnshadedShader).Instance()); - var invMatrix = _aoTarget.GetWorldToLocalMatrix(viewport.Eye!, scale); + var invMatrix = res.AOTarget.GetWorldToLocalMatrix(viewport.Eye!, scale); foreach (var entry in query.QueryAabb(mapId, worldBounds)) { @@ -106,11 +105,11 @@ public sealed class AmbientOcclusionOverlay : Overlay } }, Color.Transparent); - _clyde.BlurRenderTarget(viewport, _aoTarget, _aoBlurBuffer, viewport.Eye!, 14f); + _clyde.BlurRenderTarget(viewport, res.AOTarget, res.AOBlurBuffer, viewport.Eye!, 14f); // Need to do stencilling after blur as it will nuke it. // Draw stencil for the grid so we don't draw in space. - args.WorldHandle.RenderInRenderTarget(_aoStencilTarget, + args.WorldHandle.RenderInRenderTarget(res.AOStencilTarget, () => { // Don't want lighting affecting it. @@ -136,13 +135,36 @@ public sealed class AmbientOcclusionOverlay : Overlay // Draw the stencil texture to depth buffer. worldHandle.UseShader(_proto.Index(StencilMaskShader).Instance()); - worldHandle.DrawTextureRect(_aoStencilTarget!.Texture, worldBounds); + worldHandle.DrawTextureRect(res.AOStencilTarget!.Texture, worldBounds); // Draw the Blurred AO texture finally. worldHandle.UseShader(_proto.Index(StencilEqualDrawShader).Instance()); - worldHandle.DrawTextureRect(_aoTarget!.Texture, worldBounds, color); + worldHandle.DrawTextureRect(res.AOTarget!.Texture, worldBounds, color); args.WorldHandle.SetTransform(Matrix3x2.Identity); args.WorldHandle.UseShader(null); } + + protected override void DisposeBehavior() + { + _resources.Dispose(); + + base.DisposeBehavior(); + } + + private sealed class CachedResources : IDisposable + { + public IRenderTexture? AOTarget; + public IRenderTexture? AOBlurBuffer; + + // Couldn't figure out a way to avoid this so if you can then please do. + public IRenderTexture? AOStencilTarget; + + public void Dispose() + { + AOTarget?.Dispose(); + AOBlurBuffer?.Dispose(); + AOStencilTarget?.Dispose(); + } + } } diff --git a/Content.Client/Light/BeforeLightTargetOverlay.cs b/Content.Client/Light/BeforeLightTargetOverlay.cs index 8f1bd0e527..6afaebc146 100644 --- a/Content.Client/Light/BeforeLightTargetOverlay.cs +++ b/Content.Client/Light/BeforeLightTargetOverlay.cs @@ -1,4 +1,4 @@ -using System.Numerics; +using Content.Client.Graphics; using Robust.Client.Graphics; using Robust.Shared.Enums; @@ -13,7 +13,8 @@ public sealed class BeforeLightTargetOverlay : Overlay [Dependency] private readonly IClyde _clyde = default!; - public IRenderTexture EnlargedLightTarget = default!; + private readonly OverlayResourceCache _resources = new(); + public Box2Rotated EnlargedBounds; /// @@ -36,16 +37,42 @@ public sealed class BeforeLightTargetOverlay : Overlay var size = args.Viewport.LightRenderTarget.Size + (int) (_skirting * EyeManager.PixelsPerMeter); EnlargedBounds = args.WorldBounds.Enlarged(_skirting / 2f); + var res = _resources.GetForViewport(args.Viewport, static _ => new CachedResources()); + // This just exists to copy the lightrendertarget and write back to it. - if (EnlargedLightTarget?.Size != size) + if (res.EnlargedLightTarget?.Size != size) { - EnlargedLightTarget = _clyde + res.EnlargedLightTarget = _clyde .CreateRenderTarget(size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "enlarged-light-copy"); } - args.WorldHandle.RenderInRenderTarget(EnlargedLightTarget, + args.WorldHandle.RenderInRenderTarget(res.EnlargedLightTarget, () => { }, _clyde.GetClearColor(args.MapUid)); } + + internal CachedResources GetCachedForViewport(IClydeViewport viewport) + { + return _resources.GetForViewport(viewport, + static _ => throw new InvalidOperationException( + "Expected BeforeLightTargetOverlay to have created its resources")); + } + + protected override void DisposeBehavior() + { + _resources.Dispose(); + + base.DisposeBehavior(); + } + + internal sealed class CachedResources : IDisposable + { + public IRenderTexture EnlargedLightTarget = default!; + + public void Dispose() + { + EnlargedLightTarget?.Dispose(); + } + } } diff --git a/Content.Client/Light/LightBlurOverlay.cs b/Content.Client/Light/LightBlurOverlay.cs index 4ce80946aa..eab4a95c07 100644 --- a/Content.Client/Light/LightBlurOverlay.cs +++ b/Content.Client/Light/LightBlurOverlay.cs @@ -1,3 +1,4 @@ +using Content.Client.Graphics; using Robust.Client.Graphics; using Robust.Shared.Enums; @@ -15,7 +16,7 @@ public sealed class LightBlurOverlay : Overlay public const int ContentZIndex = TileEmissionOverlay.ContentZIndex + 1; - private IRenderTarget? _blurTarget; + private readonly OverlayResourceCache _resources = new(); public LightBlurOverlay() { @@ -29,16 +30,36 @@ public sealed class LightBlurOverlay : Overlay return; var beforeOverlay = _overlay.GetOverlay(); - var size = beforeOverlay.EnlargedLightTarget.Size; + var beforeLightRes = beforeOverlay.GetCachedForViewport(args.Viewport); + var res = _resources.GetForViewport(args.Viewport, static _ => new CachedResources()); - if (_blurTarget?.Size != size) + var size = beforeLightRes.EnlargedLightTarget.Size; + + if (res.BlurTarget?.Size != size) { - _blurTarget = _clyde + res.BlurTarget = _clyde .CreateRenderTarget(size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "enlarged-light-blur"); } - var target = beforeOverlay.EnlargedLightTarget; + var target = beforeLightRes.EnlargedLightTarget; // Yeah that's all this does keep walkin. - _clyde.BlurRenderTarget(args.Viewport, target, _blurTarget, args.Viewport.Eye, 14f * 5f); + _clyde.BlurRenderTarget(args.Viewport, target, res.BlurTarget, args.Viewport.Eye, 14f * 5f); + } + + protected override void DisposeBehavior() + { + _resources.Dispose(); + + base.DisposeBehavior(); + } + + private sealed class CachedResources : IDisposable + { + public IRenderTarget? BlurTarget; + + public void Dispose() + { + BlurTarget?.Dispose(); + } } } diff --git a/Content.Client/Light/RoofOverlay.cs b/Content.Client/Light/RoofOverlay.cs index 9be4bfe4c4..01e9bf0961 100644 --- a/Content.Client/Light/RoofOverlay.cs +++ b/Content.Client/Light/RoofOverlay.cs @@ -51,8 +51,9 @@ public sealed class RoofOverlay : Overlay var worldHandle = args.WorldHandle; var lightoverlay = _overlay.GetOverlay(); + var lightRes = lightoverlay.GetCachedForViewport(args.Viewport); var bounds = lightoverlay.EnlargedBounds; - var target = lightoverlay.EnlargedLightTarget; + var target = lightRes.EnlargedLightTarget; _grids.Clear(); _mapManager.FindGridsIntersecting(args.MapId, bounds, ref _grids, approx: true, includeMap: true); diff --git a/Content.Client/Light/SunShadowOverlay.cs b/Content.Client/Light/SunShadowOverlay.cs index f30f4c0409..59ac0a5efb 100644 --- a/Content.Client/Light/SunShadowOverlay.cs +++ b/Content.Client/Light/SunShadowOverlay.cs @@ -1,4 +1,5 @@ using System.Numerics; +using Content.Client.Graphics; using Content.Shared.Light.Components; using Robust.Client.Graphics; using Robust.Shared.Enums; @@ -24,8 +25,7 @@ public sealed class SunShadowOverlay : Overlay private readonly HashSet> _shadows = new(); - private IRenderTexture? _blurTarget; - private IRenderTexture? _target; + private readonly OverlayResourceCache _resources = new(); public SunShadowOverlay() { @@ -55,16 +55,18 @@ public sealed class SunShadowOverlay : Overlay var worldBounds = args.WorldBounds; var targetSize = viewport.LightRenderTarget.Size; - if (_target?.Size != targetSize) + var res = _resources.GetForViewport(args.Viewport, static _ => new CachedResources()); + + if (res.Target?.Size != targetSize) { - _target = _clyde + res.Target = _clyde .CreateRenderTarget(targetSize, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "sun-shadow-target"); - if (_blurTarget?.Size != targetSize) + if (res.BlurTarget?.Size != targetSize) { - _blurTarget = _clyde + res.BlurTarget = _clyde .CreateRenderTarget(targetSize, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "sun-shadow-blur"); } } @@ -93,11 +95,11 @@ public sealed class SunShadowOverlay : Overlay _shadows.Clear(); // Draw shadow polys to stencil - args.WorldHandle.RenderInRenderTarget(_target, + args.WorldHandle.RenderInRenderTarget(res.Target, () => { var invMatrix = - _target.GetWorldToLocalMatrix(eye, scale); + res.Target.GetWorldToLocalMatrix(eye, scale); var indices = new Vector2[PhysicsConstants.MaxPolygonVertices * 2]; // Go through shadows in range. @@ -142,7 +144,7 @@ public sealed class SunShadowOverlay : Overlay Color.Transparent); // Slightly blur it just to avoid aliasing issues on the later viewport-wide blur. - _clyde.BlurRenderTarget(viewport, _target, _blurTarget!, eye, 1f); + _clyde.BlurRenderTarget(viewport, res.Target, res.BlurTarget!, eye, 1f); // Draw stencil (see roofoverlay). args.WorldHandle.RenderInRenderTarget(viewport.LightRenderTarget, @@ -155,8 +157,27 @@ public sealed class SunShadowOverlay : Overlay var maskShader = _protoManager.Index(MixShader).Instance(); worldHandle.UseShader(maskShader); - worldHandle.DrawTextureRect(_target.Texture, worldBounds, Color.Black.WithAlpha(alpha)); + worldHandle.DrawTextureRect(res.Target.Texture, worldBounds, Color.Black.WithAlpha(alpha)); }, null); } } + + protected override void DisposeBehavior() + { + _resources.Dispose(); + + base.DisposeBehavior(); + } + + private sealed class CachedResources : IDisposable + { + public IRenderTexture? BlurTarget; + public IRenderTexture? Target; + + public void Dispose() + { + BlurTarget?.Dispose(); + Target?.Dispose(); + } + } } diff --git a/Content.Client/Light/TileEmissionOverlay.cs b/Content.Client/Light/TileEmissionOverlay.cs index 2f4a1390ff..2acb0ee609 100644 --- a/Content.Client/Light/TileEmissionOverlay.cs +++ b/Content.Client/Light/TileEmissionOverlay.cs @@ -47,7 +47,7 @@ public sealed class TileEmissionOverlay : Overlay var worldHandle = args.WorldHandle; var lightoverlay = _overlay.GetOverlay(); var bounds = lightoverlay.EnlargedBounds; - var target = lightoverlay.EnlargedLightTarget; + var target = lightoverlay.GetCachedForViewport(args.Viewport).EnlargedLightTarget; var viewport = args.Viewport; _grids.Clear(); _mapManager.FindGridsIntersecting(mapId, bounds, ref _grids, approx: true); diff --git a/Content.Client/Overlays/StencilOverlay.RestrictedRange.cs b/Content.Client/Overlays/StencilOverlay.RestrictedRange.cs index a5efacc16c..7218e16da1 100644 --- a/Content.Client/Overlays/StencilOverlay.RestrictedRange.cs +++ b/Content.Client/Overlays/StencilOverlay.RestrictedRange.cs @@ -7,7 +7,11 @@ namespace Content.Client.Overlays; public sealed partial class StencilOverlay { - private void DrawRestrictedRange(in OverlayDrawArgs args, RestrictedRangeComponent rangeComp, Matrix3x2 invMatrix) + private void DrawRestrictedRange( + in OverlayDrawArgs args, + CachedResources res, + RestrictedRangeComponent rangeComp, + Matrix3x2 invMatrix) { var worldHandle = args.WorldHandle; var renderScale = args.Viewport.RenderScale.X; @@ -38,7 +42,7 @@ public sealed partial class StencilOverlay // Cut out the irrelevant bits via stencil // This is why we don't just use parallax; we might want specific tiles to get drawn over // particularly for planet maps or stations. - worldHandle.RenderInRenderTarget(_blep!, () => + worldHandle.RenderInRenderTarget(res.Blep!, () => { worldHandle.UseShader(_shader); worldHandle.DrawRect(localAABB, Color.White); @@ -46,7 +50,7 @@ public sealed partial class StencilOverlay worldHandle.SetTransform(Matrix3x2.Identity); worldHandle.UseShader(_protoManager.Index(StencilMask).Instance()); - worldHandle.DrawTextureRect(_blep!.Texture, worldBounds); + worldHandle.DrawTextureRect(res.Blep!.Texture, worldBounds); var curTime = _timing.RealTime; var sprite = _sprite.GetFrame(new SpriteSpecifier.Texture(new ResPath("/Textures/Parallaxes/noise.png")), curTime); diff --git a/Content.Client/Overlays/StencilOverlay.Weather.cs b/Content.Client/Overlays/StencilOverlay.Weather.cs index 509b946ad4..66a6a799a7 100644 --- a/Content.Client/Overlays/StencilOverlay.Weather.cs +++ b/Content.Client/Overlays/StencilOverlay.Weather.cs @@ -11,7 +11,12 @@ public sealed partial class StencilOverlay { private List> _grids = new(); - private void DrawWeather(in OverlayDrawArgs args, WeatherPrototype weatherProto, float alpha, Matrix3x2 invMatrix) + private void DrawWeather( + in OverlayDrawArgs args, + CachedResources res, + WeatherPrototype weatherProto, + float alpha, + Matrix3x2 invMatrix) { var worldHandle = args.WorldHandle; var mapId = args.MapId; @@ -22,7 +27,7 @@ public sealed partial class StencilOverlay // Cut out the irrelevant bits via stencil // This is why we don't just use parallax; we might want specific tiles to get drawn over // particularly for planet maps or stations. - worldHandle.RenderInRenderTarget(_blep!, () => + worldHandle.RenderInRenderTarget(res.Blep!, () => { var xformQuery = _entManager.GetEntityQuery(); _grids.Clear(); @@ -56,7 +61,7 @@ public sealed partial class StencilOverlay worldHandle.SetTransform(Matrix3x2.Identity); worldHandle.UseShader(_protoManager.Index(StencilMask).Instance()); - worldHandle.DrawTextureRect(_blep!.Texture, worldBounds); + worldHandle.DrawTextureRect(res.Blep!.Texture, worldBounds); var curTime = _timing.RealTime; var sprite = _sprite.GetFrame(weatherProto.Sprite, curTime); diff --git a/Content.Client/Overlays/StencilOverlay.cs b/Content.Client/Overlays/StencilOverlay.cs index 55cb1811a5..276181468b 100644 --- a/Content.Client/Overlays/StencilOverlay.cs +++ b/Content.Client/Overlays/StencilOverlay.cs @@ -1,4 +1,5 @@ using System.Numerics; +using Content.Client.Graphics; using Content.Client.Parallax; using Content.Client.Weather; using Content.Shared.Salvage; @@ -34,7 +35,7 @@ public sealed partial class StencilOverlay : Overlay public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV; - private IRenderTexture? _blep; + private readonly OverlayResourceCache _resources = new(); private readonly ShaderInstance _shader; @@ -55,10 +56,12 @@ public sealed partial class StencilOverlay : Overlay var mapUid = _map.GetMapOrInvalid(args.MapId); var invMatrix = args.Viewport.GetWorldToLocalMatrix(); - if (_blep?.Texture.Size != args.Viewport.Size) + var res = _resources.GetForViewport(args.Viewport, static _ => new CachedResources()); + + if (res.Blep?.Texture.Size != args.Viewport.Size) { - _blep?.Dispose(); - _blep = _clyde.CreateRenderTarget(args.Viewport.Size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "weather-stencil"); + res.Blep?.Dispose(); + res.Blep = _clyde.CreateRenderTarget(args.Viewport.Size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "weather-stencil"); } if (_entManager.TryGetComponent(mapUid, out var comp)) @@ -69,16 +72,33 @@ public sealed partial class StencilOverlay : Overlay continue; var alpha = _weather.GetPercent(weather, mapUid); - DrawWeather(args, weatherProto, alpha, invMatrix); + DrawWeather(args, res, weatherProto, alpha, invMatrix); } } if (_entManager.TryGetComponent(mapUid, out var restrictedRangeComponent)) { - DrawRestrictedRange(args, restrictedRangeComponent, invMatrix); + DrawRestrictedRange(args, res, restrictedRangeComponent, invMatrix); } args.WorldHandle.UseShader(null); args.WorldHandle.SetTransform(Matrix3x2.Identity); } + + protected override void DisposeBehavior() + { + _resources.Dispose(); + + base.DisposeBehavior(); + } + + private sealed class CachedResources : IDisposable + { + public IRenderTexture? Blep; + + public void Dispose() + { + Blep?.Dispose(); + } + } } diff --git a/Content.Client/Silicons/StationAi/StationAiOverlay.cs b/Content.Client/Silicons/StationAi/StationAiOverlay.cs index 5c84ce0c93..7657744702 100644 --- a/Content.Client/Silicons/StationAi/StationAiOverlay.cs +++ b/Content.Client/Silicons/StationAi/StationAiOverlay.cs @@ -1,4 +1,5 @@ using System.Numerics; +using Content.Client.Graphics; using Content.Shared.Silicons.StationAi; using Robust.Client.Graphics; using Robust.Client.Player; @@ -26,8 +27,7 @@ public sealed class StationAiOverlay : Overlay private readonly HashSet _visibleTiles = new(); - private IRenderTexture? _staticTexture; - private IRenderTexture? _stencilTexture; + private readonly OverlayResourceCache _resources = new(); private float _updateRate = 1f / 30f; private float _accumulator; @@ -39,12 +39,14 @@ public sealed class StationAiOverlay : Overlay protected override void Draw(in OverlayDrawArgs args) { - if (_stencilTexture?.Texture.Size != args.Viewport.Size) + var res = _resources.GetForViewport(args.Viewport, static _ => new CachedResources()); + + if (res.StencilTexture?.Texture.Size != args.Viewport.Size) { - _staticTexture?.Dispose(); - _stencilTexture?.Dispose(); - _stencilTexture = _clyde.CreateRenderTarget(args.Viewport.Size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "station-ai-stencil"); - _staticTexture = _clyde.CreateRenderTarget(args.Viewport.Size, + res.StaticTexture?.Dispose(); + res.StencilTexture?.Dispose(); + res.StencilTexture = _clyde.CreateRenderTarget(args.Viewport.Size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "station-ai-stencil"); + res.StaticTexture = _clyde.CreateRenderTarget(args.Viewport.Size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "station-ai-static"); } @@ -78,7 +80,7 @@ public sealed class StationAiOverlay : Overlay var matty = Matrix3x2.Multiply(gridMatrix, invMatrix); // Draw visible tiles to stencil - worldHandle.RenderInRenderTarget(_stencilTexture!, () => + worldHandle.RenderInRenderTarget(res.StencilTexture!, () => { worldHandle.SetTransform(matty); @@ -91,7 +93,7 @@ public sealed class StationAiOverlay : Overlay Color.Transparent); // Once this is gucci optimise rendering. - worldHandle.RenderInRenderTarget(_staticTexture!, + worldHandle.RenderInRenderTarget(res.StaticTexture!, () => { worldHandle.SetTransform(invMatrix); @@ -104,12 +106,12 @@ public sealed class StationAiOverlay : Overlay // Not on a grid else { - worldHandle.RenderInRenderTarget(_stencilTexture!, () => + worldHandle.RenderInRenderTarget(res.StencilTexture!, () => { }, Color.Transparent); - worldHandle.RenderInRenderTarget(_staticTexture!, + worldHandle.RenderInRenderTarget(res.StaticTexture!, () => { worldHandle.SetTransform(Matrix3x2.Identity); @@ -119,14 +121,33 @@ public sealed class StationAiOverlay : Overlay // Use the lighting as a mask worldHandle.UseShader(_proto.Index(StencilMaskShader).Instance()); - worldHandle.DrawTextureRect(_stencilTexture!.Texture, worldBounds); + worldHandle.DrawTextureRect(res.StencilTexture!.Texture, worldBounds); // Draw the static worldHandle.UseShader(_proto.Index(StencilDrawShader).Instance()); - worldHandle.DrawTextureRect(_staticTexture!.Texture, worldBounds); + worldHandle.DrawTextureRect(res.StaticTexture!.Texture, worldBounds); worldHandle.SetTransform(Matrix3x2.Identity); worldHandle.UseShader(null); } + + protected override void DisposeBehavior() + { + _resources.Dispose(); + + base.DisposeBehavior(); + } + + private sealed class CachedResources : IDisposable + { + public IRenderTexture? StaticTexture; + public IRenderTexture? StencilTexture; + + public void Dispose() + { + StaticTexture?.Dispose(); + StencilTexture?.Dispose(); + } + } }