Sped up shroud rendering.

- Only update shroud within the visible screen area, rather than the whole map. This improves performance on larger maps significantly when scrolling around since large portions of the shroud do not need to be updated.
- Provide methods in Shroud to return delegates to check for explored/visibility for tiles within a certain region. This allows it to return more efficient delegates whenever the region is within the map bounds, or shroud/fog is disabled. In the typical case where the region is in bounds and shroud/fog is enabled, the fast check is almost twice as fast as the slow check.
- Use the Shroud delegate functions in shroud rendering, frozen actors, minimap rendering and resource layer areas to provide a speedup since these areas of code can often take advantage of the fact they perform checks within the map boundary.
- Cache current element in CellRegionEnumerator to prevent repeated work if the element is accessed more than once.
- Decrease the size of elements in some arrays in hopes of reducing memory needs and improving cache hits.
This commit is contained in:
RoosterDragon
2014-07-06 22:33:42 +01:00
parent 2351c43237
commit a512d9ad0a
8 changed files with 235 additions and 101 deletions

View File

@@ -180,12 +180,14 @@ namespace OpenRA.Graphics
{
var colors = (int*)bitmapData.Scan0;
var stride = bitmapData.Stride / 4;
var shroudObscured = world.ShroudObscuresTest(map.Cells);
var fogObscured = world.FogObscuresTest(map.Cells);
foreach (var cell in map.Cells)
{
var uv = Map.CellToMap(map.TileShape, cell) - offset;
if (world.ShroudObscures(cell))
if (shroudObscured(cell))
colors[uv.Y * stride + uv.X] = shroud;
else if (world.FogObscures(cell))
else if (fogObscured(cell))
colors[uv.Y * stride + uv.X] = fog;
}
}

View File

@@ -8,8 +8,10 @@
*/
#endregion
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace OpenRA
{
@@ -47,29 +49,69 @@ namespace OpenRA
return new CellRegion(region.shape, tl, br);
}
/// <summary>Returns the minimal region that covers at least the specified cells.</summary>
public static CellRegion BoundingRegion(TileShape shape, IEnumerable<CPos> cells)
{
if (cells == null || !cells.Any())
throw new ArgumentException("cells must not be null or empty.", "cells");
var minX = int.MaxValue;
var minY = int.MaxValue;
var maxX = int.MinValue;
var maxY = int.MinValue;
foreach (var cell in cells)
{
if (minX > cell.X)
minX = cell.X;
if (maxX < cell.X)
maxX = cell.X;
if (minY > cell.Y)
minY = cell.Y;
if (maxY < cell.Y)
maxY = cell.Y;
}
return new CellRegion(shape, new CPos(minX, minY), new CPos(maxX, maxY));
}
public bool Contains(CellRegion region)
{
return
TopLeft.X <= region.TopLeft.X && TopLeft.Y <= region.TopLeft.Y &&
BottomRight.X >= region.BottomRight.X && BottomRight.Y >= region.BottomRight.Y;
}
public bool Contains(CPos cell)
{
var uv = Map.CellToMap(shape, cell);
return uv.X >= mapTopLeft.X && uv.X <= mapBottomRight.X && uv.Y >= mapTopLeft.Y && uv.Y <= mapBottomRight.Y;
}
public IEnumerator<CPos> GetEnumerator()
public CellRegionEnumerator GetEnumerator()
{
return new CellRegionEnumerator(this);
}
IEnumerator<CPos> IEnumerable<CPos>.GetEnumerator()
{
return GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
class CellRegionEnumerator : IEnumerator<CPos>
public class CellRegionEnumerator : IEnumerator<CPos>
{
readonly CellRegion r;
// Current position, in map coordinates
int u, v;
// Current position, in cell coordinates
CPos current;
public CellRegionEnumerator(CellRegion region)
{
r = region;
@@ -91,6 +133,7 @@ namespace OpenRA
return false;
}
current = Map.MapToCell(r.shape, new CPos(u, v));
return true;
}
@@ -101,7 +144,7 @@ namespace OpenRA
v = r.mapTopLeft.Y;
}
public CPos Current { get { return Map.MapToCell(r.shape, new CPos(u, v)); } }
public CPos Current { get { return current; } }
object IEnumerator.Current { get { return Current; } }
public void Dispose() { }
}

View File

