1
0
Fork 0
mirror of https://github.com/Ellpeck/MLEM.git synced 2024-11-22 12:58:33 +01:00

Improved RuntimeTexturePacker performance for differently sized textures

This commit is contained in:
Ell 2023-05-15 17:50:41 +02:00
parent e623eff02d
commit 99b45b09d9
2 changed files with 35 additions and 22 deletions

View file

@ -47,6 +47,10 @@ Fixes
- Fixed Paragraph and Checkbox not reacting to SquishingGroup sizing properly - Fixed Paragraph and Checkbox not reacting to SquishingGroup sizing properly
- Fixed TextInput and Slider still reacting to input when they are selected, but not part of the active root - Fixed TextInput and Slider still reacting to input when they are selected, but not part of the active root
### MLEM.Data
Improvements
- Improved RuntimeTexturePacker performance for differently sized textures
## 6.1.0 ## 6.1.0
### MLEM ### MLEM

View file

@ -36,7 +36,7 @@ namespace MLEM.Data {
private readonly List<Request> texturesToPack = new List<Request>(); private readonly List<Request> texturesToPack = new List<Request>();
private readonly List<Request> packedTextures = new List<Request>(); private readonly List<Request> packedTextures = new List<Request>();
private readonly Dictionary<Point, Point> firstPossiblePosForSize = new Dictionary<Point, Point>(); private readonly Dictionary<Point, Request> occupiedPositions = new Dictionary<Point, Request>();
private readonly Dictionary<Texture2D, TextureData> dataCache = new Dictionary<Texture2D, TextureData>(); private readonly Dictionary<Texture2D, TextureData> dataCache = new Dictionary<Texture2D, TextureData>();
private readonly bool autoIncreaseMaxWidth; private readonly bool autoIncreaseMaxWidth;
private readonly bool forcePowerOfTwo; private readonly bool forcePowerOfTwo;
@ -162,9 +162,7 @@ namespace MLEM.Data {
// we pack larger textures first, so that smaller textures can fit in the gaps that larger ones leave // we pack larger textures first, so that smaller textures can fit in the gaps that larger ones leave
var stopwatch = Stopwatch.StartNew(); var stopwatch = Stopwatch.StartNew();
foreach (var request in this.texturesToPack.OrderByDescending(t => t.Texture.Width * t.Texture.Height)) { foreach (var request in this.texturesToPack.OrderByDescending(t => t.Texture.Width * t.Texture.Height)) {
request.PackedArea = this.FindFreeArea(request); request.PackedArea = this.OccupyFreeArea(request);
// if this is the first position that this request fit in, no other requests of the same size will find a position before it
this.firstPossiblePosForSize[new Point(request.PackedArea.Width, request.PackedArea.Height)] = request.PackedArea.Location;
this.packedTextures.Add(request); this.packedTextures.Add(request);
} }
stopwatch.Stop(); stopwatch.Stop();
@ -224,7 +222,7 @@ namespace MLEM.Data {
this.LastPackTime = TimeSpan.Zero; this.LastPackTime = TimeSpan.Zero;
this.texturesToPack.Clear(); this.texturesToPack.Clear();
this.packedTextures.Clear(); this.packedTextures.Clear();
this.firstPossiblePosForSize.Clear(); this.occupiedPositions.Clear();
this.dataCache.Clear(); this.dataCache.Clear();
} }
@ -233,31 +231,42 @@ namespace MLEM.Data {
this.Reset(); this.Reset();
} }
private Rectangle FindFreeArea(Request request) { private Rectangle OccupyFreeArea(Request request) {
var size = new Point(request.Texture.Width, request.Texture.Height); var size = new Point(request.Texture.Width, request.Texture.Height);
size.X += request.Padding * 2; size.X += request.Padding * 2;
size.Y += request.Padding * 2; size.Y += request.Padding * 2;
var pos = this.firstPossiblePosForSize.TryGetValue(size, out var first) ? first : Point.Zero; // exit early if the texture doesn't need to find a free location
if (size.X <= 0 || size.Y <= 0)
return Rectangle.Empty;
var area = new Rectangle(Point.Zero, size);
var lowestY = int.MaxValue; var lowestY = int.MaxValue;
while (true) { while (true) {
var intersected = false; // check if the current area is already occupied
var area = new Rectangle(pos.X, pos.Y, size.X, size.Y); if (!this.occupiedPositions.TryGetValue(area.Location, out var existing)) {
foreach (var tex in this.packedTextures) { existing = this.packedTextures.FirstOrDefault(t => t.PackedArea.Intersects(area));
if (tex.PackedArea.Intersects(area)) { if (existing == null) {
pos.X = tex.PackedArea.Right; // if no texture is occupying this space, we have found a free area
// when we move down, we want to move down by the smallest intersecting texture's height this.occupiedPositions.Add(area.Location, request);
if (lowestY > tex.PackedArea.Bottom) return area;
lowestY = tex.PackedArea.Bottom;
intersected = true;
break;
} }
// also cache the existing texture for this position, in case we check it again in the future
this.occupiedPositions.Add(area.Location, existing);
} }
if (!intersected)
return area; // move to the right by the existing texture's width
if (pos.X + size.X > this.maxWidth) { area.X = existing.PackedArea.Right;
pos.X = 0;
pos.Y = lowestY; // remember the smallest intersecting texture's height for when we move down
if (lowestY > existing.PackedArea.Bottom)
lowestY = existing.PackedArea.Bottom;
// move down a row if we exceed our maximum width
if (area.Right > this.maxWidth) {
area.X = 0;
area.Y = lowestY;
lowestY = int.MaxValue; lowestY = int.MaxValue;
} }
} }