From ab4a8be66e49df8f2f3f49bdaa04ce0fcb53d09d Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Tue, 2 Dec 2014 18:30:25 +1300 Subject: [PATCH 1/4] Store the radar layers in a single buffered sheet. --- OpenRA.Mods.Common/Widgets/RadarWidget.cs | 49 +++++++++++++++-------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/OpenRA.Mods.Common/Widgets/RadarWidget.cs b/OpenRA.Mods.Common/Widgets/RadarWidget.cs index 9bca1134c3..cf66fd5b68 100644 --- a/OpenRA.Mods.Common/Widgets/RadarWidget.cs +++ b/OpenRA.Mods.Common/Widgets/RadarWidget.cs @@ -37,6 +37,7 @@ namespace OpenRA.Mods.Common.Widgets int2 previewOrigin = int2.Zero; Rectangle mapRect = Rectangle.Empty; + Sheet radarSheet; Sprite terrainSprite; Sprite customTerrainSprite; Sprite actorSprite; @@ -47,6 +48,8 @@ namespace OpenRA.Mods.Common.Widgets readonly RadarPings radarPings; + bool terrainDirty = true; + [ObjectCreator.UseCtor] public RadarWidget(World world, WorldRenderer worldRenderer) { @@ -68,20 +71,14 @@ namespace OpenRA.Mods.Common.Widgets previewOrigin = new int2((int)(previewScale * (size - width) / 2), (int)(previewScale * (size - height) / 2)); mapRect = new Rectangle(previewOrigin.X, previewOrigin.Y, (int)(previewScale * width), (int)(previewScale * height)); - // Only needs to be done once - using (var terrainBitmap = Minimap.TerrainBitmap(world.Map.Rules.TileSets[world.Map.Tileset], world.Map)) - { - var r = new Rectangle(0, 0, width, height); - var s = new Size(terrainBitmap.Width, terrainBitmap.Height); - var terrainSheet = new Sheet(s); - terrainSheet.GetTexture().SetData(terrainBitmap); - terrainSprite = new Sprite(terrainSheet, r, TextureChannel.Alpha); + // The four layers are stored in a 2x2 grid within a single texture + radarSheet = new Sheet(new Size(2 * width, 2 * height).NextPowerOf2()); + radarSheet.CreateBuffer(); - // Data is set in Tick() - customTerrainSprite = new Sprite(new Sheet(s), r, TextureChannel.Alpha); - actorSprite = new Sprite(new Sheet(s), r, TextureChannel.Alpha); - shroudSprite = new Sprite(new Sheet(s), r, TextureChannel.Alpha); - } + terrainSprite = new Sprite(radarSheet, new Rectangle(0, 0, width, height), TextureChannel.Alpha); + customTerrainSprite = new Sprite(radarSheet, new Rectangle(width, 0, width, height), TextureChannel.Alpha); + actorSprite = new Sprite(radarSheet, new Rectangle(0, height, width, height), TextureChannel.Alpha); + shroudSprite = new Sprite(radarSheet, new Rectangle(width, height, width, height), TextureChannel.Alpha); } public override string GetCursor(int2 pos) @@ -199,20 +196,40 @@ namespace OpenRA.Mods.Common.Widgets // This avoids obviously stale data from being shown when first opened. // TODO: This delayed updating is a giant hack --updateTicks; + + if (terrainDirty) + { + using (var bitmap = Minimap.TerrainBitmap(world.TileSet, world.Map)) + Util.FastCopyIntoSprite(terrainSprite, bitmap); + + radarSheet.CommitData(); + terrainDirty = false; + } + if (updateTicks <= 0) { updateTicks = 12; using (var bitmap = Minimap.CustomTerrainBitmap(world)) - customTerrainSprite.sheet.GetTexture().SetData(bitmap); + Util.FastCopyIntoSprite(customTerrainSprite, bitmap); + + radarSheet.CommitData(); } if (updateTicks == 8) + { using (var bitmap = Minimap.ActorsBitmap(world)) - actorSprite.sheet.GetTexture().SetData(bitmap); + Util.FastCopyIntoSprite(actorSprite, bitmap); + + radarSheet.CommitData(); + } if (updateTicks == 4) + { using (var bitmap = Minimap.ShroudBitmap(world)) - shroudSprite.sheet.GetTexture().SetData(bitmap); + Util.FastCopyIntoSprite(shroudSprite, bitmap); + + radarSheet.CommitData(); + } // Enable/Disable the radar var enabled = IsEnabled(); From e064593961cede36fe72a6df7f6697750adc85d3 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Tue, 2 Dec 2014 18:14:52 +1300 Subject: [PATCH 2/4] Add a CellEntryChanged event to CellLayer. --- OpenRA.Game/Map/CellLayer.cs | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/OpenRA.Game/Map/CellLayer.cs b/OpenRA.Game/Map/CellLayer.cs index 9b18333442..a78d803455 100644 --- a/OpenRA.Game/Map/CellLayer.cs +++ b/OpenRA.Game/Map/CellLayer.cs @@ -20,6 +20,8 @@ namespace OpenRA { public readonly Size Size; public readonly TileShape Shape; + public event Action CellEntryChanged = null; + readonly T[] entries; public CellLayer(Map map) @@ -48,15 +50,35 @@ namespace OpenRA /// Gets or sets the using cell coordinates public T this[CPos cell] { - get { return entries[Index(cell)]; } - set { entries[Index(cell)] = value; } + get + { + return entries[Index(cell)]; + } + + set + { + entries[Index(cell)] = value; + + if (CellEntryChanged != null) + CellEntryChanged(cell); + } } /// Gets or sets the layer contents using raw map coordinates (not CPos!) public T this[int u, int v] { - get { return entries[Index(u, v)]; } - set { entries[Index(u, v)] = value; } + get + { + return entries[Index(u, v)]; + } + + set + { + entries[Index(u, v)] = value; + + if (CellEntryChanged != null) + CellEntryChanged(Map.MapToCell(Shape, new CPos(u, v))); + } } /// Clears the layer contents with a known value From 5e402e95cb35100ec529b83fb5a7dd1f83b3b25b Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Tue, 2 Dec 2014 18:29:06 +1300 Subject: [PATCH 3/4] Update only dirty radar pixels and refresh every tick. --- OpenRA.Game/Traits/World/Shroud.cs | 17 +++ OpenRA.Mods.Common/Widgets/RadarWidget.cs | 132 ++++++++++++++-------- 2 files changed, 103 insertions(+), 46 deletions(-) diff --git a/OpenRA.Game/Traits/World/Shroud.cs b/OpenRA.Game/Traits/World/Shroud.cs index 6d501b07aa..6b7037a28b 100644 --- a/OpenRA.Game/Traits/World/Shroud.cs +++ b/OpenRA.Game/Traits/World/Shroud.cs @@ -31,6 +31,23 @@ namespace OpenRA.Traits readonly CellLayer generatedShroudCount; readonly CellLayer explored; + public event Action CellEntryChanged + { + add + { + visibleCount.CellEntryChanged += value; + generatedShroudCount.CellEntryChanged += value; + explored.CellEntryChanged += value; + } + + remove + { + visibleCount.CellEntryChanged -= value; + generatedShroudCount.CellEntryChanged -= value; + explored.CellEntryChanged -= value; + } + } + readonly Lazy fogVisibilities; // Cache of visibility that was added, so no matter what crazy trait code does, it diff --git a/OpenRA.Mods.Common/Widgets/RadarWidget.cs b/OpenRA.Mods.Common/Widgets/RadarWidget.cs index cf66fd5b68..4817495730 100644 --- a/OpenRA.Mods.Common/Widgets/RadarWidget.cs +++ b/OpenRA.Mods.Common/Widgets/RadarWidget.cs @@ -12,6 +12,7 @@ using System; using System.Drawing; using System.Linq; using OpenRA.Graphics; +using OpenRA.Traits; using OpenRA.Widgets; namespace OpenRA.Mods.Common.Widgets @@ -31,25 +32,24 @@ namespace OpenRA.Mods.Common.Widgets int frame; bool hasRadar; bool cachedEnabled; - int updateTicks; float previewScale = 0; int2 previewOrigin = int2.Zero; Rectangle mapRect = Rectangle.Empty; Sheet radarSheet; + byte[] radarData; + Sprite terrainSprite; - Sprite customTerrainSprite; Sprite actorSprite; Sprite shroudSprite; + Shroud renderShroud; readonly World world; readonly WorldRenderer worldRenderer; readonly RadarPings radarPings; - bool terrainDirty = true; - [ObjectCreator.UseCtor] public RadarWidget(World world, WorldRenderer worldRenderer) { @@ -74,11 +74,60 @@ namespace OpenRA.Mods.Common.Widgets // The four layers are stored in a 2x2 grid within a single texture radarSheet = new Sheet(new Size(2 * width, 2 * height).NextPowerOf2()); radarSheet.CreateBuffer(); + radarData = radarSheet.GetData(); terrainSprite = new Sprite(radarSheet, new Rectangle(0, 0, width, height), TextureChannel.Alpha); - customTerrainSprite = new Sprite(radarSheet, new Rectangle(width, 0, width, height), TextureChannel.Alpha); actorSprite = new Sprite(radarSheet, new Rectangle(0, height, width, height), TextureChannel.Alpha); shroudSprite = new Sprite(radarSheet, new Rectangle(width, height, width, height), TextureChannel.Alpha); + + // Set initial terrain data + using (var bitmap = Minimap.TerrainBitmap(world.TileSet, world.Map)) + OpenRA.Graphics.Util.FastCopyIntoSprite(terrainSprite, bitmap); + + world.Map.MapTiles.Value.CellEntryChanged += UpdateTerrainCell; + world.Map.CustomTerrain.CellEntryChanged += UpdateTerrainCell; + } + + void UpdateTerrainCell(CPos cell) + { + var stride = radarSheet.Size.Width; + var uv = Map.CellToMap(world.Map.TileShape, cell); + var terrain = world.Map.GetTerrainInfo(cell); + + var dx = terrainSprite.bounds.Left - world.Map.Bounds.Left; + var dy = terrainSprite.bounds.Top - world.Map.Bounds.Top; + + unsafe + { + fixed (byte* _colors = &radarData[0]) + { + var colors = (int*)_colors; + colors[(uv.Y + dy) * stride + uv.X + dx] = terrain.Color.ToArgb(); + } + } + } + + void UpdateShroudCell(CPos cell) + { + var stride = radarSheet.Size.Width; + var uv = Map.CellToMap(world.Map.TileShape, cell); + var dx = shroudSprite.bounds.Left - world.Map.Bounds.Left; + var dy = shroudSprite.bounds.Top - world.Map.Bounds.Top; + + var color = 0; + if (world.ShroudObscures(cell)) + color = Color.Black.ToArgb(); + else if (world.FogObscures(cell)) + color = Color.FromArgb(128, Color.Black).ToArgb(); + + unsafe + { + fixed (byte* _colors = &radarData[0]) + { + var colors = (int*)_colors; + colors[(uv.Y + dy) * stride + uv.X + dx] = color; + } + } } public override string GetCursor(int2 pos) @@ -150,9 +199,10 @@ namespace OpenRA.Mods.Common.Widgets var rsr = Game.Renderer.RgbaSpriteRenderer; rsr.DrawSprite(terrainSprite, o, s); - rsr.DrawSprite(customTerrainSprite, o, s); rsr.DrawSprite(actorSprite, o, s); - rsr.DrawSprite(shroudSprite, o, s); + + if (renderShroud != null) + rsr.DrawSprite(shroudSprite, o, s); // Draw viewport rect if (hasRadar) @@ -192,51 +242,41 @@ namespace OpenRA.Mods.Common.Widgets public override void Tick() { - // Update the radar animation even when its closed - // This avoids obviously stale data from being shown when first opened. - // TODO: This delayed updating is a giant hack - --updateTicks; - - if (terrainDirty) - { - using (var bitmap = Minimap.TerrainBitmap(world.TileSet, world.Map)) - Util.FastCopyIntoSprite(terrainSprite, bitmap); - - radarSheet.CommitData(); - terrainDirty = false; - } - - if (updateTicks <= 0) - { - updateTicks = 12; - using (var bitmap = Minimap.CustomTerrainBitmap(world)) - Util.FastCopyIntoSprite(customTerrainSprite, bitmap); - - radarSheet.CommitData(); - } - - if (updateTicks == 8) - { - using (var bitmap = Minimap.ActorsBitmap(world)) - Util.FastCopyIntoSprite(actorSprite, bitmap); - - radarSheet.CommitData(); - } - - if (updateTicks == 4) - { - using (var bitmap = Minimap.ShroudBitmap(world)) - Util.FastCopyIntoSprite(shroudSprite, bitmap); - - radarSheet.CommitData(); - } - // Enable/Disable the radar var enabled = IsEnabled(); if (enabled != cachedEnabled) Sound.Play(enabled ? RadarOnlineSound : RadarOfflineSound); cachedEnabled = enabled; + if (enabled) + { + var rp = world.RenderPlayer; + var newRenderShroud = rp != null ? rp.Shroud : null; + if (newRenderShroud != renderShroud) + { + if (renderShroud != null) + renderShroud.CellEntryChanged -= UpdateShroudCell; + + if (newRenderShroud != null) + { + // Redraw the full shroud sprite + using (var bitmap = Minimap.ShroudBitmap(world)) + OpenRA.Graphics.Util.FastCopyIntoSprite(shroudSprite, bitmap); + + // Update the notification binding + newRenderShroud.CellEntryChanged += UpdateShroudCell; + } + + renderShroud = newRenderShroud; + } + + // The actor layer is updated every tick + using (var bitmap = Minimap.ActorsBitmap(world)) + OpenRA.Graphics.Util.FastCopyIntoSprite(actorSprite, bitmap); + + radarSheet.CommitData(); + } + var targetFrame = enabled ? AnimationLength : 0; hasRadar = enabled && frame == AnimationLength; if (frame == targetFrame) From af9a74ddd4482b7e0f504b1b308d0677aa970ac9 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Thu, 4 Dec 2014 21:18:18 +1300 Subject: [PATCH 4/4] Optimise actor radar rendering for another significant perf win. --- OpenRA.Mods.Common/Widgets/RadarWidget.cs | 32 ++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/OpenRA.Mods.Common/Widgets/RadarWidget.cs b/OpenRA.Mods.Common/Widgets/RadarWidget.cs index 4817495730..dcd64bd350 100644 --- a/OpenRA.Mods.Common/Widgets/RadarWidget.cs +++ b/OpenRA.Mods.Common/Widgets/RadarWidget.cs @@ -77,8 +77,8 @@ namespace OpenRA.Mods.Common.Widgets radarData = radarSheet.GetData(); terrainSprite = new Sprite(radarSheet, new Rectangle(0, 0, width, height), TextureChannel.Alpha); + shroudSprite = new Sprite(radarSheet, new Rectangle(width, 0, width, height), TextureChannel.Alpha); actorSprite = new Sprite(radarSheet, new Rectangle(0, height, width, height), TextureChannel.Alpha); - shroudSprite = new Sprite(radarSheet, new Rectangle(width, height, width, height), TextureChannel.Alpha); // Set initial terrain data using (var bitmap = Minimap.TerrainBitmap(world.TileSet, world.Map)) @@ -271,8 +271,34 @@ namespace OpenRA.Mods.Common.Widgets } // The actor layer is updated every tick - using (var bitmap = Minimap.ActorsBitmap(world)) - OpenRA.Graphics.Util.FastCopyIntoSprite(actorSprite, bitmap); + var stride = radarSheet.Size.Width; + var dx = actorSprite.bounds.Left - world.Map.Bounds.Left; + var dy = actorSprite.bounds.Top - world.Map.Bounds.Top; + + Array.Clear(radarData, 4 * (actorSprite.bounds.Top * stride + actorSprite.bounds.Left), 4 * actorSprite.bounds.Height * stride); + + unsafe + { + fixed (byte* _colors = &radarData[0]) + { + var colors = (int*)_colors; + + foreach (var t in world.ActorsWithTrait()) + { + if (!t.Actor.IsInWorld || world.FogObscures(t.Actor)) + continue; + + var color = t.Trait.RadarSignatureColor(t.Actor); + foreach (var cell in t.Trait.RadarSignatureCells(t.Actor)) + { + var uv = Map.CellToMap(world.Map.TileShape, cell); + + if (world.Map.Bounds.Contains(uv.X, uv.Y)) + colors[(uv.Y + dy) * stride + uv.X + dx] = color.ToArgb(); + } + } + } + } radarSheet.CommitData(); }