Merge pull request #8221 from pchote/terrain-spritelayer
Introduce a class for efficient tiled sprite rendering.
This commit is contained in:
@@ -82,6 +82,7 @@ namespace OpenRA
|
|||||||
void Bind();
|
void Bind();
|
||||||
void SetData(T[] vertices, int length);
|
void SetData(T[] vertices, int length);
|
||||||
void SetData(T[] vertices, int start, int length);
|
void SetData(T[] vertices, int start, int length);
|
||||||
|
void SetData(IntPtr data, int start, int length);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IShader
|
public interface IShader
|
||||||
|
|||||||
@@ -202,6 +202,11 @@ namespace OpenRA.Graphics
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IVertexBuffer<Vertex> CreateVertexBuffer(int length)
|
||||||
|
{
|
||||||
|
return Device.CreateVertexBuffer(length);
|
||||||
|
}
|
||||||
|
|
||||||
public void EnableScissor(Rectangle rect)
|
public void EnableScissor(Rectangle rect)
|
||||||
{
|
{
|
||||||
// Must remain inside the current scissor rect
|
// Must remain inside the current scissor rect
|
||||||
|
|||||||
@@ -103,10 +103,10 @@ namespace OpenRA.Graphics
|
|||||||
nv += 4;
|
nv += 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DrawVertexBuffer(IVertexBuffer<Vertex> buffer, int start, int length, PrimitiveType type, Sheet sheet)
|
public void DrawVertexBuffer(IVertexBuffer<Vertex> buffer, int start, int length, PrimitiveType type, Sheet sheet, BlendMode blendMode)
|
||||||
{
|
{
|
||||||
shader.SetTexture("DiffuseTexture", sheet.GetTexture());
|
shader.SetTexture("DiffuseTexture", sheet.GetTexture());
|
||||||
renderer.Device.SetBlendMode(BlendMode.Alpha);
|
renderer.Device.SetBlendMode(blendMode);
|
||||||
shader.Render(() => renderer.DrawBatch(buffer, start, length, type));
|
shader.Render(() => renderer.DrawBatch(buffer, start, length, type));
|
||||||
renderer.Device.SetBlendMode(BlendMode.None);
|
renderer.Device.SetBlendMode(BlendMode.None);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,89 +15,38 @@ namespace OpenRA.Graphics
|
|||||||
{
|
{
|
||||||
sealed class TerrainRenderer : IDisposable
|
sealed class TerrainRenderer : IDisposable
|
||||||
{
|
{
|
||||||
readonly IVertexBuffer<Vertex> vertexBuffer;
|
readonly TerrainSpriteLayer terrain;
|
||||||
readonly Vertex[] updateCellVertices = new Vertex[4];
|
|
||||||
readonly int rowStride;
|
|
||||||
|
|
||||||
readonly WorldRenderer worldRenderer;
|
|
||||||
readonly Theater theater;
|
readonly Theater theater;
|
||||||
readonly CellLayer<TerrainTile> mapTiles;
|
readonly CellLayer<TerrainTile> mapTiles;
|
||||||
readonly Map map;
|
|
||||||
|
|
||||||
float terrainPaletteIndex;
|
|
||||||
|
|
||||||
public TerrainRenderer(World world, WorldRenderer wr)
|
public TerrainRenderer(World world, WorldRenderer wr)
|
||||||
{
|
{
|
||||||
worldRenderer = wr;
|
|
||||||
theater = wr.Theater;
|
theater = wr.Theater;
|
||||||
map = world.Map;
|
mapTiles = world.Map.MapTiles.Value;
|
||||||
mapTiles = map.MapTiles.Value;
|
|
||||||
|
|
||||||
terrainPaletteIndex = wr.Palette("terrain").TextureIndex;
|
terrain = new TerrainSpriteLayer(world, wr, theater.Sheet, BlendMode.Alpha, wr.Palette("terrain"));
|
||||||
rowStride = 4 * map.Bounds.Width;
|
foreach (var cell in world.Map.Cells)
|
||||||
vertexBuffer = Game.Renderer.Device.CreateVertexBuffer(rowStride * map.Bounds.Height);
|
UpdateCell(cell);
|
||||||
|
|
||||||
UpdateMap();
|
world.Map.MapTiles.Value.CellEntryChanged += UpdateCell;
|
||||||
|
world.Map.MapHeight.Value.CellEntryChanged += UpdateCell;
|
||||||
map.MapTiles.Value.CellEntryChanged += UpdateCell;
|
|
||||||
map.MapHeight.Value.CellEntryChanged += UpdateCell;
|
|
||||||
|
|
||||||
wr.PaletteInvalidated += () =>
|
|
||||||
{
|
|
||||||
terrainPaletteIndex = wr.Palette("terrain").TextureIndex;
|
|
||||||
UpdateMap();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
void GenerateTileVertices(Vertex[] vertices, int offset, CPos cell)
|
|
||||||
{
|
|
||||||
var tile = theater.TileSprite(mapTiles[cell]);
|
|
||||||
var pos = worldRenderer.ScreenPosition(map.CenterOfCell(cell)) + tile.Offset - 0.5f * tile.Size;
|
|
||||||
Util.FastCreateQuad(vertices, pos, tile, terrainPaletteIndex, offset, tile.Size);
|
|
||||||
}
|
|
||||||
|
|
||||||
void UpdateMap()
|
|
||||||
{
|
|
||||||
var nv = 0;
|
|
||||||
var vertices = new Vertex[rowStride * map.Bounds.Height];
|
|
||||||
foreach (var cell in map.Cells)
|
|
||||||
{
|
|
||||||
GenerateTileVertices(vertices, nv, cell);
|
|
||||||
nv += 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
vertexBuffer.SetData(vertices, nv);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateCell(CPos cell)
|
public void UpdateCell(CPos cell)
|
||||||
{
|
{
|
||||||
var uv = cell.ToMPos(map.TileShape);
|
terrain.Update(cell, theater.TileSprite(mapTiles[cell]));
|
||||||
var offset = rowStride * (uv.V - map.Bounds.Top) + 4 * (uv.U - map.Bounds.Left);
|
|
||||||
|
|
||||||
GenerateTileVertices(updateCellVertices, 0, cell);
|
|
||||||
vertexBuffer.SetData(updateCellVertices, offset, 4);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Draw(WorldRenderer wr, Viewport viewport)
|
public void Draw(WorldRenderer wr, Viewport viewport)
|
||||||
{
|
{
|
||||||
var cells = viewport.VisibleCells;
|
terrain.Draw(viewport);
|
||||||
|
|
||||||
// Only draw the rows that are visible.
|
|
||||||
// VisibleCells is clamped to the map, so additional checks are unnecessary
|
|
||||||
var firstRow = cells.TopLeft.ToMPos(map).V - map.Bounds.Top;
|
|
||||||
var lastRow = cells.BottomRight.ToMPos(map).V - map.Bounds.Top + 1;
|
|
||||||
|
|
||||||
Game.Renderer.WorldSpriteRenderer.DrawVertexBuffer(
|
|
||||||
vertexBuffer, rowStride * firstRow, rowStride * (lastRow - firstRow),
|
|
||||||
PrimitiveType.QuadList, wr.Theater.Sheet);
|
|
||||||
|
|
||||||
foreach (var r in wr.World.WorldActor.TraitsImplementing<IRenderOverlay>())
|
foreach (var r in wr.World.WorldActor.TraitsImplementing<IRenderOverlay>())
|
||||||
r.Render(wr);
|
r.Render(wr);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
vertexBuffer.Dispose();
|
terrain.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
134
OpenRA.Game/Graphics/TerrainSpriteLayer.cs
Normal file
134
OpenRA.Game/Graphics/TerrainSpriteLayer.cs
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
#region Copyright & License Information
|
||||||
|
/*
|
||||||
|
* Copyright 2007-2015 The OpenRA Developers (see AUTHORS)
|
||||||
|
* This file is part of OpenRA, which is free software. It is made
|
||||||
|
* available to you under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation. For more information,
|
||||||
|
* see COPYING.
|
||||||
|
*/
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using OpenRA.Traits;
|
||||||
|
|
||||||
|
namespace OpenRA.Graphics
|
||||||
|
{
|
||||||
|
public sealed class TerrainSpriteLayer : IDisposable
|
||||||
|
{
|
||||||
|
readonly Sprite emptySprite;
|
||||||
|
|
||||||
|
readonly IVertexBuffer<Vertex> vertexBuffer;
|
||||||
|
readonly Vertex[] vertices;
|
||||||
|
readonly HashSet<int> dirtyRows = new HashSet<int>();
|
||||||
|
readonly int rowStride;
|
||||||
|
|
||||||
|
readonly WorldRenderer worldRenderer;
|
||||||
|
readonly Map map;
|
||||||
|
|
||||||
|
readonly Sheet sheet;
|
||||||
|
readonly BlendMode blendMode;
|
||||||
|
|
||||||
|
float paletteIndex;
|
||||||
|
|
||||||
|
public TerrainSpriteLayer(World world, WorldRenderer wr, Sheet sheet, BlendMode blendMode, PaletteReference palette)
|
||||||
|
{
|
||||||
|
worldRenderer = wr;
|
||||||
|
this.sheet = sheet;
|
||||||
|
this.blendMode = blendMode;
|
||||||
|
paletteIndex = palette.TextureIndex;
|
||||||
|
|
||||||
|
map = world.Map;
|
||||||
|
rowStride = 4 * map.MapSize.X;
|
||||||
|
|
||||||
|
vertices = new Vertex[rowStride * map.MapSize.Y];
|
||||||
|
vertexBuffer = Game.Renderer.Device.CreateVertexBuffer(vertices.Length);
|
||||||
|
emptySprite = new Sprite(sheet, Rectangle.Empty, TextureChannel.Alpha);
|
||||||
|
|
||||||
|
wr.PaletteInvalidated += () =>
|
||||||
|
{
|
||||||
|
paletteIndex = palette.TextureIndex;
|
||||||
|
|
||||||
|
// Everything in the layer uses the same palette,
|
||||||
|
// so we can fix the indices in one pass
|
||||||
|
for (var i = 0; i < vertices.Length; i++)
|
||||||
|
{
|
||||||
|
var v = vertices[i];
|
||||||
|
vertices[i] = new Vertex(v.X, v.Y, v.Z, v.U, v.V, paletteIndex, v.C);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var row = 0; row < map.MapSize.Y; row++)
|
||||||
|
dirtyRows.Add(row);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(CPos cell, Sprite sprite)
|
||||||
|
{
|
||||||
|
var pos = worldRenderer.ScreenPosition(map.CenterOfCell(cell)) + sprite.Offset - 0.5f * sprite.Size;
|
||||||
|
Update(cell.ToMPos(map.TileShape), sprite, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(MPos uv, Sprite sprite, float2 pos)
|
||||||
|
{
|
||||||
|
if (sprite != null)
|
||||||
|
{
|
||||||
|
if (sprite.Sheet != sheet)
|
||||||
|
throw new InvalidDataException("Attempted to add sprite from a different sheet");
|
||||||
|
|
||||||
|
if (sprite.BlendMode != blendMode)
|
||||||
|
throw new InvalidDataException("Attempted to add sprite with a different blend mode");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
sprite = emptySprite;
|
||||||
|
|
||||||
|
var offset = rowStride * uv.V + 4 * uv.U;
|
||||||
|
Util.FastCreateQuad(vertices, pos, sprite, paletteIndex, offset, sprite.Size);
|
||||||
|
|
||||||
|
dirtyRows.Add(uv.V);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Draw(Viewport viewport)
|
||||||
|
{
|
||||||
|
var cells = viewport.VisibleCells;
|
||||||
|
|
||||||
|
// Only draw the rows that are visible.
|
||||||
|
// VisibleCells is clamped to the map, so additional checks are unnecessary
|
||||||
|
var firstRow = cells.TopLeft.ToMPos(map).V;
|
||||||
|
var lastRow = cells.BottomRight.ToMPos(map).V + 1;
|
||||||
|
|
||||||
|
Game.Renderer.Flush();
|
||||||
|
|
||||||
|
// Flush any visible changes to the GPU
|
||||||
|
for (var row = firstRow; row <= lastRow; row++)
|
||||||
|
{
|
||||||
|
if (!dirtyRows.Remove(row))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var rowOffset = rowStride * row;
|
||||||
|
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
// The compiler / language spec won't let us calculate a pointer to
|
||||||
|
// an offset inside a generic array T[], and so we are forced to
|
||||||
|
// calculate the start-of-row pointer here to pass in to SetData.
|
||||||
|
fixed (Vertex* vPtr = &vertices[0])
|
||||||
|
vertexBuffer.SetData((IntPtr)(vPtr + rowOffset), rowOffset, rowStride);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Game.Renderer.WorldSpriteRenderer.DrawVertexBuffer(
|
||||||
|
vertexBuffer, rowStride * firstRow, rowStride * (lastRow - firstRow),
|
||||||
|
PrimitiveType.QuadList, sheet, blendMode);
|
||||||
|
|
||||||
|
Game.Renderer.Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
vertexBuffer.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,24 +15,19 @@ namespace OpenRA.Graphics
|
|||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
public struct Vertex
|
public struct Vertex
|
||||||
{
|
{
|
||||||
// TODO Workaround for unused field warnings in mono 2.10
|
public readonly float X, Y, Z, U, V, P, C;
|
||||||
#pragma warning disable 414
|
|
||||||
float x, y, z, u, v;
|
|
||||||
float p, c;
|
|
||||||
#pragma warning restore
|
|
||||||
|
|
||||||
public Vertex(float2 xy, float u, float v, float p, float c)
|
public Vertex(float2 xy, float u, float v, float p, float c)
|
||||||
{
|
: this(xy.X, xy.Y, 0, u, v, p, c) { }
|
||||||
this.x = xy.X; this.y = xy.Y; this.z = 0;
|
|
||||||
this.u = u; this.v = v;
|
|
||||||
this.p = p; this.c = c;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Vertex(float[] xyz, float u, float v, float p, float c)
|
public Vertex(float[] xyz, float u, float v, float p, float c)
|
||||||
|
: this(xyz[0], xyz[1], xyz[2], u, v, p, c) { }
|
||||||
|
|
||||||
|
public Vertex(float x, float y, float z, float u, float v, float p, float c)
|
||||||
{
|
{
|
||||||
this.x = xyz[0]; this.y = xyz[1]; this.z = xyz[2];
|
X = x; Y = y; Z = z;
|
||||||
this.u = u; this.v = v;
|
U = u; V = v;
|
||||||
this.p = p; this.c = c;
|
P = p; C = c;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -241,6 +241,7 @@
|
|||||||
<Compile Include="Widgets\WidgetUtils.cs" />
|
<Compile Include="Widgets\WidgetUtils.cs" />
|
||||||
<Compile Include="Widgets\WorldInteractionControllerWidget.cs" />
|
<Compile Include="Widgets\WorldInteractionControllerWidget.cs" />
|
||||||
<Compile Include="Graphics\PaletteReference.cs" />
|
<Compile Include="Graphics\PaletteReference.cs" />
|
||||||
|
<Compile Include="Graphics\TerrainSpriteLayer.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="FileSystem\D2kSoundResources.cs" />
|
<Compile Include="FileSystem\D2kSoundResources.cs" />
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ namespace OpenRA.Renderer.Null
|
|||||||
public void Bind() { }
|
public void Bind() { }
|
||||||
public void SetData(T[] vertices, int length) { }
|
public void SetData(T[] vertices, int length) { }
|
||||||
public void SetData(T[] vertices, int start, int length) { }
|
public void SetData(T[] vertices, int start, int length) { }
|
||||||
|
public void SetData(IntPtr data, int start, int length) { }
|
||||||
public void Dispose() { }
|
public void Dispose() { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,16 @@ namespace OpenRA.Renderer.Sdl2
|
|||||||
ErrorHandler.CheckGlError();
|
ErrorHandler.CheckGlError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetData(IntPtr data, int start, int length)
|
||||||
|
{
|
||||||
|
Bind();
|
||||||
|
GL.BufferSubData(BufferTarget.ArrayBuffer,
|
||||||
|
new IntPtr(VertexSize * start),
|
||||||
|
new IntPtr(VertexSize * length),
|
||||||
|
data);
|
||||||
|
ErrorHandler.CheckGlError();
|
||||||
|
}
|
||||||
|
|
||||||
public void Bind()
|
public void Bind()
|
||||||
{
|
{
|
||||||
GL.BindBuffer(BufferTarget.ArrayBuffer, buffer);
|
GL.BindBuffer(BufferTarget.ArrayBuffer, buffer);
|
||||||
|
|||||||
Reference in New Issue
Block a user