UDocumentation UE5.7 10.02.2026 (Source)
API documentation for Unreal Engine 5.7
TextureLayout.h
Go to the documentation of this file.
1// Copyright Epic Games, Inc. All Rights Reserved.
2
3/*=============================================================================
4 TextureLayout.h: Texture space allocation.
5=============================================================================*/
6
7#pragma once
8
9#include "CoreMinimal.h"
10#include "RHIDefinitions.h"
11
13{
14 None,
17};
18
24{
25public:
26
48
61 {
62 if (ElementSizeX == 0 || ElementSizeY == 0)
63 {
64 OutBaseX = 0;
65 OutBaseY = 0;
66 return true;
67 }
68
69 if (bAlignByFour)
70 {
71 // Pad to 4 to ensure alignment
72 ElementSizeX = (ElementSizeX + 3) & ~3;
73 ElementSizeY = (ElementSizeY + 3) & ~3;
74 }
75
76 // Try allocating space without enlarging the texture.
77 int32 NodeIndex = AddSurfaceInner(0, ElementSizeX, ElementSizeY, false);
78 if (NodeIndex == INDEX_NONE)
79 {
80 // Try allocating space which might enlarge the texture.
81 NodeIndex = AddSurfaceInner(0, ElementSizeX, ElementSizeY, true);
82 }
83
84 if (NodeIndex != INDEX_NONE)
85 {
86 FTextureLayoutNode& Node = Nodes[NodeIndex];
87 Node.bUsed = true;
88 OutBaseX = Node.MinX;
89 OutBaseY = Node.MinY;
90
91 UpdateSize(Node.MinX + ElementSizeX, Node.MinY + ElementSizeY);
92 return true;
93 }
94 else
95 {
96 return false;
97 }
98 }
99
106 {
107 if (bAlignByFour)
108 {
109 // Pad to 4 to ensure alignment
110 ElementSizeX = (ElementSizeX + 3) & ~3;
111 ElementSizeY = (ElementSizeY + 3) & ~3;
112 }
113
115 // Search through nodes to find the element to remove
116 //@todo - traverse the tree instead of iterating through all nodes
117 for (int32 NodeIndex = 0; NodeIndex < Nodes.Num(); NodeIndex++)
118 {
119 FTextureLayoutNode& Node = Nodes[NodeIndex];
120
121 if (Node.MinX == ElementBaseX
122 && Node.MinY == ElementBaseY
123 && Node.SizeX == ElementSizeX
124 && Node.SizeY == ElementSizeY)
125 {
126 FoundNodeIndex = NodeIndex;
127 break;
128 }
129 }
130
132 {
133 // Mark the found node as not being used anymore
134 Nodes[FoundNodeIndex].bUsed = false;
135
136 // Walk up the tree to find the node closest to the root that doesn't have any used children
137 int32 ParentNodeIndex = FindParentNode(FoundNodeIndex);
138 ParentNodeIndex = (ParentNodeIndex != INDEX_NONE && IsNodeUsed(ParentNodeIndex)) ? INDEX_NONE : ParentNodeIndex;
139 int32 LastParentNodeIndex = ParentNodeIndex;
140 while (ParentNodeIndex != INDEX_NONE
141 && !IsNodeUsed(Nodes[ParentNodeIndex].ChildA)
142 && !IsNodeUsed(Nodes[ParentNodeIndex].ChildB))
143 {
144 LastParentNodeIndex = ParentNodeIndex;
145 ParentNodeIndex = FindParentNode(ParentNodeIndex);
146 }
147
148 // Remove the children of the node closest to the root with only unused children,
149 // Which restores the tree to its state before this element was allocated,
150 // And allows allocations as large as LastParentNode in the future.
152 {
154 }
155
156 // Recalculate size
157 {
158 SizeX = MinSizeX;
159 SizeY = MinSizeY;
160
161 for (int32 NodeIndex = 0; NodeIndex < Nodes.Num(); NodeIndex++)
162 {
163 const FTextureLayoutNode& Node = Nodes[NodeIndex];
164
165 if (Node.bUsed)
166 {
167 UpdateSize(Node.MinX + Node.SizeX, Node.MinY + Node.SizeY);
168 }
169 }
170 }
171
172 return true;
173 }
174
175 return false;
176 }
177
181 uint32 GetSizeX() const { return SizeX; }
182
186 uint32 GetSizeY() const { return SizeY; }
187
188private:
189
190 struct FTextureLayoutNode
191 {
192 int32 ChildA,
193 ChildB;
194 uint16 MinX,
195 MinY,
196 SizeX,
197 SizeY;
198 bool bUsed;
199
200 FTextureLayoutNode() {}
201
202 FTextureLayoutNode(uint16 InMinX, uint16 InMinY, uint16 InSizeX, uint16 InSizeY):
203 ChildA(INDEX_NONE),
204 ChildB(INDEX_NONE),
205 MinX(InMinX),
206 MinY(InMinY),
207 SizeX(InSizeX),
208 SizeY(InSizeY),
209 bUsed(false)
210 {}
211 };
212
213 uint32 MinSizeX;
214 uint32 MinSizeY;
215 uint32 SizeX;
216 uint32 SizeY;
217 ETextureLayoutAspectRatio AspectRatio;
218 bool bPowerOfTwoSize;
219 bool bAlignByFour;
221
224 {
225 checkSlow(NodeIndex != INDEX_NONE);
226 // Store a copy of the current node on the stack for easier debugging.
227 // Can't store a pointer to the current node since Nodes may be reallocated in this function.
228 const FTextureLayoutNode CurrentNode = Nodes[NodeIndex];
229 // But do access this node via a pointer until the first recursive call. Prevents a ton of LHS.
230 const FTextureLayoutNode* CurrentNodePtr = &Nodes[NodeIndex];
231 if (CurrentNodePtr->ChildA != INDEX_NONE)
232 {
233 // Children are always allocated together
235
236 // Traverse the children
238
239 // The pointer is now invalid, be explicit!
240 CurrentNodePtr = 0;
241
242 if (Result != INDEX_NONE)
243 {
244 return Result;
245 }
246
247 return AddSurfaceInner(CurrentNode.ChildB, ElementSizeX, ElementSizeY, bAllowTextureEnlargement);
248 }
249 // Node has no children, it is a leaf
250 else
251 {
252 // Reject this node if it is already used
253 if (CurrentNodePtr->bUsed)
254 {
255 return INDEX_NONE;
256 }
257
258 // Reject this node if it is too small for the element being placed
260 {
261 return INDEX_NONE;
262 }
263
265 {
266 // Reject this node if this is an attempt to allocate space without enlarging the texture,
267 // And this node cannot hold the element without enlarging the texture.
268 if (CurrentNodePtr->MinX + ElementSizeX > SizeX || CurrentNodePtr->MinY + ElementSizeY > SizeY)
269 {
270 return INDEX_NONE;
271 }
272 }
273 else // Reject this node if this is an attempt to allocate space beyond max size.
274 {
275 const int32 MaxTextureSize = (1 << (MAX_TEXTURE_MIP_COUNT - 1));
278 if (bPowerOfTwoSize)
279 {
280 ExpectedSizeX = FMath::RoundUpToPowerOfTwo(ExpectedSizeX);
281 ExpectedSizeY = FMath::RoundUpToPowerOfTwo(ExpectedSizeY);
282
283 switch (AspectRatio)
284 {
286 break;
290 break;
292 ExpectedSizeX = FMath::Max(ExpectedSizeX, ExpectedSizeY * 2);
293 ExpectedSizeY = FMath::Max(ExpectedSizeY, ExpectedSizeX / 2);
294 break;
295 default:
296 checkNoEntry();
297 break;
298 }
299 }
300
301 if (ExpectedSizeX > MaxTextureSize ||ExpectedSizeY > MaxTextureSize)
302 {
303 return INDEX_NONE;
304 }
305 }
306
307 // Use this node if the size matches the requested element size
308 if (CurrentNodePtr->SizeX == ElementSizeX && CurrentNodePtr->SizeY == ElementSizeY)
309 {
310 return NodeIndex;
311 }
312
315
316 // The pointer to the current node may be invalidated below, be explicit!
317 CurrentNodePtr = 0;
318
319 // Add new nodes, and link them as children of the current node.
321 {
322 // Update the child indices
323 Nodes[NodeIndex].ChildA = Nodes.Num();
324
325 // Create a child with the same width as the element being placed.
326 // The height may not be the same as the element height yet, in that case another subdivision will occur when traversing this child node.
327 Nodes.Emplace(
328 CurrentNode.MinX,
329 CurrentNode.MinY,
331 CurrentNode.SizeY
332 );
333
334 // Create a second child to contain the leftover area in the X direction
335 Nodes[NodeIndex].ChildB = Nodes.Num();
336 Nodes.Emplace(
337 IntCastChecked<uint16>(CurrentNode.MinX + ElementSizeX),
338 CurrentNode.MinY,
339 IntCastChecked<uint16>(CurrentNode.SizeX - ElementSizeX),
340 CurrentNode.SizeY
341 );
342 }
343 else
344 {
345 Nodes[NodeIndex].ChildA = Nodes.Num();
346 Nodes.Emplace(
347 CurrentNode.MinX,
348 CurrentNode.MinY,
349 CurrentNode.SizeX,
351 );
352
353 Nodes[NodeIndex].ChildB = Nodes.Num();
354 Nodes.Emplace(
355 CurrentNode.MinX,
356 IntCastChecked<uint16>(CurrentNode.MinY + ElementSizeY),
357 CurrentNode.SizeX,
358 IntCastChecked<uint16>(CurrentNode.SizeY - ElementSizeY)
359 );
360 }
361
362 // Only traversing ChildA, since ChildA is always the newly created node that matches the element size
363 return AddSurfaceInner(Nodes[NodeIndex].ChildA, ElementSizeX, ElementSizeY, bAllowTextureEnlargement);
364 }
365 }
366
367 void UpdateSize(uint32 ElementMaxX, uint32 ElementMaxY)
368 {
369 if (bPowerOfTwoSize)
370 {
371 SizeX = FMath::Max<uint32>(SizeX, FMath::RoundUpToPowerOfTwo(ElementMaxX));
372 SizeY = FMath::Max<uint32>(SizeY, FMath::RoundUpToPowerOfTwo(ElementMaxY));
373
374 switch(AspectRatio)
375 {
377 break;
379 SizeX = FMath::Max(SizeX, SizeY);
380 SizeY = SizeX;
381 break;
383 SizeX = FMath::Max( SizeX, SizeY * 2 );
384 SizeY = FMath::Max( SizeY, SizeX / 2 );
385 break;
386 default:
387 checkNoEntry();
388 break;
389 }
390 }
391 else
392 {
393 SizeX = FMath::Max<uint32>(SizeX, ElementMaxX);
394 SizeY = FMath::Max<uint32>(SizeY, ElementMaxY);
395 }
396 }
397
399 int32 FindParentNode(int32 SearchNodeIndex)
400 {
401 //@todo - could be a constant time search if the nodes stored a parent index
402 for (int32 NodeIndex = 0; NodeIndex < Nodes.Num(); NodeIndex++)
403 {
404 FTextureLayoutNode& Node = Nodes[NodeIndex];
405 if (Node.ChildA == SearchNodeIndex || Node.ChildB == SearchNodeIndex)
406 {
407 return NodeIndex;
408 }
409 }
410 return INDEX_NONE;
411 }
412
414 bool IsNodeUsed(int32 NodeIndex)
415 {
416 bool bChildrenUsed = false;
417 if (Nodes[NodeIndex].ChildA != INDEX_NONE)
418 {
419 checkSlow(Nodes[NodeIndex].ChildB != INDEX_NONE);
420 bChildrenUsed = IsNodeUsed(Nodes[NodeIndex].ChildA) || IsNodeUsed(Nodes[NodeIndex].ChildB);
421 }
422 return Nodes[NodeIndex].bUsed || bChildrenUsed;
423 }
424
426 void RemoveChildren(int32 NodeIndex)
427 {
428 // Traverse the children depth first
429 if (Nodes[NodeIndex].ChildA != INDEX_NONE)
430 {
431 RemoveChildren(Nodes[NodeIndex].ChildA);
432 }
433 if (Nodes[NodeIndex].ChildB != INDEX_NONE)
434 {
435 RemoveChildren(Nodes[NodeIndex].ChildB);
436 }
437
438 if (Nodes[NodeIndex].ChildA != INDEX_NONE)
439 {
440 // Store off the index of the child since it may be changed in the code below
441 const int32 OldChildA = Nodes[NodeIndex].ChildA;
442
443 // Remove the child from the Nodes array
444 Nodes.RemoveAt(OldChildA);
445
446 // Iterate over all the Nodes and fix up their indices now that an element has been removed
448 {
449 if (Nodes[OtherNodeIndex].ChildA >= OldChildA)
450 {
451 Nodes[OtherNodeIndex].ChildA--;
452 }
453 if (Nodes[OtherNodeIndex].ChildB >= OldChildA)
454 {
455 Nodes[OtherNodeIndex].ChildB--;
456 }
457 }
458 // Mark the node as not having a ChildA
459 Nodes[NodeIndex].ChildA = INDEX_NONE;
460 }
461
462 if (Nodes[NodeIndex].ChildB != INDEX_NONE)
463 {
464 const int32 OldChildB = Nodes[NodeIndex].ChildB;
465 Nodes.RemoveAt(OldChildB);
467 {
468 if (Nodes[OtherNodeIndex].ChildA >= OldChildB)
469 {
470 Nodes[OtherNodeIndex].ChildA--;
471 }
472 if (Nodes[OtherNodeIndex].ChildB >= OldChildB)
473 {
474 Nodes[OtherNodeIndex].ChildB--;
475 }
476 }
477 Nodes[NodeIndex].ChildB = INDEX_NONE;
478 }
479 }
480};
481
489{
490public:
491
497
503
505 ENGINE_API void RemoveElement(FIntRect Element);
506
507private:
508
509 class FBinAllocation
510 {
511 public:
512 FIntRect LayoutAllocation;
513 TArray<int32> FreeList;
514 };
515
516 class FBin
517 {
518 public:
519
521 {
522 ElementSize = InElementSize;
523 bOutOfSpace = false;
524 }
525
526 FIntPoint ElementSize;
527 bool bOutOfSpace;
529 };
530
531 int32 BinSizeTexels;
532 FIntPoint MaxSize;
533 TArray<FBin> Bins;
534 FTextureLayout Layout;
535};
536
538{
547 template<typename ValueType>
548 void ComputeDifferenceArray(const ValueType* ValuesA, const ValueType* ValuesB, const int32 ValueCount, TArray< double >& OutValueDifferences)
549 {
550 OutValueDifferences.Reset();
551 OutValueDifferences.AddUninitialized(ValueCount);
552 for (int32 CurValueIndex = 0; CurValueIndex < ValueCount; ++CurValueIndex)
553 {
554 const ValueType CurValueA = ValuesA[CurValueIndex];
555 const ValueType CurValueB = ValuesB[CurValueIndex];
556
557 const double Difference = (double)CurValueA - (double)CurValueB;
559 }
560 }
561
562
571 template<typename ValueType>
572 double ComputeRootMeanSquareDeviation(const ValueType* Values, const int32 ValueCount)
573 {
574 // Sum the values
575 double ValuesSum = 0.0;
576 for (int32 CurValueIndex = 0; CurValueIndex < ValueCount; ++CurValueIndex)
577 {
578 const ValueType CurValue = Values[CurValueIndex];
579 ValuesSum += (double)CurValue;
580 }
581
582 // Compute the mean
583 const double ValuesMean = (double)ValuesSum / (double)ValueCount;
584
585 // Compute the squared sum of all mean deviations
587 for (int32 CurValueIndex = 0; CurValueIndex < ValueCount; ++CurValueIndex)
588 {
589 const ValueType CurValue = Values[CurValueIndex];
590 const double MeanDifference = (double)CurValue - ValuesMean;
592 }
593
594 // Compute the root mean square deviation
597
599 }
600}
#define checkSlow(expr)
Definition AssertionMacros.h:332
#define checkNoEntry()
Definition AssertionMacros.h:316
@ INDEX_NONE
Definition CoreMiscDefines.h:150
FPlatformTypes::int32 int32
A 32-bit signed integer.
Definition Platform.h:1125
UE_FORCEINLINE_HINT TSharedRef< CastToType, Mode > StaticCastSharedRef(TSharedRef< CastFromType, Mode > const &InSharedRef)
Definition SharedPointer.h:127
#define X(Name, Desc)
Definition FormatStringSan.h:47
@ MAX_TEXTURE_MIP_COUNT
Definition RHIDefinitions.h:268
ETextureLayoutAspectRatio
Definition TextureLayout.h:13
uint16_t uint16
Definition binka_ue_file_header.h:7
uint32_t uint32
Definition binka_ue_file_header.h:6
Definition TextureLayout.h:489
FBinnedTextureLayout(FIntPoint InMaxSize, int32 InBinSizeTexels)
Definition TextureLayout.h:492
Definition TextureLayout.h:24
uint32 GetSizeY() const
Definition TextureLayout.h:186
bool AddElement(uint32 &OutBaseX, uint32 &OutBaseY, uint32 ElementSizeX, uint32 ElementSizeY)
Definition TextureLayout.h:60
uint32 GetSizeX() const
Definition TextureLayout.h:181
FTextureLayout(uint32 InMinSizeX, uint32 InMinSizeY, uint32 MaxSizeX, uint32 MaxSizeY, bool bInPowerOfTwoSize=false, ETextureLayoutAspectRatio InAspect=ETextureLayoutAspectRatio::None, bool bInAlignByFour=true)
Definition TextureLayout.h:37
bool RemoveElement(uint32 ElementBaseX, uint32 ElementBaseY, uint32 ElementSizeX, uint32 ElementSizeY)
Definition TextureLayout.h:105
Definition Array.h:670
UE_REWRITE SizeType Num() const
Definition Array.h:1144
void RemoveAt(SizeType Index, EAllowShrinking AllowShrinking=UE::Core::Private::AllowShrinkingByDefault< AllocatorType >())
Definition Array.h:2083
UE_FORCEINLINE_HINT SizeType Emplace(ArgsType &&... Args)
Definition Array.h:2561
Definition TextureLayout.h:538
void ComputeDifferenceArray(const ValueType *ValuesA, const ValueType *ValuesB, const int32 ValueCount, TArray< double > &OutValueDifferences)
Definition TextureLayout.h:548
double ComputeRootMeanSquareDeviation(const ValueType *Values, const int32 ValueCount)
Definition TextureLayout.h:572
UE_STRING_CLASS Result(Forward< LhsType >(Lhs), RhsLen)
Definition String.cpp.inl:732
@ false
Definition radaudio_common.h:23
Definition IntPoint.h:25