Many newer overlays use IRenderTextures that are sized to the rendered viewport. This was completely broken, because a single viewport can be rendered on multiple viewports in a single frame.
The end result of this was that in the better case, constant render targets were allocated and freed, which is extremely inefficient. In the worse case, many of these overlays completely failed to Dispose() their render targets, leading to *extremely* swift VRAM OOMs.
This fixes all the overlays to properly cache resources per viewport. This uses new engine functionality, so it requires engine master.
This is still a pretty lousy way to do GPU resource management but, well, anything better needs a render graph, so...
--- /dev/null
+using Robust.Client.Graphics;
+
+namespace Content.Client.Graphics;
+
+/// <summary>
+/// A cache for <see cref="Overlay"/>s to store per-viewport render resources, such as render targets.
+/// </summary>
+/// <typeparam name="T">The type of data stored in the cache.</typeparam>
+public sealed class OverlayResourceCache<T> : IDisposable where T : class, IDisposable
+{
+ private readonly Dictionary<long, CacheEntry> _cache = new();
+
+ /// <summary>
+ /// Get the data for a specific viewport, creating a new entry if necessary.
+ /// </summary>
+ /// <remarks>
+ /// The cached data may be cleared at any time if <see cref="IClydeViewport.ClearCachedResources"/> gets invoked.
+ /// </remarks>
+ /// <param name="viewport">The viewport for which to retrieve cached data.</param>
+ /// <param name="factory">A delegate used to create the cached data, if necessary.</param>
+ public T GetForViewport(IClydeViewport viewport, Func<IClydeViewport, T> factory)
+ {
+ return GetForViewport(viewport, out _, factory);
+ }
+
+ /// <summary>
+ /// Get the data for a specific viewport, creating a new entry if necessary.
+ /// </summary>
+ /// <remarks>
+ /// The cached data may be cleared at any time if <see cref="IClydeViewport.ClearCachedResources"/> gets invoked.
+ /// </remarks>
+ /// <param name="viewport">The viewport for which to retrieve cached data.</param>
+ /// <param name="wasCached">True if the data was pulled from cache, false if it was created anew.</param>
+ /// <param name="factory">A delegate used to create the cached data, if necessary.</param>
+ public T GetForViewport(IClydeViewport viewport, out bool wasCached, Func<IClydeViewport, T> 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<IClydeViewport>(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<IClydeViewport> Viewport;
+ }
+}
return;
var lightOverlay = _overlay.GetOverlay<BeforeLightTargetOverlay>();
+ 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
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.
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);
}
}
using System.Numerics;
+using Content.Client.Graphics;
using Content.Shared.CCVar;
using Content.Shared.Maps;
using Robust.Client.Graphics;
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<CachedResources> _resources = new ();
public AmbientOcclusionOverlay()
{
var turfSystem = _entManager.System<TurfSystem>();
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))
{
}
}, 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.
// 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();
+ }
+ }
}
-using System.Numerics;
+using Content.Client.Graphics;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
[Dependency] private readonly IClyde _clyde = default!;
- public IRenderTexture EnlargedLightTarget = default!;
+ private readonly OverlayResourceCache<CachedResources> _resources = new();
+
public Box2Rotated EnlargedBounds;
/// <summary>
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();
+ }
+ }
}
+using Content.Client.Graphics;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
public const int ContentZIndex = TileEmissionOverlay.ContentZIndex + 1;
- private IRenderTarget? _blurTarget;
+ private readonly OverlayResourceCache<CachedResources> _resources = new();
public LightBlurOverlay()
{
return;
var beforeOverlay = _overlay.GetOverlay<BeforeLightTargetOverlay>();
- 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();
+ }
}
}
var worldHandle = args.WorldHandle;
var lightoverlay = _overlay.GetOverlay<BeforeLightTargetOverlay>();
+ 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);
using System.Numerics;
+using Content.Client.Graphics;
using Content.Shared.Light.Components;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
private readonly HashSet<Entity<SunShadowCastComponent>> _shadows = new();
- private IRenderTexture? _blurTarget;
- private IRenderTexture? _target;
+ private readonly OverlayResourceCache<CachedResources> _resources = new();
public SunShadowOverlay()
{
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");
}
}
_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.
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,
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();
+ }
+ }
}
var worldHandle = args.WorldHandle;
var lightoverlay = _overlay.GetOverlay<BeforeLightTargetOverlay>();
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);
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;
// 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);
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);
{
private List<Entity<MapGridComponent>> _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;
// 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<TransformComponent>();
_grids.Clear();
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);
using System.Numerics;
+using Content.Client.Graphics;
using Content.Client.Parallax;
using Content.Client.Weather;
using Content.Shared.Salvage;
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;
- private IRenderTexture? _blep;
+ private readonly OverlayResourceCache<CachedResources> _resources = new();
private readonly ShaderInstance _shader;
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<WeatherComponent>(mapUid, out var comp))
continue;
var alpha = _weather.GetPercent(weather, mapUid);
- DrawWeather(args, weatherProto, alpha, invMatrix);
+ DrawWeather(args, res, weatherProto, alpha, invMatrix);
}
}
if (_entManager.TryGetComponent<RestrictedRangeComponent>(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();
+ }
+ }
}
using System.Numerics;
+using Content.Client.Graphics;
using Content.Shared.Silicons.StationAi;
using Robust.Client.Graphics;
using Robust.Client.Player;
private readonly HashSet<Vector2i> _visibleTiles = new();
- private IRenderTexture? _staticTexture;
- private IRenderTexture? _stencilTexture;
+ private readonly OverlayResourceCache<CachedResources> _resources = new();
private float _updateRate = 1f / 30f;
private float _accumulator;
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");
}
var matty = Matrix3x2.Multiply(gridMatrix, invMatrix);
// Draw visible tiles to stencil
- worldHandle.RenderInRenderTarget(_stencilTexture!, () =>
+ worldHandle.RenderInRenderTarget(res.StencilTexture!, () =>
{
worldHandle.SetTransform(matty);
Color.Transparent);
// Once this is gucci optimise rendering.
- worldHandle.RenderInRenderTarget(_staticTexture!,
+ worldHandle.RenderInRenderTarget(res.StaticTexture!,
() =>
{
worldHandle.SetTransform(invMatrix);
// 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);
// 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();
+ }
+ }
}