// 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& Runs, const TArray& 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 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 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& 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 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() ); } }