mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
RSI meta atlas (#3545)
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
@@ -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!;
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user