Merge pull request #3282 from pchote/renderer-refactoring

Renderer refactoring - SheetBuilder
This commit is contained in:
Chris Forbes
2013-05-15 03:26:16 -07:00
11 changed files with 238 additions and 169 deletions

View File

@@ -39,17 +39,17 @@ namespace OpenRA.FileFormats
public bool PickAny; public bool PickAny;
public string Category; public string Category;
[FieldLoader.LoadUsing( "LoadTiles" )] [FieldLoader.LoadUsing("LoadTiles")]
public Dictionary<byte, string> Tiles = new Dictionary<byte, string>(); public Dictionary<byte, string> Tiles = new Dictionary<byte, string>();
public TileTemplate() {} public TileTemplate() {}
public TileTemplate(MiniYaml my) { FieldLoader.Load( this, my ); } public TileTemplate(MiniYaml my) { FieldLoader.Load(this, my); }
static object LoadTiles( MiniYaml y ) static object LoadTiles(MiniYaml y)
{ {
return y.NodesDict["Tiles"].NodesDict.ToDictionary( return y.NodesDict["Tiles"].NodesDict.ToDictionary(
t => byte.Parse(t.Key), t => byte.Parse(t.Key),
t => t.Value.Value ); t => t.Value.Value);
} }
static readonly string[] Fields = { "Id", "Image", "Size", "PickAny" }; static readonly string[] Fields = { "Id", "Image", "Size", "PickAny" };
@@ -60,12 +60,14 @@ namespace OpenRA.FileFormats
foreach (var field in Fields) foreach (var field in Fields)
{ {
FieldInfo f = this.GetType().GetField(field); FieldInfo f = this.GetType().GetField(field);
if (f.GetValue(this) == null) continue; if (f.GetValue(this) == null)
root.Add( new MiniYamlNode( field, FieldSaver.FormatValue( this, f ) ) ); continue;
root.Add(new MiniYamlNode(field, FieldSaver.FormatValue(this, f)));
} }
root.Add( new MiniYamlNode( "Tiles", null, root.Add(new MiniYamlNode("Tiles", null,
Tiles.Select( x => new MiniYamlNode( x.Key.ToString(), x.Value ) ).ToList() ) ); Tiles.Select(x => new MiniYamlNode(x.Key.ToString(), x.Value)).ToList()));
return new MiniYaml(null, root); return new MiniYaml(null, root);
} }
@@ -90,9 +92,9 @@ namespace OpenRA.FileFormats
public TileSet() {} public TileSet() {}
public TileSet( string filepath ) public TileSet(string filepath)
{ {
var yaml = MiniYaml.DictFromFile( filepath ); var yaml = MiniYaml.DictFromFile(filepath);
// General info // General info
FieldLoader.Load(this, yaml["General"]); FieldLoader.Load(this, yaml["General"]);
@@ -110,7 +112,7 @@ namespace OpenRA.FileFormats
{ {
foreach (var t in Templates) foreach (var t in Templates)
if (t.Value.Data == null) if (t.Value.Data == null)
using( var s = FileSystem.OpenWithExts(t.Value.Image, Extensions) ) using (var s = FileSystem.OpenWithExts(t.Value.Image, Extensions))
t.Value.Data = new Terrain(s, TileSize); t.Value.Data = new Terrain(s, TileSize);
} }
@@ -122,33 +124,35 @@ namespace OpenRA.FileFormats
foreach (var field in fields) foreach (var field in fields)
{ {
FieldInfo f = this.GetType().GetField(field); FieldInfo f = this.GetType().GetField(field);
if (f.GetValue(this) == null) continue; if (f.GetValue(this) == null)
gen.Add( new MiniYamlNode( field, FieldSaver.FormatValue( this, f ) ) ); continue;
gen.Add(new MiniYamlNode(field, FieldSaver.FormatValue(this, f)));
} }
root.Add( new MiniYamlNode( "General", null, gen ) ); root.Add(new MiniYamlNode("General", null, gen));
root.Add( new MiniYamlNode( "Terrain", null, root.Add(new MiniYamlNode( "Terrain", null,
Terrain.Select( t => new MiniYamlNode( Terrain.Select(t => new MiniYamlNode(
"TerrainType@{0}".F( t.Value.Type ), "TerrainType@{0}".F(t.Value.Type),
t.Value.Save() ) ).ToList() ) ); t.Value.Save())).ToList()));
root.Add( new MiniYamlNode( "Templates", null, root.Add(new MiniYamlNode("Templates", null,
Templates.Select( t => new MiniYamlNode( Templates.Select(t => new MiniYamlNode(
"Template@{0}".F( t.Value.Id ), "Template@{0}".F(t.Value.Id),
t.Value.Save() ) ).ToList() ) ); t.Value.Save())).ToList()));
root.WriteToFile(filepath); root.WriteToFile(filepath);
} }
public byte[] GetBytes(TileReference<ushort,byte> r) public byte[] GetBytes(TileReference<ushort,byte> r)
{ {
TileTemplate tile; TileTemplate tile;
if( Templates.TryGetValue( r.type, out tile ) ) if (Templates.TryGetValue(r.type, out tile))
return tile.Data.TileBitmapBytes[ r.index ]; return tile.Data.TileBitmapBytes[r.index];
byte[] missingTile = new byte[ TileSize * TileSize ]; byte[] missingTile = new byte[TileSize*TileSize];
for( int i = 0 ; i < missingTile.Length ; i++ ) for (var i = 0; i < missingTile.Length; i++)
missingTile[ i ] = 0x36; missingTile[i] = 0x36;
return missingTile; return missingTile;
} }
@@ -159,6 +163,7 @@ namespace OpenRA.FileFormats
string ret; string ret;
if (!tt.TryGetValue(r.index, out ret)) if (!tt.TryGetValue(r.index, out ret))
return "Clear"; // Default walkable return "Clear"; // Default walkable
return ret; return ret;
} }

