Add support for 32 bit BGRA sprites.

This commit is contained in:
Paul Chote
2020-01-17 15:28:06 +00:00
committed by abcdefg30
parent cdbee49280
commit 1111ce4754
16 changed files with 122 additions and 29 deletions

View File

@@ -64,7 +64,7 @@ namespace OpenRA.Graphics
return Load(fileSystem, additionalSequences); 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) public ISpriteSequence GetSequence(string unitName, string sequenceName)
@@ -130,16 +130,21 @@ namespace OpenRA.Graphics
public void Preload() 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 unitSeq in sequences.Value.Values)
foreach (var seq in unitSeq.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() public void Dispose()
{ {
if (spriteCache.IsValueCreated) if (spriteCache.IsValueCreated)
spriteCache.Value.SheetBuilder.Dispose(); foreach (var sb in SpriteCache.SheetBuilders.Values)
sb.Dispose();
} }
} }
} }

View File

@@ -79,7 +79,17 @@ namespace OpenRA.Graphics
public Png AsPng() 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) public Png AsPng(TextureChannel channel, IPalette pal)

View File

@@ -47,6 +47,16 @@ namespace OpenRA.Graphics
return new Sheet(type, new Size(sheetSize, sheetSize)); 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) public SheetBuilder(SheetType t)
: this(t, Game.Settings.Graphics.SheetSize) { } : this(t, Game.Settings.Graphics.SheetSize) { }

View File

