diff --git a/OpenRA.Game/Graphics/Renderer.cs b/OpenRA.Game/Graphics/Renderer.cs index 39e02a12f3..1dac9e3b80 100644 --- a/OpenRA.Game/Graphics/Renderer.cs +++ b/OpenRA.Game/Graphics/Renderer.cs @@ -202,6 +202,11 @@ namespace OpenRA.Graphics } } + public IVertexBuffer CreateVertexBuffer(int length) + { + return Device.CreateVertexBuffer(length); + } + public void EnableScissor(Rectangle rect) { // Must remain inside the current scissor rect diff --git a/OpenRA.Game/Graphics/SpriteRenderer.cs b/OpenRA.Game/Graphics/SpriteRenderer.cs index 7becc7c6b5..4331e7ef29 100644 --- a/OpenRA.Game/Graphics/SpriteRenderer.cs +++ b/OpenRA.Game/Graphics/SpriteRenderer.cs @@ -103,10 +103,10 @@ namespace OpenRA.Graphics nv += 4; } - public void DrawVertexBuffer(IVertexBuffer buffer, int start, int length, PrimitiveType type, Sheet sheet) + public void DrawVertexBuffer(IVertexBuffer buffer, int start, int length, PrimitiveType type, Sheet sheet, BlendMode blendMode) { shader.SetTexture("DiffuseTexture", sheet.GetTexture()); - renderer.Device.SetBlendMode(BlendMode.Alpha); + renderer.Device.SetBlendMode(blendMode); shader.Render(() => renderer.DrawBatch(buffer, start, length, type)); renderer.Device.SetBlendMode(BlendMode.None); } diff --git a/OpenRA.Game/Graphics/TerrainRenderer.cs b/OpenRA.Game/Graphics/TerrainRenderer.cs index 4420087155..40da938959 100644 --- a/OpenRA.Game/Graphics/TerrainRenderer.cs +++ b/OpenRA.Game/Graphics/TerrainRenderer.cs @@ -15,89 +15,38 @@ namespace OpenRA.Graphics { sealed class TerrainRenderer : IDisposable { - readonly IVertexBuffer vertexBuffer; - readonly Vertex[] updateCellVertices = new Vertex[4]; - readonly int rowStride; - - readonly WorldRenderer worldRenderer; + readonly TerrainSpriteLayer terrain; readonly Theater theater; readonly CellLayer mapTiles; - readonly Map map; - - float terrainPaletteIndex; public TerrainRenderer(World world, WorldRenderer wr) { - worldRenderer = wr; theater = wr.Theater; - map = world.Map; - mapTiles = map.MapTiles.Value; + mapTiles = world.Map.MapTiles.Value; - terrainPaletteIndex = wr.Palette("terrain").TextureIndex; - rowStride = 4 * map.Bounds.Width; - vertexBuffer = Game.Renderer.Device.CreateVertexBuffer(rowStride * map.Bounds.Height); + terrain = new TerrainSpriteLayer(world, wr, theater.Sheet, BlendMode.Alpha, wr.Palette("terrain")); + foreach (var cell in world.Map.Cells) + UpdateCell(cell); - UpdateMap(); - - 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); + world.Map.MapTiles.Value.CellEntryChanged += UpdateCell; + world.Map.MapHeight.Value.CellEntryChanged += UpdateCell; } public void UpdateCell(CPos cell) { - var uv = cell.ToMPos(map.TileShape); - var offset = rowStride * (uv.V - map.Bounds.Top) + 4 * (uv.U - map.Bounds.Left); - - GenerateTileVertices(updateCellVertices, 0, cell); - vertexBuffer.SetData(updateCellVertices, offset, 4); + terrain.Update(cell, theater.TileSprite(mapTiles[cell])); } public void Draw(WorldRenderer wr, 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 - 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); - + terrain.Draw(viewport); foreach (var r in wr.World.WorldActor.TraitsImplementing()) r.Render(wr); } public void Dispose() { - vertexBuffer.Dispose(); + terrain.Dispose(); } } } diff --git a/OpenRA.Game/Graphics/TerrainSpriteLayer.cs b/OpenRA.Game/Graphics/TerrainSpriteLayer.cs new file mode 100644 index 0000000000..9fd3b0f47b --- /dev/null +++ b/OpenRA.Game/Graphics/TerrainSpriteLayer.cs @@ -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 vertexBuffer; + readonly Vertex[] vertices; + readonly HashSet dirtyRows = new HashSet(); + 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(); + } + } +} diff --git a/OpenRA.Game/OpenRA.Game.csproj b/OpenRA.Game/OpenRA.Game.csproj index 6ce8a3c793..ce5e99d6b1 100644 --- a/OpenRA.Game/OpenRA.Game.csproj +++ b/OpenRA.Game/OpenRA.Game.csproj @@ -241,6 +241,7 @@ +