diff --git a/OpenRA.Game/Graphics/SequenceProvider.cs b/OpenRA.Game/Graphics/SequenceProvider.cs index 234856d228..db86dfffff 100644 --- a/OpenRA.Game/Graphics/SequenceProvider.cs +++ b/OpenRA.Game/Graphics/SequenceProvider.cs @@ -64,7 +64,7 @@ namespace OpenRA.Graphics return Load(fileSystem, additionalSequences); }); - spriteCache = Exts.Lazy(() => new SpriteCache(fileSystem, modData.SpriteLoaders, new SheetBuilder(SheetType.Indexed))); + spriteCache = Exts.Lazy(() => new SpriteCache(fileSystem, modData.SpriteLoaders)); } public ISpriteSequence GetSequence(string unitName, string sequenceName) @@ -130,16 +130,21 @@ namespace OpenRA.Graphics public void Preload() { - SpriteCache.SheetBuilder.Current.CreateBuffer(); + foreach (var sb in SpriteCache.SheetBuilders.Values) + sb.Current.CreateBuffer(); + foreach (var unitSeq in sequences.Value.Values) foreach (var seq in unitSeq.Value.Values) { } - SpriteCache.SheetBuilder.Current.ReleaseBuffer(); + + foreach (var sb in SpriteCache.SheetBuilders.Values) + sb.Current.ReleaseBuffer(); } public void Dispose() { if (spriteCache.IsValueCreated) - spriteCache.Value.SheetBuilder.Dispose(); + foreach (var sb in SpriteCache.SheetBuilders.Values) + sb.Dispose(); } } } diff --git a/OpenRA.Game/Graphics/Sheet.cs b/OpenRA.Game/Graphics/Sheet.cs index 95bd31822a..51da5762f5 100644 --- a/OpenRA.Game/Graphics/Sheet.cs +++ b/OpenRA.Game/Graphics/Sheet.cs @@ -79,7 +79,17 @@ namespace OpenRA.Graphics public Png AsPng() { - return new Png(GetData(), Size.Width, Size.Height); + var data = GetData(); + + // Convert BGRA to RGBA + for (var i = 0; i < Size.Width * Size.Height; i++) + { + var temp = data[i * 4]; + data[i * 4] = data[i * 4 + 2]; + data[i * 4 + 2] = temp; + } + + return new Png(data, Size.Width, Size.Height); } public Png AsPng(TextureChannel channel, IPalette pal) diff --git a/OpenRA.Game/Graphics/SheetBuilder.cs b/OpenRA.Game/Graphics/SheetBuilder.cs index 3393ebd92a..61b1fff8ac 100644 --- a/OpenRA.Game/Graphics/SheetBuilder.cs +++ b/OpenRA.Game/Graphics/SheetBuilder.cs @@ -47,6 +47,16 @@ namespace OpenRA.Graphics return new Sheet(type, new Size(sheetSize, sheetSize)); } + public static SheetType FrameTypeToSheetType(SpriteFrameType t) + { + switch (t) + { + case SpriteFrameType.Indexed: return SheetType.Indexed; + case SpriteFrameType.BGRA: return SheetType.BGRA; + default: throw new NotImplementedException("Unknown SpriteFrameType {0}".F(t)); + } + } + public SheetBuilder(SheetType t) : this(t, Game.Settings.Graphics.SheetSize) { } diff --git a/OpenRA.Game/Graphics/SpriteLoader.cs b/OpenRA.Game/Graphics/SpriteLoader.cs index eac91c4ce0..f8ac8d43da 100644 --- a/OpenRA.Game/Graphics/SpriteLoader.cs +++ b/OpenRA.Game/Graphics/SpriteLoader.cs @@ -18,6 +18,8 @@ using OpenRA.Primitives; namespace OpenRA.Graphics { + public enum SpriteFrameType { Indexed, BGRA } + public interface ISpriteLoader { bool TryParseSprite(Stream s, out ISpriteFrame[] frames, out TypeDictionary metadata); @@ -25,6 +27,8 @@ namespace OpenRA.Graphics public interface ISpriteFrame { + SpriteFrameType Type { get; } + /// /// Size of the frame's `Data`. /// @@ -43,7 +47,7 @@ namespace OpenRA.Graphics public class SpriteCache { - public readonly SheetBuilder SheetBuilder; + public readonly Cache SheetBuilders; readonly ISpriteLoader[] loaders; readonly IReadOnlyFileSystem fileSystem; @@ -51,9 +55,10 @@ namespace OpenRA.Graphics readonly Dictionary unloadedFrames = new Dictionary(); readonly Dictionary metadata = new Dictionary(); - public SpriteCache(IReadOnlyFileSystem fileSystem, ISpriteLoader[] loaders, SheetBuilder sheetBuilder) + public SpriteCache(IReadOnlyFileSystem fileSystem, ISpriteLoader[] loaders) { - SheetBuilder = sheetBuilder; + SheetBuilders = new Cache(t => new SheetBuilder(SheetBuilder.FrameTypeToSheetType(t))); + this.fileSystem = fileSystem; this.loaders = loaders; } @@ -89,7 +94,7 @@ namespace OpenRA.Graphics allSprites.Add(sprite); } - // HACK: The sequency code relies on side-effects from getUsedFrames + // HACK: The sequence code relies on side-effects from getUsedFrames var indices = getUsedFrames != null ? getUsedFrames(sprite.Length) : Enumerable.Range(0, sprite.Length); @@ -100,7 +105,7 @@ namespace OpenRA.Graphics { if (unloaded[i] != null) { - sprite[i] = SheetBuilder.Add(unloaded[i]); + sprite[i] = SheetBuilders[unloaded[i].Type].Add(unloaded[i]); unloaded[i] = null; } } diff --git a/OpenRA.Game/Graphics/Theater.cs b/OpenRA.Game/Graphics/Theater.cs index 6fb6c0ba63..af4a2ecc6a 100644 --- a/OpenRA.Game/Graphics/Theater.cs +++ b/OpenRA.Game/Graphics/Theater.cs @@ -11,6 +11,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using OpenRA.Primitives; using OpenRA.Support; @@ -34,7 +35,7 @@ namespace OpenRA.Graphics public sealed class Theater : IDisposable { readonly Dictionary templates = new Dictionary(); - readonly SheetBuilder sheetBuilder; + SheetBuilder sheetBuilder; readonly Sprite missingTile; readonly MersenneTwister random; TileSet tileset; @@ -53,7 +54,6 @@ namespace OpenRA.Graphics return new Sheet(SheetType.Indexed, new Size(tileset.SheetSize, tileset.SheetSize)); }; - sheetBuilder = new SheetBuilder(SheetType.Indexed, allocate); random = new MersenneTwister(); var frameCache = new FrameCache(Game.ModData.DefaultFileSystem, Game.ModData.SpriteLoaders); @@ -75,6 +75,15 @@ namespace OpenRA.Graphics var zOffset = tile != null ? -tile.ZOffset : 0; var zRamp = tile != null ? tile.ZRamp : 1f; var offset = new float3(f.Offset, zOffset); + var type = SheetBuilder.FrameTypeToSheetType(f.Type); + + // Defer SheetBuilder creation until we know what type of frames we are loading! + // TODO: Support mixed indexed and BGRA frames + if (sheetBuilder == null) + sheetBuilder = new SheetBuilder(SheetBuilder.FrameTypeToSheetType(f.Type), allocate); + else if (type != sheetBuilder.Type) + throw new InvalidDataException("Sprite type mismatch. Terrain sprites must all be either Indexed or RGBA."); + var s = sheetBuilder.Allocate(f.Size, zRamp, offset); Util.FastCopyIntoChannel(s, f.Data); @@ -102,7 +111,7 @@ namespace OpenRA.Graphics } // 1x1px transparent tile - missingTile = sheetBuilder.Add(new byte[1], new Size(1, 1)); + missingTile = sheetBuilder.Add(new byte[sheetBuilder.Type == SheetType.BGRA ? 4 : 1], new Size(1, 1)); Sheet.ReleaseBuffer(); } diff --git a/OpenRA.Game/Graphics/Util.cs b/OpenRA.Game/Graphics/Util.cs index 8178ebe36b..123b2a4eab 100644 --- a/OpenRA.Game/Graphics/Util.cs +++ b/OpenRA.Game/Graphics/Util.cs @@ -61,23 +61,56 @@ namespace OpenRA.Graphics public static void FastCopyIntoChannel(Sprite dest, byte[] src) { - var data = dest.Sheet.GetData(); - var srcStride = dest.Bounds.Width; - var destStride = dest.Sheet.Size.Width * 4; - var destOffset = destStride * dest.Bounds.Top + dest.Bounds.Left * 4 + ChannelMasks[(int)dest.Channel]; - var destSkip = destStride - 4 * srcStride; + var destData = dest.Sheet.GetData(); + var width = dest.Bounds.Width; var height = dest.Bounds.Height; - var srcOffset = 0; - for (var j = 0; j < height; j++) + if (dest.Channel == TextureChannel.RGBA) { - for (var i = 0; i < srcStride; i++, srcOffset++) + var destStride = dest.Sheet.Size.Width; + unsafe { - data[destOffset] = src[srcOffset]; - destOffset += 4; - } + // Cast the data to an int array so we can copy the src data directly + fixed (byte* bd = &destData[0]) + { + var data = (int*)bd; + var x = dest.Bounds.Left; + var y = dest.Bounds.Top; - destOffset += destSkip; + var k = 0; + for (var j = 0; j < height; j++) + { + for (var i = 0; i < width; i++) + { + var r = src[k++]; + var g = src[k++]; + var b = src[k++]; + var a = src[k++]; + var cc = Color.FromArgb(a, r, g, b); + + data[(y + j) * destStride + x + i] = PremultiplyAlpha(cc).ToArgb(); + } + } + } + } + } + else + { + var destStride = dest.Sheet.Size.Width * 4; + var destOffset = destStride * dest.Bounds.Top + dest.Bounds.Left * 4 + ChannelMasks[(int)dest.Channel]; + var destSkip = destStride - 4 * width; + + var srcOffset = 0; + for (var j = 0; j < height; j++) + { + for (var i = 0; i < width; i++, srcOffset++) + { + destData[destOffset] = src[srcOffset]; + destOffset += 4; + } + + destOffset += destSkip; + } } } diff --git a/OpenRA.Mods.Cnc/SpriteLoaders/ShpD2Loader.cs b/OpenRA.Mods.Cnc/SpriteLoaders/ShpD2Loader.cs index 6df9f93253..f9df876880 100644 --- a/OpenRA.Mods.Cnc/SpriteLoaders/ShpD2Loader.cs +++ b/OpenRA.Mods.Cnc/SpriteLoaders/ShpD2Loader.cs @@ -29,6 +29,7 @@ namespace OpenRA.Mods.Cnc.SpriteLoaders class ShpD2Frame : ISpriteFrame { + public SpriteFrameType Type { get { return SpriteFrameType.Indexed; } } public Size Size { get; private set; } public Size FrameSize { get { return Size; } } public float2 Offset { get { return float2.Zero; } } diff --git a/OpenRA.Mods.Cnc/SpriteLoaders/ShpTDLoader.cs b/OpenRA.Mods.Cnc/SpriteLoaders/ShpTDLoader.cs index 559b19092b..1eed81d813 100644 --- a/OpenRA.Mods.Cnc/SpriteLoaders/ShpTDLoader.cs +++ b/OpenRA.Mods.Cnc/SpriteLoaders/ShpTDLoader.cs @@ -78,6 +78,7 @@ namespace OpenRA.Mods.Cnc.SpriteLoaders class ImageHeader : ISpriteFrame { + public SpriteFrameType Type { get { return SpriteFrameType.Indexed; } } public Size Size { get { return reader.Size; } } public Size FrameSize { get { return reader.Size; } } public float2 Offset { get { return float2.Zero; } } diff --git a/OpenRA.Mods.Cnc/SpriteLoaders/TmpRALoader.cs b/OpenRA.Mods.Cnc/SpriteLoaders/TmpRALoader.cs index c55c491216..3285488583 100644 --- a/OpenRA.Mods.Cnc/SpriteLoaders/TmpRALoader.cs +++ b/OpenRA.Mods.Cnc/SpriteLoaders/TmpRALoader.cs @@ -19,6 +19,7 @@ namespace OpenRA.Mods.Cnc.SpriteLoaders { class TmpRAFrame : ISpriteFrame { + public SpriteFrameType Type { get { return SpriteFrameType.Indexed; } } public Size Size { get; private set; } public Size FrameSize { get; private set; } public float2 Offset { get { return float2.Zero; } } diff --git a/OpenRA.Mods.Cnc/SpriteLoaders/TmpTDLoader.cs b/OpenRA.Mods.Cnc/SpriteLoaders/TmpTDLoader.cs index 1d4cddc002..6c3d80a0e0 100644 --- a/OpenRA.Mods.Cnc/SpriteLoaders/TmpTDLoader.cs +++ b/OpenRA.Mods.Cnc/SpriteLoaders/TmpTDLoader.cs @@ -19,6 +19,7 @@ namespace OpenRA.Mods.Cnc.SpriteLoaders { class TmpTDFrame : ISpriteFrame { + public SpriteFrameType Type { get { return SpriteFrameType.Indexed; } } public Size Size { get; private set; } public Size FrameSize { get; private set; } public float2 Offset { get { return float2.Zero; } } diff --git a/OpenRA.Mods.Cnc/SpriteLoaders/TmpTSLoader.cs b/OpenRA.Mods.Cnc/SpriteLoaders/TmpTSLoader.cs index 4e503a2ef7..85abab2c47 100644 --- a/OpenRA.Mods.Cnc/SpriteLoaders/TmpTSLoader.cs +++ b/OpenRA.Mods.Cnc/SpriteLoaders/TmpTSLoader.cs @@ -21,6 +21,7 @@ namespace OpenRA.Mods.Cnc.SpriteLoaders { readonly TmpTSFrame parent; + public SpriteFrameType Type { get { return SpriteFrameType.Indexed; } } public Size Size { get { return parent.Size; } } public Size FrameSize { get { return Size; } } public float2 Offset { get { return parent.Offset; } } @@ -35,6 +36,7 @@ namespace OpenRA.Mods.Cnc.SpriteLoaders class TmpTSFrame : ISpriteFrame { + public SpriteFrameType Type { get { return SpriteFrameType.Indexed; } } public Size Size { get; private set; } public Size FrameSize { get { return Size; } } public float2 Offset { get; private set; } diff --git a/OpenRA.Mods.Common/SpriteLoaders/ShpTSLoader.cs b/OpenRA.Mods.Common/SpriteLoaders/ShpTSLoader.cs index 0c9d6e833b..925afa482f 100644 --- a/OpenRA.Mods.Common/SpriteLoaders/ShpTSLoader.cs +++ b/OpenRA.Mods.Common/SpriteLoaders/ShpTSLoader.cs @@ -20,6 +20,7 @@ namespace OpenRA.Mods.Common.SpriteLoaders { class ShpTSFrame : ISpriteFrame { + public SpriteFrameType Type { get { return SpriteFrameType.Indexed; } } public Size Size { get; private set; } public Size FrameSize { get; private set; } public float2 Offset { get; private set; } diff --git a/OpenRA.Mods.Common/UtilityCommands/CheckSequenceSprites.cs b/OpenRA.Mods.Common/UtilityCommands/CheckSequenceSprites.cs index b3b557815a..a382cb9880 100644 --- a/OpenRA.Mods.Common/UtilityCommands/CheckSequenceSprites.cs +++ b/OpenRA.Mods.Common/UtilityCommands/CheckSequenceSprites.cs @@ -37,7 +37,7 @@ namespace OpenRA.Mods.Common.UtilityCommands { var ts = new TileSet(modData.DefaultFileSystem, t); Console.WriteLine("Tileset: " + ts.Name); - var sc = new SpriteCache(modData.DefaultFileSystem, modData.SpriteLoaders, new SheetBuilder(SheetType.Indexed)); + var sc = new SpriteCache(modData.DefaultFileSystem, modData.SpriteLoaders); var nodes = MiniYaml.Merge(modData.Manifest.Sequences.Select(s => MiniYaml.FromStream(modData.DefaultFileSystem.Open(s), s))); foreach (var n in nodes.Where(node => !node.Key.StartsWith(ActorInfo.AbstractActorPrefix, StringComparison.Ordinal))) modData.SpriteSequenceLoader.ParseSequences(modData, ts, sc, n); diff --git a/OpenRA.Mods.Common/UtilityCommands/ConvertSpriteToPngCommand.cs b/OpenRA.Mods.Common/UtilityCommands/ConvertSpriteToPngCommand.cs index 768f14cc8c..8a78e05203 100644 --- a/OpenRA.Mods.Common/UtilityCommands/ConvertSpriteToPngCommand.cs +++ b/OpenRA.Mods.Common/UtilityCommands/ConvertSpriteToPngCommand.cs @@ -79,8 +79,16 @@ namespace OpenRA.Mods.Common.UtilityCommands frame.Size.Width); } - var png = new Png(pngData, frameSize.Width, frameSize.Height, palColors); - png.Save("{0}-{1:D4}.png".F(prefix, count++)); + if (frame.Type == SpriteFrameType.BGRA) + { + var png = new Png(pngData, frameSize.Width, frameSize.Height); + png.Save("{0}-{1:D4}.png".F(prefix, count++)); + } + else + { + var png = new Png(pngData, frameSize.Width, frameSize.Height, palColors); + png.Save("{0}-{1:D4}.png".F(prefix, count++)); + } } Console.WriteLine("Saved {0}-[0..{1}].png", prefix, count - 1); diff --git a/OpenRA.Mods.Common/UtilityCommands/DumpSequenceSheetsCommand.cs b/OpenRA.Mods.Common/UtilityCommands/DumpSequenceSheetsCommand.cs index f420584a02..ac2af3a4a2 100644 --- a/OpenRA.Mods.Common/UtilityCommands/DumpSequenceSheetsCommand.cs +++ b/OpenRA.Mods.Common/UtilityCommands/DumpSequenceSheetsCommand.cs @@ -44,7 +44,8 @@ namespace OpenRA.Mods.Common.UtilityCommands sequences.Preload(); var count = 0; - var sb = sequences.SpriteCache.SheetBuilder; + + var sb = sequences.SpriteCache.SheetBuilders[SpriteFrameType.Indexed]; foreach (var s in sb.AllSheets) { var max = s == sb.Current ? (int)sb.CurrentChannel + 1 : 4; @@ -52,6 +53,10 @@ namespace OpenRA.Mods.Common.UtilityCommands s.AsPng((TextureChannel)ChannelMasks[i], palette).Save("{0}.png".F(count++)); } + sb = sequences.SpriteCache.SheetBuilders[SpriteFrameType.BGRA]; + foreach (var s in sb.AllSheets) + s.AsPng().Save("{0}.png".F(count++)); + Console.WriteLine("Saved [0..{0}].png", count - 1); } } diff --git a/OpenRA.Mods.D2k/SpriteLoaders/R8Loader.cs b/OpenRA.Mods.D2k/SpriteLoaders/R8Loader.cs index 1f2a017388..ec04fdc24a 100644 --- a/OpenRA.Mods.D2k/SpriteLoaders/R8Loader.cs +++ b/OpenRA.Mods.D2k/SpriteLoaders/R8Loader.cs @@ -22,6 +22,7 @@ namespace OpenRA.Mods.D2k.SpriteLoaders { class R8Frame : ISpriteFrame { + public SpriteFrameType Type { get; set; } public Size Size { get; private set; } public Size FrameSize { get; private set; } public float2 Offset { get; private set; }