From 4df64eb18fafdd249b9e449fe5ac63690c0bef43 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sat, 23 May 2015 09:17:25 +0100 Subject: [PATCH 1/3] Expose vertex components. --- OpenRA.Game/Graphics/Vertex.cs | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/OpenRA.Game/Graphics/Vertex.cs b/OpenRA.Game/Graphics/Vertex.cs index 67acc0f49b..0b94af2230 100644 --- a/OpenRA.Game/Graphics/Vertex.cs +++ b/OpenRA.Game/Graphics/Vertex.cs @@ -15,24 +15,19 @@ namespace OpenRA.Graphics [StructLayout(LayoutKind.Sequential)] public struct Vertex { - // TODO Workaround for unused field warnings in mono 2.10 - #pragma warning disable 414 - float x, y, z, u, v; - float p, c; - #pragma warning restore + public readonly float X, Y, Z, U, V, P, C; public Vertex(float2 xy, float u, float v, float p, float c) - { - this.x = xy.X; this.y = xy.Y; this.z = 0; - this.u = u; this.v = v; - this.p = p; this.c = c; - } + : this(xy.X, xy.Y, 0, u, v, p, 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]; - this.u = u; this.v = v; - this.p = p; this.c = c; + X = x; Y = y; Z = z; + U = u; V = v; + P = p; C = c; } } } From b1f4bd85d6199c55d6cf0dec5b808e10c02f013b Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sat, 23 May 2015 10:34:02 +0100 Subject: [PATCH 2/3] Accept an IntPtr for VertexBuffer.SetData. --- OpenRA.Game/Graphics/IGraphicsDevice.cs | 1 + OpenRA.Renderer.Null/NullGraphicsDevice.cs | 1 + OpenRA.Renderer.Sdl2/VertexBuffer.cs | 10 ++++++++++ 3 files changed, 12 insertions(+) diff --git a/OpenRA.Game/Graphics/IGraphicsDevice.cs b/OpenRA.Game/Graphics/IGraphicsDevice.cs index 8c54c606c2..3cfd8fe645 100755 --- a/OpenRA.Game/Graphics/IGraphicsDevice.cs +++ b/OpenRA.Game/Graphics/IGraphicsDevice.cs @@ -82,6 +82,7 @@ namespace OpenRA void Bind(); void SetData(T[] vertices, int length); void SetData(T[] vertices, int start, int length); + void SetData(IntPtr data, int start, int length); } public interface IShader diff --git a/OpenRA.Renderer.Null/NullGraphicsDevice.cs b/OpenRA.Renderer.Null/NullGraphicsDevice.cs index 98c05946e3..699a3c3933 100644 --- a/OpenRA.Renderer.Null/NullGraphicsDevice.cs +++ b/OpenRA.Renderer.Null/NullGraphicsDevice.cs @@ -105,6 +105,7 @@ namespace OpenRA.Renderer.Null public void Bind() { } public void SetData(T[] vertices, int length) { } public void SetData(T[] vertices, int start, int length) { } + public void SetData(IntPtr data, int start, int length) { } public void Dispose() { } } } diff --git a/OpenRA.Renderer.Sdl2/VertexBuffer.cs b/OpenRA.Renderer.Sdl2/VertexBuffer.cs index 5ecd000b83..c4445504fd 100644 --- a/OpenRA.Renderer.Sdl2/VertexBuffer.cs +++ b/OpenRA.Renderer.Sdl2/VertexBuffer.cs @@ -48,6 +48,16 @@ namespace OpenRA.Renderer.Sdl2 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() { GL.BindBuffer(BufferTarget.ArrayBuffer, buffer); From 629f87703241917618757c4063a6eddf4e37bcbc Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sun, 21 Dec 2014 13:55:01 +1300 Subject: [PATCH 3/3] Extract terrain vertex buffer into a reusable class. --- OpenRA.Game/Graphics/Renderer.cs | 5 + OpenRA.Game/Graphics/SpriteRenderer.cs | 4 +- OpenRA.Game/Graphics/TerrainRenderer.cs | 71 ++--------- OpenRA.Game/Graphics/TerrainSpriteLayer.cs | 134 +++++++++++++++++++++ OpenRA.Game/OpenRA.Game.csproj | 1 + 5 files changed, 152 insertions(+), 63 deletions(-) create mode 100644 OpenRA.Game/Graphics/TerrainSpriteLayer.cs 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 @@ +