Centralize shroud changes in one pass to improve performance.

This commit is contained in:
tovl
2019-11-25 19:39:45 +01:00
committed by abcdefg30
parent 0106ed3669
commit 695d9a6cb1
7 changed files with 123 additions and 199 deletions

View File

@@ -31,7 +31,6 @@ namespace OpenRA.Mods.Common.Traits
readonly World world;
CellLayer<Pair<int, int>> terrainColor;
readonly HashSet<MPos> dirtyTerrainCells = new HashSet<MPos>();
readonly Shroud shroud;
public event Action<MPos> CellTerrainColorChanged = null;
@@ -40,33 +39,22 @@ namespace OpenRA.Mods.Common.Traits
{
world = self.World;
shroud = self.Trait<Shroud>();
shroud.CellsChanged += OnShroudCellsChanged;
shroud.OnShroudChanged += UpdateShroudCell;
}
void OnShroudCellsChanged(IEnumerable<PPos> puvs)
void UpdateShroudCell(PPos puv)
{
foreach (var puv in puvs)
{
foreach (var uv in world.Map.Unproject(puv))
{
if (dirtyTerrainCells.Contains(uv))
{
UpdateTerrainCellColor(uv);
dirtyTerrainCells.Remove(uv);
}
}
}
var uvs = world.Map.Unproject(puv);
foreach (var uv in uvs)
UpdateTerrainCell(uv);
}
void UpdateTerrainCell(CPos cell)
void UpdateTerrainCell(MPos uv)
{
var uv = cell.ToMPos(world.Map);
if (!world.Map.CustomTerrain.Contains(uv))
return;
if (!shroud.IsVisible(uv))
dirtyTerrainCells.Add(uv);
else
if (shroud.IsVisible(uv))
UpdateTerrainCellColor(uv);
}
@@ -88,8 +76,8 @@ namespace OpenRA.Mods.Common.Traits
foreach (var uv in world.Map.AllCells.MapCoords)
UpdateTerrainCellColor(uv);
world.Map.Tiles.CellEntryChanged += UpdateTerrainCell;
world.Map.CustomTerrain.CellEntryChanged += UpdateTerrainCell;
world.Map.Tiles.CellEntryChanged += cell => UpdateTerrainCell(cell.ToMPos(world.Map));
world.Map.CustomTerrain.CellEntryChanged += cell => UpdateTerrainCell(cell.ToMPos(world.Map));
IsInitialized = true;
});

View File