View File

@@ -24,15 +24,13 @@ namespace OpenRA.Graphics
public string Name { get { return name; } } public string Name { get { return name; } }
public Animation( string name ) public Animation(string name)
: this( name, () => 0 ) : this(name, () => 0) {}
{
}
public Animation( string name, Func<int> facingFunc ) public Animation(string name, Func<int> facingFunc)
{ {
this.name = name.ToLowerInvariant(); this.name = name.ToLowerInvariant();
this.tickFunc = () => { }; this.tickFunc = () => {};
this.facingFunc = facingFunc; this.facingFunc = facingFunc;
} }
@@ -46,12 +44,12 @@ namespace OpenRA.Graphics
} }
} }
public void Play( string sequenceName ) public void Play(string sequenceName)
{ {
PlayThen(sequenceName, null); PlayThen(sequenceName, null);
} }
public void PlayRepeating( string sequenceName ) public void PlayRepeating(string sequenceName)
{ {
backwards = false; backwards = false;
tickAlways = false; tickAlways = false;
@@ -75,16 +73,16 @@ namespace OpenRA.Graphics
return true; return true;
} }
public void PlayThen( string sequenceName, Action after ) public void PlayThen(string sequenceName, Action after)
{ {
backwards = false; backwards = false;
tickAlways = false; tickAlways = false;
CurrentSequence = SequenceProvider.GetSequence( name, sequenceName ); CurrentSequence = SequenceProvider.GetSequence(name, sequenceName);
frame = 0; frame = 0;
tickFunc = () => tickFunc = () =>
{ {
++frame; ++frame;
if( frame >= CurrentSequence.Length ) if (frame >= CurrentSequence.Length)
{ {
frame = CurrentSequence.Length - 1; frame = CurrentSequence.Length - 1;
tickFunc = () => { }; tickFunc = () => { };
@@ -99,11 +97,11 @@ namespace OpenRA.Graphics
backwards = true; backwards = true;
} }
public void PlayFetchIndex( string sequenceName, Func<int> func ) public void PlayFetchIndex(string sequenceName, Func<int> func)
{ {
backwards = false; backwards = false;
tickAlways = true; tickAlways = true;
CurrentSequence = SequenceProvider.GetSequence( name, sequenceName ); CurrentSequence = SequenceProvider.GetSequence(name, sequenceName);
frame = func(); frame = func();
tickFunc = () => frame = func(); tickFunc = () => frame = func();
} }
@@ -113,19 +111,19 @@ namespace OpenRA.Graphics
public void Tick() public void Tick()
{ {
Tick( 40 ); // tick one frame Tick(40); // tick one frame
} }
public bool HasSequence(string seq) { return SequenceProvider.HasSequence( name, seq ); } public bool HasSequence(string seq) { return SequenceProvider.HasSequence(name, seq); }
public void Tick( int t ) public void Tick(int t)
{ {
if( tickAlways ) if (tickAlways)
tickFunc(); tickFunc();
else else
{ {
timeUntilNextFrame -= t; timeUntilNextFrame -= t;
while( timeUntilNextFrame <= 0 ) while (timeUntilNextFrame <= 0)
{ {
tickFunc(); tickFunc();
timeUntilNextFrame += CurrentSequence != null ? CurrentSequence.Tick : 40; // 25 fps == 40 ms timeUntilNextFrame += CurrentSequence != null ? CurrentSequence.Tick : 40; // 25 fps == 40 ms
@@ -145,9 +143,9 @@ namespace OpenRA.Graphics
} }
} }
public Sequence GetSequence( string sequenceName ) public Sequence GetSequence(string sequenceName)
{ {
return SequenceProvider.GetSequence( name, sequenceName ); return SequenceProvider.GetSequence(name, sequenceName);
} }
} }
} }

View File

@@ -9,6 +9,7 @@
#endregion #endregion
using System.Drawing; using System.Drawing;
using System.Drawing.Imaging;
using OpenRA.FileFormats; using OpenRA.FileFormats;
using OpenRA.FileFormats.Graphics; using OpenRA.FileFormats.Graphics;
@@ -16,21 +17,44 @@ namespace OpenRA.Graphics
{ {
public class Sheet public class Sheet
{ {
Bitmap bitmap;
ITexture texture; ITexture texture;
bool dirty; bool dirty;
byte[] data; public byte[] Data { get; private set; }
public readonly Size Size; public readonly Size Size;
public Sheet(Size size) public Sheet(Size size)
{ {
Size = size; Size = size;
Data = new byte[4*Size.Width*Size.Height];
} }
public Sheet(string filename) public Sheet(string filename)
{ {
bitmap = (Bitmap)Image.FromStream(FileSystem.Open(filename)); var bitmap = (Bitmap)Image.FromStream(FileSystem.Open(filename));
Size = bitmap.Size; Size = bitmap.Size;
Data = new byte[4*Size.Width*Size.Height];
var b = bitmap.LockBits(bitmap.Bounds(),
ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
unsafe
{
int* c = (int*)b.Scan0;
for (var x = 0; x < Size.Width; x++)
for (var y = 0; y < Size.Height; y++)
{
var i = 4*Size.Width*y + 4*x;
// Convert argb to bgra
var argb = *(c + (y * b.Stride >> 2) + x);
Data[i++] = (byte)(argb >> 0);
Data[i++] = (byte)(argb >> 8);
Data[i++] = (byte)(argb >> 16);
Data[i++] = (byte)(argb >> 24);
}
}
bitmap.UnlockBits(b);
} }
public ITexture Texture public ITexture Texture
@@ -45,23 +69,61 @@ namespace OpenRA.Graphics
if (dirty) if (dirty)
{ {
if (data != null) texture.SetData(Data, Size.Width, Size.Height);
{ dirty = false;
texture.SetData(data, Size.Width, Size.Height);
dirty = false;
}
else if (bitmap != null)
{
texture.SetData(bitmap);
dirty = false;
}
} }
return texture; return texture;
} }
} }
public byte[] Data { get { if (data == null) data = new byte[4 * Size.Width * Size.Height]; return data; } } public Bitmap AsBitmap()
{
var b = new Bitmap(Size.Width, Size.Height);
var output = b.LockBits(new Rectangle(0, 0, Size.Width, Size.Height),
ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
unsafe
{
int* c = (int*)output.Scan0;
for (var x = 0; x < Size.Width; x++)
for (var y = 0; y < Size.Height; y++)
{
var i = 4*Size.Width*y + 4*x;
// Convert bgra to argb
var argb = (Data[i+3] << 24) | (Data[i+2] << 16) | (Data[i+1] << 8) | Data[i];
*(c + (y * output.Stride >> 2) + x) = argb;
}
}
b.UnlockBits(output);
return b;
}
public Bitmap AsBitmap(TextureChannel channel, Palette pal)
{
var b = new Bitmap(Size.Width, Size.Height);
var output = b.LockBits(new Rectangle(0, 0, Size.Width, Size.Height),
ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
unsafe
{
int* c = (int*)output.Scan0;
for (var x = 0; x < Size.Width; x++)
for (var y = 0; y < Size.Height; y++)
{
var index = Data[4*Size.Width*y + 4*x + (int)channel];
*(c + (y * output.Stride >> 2) + x) = pal.GetColor(index).ToArgb();
}
}
b.UnlockBits(output);
return b;
}
public void MakeDirty() { dirty = true; } public void MakeDirty() { dirty = true; }
} }
} }

View File

@@ -8,68 +8,69 @@
*/ */
#endregion #endregion
using System;
using System.Drawing; using System.Drawing;
namespace OpenRA.Graphics namespace OpenRA.Graphics
{ {
public class SheetOverflowException : Exception
{
public SheetOverflowException()
: base("Sprite sequence spans multiple sheets.\n"+
"This should be considered as a bug, but you "+
"can increase the Graphics.SheetSize setting "+
"to temporarily avoid the problem.") {}
}
public enum SheetType
{
Indexed = 1,
DualIndexed = 2,
BGRA = 4,
}
public class SheetBuilder public class SheetBuilder
{ {
internal SheetBuilder(TextureChannel ch) Sheet current;
TextureChannel channel;
SheetType type;
int rowHeight = 0;
Point p;
internal SheetBuilder(SheetType t)
{ {
current = null; current = new Sheet(new Size(Renderer.SheetSize, Renderer.SheetSize));;
rowHeight = 0; channel = TextureChannel.Red;
channel = null; type = t;
initialChannel = ch;
} }
public Sprite Add(byte[] src, Size size) public Sprite Add(byte[] src, Size size, bool allowSheetOverflow)
{ {
Sprite rect = Allocate(size); var rect = Allocate(size, allowSheetOverflow);
Util.FastCopyIntoChannel(rect, src); Util.FastCopyIntoChannel(rect, src);
return rect; return rect;
} }
public Sprite Add(Size size, byte paletteIndex) public Sprite Add(Size size, byte paletteIndex, bool allowSheetOverflow)
{ {
byte[] data = new byte[size.Width * size.Height]; var data = new byte[size.Width * size.Height];
for (int i = 0; i < data.Length; i++) for (var i = 0; i < data.Length; i++)
data[i] = paletteIndex; data[i] = paletteIndex;
return Add(data, size); return Add(data, size, allowSheetOverflow);
} }
Sheet NewSheet() { return new Sheet(new Size( Renderer.SheetSize, Renderer.SheetSize ) ); } TextureChannel? NextChannel(TextureChannel t)
Sheet current = null;
int rowHeight = 0;
Point p;
TextureChannel? channel = null;
TextureChannel initialChannel;
TextureChannel? NextChannel(TextureChannel? t)
{ {
if (t == null) var nextChannel = (int)t + (int)type;
return initialChannel; if (nextChannel > (int)TextureChannel.Alpha)
return null;
switch (t.Value) return (TextureChannel)nextChannel;
{
case TextureChannel.Red: return TextureChannel.Green;
case TextureChannel.Green: return TextureChannel.Blue;
case TextureChannel.Blue: return TextureChannel.Alpha;
case TextureChannel.Alpha: return null;
default: return null;
}
} }
public Sprite Allocate(Size imageSize) public Sprite Allocate(Size imageSize, bool allowSheetOverflow)
{ {
if (current == null)
{
current = NewSheet();
channel = NextChannel(null);
}
if (imageSize.Width + p.X > current.Size.Width) if (imageSize.Width + p.X > current.Size.Width)
{ {
p = new Point(0, p.Y + rowHeight); p = new Point(0, p.Y + rowHeight);
@@ -81,22 +82,29 @@ namespace OpenRA.Graphics
if (p.Y + imageSize.Height > current.Size.Height) if (p.Y + imageSize.Height > current.Size.Height)
{ {
var next = NextChannel(channel);
if (null == (channel = NextChannel(channel))) if (next == null)
{ {
current = NewSheet(); if (!allowSheetOverflow)
channel = NextChannel(channel); throw new SheetOverflowException();
current = new Sheet(new Size(Renderer.SheetSize, Renderer.SheetSize));
channel = TextureChannel.Red;
} }
else
channel = next.Value;
rowHeight = imageSize.Height; rowHeight = imageSize.Height;
p = new Point(0,0); p = new Point(0,0);
} }
Sprite rect = new Sprite(current, new Rectangle(p, imageSize), channel.Value); var rect = new Sprite(current, new Rectangle(p, imageSize), channel);
current.MakeDirty(); current.MakeDirty();
p.X += imageSize.Width; p.X += imageSize.Width;
return rect; return rect;
} }
public Sheet Current { get { return current; } }
} }
} }

View File

@@ -17,57 +17,52 @@ namespace OpenRA.Graphics
public readonly Rectangle bounds; public readonly Rectangle bounds;
public readonly Sheet sheet; public readonly Sheet sheet;
public readonly TextureChannel channel; public readonly TextureChannel channel;
public readonly RectangleF uv;
public readonly float2 size; public readonly float2 size;
readonly float2[] textureCoords;
readonly float2[] uvhax;
public Sprite(Sheet sheet, Rectangle bounds, TextureChannel channel) public Sprite(Sheet sheet, Rectangle bounds, TextureChannel channel)
{ {
this.bounds = bounds; this.bounds = bounds;
this.sheet = sheet; this.sheet = sheet;
this.channel = channel; this.channel = channel;
uv = new RectangleF(
(float)(bounds.Left) / sheet.Size.Width,
(float)(bounds.Top) / sheet.Size.Height,
(float)(bounds.Width) / sheet.Size.Width,
(float)(bounds.Height) / sheet.Size.Height);
uvhax = new float2[]
{
new float2( uv.Left, uv.Top ),
new float2( uv.Right, uv.Top ),
new float2( uv.Left, uv.Bottom ),
new float2( uv.Right, uv.Bottom ),
};
this.size = new float2(bounds.Size); this.size = new float2(bounds.Size);
var left = (float)(bounds.Left) / sheet.Size.Width;
var top = (float)(bounds.Top) / sheet.Size.Height;
var right = (float)(bounds.Right) / sheet.Size.Width;
var bottom = (float)(bounds.Bottom) / sheet.Size.Height;
textureCoords = new float2[]
{
new float2(left, top),
new float2(right, top),
new float2(left, bottom),
new float2(right, bottom),
};
} }
public float2 FastMapTextureCoords( int k ) public float2 FastMapTextureCoords(int k)
{ {
return uvhax[ k ]; return textureCoords[k];
} }
public void DrawAt( WorldRenderer wr, float2 location, string palette ) public void DrawAt(WorldRenderer wr, float2 location, string palette)
{ {
Game.Renderer.WorldSpriteRenderer.DrawSprite( this, location, wr, palette, this.size ); Game.Renderer.WorldSpriteRenderer.DrawSprite(this, location, wr, palette, size);
} }
public void DrawAt( float2 location, int paletteIndex ) public void DrawAt(float2 location, int paletteIndex)
{ {
Game.Renderer.WorldSpriteRenderer.DrawSprite( this, location, paletteIndex, this.size ); Game.Renderer.WorldSpriteRenderer.DrawSprite(this, location, paletteIndex, size);
} }
public void DrawAt(float2 location, int paletteIndex, float scale) public void DrawAt(float2 location, int paletteIndex, float scale)
{ {
Game.Renderer.WorldSpriteRenderer.DrawSprite(this, location, paletteIndex, this.size * scale); Game.Renderer.WorldSpriteRenderer.DrawSprite(this, location, paletteIndex, size*scale);
} }
public void DrawAt( float2 location, int paletteIndex, float2 size ) public void DrawAt(float2 location, int paletteIndex, float2 size)
{ {
Game.Renderer.WorldSpriteRenderer.DrawSprite( this, location, paletteIndex, size ); Game.Renderer.WorldSpriteRenderer.DrawSprite(this, location, paletteIndex, size);
} }
} }

View File

@@ -31,8 +31,10 @@ namespace OpenRA.Graphics
glyphs = new Cache<Pair<char, Color>, GlyphInfo>(CreateGlyph, glyphs = new Cache<Pair<char, Color>, GlyphInfo>(CreateGlyph,
Pair<char,Color>.EqualityComparer); Pair<char,Color>.EqualityComparer);
// setup a 1-channel SheetBuilder for our private use // setup a SheetBuilder for our private use
if (builder == null) builder = new SheetBuilder(TextureChannel.Alpha); // TODO: SheetBuilder state is leaked between mod switches
if (builder == null)
builder = new SheetBuilder(SheetType.BGRA);
PrecacheColor(Color.White); PrecacheColor(Color.White);
PrecacheColor(Color.Red); PrecacheColor(Color.Red);
@@ -46,7 +48,7 @@ namespace OpenRA.Graphics
throw new InvalidOperationException(); throw new InvalidOperationException();
} }
public void DrawText (string text, float2 location, Color c) public void DrawText(string text, float2 location, Color c)
{ {
location.Y += size; // baseline vs top location.Y += size; // baseline vs top
@@ -84,7 +86,7 @@ namespace OpenRA.Graphics
public int2 Measure(string text) public int2 Measure(string text)
{ {
return new int2((int)text.Split( '\n' ).Max( s => s.Sum(a => glyphs[Pair.New(a, Color.White)].Advance)), text.Split('\n').Count()*size); return new int2((int)text.Split('\n').Max(s => s.Sum(a => glyphs[Pair.New(a, Color.White)].Advance)), text.Split('\n').Count()*size);
} }
Cache<Pair<char,Color>, GlyphInfo> glyphs; Cache<Pair<char,Color>, GlyphInfo> glyphs;
@@ -96,9 +98,8 @@ namespace OpenRA.Graphics
face.LoadGlyph(index, LoadFlags.Default, LoadTarget.Normal); face.LoadGlyph(index, LoadFlags.Default, LoadTarget.Normal);
face.Glyph.RenderGlyph(RenderMode.Normal); face.Glyph.RenderGlyph(RenderMode.Normal);
var s = builder.Allocate( var size = new Size((int)face.Glyph.Metrics.Width >> 6, (int)face.Glyph.Metrics.Height >> 6);
new Size((int)face.Glyph.Metrics.Width >> 6, var s = builder.Allocate(size, true);
(int)face.Glyph.Metrics.Height >> 6));
var g = new GlyphInfo var g = new GlyphInfo
{ {

View File

@@ -35,12 +35,12 @@ namespace OpenRA.Graphics
if (ImageCount == 0) if (ImageCount == 0)
{ {
var shp = new ShpTSReader(FileSystem.OpenWithExts(filename, exts)); var shp = new ShpTSReader(FileSystem.OpenWithExts(filename, exts));
return shp.Select(a => Game.modData.SheetBuilder.Add(a.Image, shp.Size)).ToArray(); return shp.Select(a => SheetBuilder.Add(a.Image, shp.Size, true)).ToArray();
} }
else else
{ {
var shp = new ShpReader(FileSystem.OpenWithExts(filename, exts)); var shp = new ShpReader(FileSystem.OpenWithExts(filename, exts));
return shp.Frames.Select(a => SheetBuilder.Add(a.Image, shp.Size)).ToArray(); return shp.Frames.Select(a => SheetBuilder.Add(a.Image, shp.Size, true)).ToArray();
} }
} }

View File

@@ -18,8 +18,8 @@ namespace OpenRA.Graphics
{ {
class TerrainRenderer class TerrainRenderer
{ {
SheetBuilder sheetBuilder;
IVertexBuffer<Vertex> vertexBuffer; IVertexBuffer<Vertex> vertexBuffer;
Sheet terrainSheet;
World world; World world;
Map map; Map map;
@@ -29,37 +29,31 @@ namespace OpenRA.Graphics
this.world = world; this.world = world;
this.map = world.Map; this.map = world.Map;
var tileSize = new Size( Game.CellSize, Game.CellSize ); // TODO: Use a fixed sheet size specified in the tileset yaml
sheetBuilder = new SheetBuilder(SheetType.Indexed);
var tileSize = new Size(Game.CellSize, Game.CellSize);
var tileMapping = new Cache<TileReference<ushort,byte>, Sprite>( var tileMapping = new Cache<TileReference<ushort,byte>, Sprite>(
x => Game.modData.SheetBuilder.Add(world.TileSet.GetBytes(x), tileSize)); x => sheetBuilder.Add(world.TileSet.GetBytes(x), tileSize, false));
var vertices = new Vertex[4 * map.Bounds.Height * map.Bounds.Width];
terrainSheet = tileMapping[map.MapTiles.Value[map.Bounds.Left, map.Bounds.Top]].sheet;
int nv = 0;
var terrainPalette = wr.Palette("terrain").Index; var terrainPalette = wr.Palette("terrain").Index;
var vertices = new Vertex[4 * map.Bounds.Height * map.Bounds.Width];
int nv = 0;
for( int j = map.Bounds.Top; j < map.Bounds.Bottom; j++ ) for (var j = map.Bounds.Top; j < map.Bounds.Bottom; j++)
for( int i = map.Bounds.Left; i < map.Bounds.Right; i++ ) for (var i = map.Bounds.Left; i < map.Bounds.Right; i++)
{ {
var tile = tileMapping[map.MapTiles.Value[i, j]]; var tile = tileMapping[map.MapTiles.Value[i, j]];
// TODO: move GetPaletteIndex out of the inner loop.
Util.FastCreateQuad(vertices, Game.CellSize * new float2(i, j), tile, terrainPalette, nv, tile.size); Util.FastCreateQuad(vertices, Game.CellSize * new float2(i, j), tile, terrainPalette, nv, tile.size);
nv += 4; nv += 4;
if (tileMapping[map.MapTiles.Value[i, j]].sheet != terrainSheet)
throw new InvalidOperationException("Terrain sprites span multiple sheets. Try increasing Game.Settings.Graphics.SheetSize.");
} }
vertexBuffer = Game.Renderer.Device.CreateVertexBuffer( vertices.Length ); vertexBuffer = Game.Renderer.Device.CreateVertexBuffer(vertices.Length);
vertexBuffer.SetData( vertices, nv ); vertexBuffer.SetData(vertices, nv);
} }
public void Draw( WorldRenderer wr, Viewport viewport ) public void Draw(WorldRenderer wr, Viewport viewport)
{ {
int verticesPerRow = map.Bounds.Width * 4; int verticesPerRow = 4*map.Bounds.Width;
int visibleRows = (int)(viewport.Height * 1f / Game.CellSize / viewport.Zoom + 2); int visibleRows = (int)(viewport.Height * 1f / Game.CellSize / viewport.Zoom + 2);
@@ -79,17 +73,22 @@ namespace OpenRA.Graphics
firstRow = r.Bottom - map.Bounds.Top; firstRow = r.Bottom - map.Bounds.Top;
} }
if (firstRow < 0) firstRow = 0; // Sanity checking
if (lastRow > map.Bounds.Height) lastRow = map.Bounds.Height; if (firstRow < 0)
firstRow = 0;
if( lastRow < firstRow ) lastRow = firstRow; if (lastRow > map.Bounds.Height)
lastRow = map.Bounds.Height;
if (lastRow < firstRow)
lastRow = firstRow;
Game.Renderer.WorldSpriteRenderer.DrawVertexBuffer( Game.Renderer.WorldSpriteRenderer.DrawVertexBuffer(
vertexBuffer, verticesPerRow * firstRow, verticesPerRow * (lastRow - firstRow), vertexBuffer, verticesPerRow * firstRow, verticesPerRow * (lastRow - firstRow),
PrimitiveType.QuadList, terrainSheet); PrimitiveType.QuadList, sheetBuilder.Current);
foreach (var r in world.WorldActor.TraitsImplementing<IRenderOverlay>()) foreach (var r in world.WorldActor.TraitsImplementing<IRenderOverlay>())
r.Render( wr ); r.Render(wr);
} }
} }
} }

