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();
+ }
+ }
}