@@ -23,6 +23,7 @@ namespace OpenRA.Traits
public class FrozenActor
{
public readonly CPos[] Footprint;
public readonly CellRegion FootprintRegion;
public readonly WPos CenterPosition;
public readonly Rectangle Bounds;
readonly Actor actor;
@@ -38,10 +39,12 @@ namespace OpenRA.Traits
public bool Visible;
public FrozenActor(Actor self, IEnumerable<CPos> footprint)
public FrozenActor(Actor self, CPos[] footprint, CellRegion footprintRegion)
{
actor = self;
Footprint = footprint.ToArray();
Footprint = footprint;
FootprintRegion = footprintRegion;
CenterPosition = self.CenterPosition;
Bounds = self.Bounds.Value;
}
@@ -54,16 +57,7 @@ namespace OpenRA.Traits
int flashTicks;
public void Tick(World world, Shroud shroud)
{
Visible = true;
foreach (var pos in Footprint)
{
if (shroud.IsVisible(pos))
{
Visible = false;
break;
}
}
Visible = !Footprint.Any(shroud.IsVisibleTest(FootprintRegion));
if (flashTicks > 0)
flashTicks--;
}

View File

@@ -26,8 +26,8 @@ namespace OpenRA.Traits
readonly Actor self;
readonly Map map;
readonly CellLayer<int> visibleCount;
readonly CellLayer<int> generatedShroudCount;
readonly CellLayer<short> visibleCount;
readonly CellLayer<short> generatedShroudCount;
readonly CellLayer<bool> explored;
readonly Lazy<IFogVisibilityModifier[]> fogVisibilities;
@@ -39,13 +39,19 @@ namespace OpenRA.Traits
public int Hash { get; private set; }
static readonly Func<CPos, bool> TruthPredicate = cell => true;
readonly Func<CPos, bool> fastExploredTest;
readonly Func<CPos, bool> slowExploredTest;
readonly Func<CPos, bool> fastVisibleTest;
readonly Func<CPos, bool> slowVisibleTest;
public Shroud(Actor self)
{
this.self = self;
map = self.World.Map;
visibleCount = new CellLayer<int>(map);
generatedShroudCount = new CellLayer<int>(map);
visibleCount = new CellLayer<short>(map);
generatedShroudCount = new CellLayer<short>(map);
explored = new CellLayer<bool>(map);
self.World.ActorAdded += AddVisibility;
@@ -55,6 +61,11 @@ namespace OpenRA.Traits
self.World.ActorRemoved += RemoveShroudGeneration;
fogVisibilities = Exts.Lazy(() => self.TraitsImplementing<IFogVisibilityModifier>().ToArray());
fastExploredTest = IsExploredCore;
slowExploredTest = IsExplored;
fastVisibleTest = IsVisibleCore;
slowVisibleTest = IsVisible;
}
void Invalidate()
@@ -223,10 +234,32 @@ namespace OpenRA.Traits
if (!map.Contains(cell))
return false;
if (Disabled || !self.World.LobbyInfo.GlobalSettings.Shroud)
if (!ShroudEnabled)
return true;
return explored[cell] && (generatedShroudCount[cell] == 0 || visibleCount[cell] > 0);
return IsExploredCore(cell);
}
bool ShroudEnabled { get { return !Disabled && self.World.LobbyInfo.GlobalSettings.Shroud; } }
bool IsExploredCore(CPos cell)
{
var uv = Map.CellToMap(map.TileShape, cell);
return explored[uv.X, uv.Y] && (generatedShroudCount[uv.X, uv.Y] == 0 || visibleCount[uv.X, uv.Y] > 0);
}
public Func<CPos, bool> IsExploredTest(CellRegion region)
{
// If the region to test extends outside the map we must use the slow test that checks the map boundary every time.
if (!map.Cells.Contains(region))
return slowExploredTest;
// If shroud isn't enabled, then we can see everything.
if (!ShroudEnabled)
return TruthPredicate;
// If shroud is enabled, we can use the fast test that just does the core check.
return fastExploredTest;
}
public bool IsExplored(Actor a)
@@ -239,12 +272,33 @@ namespace OpenRA.Traits
if (!map.Contains(cell))
return false;
if (Disabled || !self.World.LobbyInfo.GlobalSettings.Fog)
if (!FogEnabled)
return true;
return IsVisibleCore(cell);
}
bool FogEnabled { get { return !Disabled && self.World.LobbyInfo.GlobalSettings.Fog; } }
bool IsVisibleCore(CPos cell)
{
return visibleCount[cell] > 0;
}
public Func<CPos, bool> IsVisibleTest(CellRegion region)
{
// If the region to test extends outside the map we must use the slow test that checks the map boundary every time.
if (!map.Cells.Contains(region))
return slowVisibleTest;
// If fog isn't enabled, then we can see everything.
if (!FogEnabled)
return TruthPredicate;
// If fog is enabled, we can use the fast test that just does the core check.
return fastVisibleTest;
}
// Actors are hidden under shroud, but not under fog by default
public bool IsVisible(Actor a)
{

View File

@@ -24,6 +24,7 @@ namespace OpenRA
{
public class World
{
static readonly Func<CPos, bool> FalsePredicate = cell => false;
internal readonly TraitDictionary traitDict = new TraitDictionary();
readonly HashSet<Actor> actors = new HashSet<Actor>();
readonly List<IEffect> effects = new List<IEffect>();
@@ -53,6 +54,24 @@ namespace OpenRA
public bool FogObscures(CPos p) { return RenderPlayer != null && !RenderPlayer.Shroud.IsVisible(p); }
public bool ShroudObscures(Actor a) { return RenderPlayer != null && !RenderPlayer.Shroud.IsExplored(a); }
public bool ShroudObscures(CPos p) { return RenderPlayer != null && !RenderPlayer.Shroud.IsExplored(p); }
public Func<CPos, bool> FogObscuresTest(CellRegion region)
{
var rp = RenderPlayer;
if (rp == null)
return FalsePredicate;
var predicate = rp.Shroud.IsVisibleTest(region);
return cell => !predicate(cell);
}
public Func<CPos, bool> ShroudObscuresTest(CellRegion region)
{
var rp = RenderPlayer;
if (rp == null)
return FalsePredicate;
var predicate = rp.Shroud.IsExploredTest(region);
return cell => !predicate(cell);
}
public bool IsReplay
{