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...
This commit is contained in:
Pieter-Jan Briers
2025-09-21 07:16:17 +02:00
committed by GitHub
parent cc4cab5677
commit 8c16b4580b
12 changed files with 303 additions and 70 deletions

View File

@@ -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<Entity<SunShadowCastComponent>> _shadows = new();
private IRenderTexture? _blurTarget;
private IRenderTexture? _target;
private readonly OverlayResourceCache<CachedResources> _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();
}
}
}