From d9fd0f272c7d76888c573346e1dcec93bc73b41c Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Mon, 30 Apr 2018 18:40:16 +0000 Subject: [PATCH] Add support on-demand sprite frame loading. Passing the getUsedFrames argument will cause frames to be buffered locally (and not added to the SheetBuilder) until they are explicitly requested. This allows unused frames in sprite files to be skipped instead of wasting space in GPU memory. --- OpenRA.Game/Graphics/SpriteLoader.cs | 73 ++++++++++++++----- .../ConvertSpriteToPngCommand.cs | 2 +- 2 files changed, 56 insertions(+), 19 deletions(-) diff --git a/OpenRA.Game/Graphics/SpriteLoader.cs b/OpenRA.Game/Graphics/SpriteLoader.cs index 86cbff57ea..5bad9d2da6 100644 --- a/OpenRA.Game/Graphics/SpriteLoader.cs +++ b/OpenRA.Game/Graphics/SpriteLoader.cs @@ -9,6 +9,7 @@ */ #endregion +using System; using System.Collections.Generic; using System.Drawing; using System.IO; @@ -48,6 +49,7 @@ namespace OpenRA.Graphics readonly IReadOnlyFileSystem fileSystem; readonly Dictionary> sprites = new Dictionary>(); + readonly Dictionary unloadedFrames = new Dictionary(); public SpriteCache(IReadOnlyFileSystem fileSystem, ISpriteLoader[] loaders, SheetBuilder sheetBuilder) { @@ -56,21 +58,56 @@ namespace OpenRA.Graphics this.loaders = loaders; } - Sprite[] LoadSprite(string filename, List cache) - { - var sprite = SpriteLoader.GetSprites(fileSystem, filename, loaders, SheetBuilder); - cache.Add(sprite); - return sprite; - } - - /// Returns the first set of sprites with the given filename. - public Sprite[] this[string filename] + /// + /// Returns the first set of sprites with the given filename. + /// If getUsedFrames is defined then the indices returned by the function call + /// are guaranteed to be loaded. The value of other indices in the returned + /// array are undefined and should never be accessed. + /// + public Sprite[] this[string filename, Func> getUsedFrames = null] { get { var allSprites = sprites.GetOrAdd(filename); var sprite = allSprites.FirstOrDefault(); - return sprite ?? LoadSprite(filename, allSprites); + + ISpriteFrame[] unloaded; + if (!unloadedFrames.TryGetValue(filename, out unloaded)) + unloaded = null; + + // This is the first time that the file has been requested + // Load all of the frames into the unused buffer and initialize + // the loaded cache (initially empty) + if (sprite == null) + { + unloaded = FrameLoader.GetFrames(fileSystem, filename, loaders); + unloadedFrames[filename] = unloaded; + + sprite = new Sprite[unloaded.Length]; + allSprites.Add(sprite); + } + + // Load any unused frames into the SheetBuilder + if (unloaded != null) + { + var indices = getUsedFrames != null ? getUsedFrames(sprite.Length) : + Enumerable.Range(0, sprite.Length); + + foreach (var i in indices) + { + if (unloaded[i] != null) + { + sprite[i] = SheetBuilder.Add(unloaded[i]); + unloaded[i] = null; + } + } + + // All frames have been loaded + if (unloaded.All(f => f == null)) + unloadedFrames.Remove(filename); + } + + return sprite; } } @@ -83,7 +120,12 @@ namespace OpenRA.Graphics /// Loads and caches a new instance of sprites with the given filename public Sprite[] Reload(string filename) { - return LoadSprite(filename, sprites.GetOrAdd(filename)); + var sprite = FrameLoader.GetFrames(fileSystem, filename, loaders) + .Select(a => SheetBuilder.Add(a)) + .ToArray(); + + sprites.GetOrAdd(filename).Add(sprite); + return sprite; } } @@ -93,19 +135,14 @@ namespace OpenRA.Graphics public FrameCache(IReadOnlyFileSystem fileSystem, ISpriteLoader[] loaders) { - frames = new Cache(filename => SpriteLoader.GetFrames(fileSystem, filename, loaders)); + frames = new Cache(filename => FrameLoader.GetFrames(fileSystem, filename, loaders)); } public ISpriteFrame[] this[string filename] { get { return frames[filename]; } } } - public static class SpriteLoader + public static class FrameLoader { - public static Sprite[] GetSprites(IReadOnlyFileSystem fileSystem, string filename, ISpriteLoader[] loaders, SheetBuilder sheetBuilder) - { - return GetFrames(fileSystem, filename, loaders).Select(a => sheetBuilder.Add(a)).ToArray(); - } - public static ISpriteFrame[] GetFrames(IReadOnlyFileSystem fileSystem, string filename, ISpriteLoader[] loaders) { using (var stream = fileSystem.Open(filename)) diff --git a/OpenRA.Mods.Common/UtilityCommands/ConvertSpriteToPngCommand.cs b/OpenRA.Mods.Common/UtilityCommands/ConvertSpriteToPngCommand.cs index d1b80a1e8c..26c6d6ef1c 100644 --- a/OpenRA.Mods.Common/UtilityCommands/ConvertSpriteToPngCommand.cs +++ b/OpenRA.Mods.Common/UtilityCommands/ConvertSpriteToPngCommand.cs @@ -47,7 +47,7 @@ namespace OpenRA.Mods.Common.UtilityCommands var palette = new ImmutablePalette(args[2], shadowIndex); - var frames = SpriteLoader.GetFrames(File.OpenRead(src), modData.SpriteLoaders); + var frames = FrameLoader.GetFrames(File.OpenRead(src), modData.SpriteLoaders); var usePadding = !args.Contains("--nopadding"); var count = 0;