RSI meta atlas (#3545)

This commit is contained in:
Leon Friedrich
2023-01-24 12:53:33 +13:00
committed by GitHub
parent 8495011f29
commit 06fba240b7
3 changed files with 85 additions and 19 deletions

View File

@@ -1,12 +1,18 @@
using System;
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Client.Graphics;
using Robust.Client.Utility;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
namespace Robust.Client.ResourceManagement
{
@@ -130,20 +136,77 @@ namespace Robust.Client.ResourceManagement
}
});
foreach (var data in rsiList)
// This combines individual RSI atlases into larger atlases to reduce draw batches. currently this is a VERY
// lazy bundling and is not at all compact, its basically an atlas of RSI atlases. Really what this should
// try to do is to have each RSI write directly to the atlas, rather than having each RSI write to its own
// sub-atlas first.
//
// Also if the max texture size is too small, such that there needs to be more than one atlas, then each
// atlas should somehow try to group things by draw-depth & frequency to minimize batches? But currently
// everything fits onto a single 8k x 8k image so as long as the computer can manage that, it should be
// fine.
// TODO allow RSIs to opt out (useful for very big & rare RSIs)
// TODO combine with (non-rsi) texture atlas?
Array.Sort(rsiList, (b, a) => b.AtlasSheet.Height.CompareTo(a.AtlasSheet.Height));
// Each RSI sub atlas has a different size.
// Even if we iterate through them once to estimate total area, I have NFI how to sanely estimate an optimal square-texture size.
// So fuck it, just default to letting it be as large as it needs to and crop it as needed?
var maxSize = Math.Min(GL.GetInteger(GetPName.MaxTextureSize), _configurationManager.GetCVar(CVars.ResRSIAtlasSize));
var sheet = new Image<Rgba32>(maxSize, maxSize);
var deltaY = 0;
Vector2i offset = default;
int finalized = -1;
int atlasCount = 0;
for (int i = 0; i < rsiList.Length; i++)
{
if (data.Bad)
var rsi = rsiList[i];
if (rsi.Bad)
continue;
try
DebugTools.Assert(rsi.AtlasSheet.Width < sheet.Width);
DebugTools.Assert(rsi.AtlasSheet.Height < sheet.Height);
if (offset.X + rsi.AtlasSheet.Width > sheet.Width)
{
RSIResource.LoadTexture(_clyde, data);
offset.X = 0;
offset.Y += deltaY;
}
catch (Exception e)
if (offset.Y + rsi.AtlasSheet.Height > sheet.Height)
{
sawmill.Error($"Exception while loading RSI {data.Path}:\n{e}");
data.Bad = true;
FinalizeMetaAtlas(i-1, sheet);
sheet = new Image<Rgba32>(maxSize, maxSize);
deltaY = 0;
offset = default;
}
deltaY = Math.Max(deltaY, rsi.AtlasSheet.Height);
var box = new UIBox2i(0, 0, rsi.AtlasSheet.Width, rsi.AtlasSheet.Height);
rsi.AtlasSheet.Blit(box, sheet, offset);
rsi.AtlasOffset = offset;
offset.X += rsi.AtlasSheet.Width;
}
var height = offset.Y + deltaY;
var croppedSheet = new Image<Rgba32>(maxSize, height);
sheet.Blit(new UIBox2i(0, 0, maxSize, height), croppedSheet, default);
FinalizeMetaAtlas(rsiList.Length - 1, croppedSheet);
void FinalizeMetaAtlas(int toIndex, Image<Rgba32> sheet)
{
var atlas = _clyde.LoadTextureFromImage(sheet);
for (int i = finalized + 1; i <= toIndex; i++)
{
var rsi = rsiList[i];
rsi.AtlasTexture = atlas;
}
finalized = toIndex;
atlasCount++;
}
Parallel.ForEach(rsiList, data =>
@@ -186,8 +249,9 @@ namespace Robust.Client.ResourceManagement
}
sawmill.Debug(
"Preloaded {CountLoaded} RSIs ({CountErrored} errored) in {LoadTime}",
"Preloaded {CountLoaded} RSIs into {CountAtlas} Atlas(es?) ({CountErrored} errored) in {LoadTime}",
rsiList.Length,
atlasCount,
errors,
sw.Elapsed);

View File

@@ -39,8 +39,9 @@ namespace Robust.Client.ResourceManagement
var loadStepData = new LoadStepData {Path = path};
LoadPreTexture(cache, loadStepData);
// Load atlas.
LoadTexture(clyde, loadStepData);
loadStepData.AtlasTexture = clyde.LoadTextureFromImage(
loadStepData.AtlasSheet,
loadStepData.Path.ToString());
LoadPostTexture(loadStepData);
LoadFinish(cache, loadStepData);
@@ -48,13 +49,6 @@ namespace Robust.Client.ResourceManagement
loadStepData.AtlasSheet.Dispose();
}
internal static void LoadTexture(IClyde clyde, LoadStepData loadStepData)
{
loadStepData.AtlasTexture = clyde.LoadTextureFromImage(
loadStepData.AtlasSheet,
loadStepData.Path.ToString());
}
internal static void LoadPreTexture(IResourceCache cache, LoadStepData data)
{
var manifestPath = data.Path / "meta.json";
@@ -210,7 +204,7 @@ namespace Robust.Client.ResourceManagement
var sheetPos = (sheetColumn * frameSize.X, sheetRow * frameSize.Y);
dirOffsets[j] = sheetPos;
dirOutput[j] = new AtlasTexture(texture, UIBox2.FromDimensions(sheetPos, frameSize));
dirOutput[j] = new AtlasTexture(texture, UIBox2.FromDimensions(data.AtlasOffset + sheetPos, frameSize));
}
}
@@ -386,6 +380,7 @@ namespace Robust.Client.ResourceManagement
public Vector2i FrameSize;
public Dictionary<RSI.StateId, Vector2i[][]> CallbackOffsets = default!;
public Texture AtlasTexture = default!;
public Vector2i AtlasOffset;
public RSI Rsi = default!;
}

View File

@@ -1175,6 +1175,13 @@ namespace Robust.Shared
public static readonly CVarDef<bool> ResTexturePreloadingEnabled =
CVarDef.Create("res.texturepreloadingenabled", true, CVar.CLIENTONLY);
/// <summary>
/// Upper limit on the size of the RSI atlas texture. A lower limit might waste less vram, but start to defeat
/// the purpose of using an atlas if it gets too small.
/// </summary>
public static readonly CVarDef<int> ResRSIAtlasSize =
CVarDef.Create("res.rsi_atlas_size", 8192, CVar.CLIENTONLY);
// TODO: Currently unimplemented.
/// <summary>
/// Cache texture preload data to speed things up even further.