View File

@@ -32,12 +32,13 @@ namespace OpenRA.Graphics
static readonly int[] channelMasks = { 2, 1, 0, 3 }; // yes, our channel order is nuts. static readonly int[] channelMasks = { 2, 1, 0, 3 }; // yes, our channel order is nuts.
public static void FastCopyIntoChannel(Sprite dest, byte[] src) public static void FastCopyIntoChannel(Sprite dest, byte[] src) { FastCopyIntoChannel(dest, 0, src); }
public static void FastCopyIntoChannel(Sprite dest, int channelOffset, byte[] src)
{ {
var data = dest.sheet.Data; var data = dest.sheet.Data;
var srcStride = dest.bounds.Width; var srcStride = dest.bounds.Width;
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 + channelOffset];
var destSkip = destStride - 4 * srcStride; var destSkip = destStride - 4 * srcStride;
var height = dest.bounds.Height; var height = dest.bounds.Height;

View File

@@ -53,7 +53,7 @@ namespace OpenRA
ChromeMetrics.Initialize(Manifest.ChromeMetrics); ChromeMetrics.Initialize(Manifest.ChromeMetrics);
ChromeProvider.Initialize(Manifest.Chrome); ChromeProvider.Initialize(Manifest.Chrome);
SheetBuilder = new SheetBuilder(TextureChannel.Red); SheetBuilder = new SheetBuilder(SheetType.Indexed);
SpriteLoader = new SpriteLoader(new string[] { ".shp" }, SheetBuilder); SpriteLoader = new SpriteLoader(new string[] { ".shp" }, SheetBuilder);
CursorProvider.Initialize(Manifest.Cursors); CursorProvider.Initialize(Manifest.Cursors);
} }

View File

@@ -99,9 +99,9 @@ namespace OpenRA.Mods.RA
if (cachedTileset != self.World.Map.Tileset) if (cachedTileset != self.World.Map.Tileset)
{ {
cachedTileset = self.World.Map.Tileset; cachedTileset = self.World.Map.Tileset;
var tileSize = new Size(Game.CellSize, Game.CellSize);
sprites = new Cache<TileReference<ushort,byte>, Sprite>( sprites = new Cache<TileReference<ushort,byte>, Sprite>(
x => Game.modData.SheetBuilder.Add(self.World.TileSet.GetBytes(x), x => Game.modData.SheetBuilder.Add(self.World.TileSet.GetBytes(x), tileSize, true));
new Size(Game.CellSize, Game.CellSize)));
} }
// Cache templates and tiles for the different states // Cache templates and tiles for the different states