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; }