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

881 lines
22 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Allocator2D.h"
#include "Misc/SecureHash.h"
#include "ProfilingDebugging/MiscTrace.h"
FAllocator2D::FAllocator2D( FAllocator2D::EMode InMode, uint32 InWidth, uint32 InHeight, ELightmapUVVersion InLayoutVersion)
: Mode( InMode )
, Width( InWidth )
, Height( InHeight )
, Pitch( ( InWidth + 63 ) / 64 )
, LayoutVersion(InLayoutVersion)
, RasterWidth(0)
, RasterHeight(0)
{
check(Width <= MAX_uint16);
check(Height <= MAX_uint16);
Bits.SetNumZeroed(Pitch * Height + 1); // alloc +1 to avoid buffer overrun
Rows.SetNum( Height );
SortedRowsIndex.SetNum(Height);
for (uint32 Index = 0; Index < Height; ++Index)
{
SortedRowsIndex[Index] = (uint16)Index;
}
if (LayoutVersion >= ELightmapUVVersion::Segments2D)
{
Columns.SetNum( Width );
SortedColumnsIndex.SetNum(Width);
for (uint32 Index = 0; Index < Width; ++Index)
{
SortedColumnsIndex[Index] = (uint16)Index;
}
}
Clear();
}
void FAllocator2D::Clear()
{
InitSegments();
// Only clear the section we used to reduce memory traffic
FPlatformMemory::Memzero(Bits.GetData(), Pitch * RasterHeight * Bits.GetTypeSize());
RasterWidth = 0;
RasterHeight = 0;
}
void FAllocator2D::CopyRuns(TArray<FRun>& Runs, const TArray<FRun>& OtherRuns, int32 MaxSize)
{
Runs.SetNum(OtherRuns.Num(), EAllowShrinking::No);
MaxSize = FMath::Min(Runs.Num(), MaxSize);
FRun* RunsPtr = Runs.GetData();
const FRun* OtherRunsPtr = OtherRuns.GetData();
for (int32 Index = 0; Index < MaxSize; ++Index, ++RunsPtr, ++OtherRunsPtr)
{
*RunsPtr = *OtherRunsPtr;
}
}
// Optimized version to alloc and copy as less data possible.
FAllocator2D& FAllocator2D::operator = (const FAllocator2D & Other)
{
if (&Other == this)
{
return *this;
}
check(Mode == Other.Mode);
check(Width == Other.Width);
check(Height == Other.Height);
check(Pitch == Other.Pitch);
FPlatformMemory::Memcpy(Bits.GetData(), Other.Bits.GetData(), Pitch * Other.RasterHeight * Bits.GetTypeSize());
// Zero out anything that was used and not overwritten by the copy
if (RasterHeight > Other.RasterHeight)
{
FPlatformMemory::Memzero(Bits.GetData() + Pitch * Other.RasterHeight, Pitch * (RasterHeight - Other.RasterHeight) * Bits.GetTypeSize());
}
RasterWidth = Other.RasterWidth;
RasterHeight = Other.RasterHeight;
CopyRuns(Rows, Other.Rows, Other.RasterHeight);
SortedRowsIndex = Other.SortedRowsIndex;
if (LayoutVersion >= ELightmapUVVersion::Segments2D)
{
CopyRuns(Columns, Other.Columns, Other.RasterWidth);
SortedColumnsIndex = Other.SortedColumnsIndex;
}
return *this;
}
FMD5Hash FAllocator2D::GetRasterMD5() const
{
FMD5 MD5;
const FRun* RowPtr = Rows.GetData();
const FRun* RowPtrEnd = RowPtr + RasterHeight;
for (; RowPtr < RowPtrEnd; ++RowPtr)
{
uint32 SegmentsNum = RowPtr->Segments.Num();
// It is important for empty rows to also have a footprint in the MD5
MD5.Update((const uint8*)&SegmentsNum, sizeof(SegmentsNum));
MD5.Update((const uint8*)RowPtr->Segments.GetData(), SegmentsNum * RowPtr->Segments.GetTypeSize() );
}
FMD5Hash MD5Hash;
MD5Hash.Set(MD5);
return MD5Hash;
}
bool FAllocator2D::Find( FRect& Rect )
{
FRect TestRect = Rect;
for( TestRect.X = 0; TestRect.X <= Width - TestRect.W; TestRect.X++ )
{
for( TestRect.Y = 0; TestRect.Y <= Height - TestRect.H; TestRect.Y++ )
{
if( Test( TestRect ) )
{
Rect = TestRect;
return true;
}
}
}
return false;
}
FString FAllocator2D::ToString() const
{
TArray<TCHAR> Text;
Text.SetNum(Width+2);
// Truncate output to last meaningful row
int32 lastRow = 1;
for (const FRun& Row : Rows)
{
if ((Mode == EMode::FreeSegments && Row.Segments.Num() > 1) ||
(Mode == EMode::UsedSegments && Row.Segments.Num() != 0))
{
lastRow++;
}
}
FString Output;
Output.Append(TEXT("BEGIN -----------------------\n"));
const TCHAR FillChar = Mode == EMode::FreeSegments ? TEXT('-') : TEXT(' ');
const TCHAR UsedChar = Mode == EMode::FreeSegments ? TEXT(' ') : TEXT('+');
for (const FRun& Row : Rows)
{
for (uint32 x = 0; x < Width; ++x)
{
Text[x] = FillChar;
}
for (const FSegment& Segment : Row.Segments)
{
for (uint32 x = Segment.StartPos; x < Segment.StartPos + Segment.Length; ++x)
{
Text[x] = UsedChar;
}
}
Text[Text.Num()-2] = TEXT('\n');
Text[Text.Num()-1] = TEXT('\0');
Output.Append(Text.GetData());
if (--lastRow == 0)
{
break;
}
}
Output.Appendf(TEXT("MD5 %s\n"), *LexToString(GetRasterMD5()));
Output.Append(TEXT("END -----------------------\n"));
return Output;
}
bool FAllocator2D::FindBitByBit( FRect& Rect, const FAllocator2D& Other )
{
FRect TestRect = Rect;
for( TestRect.X = 0; TestRect.X <= Width - TestRect.W; TestRect.X++ )
{
for( TestRect.Y = 0; TestRect.Y <= Height - TestRect.H; TestRect.Y++ )
{
if( Test( TestRect, Other ) )
{
Rect = TestRect;
return true;
}
}
}
return false;
}
bool FAllocator2D::FindWithSegments( FRect& Rect, const FAllocator2D& Other, TFunctionRef<bool (const FRect&)> IsBestRect) const
{
FRect TestRect = Rect;
const uint32 MaxWidth = Width - TestRect.W;
const uint32 MaxHeight = Height - TestRect.H;
// For charts that are longer on the Y axis, its a lot faster to use
// another grid optimized for searching vertically because it will reduce
// iteration count tremendously by improving strides we make for each iteration.
// We need a version check to activate this feature because it will favor
// placement along the Y axis leading to (still efficient) but different results.
if (LayoutVersion >= ELightmapUVVersion::Segments2D && TestRect.H > TestRect.W)
{
// Perfect fit algorithm will try every position to fit our chart
for( ; TestRect.X <= MaxWidth; ++TestRect.X)
{
Stats.FindWithSegmentsIterationsX++;
// This represents the length we know for sure we won't fit in so we can skip over
uint32 FailedLength = 1;
for( TestRect.Y = 0; TestRect.Y <= MaxHeight; TestRect.Y += FailedLength)
{
Stats.FindWithSegmentsIterationsY++;
// Do not bother continue if another orientation had a better result
if( !IsBestRect(Rect) )
{
Stats.FindWithSegmentsMovedPastPreviousBest++;
return false;
}
if( TestAllColumns( TestRect, Other, FailedLength ) )
{
Rect = TestRect;
return true;
}
}
}
}
else
{
// Perfect fit algorithm will try every position to fit our chart
for( ; TestRect.Y <= MaxHeight; ++TestRect.Y)
{
Stats.FindWithSegmentsIterationsY++;
// This represents the length we know for sure we won't fit in so we can skip over
uint32 FailedLength = 1;
for( TestRect.X = 0; TestRect.X <= MaxWidth; TestRect.X += FailedLength)
{
Stats.FindWithSegmentsIterationsX++;
// Do not bother continue if another orientation had a better result
if( !IsBestRect(Rect) )
{
Stats.FindWithSegmentsMovedPastPreviousBest++;
return false;
}
if( TestAllRows( TestRect, Other, FailedLength ) )
{
Rect = TestRect;
return true;
}
}
}
}
return false;
}
uint32 FAllocator2D::GetUsedTexels() const
{
uint32 Texels = 0;
for (const FRun& Row : Rows)
{
for (const FSegment& Segment : Row.Segments)
{
Texels += Segment.Length;
}
}
if (Mode == EMode::FreeSegments)
{
Texels = Width * Height - Texels;
}
return Texels;
}
void FAllocator2D::Alloc( FRect Rect )
{
for( uint32 y = Rect.Y; y < Rect.Y + Rect.H; y++ )
{
for( uint32 x = Rect.X; x < Rect.X + Rect.W; x++ )
{
SetBit( x, y );
}
}
}
void FAllocator2D::Alloc( FRect Rect, const FAllocator2D& Other )
{
for( uint32 y = 0; y < Rect.H; y++ )
{
for( uint32 x = 0; x < Rect.W; x++ )
{
if( Other.GetBit( x, y ) )
{
SetBit( x + Rect.X, y + Rect.Y );
}
}
}
MergeSegments( Rect, Other );
}
bool FAllocator2D::TestAllRows( const FRect& Rect, const FAllocator2D& Other, uint32& OutFailedLength ) const
{
for ( uint32 Index = 0; Index < Other.RasterHeight; ++Index)
{
Stats.TestAllRowsIterationsY++;
// Longest rows first have a higher chance of giving us a big stride when it fails
const uint16 y = Other.SortedRowsIndex.GetData()[Index];
const FRun& ThisRow = Rows.GetData()[ Rect.Y + y ];
const FRun& OtherRow = Other.Rows.GetData()[ y ];
if (!TestOneRun(ThisRow, OtherRow, Rect.X, Rect.W, Width, OutFailedLength))
{
return false;
}
}
return true;
}
bool FAllocator2D::TestAllColumns( const FRect& Rect, const FAllocator2D& Other, uint32& OutFailedLength ) const
{
for ( uint32 Index = 0; Index < Other.RasterWidth; ++Index)
{
// Longest columns first have a higher chance of giving us a big stride when it fails
const uint16 x = Other.SortedColumnsIndex.GetData()[Index];
const FRun& ThisColumn = Columns.GetData()[ Rect.X + x ];
const FRun& OtherColumn = Other.Columns.GetData()[ x ];
if (!TestOneRun(ThisColumn, OtherColumn, Rect.Y, Rect.H, Height, OutFailedLength))
{
return false;
}
}
return true;
}
bool FAllocator2D::TestOneRun(const FRun& ThisRun, const FRun& OtherRun, uint32 RectOffset, uint32 RectLength, uint32 PrimaryResolution, uint32& OutFailedLength) const
{
// Early out if we can't fit the longest used segment into our longest free segment
if ( ThisRun.LongestSegment < OtherRun.LongestSegment )
{
OutFailedLength = PrimaryResolution;
return false;
}
// Threshold to perform a linear scan until the cache miss induced by the lookup is worth it
const int32 LoopkupThreshold = 16;
const FSegment* FreeSegments = ThisRun.Segments.GetData();
const FSegment* FreeSegmentsEnd = ThisRun.Segments.GetData() + ThisRun.Segments.Num();
const uint16* FreeSegmentLookup = ThisRun.Segments.Num() > LoopkupThreshold ? ThisRun.FreeSegmentsLookup.GetData() : nullptr;
const FSegment* UsedSegments = OtherRun.Segments.GetData();
const FSegment* UsedSegmentsEnd = OtherRun.Segments.GetData() + OtherRun.Segments.Num();
// Running pointer for used segments
const FSegment* UsedSegment = UsedSegments;
for ( ; UsedSegment < UsedSegmentsEnd; ++UsedSegment)
{
// #TODO maintain this for backward compatibility but not sure if this could ever happen
// and if the result is valid if we break instead of returning false when a used segment doesn't fit
if ( UsedSegment->StartPos >= RectLength )
{
break;
}
const uint32 StartPos = RectOffset + UsedSegment->StartPos;
const uint32 EndPos = RectOffset + FMath::Min( UsedSegment->StartPos + UsedSegment->Length, RectLength );
// Running pointer for free segments
const FSegment* FreeSegment = FreeSegments;
// If lookup exist, advance pointer to the exact spot instead of scanning
if (FreeSegmentLookup)
{
FreeSegment += FreeSegmentLookup[StartPos];
Stats.FreeSegmentLookupCount++;
}
// Scan the search range to see if the current used segment fits in
// we use a for loop to support scanning from the beginning if no lookup exists
bool bFoundSpaceForSegment = false;
for (; FreeSegment < FreeSegmentsEnd && StartPos >= FreeSegment->StartPos; ++FreeSegment)
{
Stats.FreeSegmentRangeIterations++;
// Check if there's a free segment that can fit the used segment
if (EndPos <= FreeSegment->StartPos + FreeSegment->Length)
{
bFoundSpaceForSegment = true;
break;
}
}
// If nothing was found in the search range, scan other free segments to hint our
// caller of how much farther it should advance for next test
if (!bFoundSpaceForSegment)
{
for (; FreeSegment < FreeSegmentsEnd; ++FreeSegment)
{
Stats.FreeSegmentFutureIterations++;
if (UsedSegment->Length <= FreeSegment->Length)
{
OutFailedLength = FreeSegment->StartPos - StartPos;
Stats.FreeSegmentFutureHit++;
Stats.FreeSegmentFutureHitStep += OutFailedLength;
return false;
}
}
// We couldn't find any free segment big enough
// set failed length so the whole run will be skipped
OutFailedLength = PrimaryResolution;
Stats.FreeSegmentFutureMiss++;
Stats.FreeSegmentFutureMissStep += OutFailedLength;
return false;
}
}
return true;
}
void FAllocator2D::FlipX( const FRect& Rect )
{
// If we have empty padding around the Rect, keep it there
uint32 MaxX = 0;
uint32 MaxY = RasterHeight - 1;
if ( LayoutVersion >= ELightmapUVVersion::Allocator2DFlipFix )
{
MaxX = RasterWidth - 1;
}
else
{
MaxX = Rect.W - 1;
}
for ( uint32 Y = 0; Y <= MaxY; ++Y )
{
for ( uint32 LowX = 0; LowX < ( MaxX + 1 ) / 2; ++LowX )
{
uint32 HighX = MaxX - LowX;
const uint64 BitLow = GetBit( LowX, Y );
const uint64 BitHigh = GetBit( HighX, Y );
if ( BitLow != 0ull )
{
SetBit( HighX, Y );
}
else
{
ClearBit( HighX, Y );
}
if ( BitHigh != 0ull )
{
SetBit( LowX, Y );
}
else
{
ClearBit( LowX, Y );
}
}
}
CreateUsedSegments();
}
void FAllocator2D::FlipY( const FRect& Rect )
{
uint32 MinY = 0;
uint32 MaxY = RasterHeight - 1;
for ( uint32 LowY = 0; LowY < ( MaxY + 1 ) / 2; ++LowY )
{
for ( uint32 X = 0; X < RasterWidth; ++X )
{
uint32 HighY = MaxY - LowY;
const uint64 BitLow = GetBit( X, LowY );
const uint64 BitHigh = GetBit( X, HighY );
if ( BitLow != 0ull )
{
SetBit( X, HighY );
}
else
{
ClearBit( X, HighY );
}
if ( BitHigh != 0ull )
{
SetBit( X, LowY );
}
else
{
ClearBit( X, LowY );
}
}
}
CreateUsedSegments();
}
void FAllocator2D::InitRuns(TArray<FRun>& Runs, uint32 PrimaryResolution, uint32 PerpendicularRasterSize)
{
if (Mode == EMode::FreeSegments)
{
FSegment FreeSegment;
FreeSegment.StartPos = 0;
FreeSegment.Length = PrimaryResolution;
for ( FRun& Run : Runs )
{
Run.Segments.Reset();
Run.Segments.Add( FreeSegment );
Run.LongestSegment = PrimaryResolution;
Run.FreeSegmentsLookup.Reset();
}
}
else
{
// This is called inside a hot loop, get rid of RangeCheck.
FRun* Run = Runs.GetData();
for ( uint32 Index = 0; Index < PerpendicularRasterSize; ++Index )
{
Run[Index].Segments.Reset();
Run[Index].LongestSegment = 0;
}
}
}
void FAllocator2D::InitSegments()
{
InitRuns(Rows, Width, RasterHeight);
if (LayoutVersion >= ELightmapUVVersion::Segments2D)
{
InitRuns(Columns, Height, RasterWidth);
}
}
void FAllocator2D::CreateUsedSegments()
{
check(Mode == EMode::UsedSegments);
SortedRowsIndex.SetNum(RasterHeight, EAllowShrinking::No);
uint64* BitsData = Bits.GetData();
// Create segments along the X axis for each rows
for ( uint32 y = 0; y < RasterHeight; ++y )
{
SortedRowsIndex[y] = y;
FRun& CurrentRow = Rows[y];
CurrentRow.LongestSegment = 0;
CurrentRow.Segments.Reset();
int32 FirstUsedX = -1;
for ( uint32 k = 0; k < Pitch; ++k, ++BitsData )
{
const uint32 x = k * 64;
const uint64 BitsValue = *BitsData;
// If all bits are set
if ( BitsValue == ~0ull )
{
if ( FirstUsedX < 0 )
{
FirstUsedX = x;
}
if ( k == Pitch - 1 )
{
AddUsedSegment( CurrentRow, FirstUsedX, x + 64 - FirstUsedX );
FirstUsedX = -1;
}
}
// No bits are set
else if ( BitsValue == 0ull )
{
if ( FirstUsedX >= 0 )
{
AddUsedSegment( CurrentRow, FirstUsedX, x - FirstUsedX );
FirstUsedX = -1;
}
}
// Some bits are set
else
{
for ( uint32 i = 0; i < 64; ++i )
{
const uint32 SubX = x + i;
if ( BitsValue & ( 1ull << i ) )
{
if ( FirstUsedX < 0 )
{
FirstUsedX = SubX;
}
if ( SubX == Width - 1 )
{
AddUsedSegment( CurrentRow, FirstUsedX, SubX + 1 - FirstUsedX );
FirstUsedX = -1;
}
}
else if ( FirstUsedX >= 0 )
{
AddUsedSegment( CurrentRow, FirstUsedX, SubX - FirstUsedX );
FirstUsedX = -1;
}
}
}
}
}
SortedRowsIndex.Sort([this](uint32 a, uint32 b) { return Rows[b].LongestSegment < Rows[a].LongestSegment;});
// Create segments along the Y axis for each columns
if (LayoutVersion >= ELightmapUVVersion::Segments2D)
{
SortedColumnsIndex.SetNum(RasterWidth, EAllowShrinking::No);
for ( uint32 x = 0; x < RasterWidth; ++x )
{
SortedColumnsIndex[x] = x;
FRun& CurrentColumn = Columns[x];
CurrentColumn.LongestSegment = 0;
CurrentColumn.Segments.Reset();
int32 FirstUsedY = -1;
for ( uint32 y = 0; y < RasterHeight; ++y )
{
if (GetBit(x, y))
{
if ( FirstUsedY < 0 )
{
FirstUsedY = y;
}
if ( y == RasterHeight - 1 )
{
AddUsedSegment( CurrentColumn, FirstUsedY, y + 1 - FirstUsedY );
FirstUsedY = -1;
}
}
else if ( FirstUsedY >= 0 )
{
AddUsedSegment( CurrentColumn, FirstUsedY, y - FirstUsedY );
FirstUsedY = -1;
}
}
}
SortedColumnsIndex.Sort([this](uint32 a, uint32 b) { return Columns[b].LongestSegment < Columns[a].LongestSegment;});
}
}
void FAllocator2D::AddUsedSegment( FRun& Row, uint32 StartPos, uint32 Length )
{
FSegment UsedSegment;
UsedSegment.StartPos = StartPos;
UsedSegment.Length = Length;
Row.Segments.Add(UsedSegment);
if ( UsedSegment.Length > Row.LongestSegment )
{
Row.LongestSegment = UsedSegment.Length;
}
}
void FAllocator2D::MergeRun(FRun& ThisRun, const FRun& OtherRun, uint32 RectOffset, uint32 RectLength, uint32 PrimaryResolution, uint32 PerpendicularResolution)
{
// #TODO
// Perform a linear scan once for both array at the same time zipping them together
// Ensure we keep the order of the segments while merging them to avoid costly sort when number of free segments is high
for ( const FSegment& OtherUsedSegment : OtherRun.Segments )
{
for ( int32 Index = 0; Index < ThisRun.Segments.Num(); ++Index )
{
FSegment& ThisFreeSegment = ThisRun.Segments[Index];
uint32 StartPos = RectOffset + OtherUsedSegment.StartPos;
if ( StartPos >= ThisFreeSegment.StartPos &&
StartPos < ThisFreeSegment.StartPos + ThisFreeSegment.Length )
{
if ( ThisFreeSegment.Length == 1 )
{
ThisRun.Segments.RemoveAtSwap(Index);
break;
}
FSegment FirstSegment;
FirstSegment.StartPos = ThisFreeSegment.StartPos;
FirstSegment.Length = StartPos - ThisFreeSegment.StartPos;
uint32 EndPos = RectOffset + FMath::Min( OtherUsedSegment.StartPos + OtherUsedSegment.Length, RectLength ) - 1;
FSegment SecondSegment;
SecondSegment.StartPos = EndPos + 1;
SecondSegment.Length = ThisFreeSegment.StartPos + ThisFreeSegment.Length - SecondSegment.StartPos;
ThisRun.Segments.RemoveAtSwap(Index);
if ( FirstSegment.Length > 0 )
{
ThisRun.Segments.Add( FirstSegment );
}
if ( SecondSegment.Length > 0 )
{
ThisRun.Segments.Add( SecondSegment );
}
break;
}
}
}
ThisRun.Segments.Sort();
// When we're dealing with an Axis, the lookup table is the size of the perpendicular Axis
ThisRun.FreeSegmentsLookup.SetNum(PerpendicularResolution);
ThisRun.LongestSegment = 0;
uint32 LastIndex = 0;
// Avoid RangeCheck by indexing the pointer directly since we're in a hot loop
FSegment* SegmentsPtr = ThisRun.Segments.GetData();
uint16* FreeSegmentsLookupPtr = ThisRun.FreeSegmentsLookup.GetData();
int32 SegmentIndex = 0;
for ( ; SegmentIndex < ThisRun.Segments.Num(); ++SegmentIndex )
{
FSegment& Segment = SegmentsPtr[SegmentIndex];
if (Segment.Length > ThisRun.LongestSegment)
{
ThisRun.LongestSegment = Segment.Length;
}
uint32 StopIndex = Segment.StartPos + Segment.Length;
for (uint32 LookupIndex = LastIndex; LookupIndex < StopIndex; ++LookupIndex)
{
FreeSegmentsLookupPtr[LookupIndex] = SegmentIndex;
}
LastIndex = StopIndex;
}
for (uint32 Index = LastIndex; Index < PerpendicularResolution; ++Index)
{
FreeSegmentsLookupPtr[Index] = SegmentIndex;
}
}
void FAllocator2D::MergeSegments( const FRect& Rect, const FAllocator2D& Other )
{
check(Mode == EMode::FreeSegments);
check(Other.Mode == EMode::UsedSegments);
for ( uint32 y = 0; y < Other.RasterHeight; ++y )
{
MergeRun(Rows[Rect.Y + y], Other.Rows[y], Rect.X, Rect.W, Width, Height);
}
if (LayoutVersion >= ELightmapUVVersion::Segments2D)
{
for ( uint32 x = 0; x < Other.RasterWidth; ++x )
{
MergeRun(Columns[Rect.X + x], Other.Columns[x], Rect.Y, Rect.H, Height, Width);
}
}
}
void FAllocator2D::ResetStats()
{
#if DEBUG_LAYOUT_STATS
Stats.Reset();
#endif
}
void FAllocator2D::PublishStats(int32 ChartIndex, int32 Orientation, bool bFound, const FRect& Rect, const FRect& BestRect, const FMD5Hash& ChartMD5, TFunctionRef<bool (const FAllocator2D::FRect&)> IsBestRect)
{
//This is super helpful in Insights to inspect long running charts behavior
//Ensure we compile it even when not used so the code doesn't rot
#if DEBUG_LAYOUT_STATS
const bool bTrace = true;
#else
const bool bTrace = false;
#endif
if (bTrace)
{
TRACE_BOOKMARK(
TEXT(
"C:%d O:%d R:%d X:%d Y:%d W:%d H:%d\n"
"MD5: %s\n"
"BestRect X:%d Y:%d W:%d H:%d\n"
"BestRectBeaten %d\n"
"FindWithSegmentsIterationsY %llu\n"
"FindWithSegmentsIterationsX %llu\n"
"FindWithSegmentsPastBestRect %llu\n"
"TestAllRowsIterationsY %llu\n"
"FreeSegmentLookupCount %llu\n"
"FreeSegmentRangeIterations %llu\n"
"FreeSegmentFutureIterations %llu\n"
"FreeSegmentFutureHit %llu\n"
"FreeSegmentFutureHitStep %llu\n"
"FreeSegmentFutureMiss %llu\n"
"FreeSegmentFutureMissStep %llu\n"
),
ChartIndex,
Orientation,
bFound ? 1 : 0,
Rect.X,
Rect.Y,
Rect.W,
Rect.H,
*LexToString(ChartMD5),
BestRect.X,
BestRect.Y,
BestRect.W,
BestRect.H,
(bFound && IsBestRect(Rect)) ? 1 : 0,
Stats.FindWithSegmentsIterationsY.GetValue(),
Stats.FindWithSegmentsIterationsX.GetValue(),
Stats.FindWithSegmentsMovedPastPreviousBest.GetValue(),
Stats.TestAllRowsIterationsY.GetValue(),
Stats.FreeSegmentLookupCount.GetValue(),
Stats.FreeSegmentRangeIterations.GetValue(),
Stats.FreeSegmentFutureIterations.GetValue(),
Stats.FreeSegmentFutureHit.GetValue(),
Stats.FreeSegmentFutureHitStep.GetValue(),
Stats.FreeSegmentFutureMiss.GetValue(),
Stats.FreeSegmentFutureMissStep.GetValue()
);
}
}