Files
UnrealEngine/Engine/Source/Runtime/MeshUtilitiesCommon/Public/Allocator2D.h
2025-05-18 13:04:45 +08:00

261 lines
8.1 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Containers/Array.h"
#include "Containers/UnrealString.h"
#include "CoreMinimal.h"
#include "HAL/Platform.h"
#include "HAL/PlatformMemory.h"
#include "MeshUtilitiesCommon.h" // For ELightmapUVVersion
struct FMD5Hash;
struct FRect;
struct Rect;
template <typename FuncType> class TFunctionRef;
#define DEBUG_LAYOUT_STATS 0
class FAllocator2D
{
public:
enum class EMode
{
// In this mode, segments represents free space
// Used for the layout merging usedsegments
FreeSegments,
// In this mode, segments represents used space
// Used for the rasterization of charts.
UsedSegments
};
struct FRect
{
uint32 X;
uint32 Y;
uint32 W;
uint32 H;
};
struct FSegment
{
uint32 StartPos;
uint32 Length;
bool operator<( const FSegment& Other ) const { return StartPos < Other.StartPos; }
};
struct FRun
{
uint32 LongestSegment;
TArray< FSegment > Segments;
// Contains mapping from pixel position to first segment index in search range.
// Only computed when we're in FreeSegments mode to help TestOneRun find
// the proper segment in O(c) at the expense of a likely cache miss.
// We'll use a threshold to use this method when the number of iterations
// saved is worth the cache miss.
// Obviously, using a uint16 here will reduce cache misses but impose
// an hopefully enough limitation of texture size 65536x65536 (4GB).
TArray< uint16 > FreeSegmentsLookup;
};
public:
MESHUTILITIESCOMMON_API FAllocator2D( EMode Mode, uint32 Width, uint32 Height, ELightmapUVVersion LayoutVersion );
// Must clear before using
MESHUTILITIESCOMMON_API void Clear();
MESHUTILITIESCOMMON_API bool Find( FRect& Rect );
bool Test( FRect Rect );
MESHUTILITIESCOMMON_API void Alloc( FRect Rect );
MESHUTILITIESCOMMON_API bool FindBitByBit( FRect& Rect, const FAllocator2D& Other );
MESHUTILITIESCOMMON_API bool FindWithSegments( FRect& Rect, const FAllocator2D& Other, TFunctionRef<bool (const FAllocator2D::FRect&)> IsBestRect ) const;
bool Test( FRect Rect, const FAllocator2D& Other );
MESHUTILITIESCOMMON_API void Alloc( FRect Rect, const FAllocator2D& Other );
uint64 GetBit( uint32 x, uint32 y ) const;
void SetBit( uint32 x, uint32 y );
void ClearBit( uint32 x, uint32 y );
MESHUTILITIESCOMMON_API void CreateUsedSegments();
MESHUTILITIESCOMMON_API void MergeRun( FRun& Run, const FRun& OtherRun, uint32 RectOffset, uint32 RectLength, uint32 PrimaryResolution /* Resolution along the axis the run belongs to */, uint32 PerpendicularResolution );
MESHUTILITIESCOMMON_API void MergeSegments( const FRect& Rect, const FAllocator2D& Other );
MESHUTILITIESCOMMON_API void FlipX( const FRect& Rect );
MESHUTILITIESCOMMON_API void FlipY( const FRect& Rect );
MESHUTILITIESCOMMON_API uint32 GetUsedTexels() const;
MESHUTILITIESCOMMON_API void CopyRuns( TArray<FRun>& Runs, const TArray<FRun>& OtherRuns, int32 MaxSize );
// Take control of the copy assignment to reduce the amount of data movement to the strict minimum
FAllocator2D(const FAllocator2D& Other) = default;
MESHUTILITIESCOMMON_API FAllocator2D& operator = (const FAllocator2D& Other);
// Allow to visualize the content in ascii for debugging purpose. (i.e Watch or Immediate window).
MESHUTILITIESCOMMON_API FString ToString() const;
// Get the MD5 hash of the rasterized content
MESHUTILITIESCOMMON_API FMD5Hash GetRasterMD5() const;
uint32 GetRasterWidth() const { return RasterWidth; }
uint32 GetRasterHeight() const { return RasterHeight; }
MESHUTILITIESCOMMON_API void ResetStats();
MESHUTILITIESCOMMON_API void PublishStats( int32 ChartIndex, int32 Orientation, bool bFound, const FRect& Rect, const FRect& BestRect, const FMD5Hash& ChartMD5, TFunctionRef<bool (const FAllocator2D::FRect&)> IsBestRect );
protected:
MESHUTILITIESCOMMON_API bool TestOneRun( const FRun& Run, const FRun& OtherRun, uint32 RectOffset, uint32 RectLength, uint32 PrimaryResolution, uint32& OutFailedLength ) const;
MESHUTILITIESCOMMON_API bool TestAllRows( const FRect& Rect, const FAllocator2D& Other, uint32& FailedLength ) const;
MESHUTILITIESCOMMON_API bool TestAllColumns( const FRect& Rect, const FAllocator2D& Other, uint32& FailedLength ) const;
MESHUTILITIESCOMMON_API void InitRuns( TArray<FRun>& Runs, uint32 PrimaryResolution, uint32 PerpendicularRasterSize);
MESHUTILITIESCOMMON_API void InitSegments();
MESHUTILITIESCOMMON_API void AddUsedSegment( FRun& Run, uint32 StartPos, uint32 Length );
// Enforce that those cannot be changed in flight
const EMode Mode;
const uint32 Width;
const uint32 Height;
const uint32 Pitch;
const ELightmapUVVersion LayoutVersion;
uint32 RasterWidth;
uint32 RasterHeight;
TArray< FRun > Rows; // Represent rows in the grid
TArray< FRun > Columns; // Represent columns in the grid (used when version >= Segments2D).
TArray< uint64 > Bits;
// Index inside rows that will be sorted by rows with longest used segment first
TArray< uint16 > SortedRowsIndex;
// Index inside columns that will be sorted by columns with longest used segment first
TArray< uint16 > SortedColumnsIndex;
private:
// Store iteration stats of the principal algorithms.
struct FStats
{
struct FStat
{
#if DEBUG_LAYOUT_STATS
FORCEINLINE void operator++(int) { Value++; }
FORCEINLINE void operator+=(uint64 InValue) { Value += InValue; }
FORCEINLINE uint64 GetValue() const { return Value; }
private:
uint64 Value = 0;
#else
// These will be optimized away when stats are not activated
FORCEINLINE void operator++(int) { }
FORCEINLINE void operator+=(uint64 InValue) { }
FORCEINLINE uint64 GetValue() const { return 0; }
#endif
};
FStat FindWithSegmentsIterationsY;
FStat FindWithSegmentsIterationsX;
FStat FindWithSegmentsMovedPastPreviousBest;
FStat TestAllRowsIterationsY;
FStat FreeSegmentLookupCount;
FStat FreeSegmentRangeIterations;
FStat FreeSegmentFutureIterations;
FStat FreeSegmentFutureHit;
FStat FreeSegmentFutureHitStep;
FStat FreeSegmentFutureMiss;
FStat FreeSegmentFutureMissStep;
void Reset()
{
FPlatformMemory::Memzero(this, sizeof(FStats));
}
};
mutable FStats Stats;
};
// Returns non-zero if set
FORCEINLINE uint64 FAllocator2D::GetBit( uint32 x, uint32 y ) const
{
return Bits[ (x >> 6) + y * Pitch ] & ( 1ull << ( x & 63 ) );
}
FORCEINLINE void FAllocator2D::SetBit( uint32 x, uint32 y )
{
Bits[ (x >> 6) + y * Pitch ] |= ( 1ull << ( x & 63 ) );
// Keep track of the rasterized dimension to optimize operations on that area only
if (y >= RasterHeight)
{
RasterHeight = y + 1;
}
if (x >= RasterWidth)
{
RasterWidth = x + 1;
}
}
FORCEINLINE void FAllocator2D::ClearBit( uint32 x, uint32 y )
{
Bits[ (x >> 6) + y * Pitch ] &= ~( 1ull << ( x & 63 ) );
}
inline bool FAllocator2D::Test( FRect Rect )
{
for( uint32 y = Rect.Y; y < Rect.Y + Rect.H; y++ )
{
for( uint32 x = Rect.X; x < Rect.X + Rect.W; x++ )
{
if( GetBit( x, y ) )
{
return false;
}
}
}
return true;
}
inline bool FAllocator2D::Test( FRect Rect, const FAllocator2D& Other )
{
const uint32 LowShift = Rect.X & 63;
const uint32 HighShift = 64 - LowShift;
for( uint32 y = 0; y < Rect.H; y++ )
{
#if 1
uint32 ThisIndex = (Rect.X >> 6) + (y + Rect.Y) * Pitch;
uint32 OtherIndex = y * Pitch;
// Test a uint64 at a time
for( uint32 x = 0; x < Rect.W; x += 64 )
{
// no need to zero out HighInt on wrap around because Other will always be zero outside Rect.
uint64 LowInt = Bits[ ThisIndex ];
uint64 HighInt = Bits[ ThisIndex + 1 ];
uint64 ThisInt = (HighInt << HighShift) | (LowInt >> LowShift);
uint64 OtherInt = Other.Bits[ OtherIndex ];
if( ThisInt & OtherInt )
{
return false;
}
ThisIndex++;
OtherIndex++;
}
#else
for( uint32 x = 0; x < Rect.W; x++ )
{
if( Other.GetBit( x, y ) && GetBit( x + Rect.X, y + Rect.Y ) )
{
return false;
}
}
#endif
}
return true;
}