Files
tbd-station-14/Content.Client/Light/BeforeLightTargetOverlay.cs
Pieter-Jan Briers 8c16b4580b Fix render target caching in overlays (#40181)
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...
2025-09-21 17:16:17 +12:00

79 lines
2.5 KiB
C#

using Content.Client.Graphics;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
namespace Content.Client.Light;
/// <summary>
/// Handles an enlarged lighting target so content can use large blur radii.
/// </summary>
public sealed class BeforeLightTargetOverlay : Overlay
{
public override OverlaySpace Space => OverlaySpace.BeforeLighting;
[Dependency] private readonly IClyde _clyde = default!;
private readonly OverlayResourceCache<CachedResources> _resources = new();
public Box2Rotated EnlargedBounds;
/// <summary>
/// In metres
/// </summary>
private float _skirting = 2f;
public const int ContentZIndex = -10;
public BeforeLightTargetOverlay()
{
IoCManager.InjectDependencies(this);
ZIndex = ContentZIndex;
}
protected override void Draw(in OverlayDrawArgs args)
{
// Code is weird but I don't think engine should be enlarging the lighting render target arbitrarily either, maybe via cvar?
// The problem is the blur has no knowledge of pixels outside the viewport so with a large enough blur radius you get sampling issues.
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 (res.EnlargedLightTarget?.Size != size)
{
res.EnlargedLightTarget = _clyde
.CreateRenderTarget(size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "enlarged-light-copy");
}
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();
}
}
}