diff --git a/OpenRA.Game/Graphics/SpriteRenderer.cs b/OpenRA.Game/Graphics/SpriteRenderer.cs index 6d3bfb9baa..6eb0ec10a9 100644 --- a/OpenRA.Game/Graphics/SpriteRenderer.cs +++ b/OpenRA.Game/Graphics/SpriteRenderer.cs @@ -10,6 +10,7 @@ #endregion using System; +using System.Collections.Generic; using System.Linq; using OpenRA.Primitives; @@ -17,7 +18,7 @@ namespace OpenRA.Graphics { public class SpriteRenderer : Renderer.IBatchRenderer { - const int SheetCount = 7; + public const int SheetCount = 7; static readonly string[] SheetIndexToTextureName = Exts.MakeArray(SheetCount, i => "Texture{0}".F(i)); readonly Renderer renderer; @@ -151,15 +152,30 @@ namespace OpenRA.Graphics nv += 6; } - public void DrawVertexBuffer(IVertexBuffer buffer, int start, int length, PrimitiveType type, Sheet sheet, BlendMode blendMode) + public void DrawVertexBuffer(IVertexBuffer buffer, int start, int length, PrimitiveType type, IEnumerable sheets, BlendMode blendMode) { - shader.SetTexture("Texture0", sheet.GetTexture()); + var i = 0; + foreach (var s in sheets) + { + if (i >= SheetCount) + ThrowSheetOverflow(nameof(sheets)); + + if (s != null) + shader.SetTexture(SheetIndexToTextureName[i++], s.GetTexture()); + } + renderer.Context.SetBlendMode(blendMode); shader.PrepareRender(); renderer.DrawBatch(buffer, start, length, type); renderer.Context.SetBlendMode(BlendMode.None); } + // PERF: methods that throw won't be inlined by the JIT, so extract a static helper for use on hot paths + static void ThrowSheetOverflow(string paramName) + { + throw new ArgumentException("SpriteRenderer only supports {0} simultaneous textures".F(SheetCount), paramName); + } + // For RGBAColorRenderer internal void DrawRGBAVertices(Vertex[] v) { diff --git a/OpenRA.Game/Graphics/TerrainSpriteLayer.cs b/OpenRA.Game/Graphics/TerrainSpriteLayer.cs index 78e54f8917..9a2ea6a637 100644 --- a/OpenRA.Game/Graphics/TerrainSpriteLayer.cs +++ b/OpenRA.Game/Graphics/TerrainSpriteLayer.cs @@ -20,9 +20,9 @@ namespace OpenRA.Graphics { static readonly int[] CornerVertexMap = { 0, 1, 2, 2, 3, 0 }; - public readonly Sheet Sheet; public readonly BlendMode BlendMode; + readonly Sheet[] sheets; readonly Sprite emptySprite; readonly IVertexBuffer vertexBuffer; @@ -37,11 +37,12 @@ namespace OpenRA.Graphics readonly PaletteReference[] palettes; - public TerrainSpriteLayer(World world, WorldRenderer wr, Sheet sheet, BlendMode blendMode, bool restrictToBounds) + public TerrainSpriteLayer(World world, WorldRenderer wr, Sprite emptySprite, BlendMode blendMode, bool restrictToBounds) { worldRenderer = wr; this.restrictToBounds = restrictToBounds; - Sheet = sheet; + this.emptySprite = emptySprite; + sheets = new Sheet[SpriteRenderer.SheetCount]; BlendMode = blendMode; map = world.Map; @@ -50,7 +51,6 @@ namespace OpenRA.Graphics vertices = new Vertex[rowStride * map.MapSize.Y]; palettes = new PaletteReference[map.MapSize.X * map.MapSize.Y]; vertexBuffer = Game.Renderer.Context.CreateVertexBuffer(vertices.Length); - emptySprite = new Sprite(sheet, Rectangle.Empty, TextureChannel.Alpha); wr.PaletteInvalidated += UpdatePaletteIndices; @@ -136,25 +136,48 @@ namespace OpenRA.Graphics dirtyRows.Add(uv.V); } + int GetOrAddSheetIndex(Sheet sheet) + { + if (sheet == null) + return 0; + + for (var i = 0; i < sheets.Length; i++) + { + if (sheets[i] == sheet) + return i; + + if (sheets[i] == null) + { + sheets[i] = sheet; + return i; + } + } + + throw new InvalidDataException("Sheet overflow"); + } + public void Update(MPos uv, Sprite sprite, PaletteReference palette, in float3 pos, bool ignoreTint) { + int2 samplers; 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"); + + samplers = new int2(GetOrAddSheetIndex(sprite.Sheet), GetOrAddSheetIndex((sprite as SpriteWithSecondaryData)?.SecondarySheet)); } else + { sprite = emptySprite; + samplers = int2.Zero; + } // The vertex buffer does not have geometry for cells outside the map if (!map.Tiles.Contains(uv)) return; var offset = rowStride * uv.V + 6 * uv.U; - Util.FastCreateQuad(vertices, pos, sprite, int2.Zero, palette?.TextureIndex ?? 0, offset, sprite.Size, float3.Ones, 1f); + Util.FastCreateQuad(vertices, pos, sprite, samplers, palette?.TextureIndex ?? 0, offset, sprite.Size, float3.Ones, 1f); palettes[uv.V * map.MapSize.X + uv.U] = palette; if (worldRenderer.TerrainLighting != null) @@ -188,7 +211,7 @@ namespace OpenRA.Graphics Game.Renderer.WorldSpriteRenderer.DrawVertexBuffer( vertexBuffer, rowStride * firstRow, rowStride * (lastRow - firstRow), - PrimitiveType.TriangleList, Sheet, BlendMode); + PrimitiveType.TriangleList, sheets, BlendMode); Game.Renderer.Flush(); } diff --git a/OpenRA.Mods.Common/Traits/World/ResourceRenderer.cs b/OpenRA.Mods.Common/Traits/World/ResourceRenderer.cs index 21020a0877..4ac6e20026 100644 --- a/OpenRA.Mods.Common/Traits/World/ResourceRenderer.cs +++ b/OpenRA.Mods.Common/Traits/World/ResourceRenderer.cs @@ -13,6 +13,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using OpenRA.Graphics; +using OpenRA.Primitives; using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits @@ -63,14 +64,12 @@ namespace OpenRA.Mods.Common.Traits if (spriteLayer == null) { var first = r.Value.Variants.First().Value.GetSprite(0); - spriteLayer = new TerrainSpriteLayer(w, wr, first.Sheet, first.BlendMode, wr.World.Type != WorldType.Editor); + var emptySprite = new Sprite(first.Sheet, Rectangle.Empty, TextureChannel.Alpha); + spriteLayer = new TerrainSpriteLayer(w, wr, emptySprite, first.BlendMode, wr.World.Type != WorldType.Editor); } // All resources must share a sheet and blend mode var sprites = r.Value.Variants.Values.SelectMany(v => Exts.MakeArray(v.Length, x => v.GetSprite(x))); - if (sprites.Any(s => s.Sheet != spriteLayer.Sheet)) - throw new InvalidDataException("Resource sprites span multiple sheets. Try loading their sequences earlier."); - if (sprites.Any(s => s.BlendMode != spriteLayer.BlendMode)) throw new InvalidDataException("Resource sprites specify different blend modes. " + "Try using different ResourceRenderer traits for resource types that use different blend modes."); diff --git a/OpenRA.Mods.Common/Traits/World/ShroudRenderer.cs b/OpenRA.Mods.Common/Traits/World/ShroudRenderer.cs index ad24ded801..6e22563434 100644 --- a/OpenRA.Mods.Common/Traits/World/ShroudRenderer.cs +++ b/OpenRA.Mods.Common/Traits/World/ShroudRenderer.cs @@ -14,6 +14,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using OpenRA.Graphics; +using OpenRA.Primitives; using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits @@ -190,26 +191,19 @@ namespace OpenRA.Mods.Common.Traits visibleUnderFog = puv => map.Contains(puv); - var shroudSheet = shroudSprites[0].Sheet; - if (shroudSprites.Any(s => s.Sheet != shroudSheet)) - throw new InvalidDataException("Shroud sprites span multiple sheets. Try loading their sequences earlier."); - var shroudBlend = shroudSprites[0].BlendMode; if (shroudSprites.Any(s => s.BlendMode != shroudBlend)) throw new InvalidDataException("Shroud sprites must all use the same blend mode."); - var fogSheet = fogSprites[0].Sheet; - if (fogSprites.Any(s => s.Sheet != fogSheet)) - throw new InvalidDataException("Fog sprites span multiple sheets. Try loading their sequences earlier."); - var fogBlend = fogSprites[0].BlendMode; if (fogSprites.Any(s => s.BlendMode != fogBlend)) throw new InvalidDataException("Fog sprites must all use the same blend mode."); + var emptySprite = new Sprite(shroudSprites[0].Sheet, Rectangle.Empty, TextureChannel.Alpha); shroudPaletteReference = wr.Palette(info.ShroudPalette); fogPaletteReference = wr.Palette(info.FogPalette); - shroudLayer = new TerrainSpriteLayer(w, wr, shroudSheet, shroudBlend, false); - fogLayer = new TerrainSpriteLayer(w, wr, fogSheet, fogBlend, false); + shroudLayer = new TerrainSpriteLayer(w, wr, emptySprite, shroudBlend, false); + fogLayer = new TerrainSpriteLayer(w, wr, emptySprite, fogBlend, false); WorldOnRenderPlayerChanged(world.RenderPlayer); } diff --git a/OpenRA.Mods.Common/Traits/World/SmudgeLayer.cs b/OpenRA.Mods.Common/Traits/World/SmudgeLayer.cs index 6ed9178c0f..3cd090e41a 100644 --- a/OpenRA.Mods.Common/Traits/World/SmudgeLayer.cs +++ b/OpenRA.Mods.Common/Traits/World/SmudgeLayer.cs @@ -14,6 +14,7 @@ using System.IO; using System.Linq; using OpenRA.Graphics; using OpenRA.Mods.Common.Effects; +using OpenRA.Primitives; using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits @@ -114,16 +115,14 @@ namespace OpenRA.Mods.Common.Traits var sprites = smudges.Values.SelectMany(v => Exts.MakeArray(v.Length, x => v.GetSprite(x))).ToList(); var sheet = sprites[0].Sheet; var blendMode = sprites[0].BlendMode; - - if (sprites.Any(s => s.Sheet != sheet)) - throw new InvalidDataException("Resource sprites span multiple sheets. Try loading their sequences earlier."); + var emptySprite = new Sprite(sheet, Rectangle.Empty, TextureChannel.Alpha); if (sprites.Any(s => s.BlendMode != blendMode)) throw new InvalidDataException("Smudges specify different blend modes. " + "Try using different smudge types for smudges that use different blend modes."); paletteReference = wr.Palette(Info.Palette); - render = new TerrainSpriteLayer(w, wr, sheet, blendMode, w.Type != WorldType.Editor); + render = new TerrainSpriteLayer(w, wr, emptySprite, blendMode, w.Type != WorldType.Editor); // Add map smudges foreach (var kv in Info.InitialSmudges) diff --git a/OpenRA.Mods.Common/Traits/World/TerrainRenderer.cs b/OpenRA.Mods.Common/Traits/World/TerrainRenderer.cs index 8ec562205e..5ee896e7ff 100644 --- a/OpenRA.Mods.Common/Traits/World/TerrainRenderer.cs +++ b/OpenRA.Mods.Common/Traits/World/TerrainRenderer.cs @@ -80,7 +80,9 @@ namespace OpenRA.Mods.Common.Traits void IWorldLoaded.WorldLoaded(World world, WorldRenderer wr) { worldRenderer = wr; - spriteLayer = new TerrainSpriteLayer(world, wr, tileCache.Sheet, BlendMode.Alpha, world.Type != WorldType.Editor); + var emptySprite = new Sprite(tileCache.Sheet, Rectangle.Empty, TextureChannel.Alpha); + spriteLayer = new TerrainSpriteLayer(world, wr, emptySprite, BlendMode.Alpha, world.Type != WorldType.Editor); + foreach (var cell in map.AllCells) UpdateCell(cell); diff --git a/OpenRA.Mods.D2k/Traits/World/BuildableTerrainLayer.cs b/OpenRA.Mods.D2k/Traits/World/BuildableTerrainLayer.cs index 5a30704b6f..a0f4133d2e 100644 --- a/OpenRA.Mods.D2k/Traits/World/BuildableTerrainLayer.cs +++ b/OpenRA.Mods.D2k/Traits/World/BuildableTerrainLayer.cs @@ -13,6 +13,7 @@ using System.Collections.Generic; using System.Linq; using OpenRA.Graphics; using OpenRA.Mods.Common.Traits; +using OpenRA.Primitives; using OpenRA.Traits; namespace OpenRA.Mods.D2k.Traits @@ -51,7 +52,8 @@ namespace OpenRA.Mods.D2k.Traits void IWorldLoaded.WorldLoaded(World w, WorldRenderer wr) { - render = new TerrainSpriteLayer(w, wr, terrainRenderer.Sheet, BlendMode.Alpha, wr.World.Type != WorldType.Editor); + var emptySprite = new Sprite(terrainRenderer.Sheet, Rectangle.Empty, TextureChannel.Alpha); + render = new TerrainSpriteLayer(w, wr, emptySprite, BlendMode.Alpha, wr.World.Type != WorldType.Editor); paletteReference = wr.Palette(info.Palette); }