@@ -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
@@ -92,17 +93,17 @@ namespace OpenRA.Mods.Common.Traits
}
readonly ShroudRendererInfo info;
readonly World world;
readonly Map map;
readonly Edges notVisibleEdges;
readonly byte variantStride;
readonly byte[] edgesToSpriteIndexOffset;
readonly CellLayer<TileInfo> tileInfos;
readonly CellLayer<bool> cellsDirty;
readonly Sprite[] fogSprites, shroudSprites;
readonly HashSet<PPos> cellsDirty = new HashSet<PPos>();
readonly HashSet<PPos> cellsAndNeighborsDirty = new HashSet<PPos>();
Shroud currentShroud;
Shroud shroud;
Func<PPos, bool> visibleUnderShroud, visibleUnderFog;
TerrainSpriteLayer shroudLayer, fogLayer;
bool disposed;
@@ -122,10 +123,13 @@ namespace OpenRA.Mods.Common.Traits
throw new ArgumentException("ShroudRenderer cannot define this many indexes for shroud directions.", "info");
this.info = info;
this.world = world;
map = world.Map;
tileInfos = new CellLayer<TileInfo>(map);
cellsDirty = new CellLayer<bool>(map);
// Load sprite variants
var variantCount = info.ShroudVariants.Length;
variantStride = (byte)(info.Index.Length + (info.OverrideFullShroud != null ? 1 : 0));
@@ -135,12 +139,12 @@ namespace OpenRA.Mods.Common.Traits
var sequenceProvider = map.Rules.Sequences;
for (var j = 0; j < variantCount; j++)
{
var shroud = sequenceProvider.GetSequence(info.Sequence, info.ShroudVariants[j]);
var fog = sequenceProvider.GetSequence(info.Sequence, info.FogVariants[j]);
var shroudSequence = sequenceProvider.GetSequence(info.Sequence, info.ShroudVariants[j]);
var fogSequence = sequenceProvider.GetSequence(info.Sequence, info.FogVariants[j]);
for (var i = 0; i < info.Index.Length; i++)
{
shroudSprites[j * variantStride + i] = shroud.GetSprite(i);
fogSprites[j * variantStride + i] = fog.GetSprite(i);
shroudSprites[j * variantStride + i] = shroudSequence.GetSprite(i);
fogSprites[j * variantStride + i] = fogSequence.GetSprite(i);
}
if (info.OverrideFullShroud != null)
@@ -160,6 +164,8 @@ namespace OpenRA.Mods.Common.Traits
edgesToSpriteIndexOffset[info.OverrideShroudIndex] = (byte)(variantStride - 1);
notVisibleEdges = info.UseExtendedIndex ? Edges.AllSides : Edges.AllCorners;
world.RenderPlayerChanged += WorldOnRenderPlayerChanged;
}
void IWorldLoaded.WorldLoaded(World w, WorldRenderer wr)
@@ -174,9 +180,6 @@ namespace OpenRA.Mods.Common.Traits
tileInfos[uv] = new TileInfo(screen, variant);
}
// Dirty the whole projected space
DirtyCells(map.AllCells.MapCoords.Select(uv => (PPos)uv));
// All tiles are visible in the editor
if (w.Type == WorldType.Editor)
visibleUnderShroud = _ => true;
@@ -203,6 +206,8 @@ namespace OpenRA.Mods.Common.Traits
shroudLayer = new TerrainSpriteLayer(w, wr, shroudSheet, shroudBlend, wr.Palette(info.ShroudPalette), false);
fogLayer = new TerrainSpriteLayer(w, wr, fogSheet, fogBlend, wr.Palette(info.FogPalette), false);
WorldOnRenderPlayerChanged(world.RenderPlayer);
}
Edges GetEdges(PPos puv, Func<PPos, bool> isVisible)
@@ -234,54 +239,48 @@ namespace OpenRA.Mods.Common.Traits
return info.UseExtendedIndex ? edge ^ ucorner : edge & Edges.AllCorners;
}
void DirtyCells(IEnumerable<PPos> cells)
void WorldOnRenderPlayerChanged(Player player)
{
// PERF: Many cells in the shroud change every tick. We only track the changes here and defer the real work
// we need to do until we render. This allows us to avoid wasted work.
cellsDirty.UnionWith(cells);
var newShroud = player != null ? player.Shroud : null;
if (shroud != newShroud)
{
if (shroud != null)
shroud.OnShroudChanged -= UpdateShroudCell;
if (newShroud != null)
{
visibleUnderShroud = puv => newShroud.IsExplored(puv);
visibleUnderFog = puv => newShroud.IsVisible(puv);
newShroud.OnShroudChanged += UpdateShroudCell;
}
else
{
visibleUnderShroud = puv => map.Contains(puv);
visibleUnderFog = puv => map.Contains(puv);
}
shroud = newShroud;
}
// Dirty the full projected space so the cells outside
// the map bounds can be initialized as fully shrouded.
cellsDirty.Clear(true);
var tl = new PPos(0, 0);
var br = new PPos(map.MapSize.X - 1, map.MapSize.Y - 1);
UpdateShroud(new ProjectedCellRegion(map, tl, br));
}
void IRenderShroud.RenderShroud(Shroud shroud, WorldRenderer wr)
void UpdateShroud(ProjectedCellRegion region)
{
if (currentShroud != shroud)
{
if (currentShroud != null)
currentShroud.CellsChanged -= DirtyCells;
if (shroud != null)
shroud.CellsChanged += DirtyCells;
// Needs the anonymous function to ensure the correct overload is chosen
if (shroud != null)
visibleUnderShroud = puv => currentShroud.IsExplored(puv);
else
visibleUnderShroud = puv => map.Contains(puv);
if (shroud != null)
visibleUnderFog = puv => currentShroud.IsVisible(puv);
else
visibleUnderFog = puv => map.Contains(puv);
currentShroud = shroud;
DirtyCells(map.ProjectedCellBounds);
}
// We need to update newly dirtied areas of the shroud.
// Expand the dirty area to cover the neighboring cells, since shroud is affected by neighboring cells.
foreach (var uv in cellsDirty)
{
cellsAndNeighborsDirty.Add(uv);
var cell = ((MPos)uv).ToCPos(map);
foreach (var direction in CVec.Directions)
cellsAndNeighborsDirty.Add((PPos)(cell + direction).ToMPos(map));
}
foreach (var puv in cellsAndNeighborsDirty)
foreach (var puv in region)
{
var uv = (MPos)puv;
if (!tileInfos.Contains(uv))
if (!cellsDirty[uv] || !tileInfos.Contains(uv))
continue;
cellsDirty[uv] = false;
var tileInfo = tileInfos[uv];
var shroudSprite = GetSprite(shroudSprites, GetEdges(puv, visibleUnderShroud), tileInfo.Variant);
var shroudPos = tileInfo.ScreenPosition;
@@ -296,14 +295,25 @@ namespace OpenRA.Mods.Common.Traits
shroudLayer.Update(uv, shroudSprite, shroudPos);
fogLayer.Update(uv, fogSprite, fogPos);
}
}
cellsDirty.Clear();
cellsAndNeighborsDirty.Clear();
void IRenderShroud.RenderShroud(WorldRenderer wr)
{
UpdateShroud(map.ProjectedCellBounds);
fogLayer.Draw(wr.Viewport);
shroudLayer.Draw(wr.Viewport);
}
void UpdateShroudCell(PPos puv)
{
var uv = (MPos)puv;
cellsDirty[uv] = true;
var cell = uv.ToCPos(map);
foreach (var direction in CVec.Directions)
if (map.Contains((PPos)(cell + direction).ToMPos(map)))
cellsDirty[cell + direction] = true;
}
Sprite GetSprite(Sprite[] sprites, Edges edges, int variant)
{
if (edges == Edges.None)

View File

@@ -39,8 +39,6 @@ namespace OpenRA.Mods.Common.Widgets
readonly int previewWidth;
readonly int previewHeight;
readonly HashSet<PPos> dirtyShroudCells = new HashSet<PPos>();
float radarMinimapHeight;
int frame;
bool hasRadar;
@@ -56,7 +54,7 @@ namespace OpenRA.Mods.Common.Widgets
Sprite terrainSprite;
Sprite actorSprite;
Sprite shroudSprite;
Shroud renderShroud;
Shroud shroud;
PlayerRadarTerrain playerRadarTerrain;
Player currentPlayer;
@@ -70,7 +68,6 @@ namespace OpenRA.Mods.Common.Widgets
this.worldRenderer = worldRenderer;
radarPings = world.WorldActor.TraitOrDefault<RadarPings>();
MarkShroudDirty(world.Map.AllCells.MapCoords.Select(uv => (PPos)uv));
isRectangularIsometric = world.Map.Grid.Type == MapGridType.RectangularIsometric;
cellWidth = isRectangularIsometric ? 2 : 1;
@@ -131,22 +128,21 @@ namespace OpenRA.Mods.Common.Widgets
{
currentPlayer = player;
var newRenderShroud = player != null ? player.Shroud : null;
if (newRenderShroud != renderShroud)
var newShroud = player != null ? player.Shroud : null;
if (newShroud != shroud)
{
if (renderShroud != null)
renderShroud.CellsChanged -= MarkShroudDirty;
if (shroud != null)
shroud.OnShroudChanged -= UpdateShroudCell;
if (newRenderShroud != null)
if (newShroud != null)
{
// Redraw the full shroud sprite
MarkShroudDirty(world.Map.AllCells.MapCoords.Select(uv => (PPos)uv));
// Update the notification binding
newRenderShroud.CellsChanged += MarkShroudDirty;
newShroud.OnShroudChanged += UpdateShroudCell;
foreach (var puv in world.Map.ProjectedCellBounds)
UpdateShroudCell(puv);
}
renderShroud = newRenderShroud;
shroud = newShroud;
}
var newPlayerRadarTerrain =
@@ -233,14 +229,10 @@ namespace OpenRA.Mods.Common.Widgets
void UpdateShroudCell(PPos puv)
{
var color = 0;
var rp = world.RenderPlayer;
if (rp != null)
{
if (!rp.Shroud.IsExplored(puv))
color = Color.Black.ToArgb();
else if (!rp.Shroud.IsVisible(puv))
color = Color.FromArgb(128, Color.Black).ToArgb();
}
if (!currentPlayer.Shroud.IsExplored(puv))
color = Color.Black.ToArgb();
else if (!currentPlayer.Shroud.IsVisible(puv))
color = Color.FromArgb(128, Color.Black).ToArgb();
var stride = radarSheet.Size.Width;
unsafe
@@ -248,32 +240,25 @@ namespace OpenRA.Mods.Common.Widgets
fixed (byte* colorBytes = &radarData[0])
{
var colors = (int*)colorBytes;
foreach (var uv in world.Map.Unproject(puv))
foreach (var iuv in world.Map.Unproject(puv))
{
if (isRectangularIsometric)
{
// Odd rows are shifted right by 1px
var dx = uv.V & 1;
if (uv.U + dx > 0)
colors[uv.V * stride + 2 * uv.U + dx - 1 + previewWidth] = color;
var dx = iuv.V & 1;
if (iuv.U + dx > 0)
colors[iuv.V * stride + 2 * iuv.U + dx - 1 + previewWidth] = color;
if (2 * uv.U + dx < stride)
colors[uv.V * stride + 2 * uv.U + dx + previewWidth] = color;
if (2 * iuv.U + dx < stride)
colors[iuv.V * stride + 2 * iuv.U + dx + previewWidth] = color;
}
else
colors[uv.V * stride + uv.U + previewWidth] = color;
colors[iuv.V * stride + iuv.U + previewWidth] = color;
}
}
}
}
void MarkShroudDirty(IEnumerable<PPos> projectedCellsChanged)
{
// PERF: Many cells in the shroud change every tick. We only track the changes here and defer the real work
// we need to do until we render. This allows us to avoid wasted work.
dirtyShroudCells.UnionWith(projectedCellsChanged);
}
public override string GetCursor(int2 pos)
{
if (world == null || !hasRadar)
@@ -342,13 +327,6 @@ namespace OpenRA.Mods.Common.Widgets
if (world == null)
return;
if (renderShroud != null)
{
foreach (var cell in dirtyShroudCells)
UpdateShroudCell(cell);
dirtyShroudCells.Clear();
}
radarSheet.CommitBufferedData();
var o = new float2(mapRect.Location.X, mapRect.Location.Y + world.Map.Bounds.Height * previewScale * (1 - radarMinimapHeight) / 2);
@@ -358,7 +336,7 @@ namespace OpenRA.Mods.Common.Widgets
rsr.DrawSprite(terrainSprite, o, s);
rsr.DrawSprite(actorSprite, o, s);
if (renderShroud != null)
if (shroud != null)
rsr.DrawSprite(shroudSprite, o, s);
// Draw viewport rect