@@ -18,6 +18,8 @@ using OpenRA.Primitives;
namespace OpenRA.Graphics namespace OpenRA.Graphics
{ {
public enum SpriteFrameType { Indexed, BGRA }
public interface ISpriteLoader public interface ISpriteLoader
{ {
bool TryParseSprite(Stream s, out ISpriteFrame[] frames, out TypeDictionary metadata); bool TryParseSprite(Stream s, out ISpriteFrame[] frames, out TypeDictionary metadata);
@@ -25,6 +27,8 @@ namespace OpenRA.Graphics
public interface ISpriteFrame public interface ISpriteFrame
{ {
SpriteFrameType Type { get; }
/// <summary> /// <summary>
/// Size of the frame's `Data`. /// Size of the frame's `Data`.
/// </summary> /// </summary>
@@ -43,7 +47,7 @@ namespace OpenRA.Graphics
public class SpriteCache public class SpriteCache
{ {
public readonly SheetBuilder SheetBuilder; public readonly Cache<SpriteFrameType, SheetBuilder> SheetBuilders;
readonly ISpriteLoader[] loaders; readonly ISpriteLoader[] loaders;
readonly IReadOnlyFileSystem fileSystem; readonly IReadOnlyFileSystem fileSystem;
@@ -51,9 +55,10 @@ namespace OpenRA.Graphics
readonly Dictionary<string, ISpriteFrame[]> unloadedFrames = new Dictionary<string, ISpriteFrame[]>(); readonly Dictionary<string, ISpriteFrame[]> unloadedFrames = new Dictionary<string, ISpriteFrame[]>();
readonly Dictionary<string, TypeDictionary> metadata = new Dictionary<string, TypeDictionary>(); readonly Dictionary<string, TypeDictionary> metadata = new Dictionary<string, TypeDictionary>();
public SpriteCache(IReadOnlyFileSystem fileSystem, ISpriteLoader[] loaders, SheetBuilder sheetBuilder) public SpriteCache(IReadOnlyFileSystem fileSystem, ISpriteLoader[] loaders)
{ {
SheetBuilder = sheetBuilder; SheetBuilders = new Cache<SpriteFrameType, SheetBuilder>(t => new SheetBuilder(SheetBuilder.FrameTypeToSheetType(t)));
this.fileSystem = fileSystem; this.fileSystem = fileSystem;
this.loaders = loaders; this.loaders = loaders;
} }
@@ -89,7 +94,7 @@ namespace OpenRA.Graphics
allSprites.Add(sprite); 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) : var indices = getUsedFrames != null ? getUsedFrames(sprite.Length) :
Enumerable.Range(0, sprite.Length); Enumerable.Range(0, sprite.Length);
@@ -100,7 +105,7 @@ namespace OpenRA.Graphics
{ {
if (unloaded[i] != null) if (unloaded[i] != null)
{ {
sprite[i] = SheetBuilder.Add(unloaded[i]); sprite[i] = SheetBuilders[unloaded[i].Type].Add(unloaded[i]);
unloaded[i] = null; unloaded[i] = null;
} }
} }

View File

@@ -11,6 +11,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using OpenRA.Primitives; using OpenRA.Primitives;
using OpenRA.Support; using OpenRA.Support;
@@ -34,7 +35,7 @@ namespace OpenRA.Graphics
public sealed class Theater : IDisposable public sealed class Theater : IDisposable
{ {
readonly Dictionary<ushort, TheaterTemplate> templates = new Dictionary<ushort, TheaterTemplate>(); readonly Dictionary<ushort, TheaterTemplate> templates = new Dictionary<ushort, TheaterTemplate>();
readonly SheetBuilder sheetBuilder; SheetBuilder sheetBuilder;
readonly Sprite missingTile; readonly Sprite missingTile;
readonly MersenneTwister random; readonly MersenneTwister random;
TileSet tileset; TileSet tileset;
@@ -53,7 +54,6 @@ namespace OpenRA.Graphics
return new Sheet(SheetType.Indexed, new Size(tileset.SheetSize, tileset.SheetSize)); return new Sheet(SheetType.Indexed, new Size(tileset.SheetSize, tileset.SheetSize));
}; };
sheetBuilder = new SheetBuilder(SheetType.Indexed, allocate);
random = new MersenneTwister(); random = new MersenneTwister();
var frameCache = new FrameCache(Game.ModData.DefaultFileSystem, Game.ModData.SpriteLoaders); 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 zOffset = tile != null ? -tile.ZOffset : 0;
var zRamp = tile != null ? tile.ZRamp : 1f; var zRamp = tile != null ? tile.ZRamp : 1f;
var offset = new float3(f.Offset, zOffset); 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); var s = sheetBuilder.Allocate(f.Size, zRamp, offset);
Util.FastCopyIntoChannel(s, f.Data); Util.FastCopyIntoChannel(s, f.Data);
@@ -102,7 +111,7 @@ namespace OpenRA.Graphics
} }
// 1x1px transparent tile // 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(); Sheet.ReleaseBuffer();
} }

View File

@@ -61,25 +61,58 @@ namespace OpenRA.Graphics
public static void FastCopyIntoChannel(Sprite dest, byte[] src) public static void FastCopyIntoChannel(Sprite dest, byte[] src)
{ {
var data = dest.Sheet.GetData(); var destData = dest.Sheet.GetData();
var srcStride = dest.Bounds.Width; var width = dest.Bounds.Width;
var height = dest.Bounds.Height;
if (dest.Channel == TextureChannel.RGBA)
{
var destStride = dest.Sheet.Size.Width;
unsafe
{
// 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;
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 destStride = dest.Sheet.Size.Width * 4;
var destOffset = destStride * dest.Bounds.Top + dest.Bounds.Left * 4 + ChannelMasks[(int)dest.Channel]; var destOffset = destStride * dest.Bounds.Top + dest.Bounds.Left * 4 + ChannelMasks[(int)dest.Channel];
var destSkip = destStride - 4 * srcStride; var destSkip = destStride - 4 * width;
var height = dest.Bounds.Height;
var srcOffset = 0; var srcOffset = 0;
for (var j = 0; j < height; j++) for (var j = 0; j < height; j++)
{ {
for (var i = 0; i < srcStride; i++, srcOffset++) for (var i = 0; i < width; i++, srcOffset++)
{ {
data[destOffset] = src[srcOffset]; destData[destOffset] = src[srcOffset];
destOffset += 4; destOffset += 4;
} }
destOffset += destSkip; destOffset += destSkip;
} }
} }
}
public static void FastCopyIntoSprite(Sprite dest, Png src) public static void FastCopyIntoSprite(Sprite dest, Png src)
{ {

View File

@@ -29,6 +29,7 @@ namespace OpenRA.Mods.Cnc.SpriteLoaders
class ShpD2Frame : ISpriteFrame class ShpD2Frame : ISpriteFrame
{ {
public SpriteFrameType Type { get { return SpriteFrameType.Indexed; } }
public Size Size { get; private set; } public Size Size { get; private set; }
public Size FrameSize { get { return Size; } } public Size FrameSize { get { return Size; } }
public float2 Offset { get { return float2.Zero; } } public float2 Offset { get { return float2.Zero; } }

View File

@@ -78,6 +78,7 @@ namespace OpenRA.Mods.Cnc.SpriteLoaders
class ImageHeader : ISpriteFrame class ImageHeader : ISpriteFrame
{ {
public SpriteFrameType Type { get { return SpriteFrameType.Indexed; } }
public Size Size { get { return reader.Size; } } public Size Size { get { return reader.Size; } }
public Size FrameSize { get { return reader.Size; } } public Size FrameSize { get { return reader.Size; } }
public float2 Offset { get { return float2.Zero; } } public float2 Offset { get { return float2.Zero; } }

View File

@@ -19,6 +19,7 @@ namespace OpenRA.Mods.Cnc.SpriteLoaders
{ {
class TmpRAFrame : ISpriteFrame class TmpRAFrame : ISpriteFrame
{ {
public SpriteFrameType Type { get { return SpriteFrameType.Indexed; } }
public Size Size { get; private set; } public Size Size { get; private set; }
public Size FrameSize { get; private set; } public Size FrameSize { get; private set; }
public float2 Offset { get { return float2.Zero; } } public float2 Offset { get { return float2.Zero; } }

View File

@@ -19,6 +19,7 @@ namespace OpenRA.Mods.Cnc.SpriteLoaders
{ {
class TmpTDFrame : ISpriteFrame class TmpTDFrame : ISpriteFrame
{ {
public SpriteFrameType Type { get { return SpriteFrameType.Indexed; } }
public Size Size { get; private set; } public Size Size { get; private set; }
public Size FrameSize { get; private set; } public Size FrameSize { get; private set; }
public float2 Offset { get { return float2.Zero; } } public float2 Offset { get { return float2.Zero; } }

View File

@@ -21,6 +21,7 @@ namespace OpenRA.Mods.Cnc.SpriteLoaders
{ {
readonly TmpTSFrame parent; readonly TmpTSFrame parent;
public SpriteFrameType Type { get { return SpriteFrameType.Indexed; } }
public Size Size { get { return parent.Size; } } public Size Size { get { return parent.Size; } }
public Size FrameSize { get { return Size; } } public Size FrameSize { get { return Size; } }
public float2 Offset { get { return parent.Offset; } } public float2 Offset { get { return parent.Offset; } }
@@ -35,6 +36,7 @@ namespace OpenRA.Mods.Cnc.SpriteLoaders
class TmpTSFrame : ISpriteFrame class TmpTSFrame : ISpriteFrame
{ {
public SpriteFrameType Type { get { return SpriteFrameType.Indexed; } }
public Size Size { get; private set; } public Size Size { get; private set; }
public Size FrameSize { get { return Size; } } public Size FrameSize { get { return Size; } }
public float2 Offset { get; private set; } public float2 Offset { get; private set; }

View File

@@ -20,6 +20,7 @@ namespace OpenRA.Mods.Common.SpriteLoaders
{ {
class ShpTSFrame : ISpriteFrame class ShpTSFrame : ISpriteFrame
{ {
public SpriteFrameType Type { get { return SpriteFrameType.Indexed; } }
public Size Size { get; private set; } public Size Size { get; private set; }
public Size FrameSize { get; private set; } public Size FrameSize { get; private set; }
public float2 Offset { get; private set; } public float2 Offset { get; private set; }

View File

@@ -37,7 +37,7 @@ namespace OpenRA.Mods.Common.UtilityCommands
{ {
var ts = new TileSet(modData.DefaultFileSystem, t); var ts = new TileSet(modData.DefaultFileSystem, t);
Console.WriteLine("Tileset: " + ts.Name); 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))); 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))) foreach (var n in nodes.Where(node => !node.Key.StartsWith(ActorInfo.AbstractActorPrefix, StringComparison.Ordinal)))
modData.SpriteSequenceLoader.ParseSequences(modData, ts, sc, n); modData.SpriteSequenceLoader.ParseSequences(modData, ts, sc, n);

View File

@@ -79,9 +79,17 @@ namespace OpenRA.Mods.Common.UtilityCommands
frame.Size.Width); frame.Size.Width);
} }
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); var png = new Png(pngData, frameSize.Width, frameSize.Height, palColors);
png.Save("{0}-{1:D4}.png".F(prefix, count++)); png.Save("{0}-{1:D4}.png".F(prefix, count++));
} }
}
Console.WriteLine("Saved {0}-[0..{1}].png", prefix, count - 1); Console.WriteLine("Saved {0}-[0..{1}].png", prefix, count - 1);
} }

View File

@@ -44,7 +44,8 @@ namespace OpenRA.Mods.Common.UtilityCommands
sequences.Preload(); sequences.Preload();
var count = 0; var count = 0;
var sb = sequences.SpriteCache.SheetBuilder;
var sb = sequences.SpriteCache.SheetBuilders[SpriteFrameType.Indexed];
foreach (var s in sb.AllSheets) foreach (var s in sb.AllSheets)
{ {
var max = s == sb.Current ? (int)sb.CurrentChannel + 1 : 4; 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++)); 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); Console.WriteLine("Saved [0..{0}].png", count - 1);
} }
} }

View File

@@ -22,6 +22,7 @@ namespace OpenRA.Mods.D2k.SpriteLoaders
{ {
class R8Frame : ISpriteFrame class R8Frame : ISpriteFrame
{ {
public SpriteFrameType Type { get; set; }
public Size Size { get; private set; } public Size Size { get; private set; }
public Size FrameSize { get; private set; } public Size FrameSize { get; private set; }
public float2 Offset { get; private set; } public float2 Offset { get; private set; }